Пишување оператор за Кубернетес во Голанг

Забелешка. превод.: Операторите се помошен софтвер за Kubernetes, дизајниран да го автоматизира извршувањето на рутинските дејства на објектите на кластерот кога се случуваат одредени настани. Веќе пишувавме за операторите во овој напис, каде што зборуваа за темелните идеи и принципи на нивната работа. Но, ако тој материјал беше повеќе поглед од страната на оперативните готови компоненти за Kubernetes, тогаш преводот на новата статија сега предложена е веќе визија на развивач/инженер на DevOps збунет од имплементацијата на нов оператор.

Пишување оператор за Кубернетес во Голанг

Решив да го напишам овој пост со пример од реалниот живот по моите обиди да најдам документација за создавање оператор за Kubernetes, што помина низ проучување на кодот.

Примерот што ќе биде опишан е овој: во нашиот кластер Kubernetes, секој Namespace ја претставува околината на песокот на тимот и сакавме да го ограничиме пристапот до нив, така што тимовите ќе можат да играат само во нивните сопствени песок.

Можете да го постигнете она што го сакате со доделување на корисник група која има RoleBinding до конкретни Namespace и ClusterRole со права на уредување. Претставувањето на YAML ќе изгледа вака:

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: kubernetes-team-1
  namespace: team-1
subjects:
- kind: Group
  name: kubernetes-team-1
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: edit
apiGroup: rbac.authorization.k8s.io

(врзување на улоги.yamlво суровини)

Направете еден RoleBinding Можете да го направите тоа рачно, но откако ќе ја преминете ознаката за стотина имиња, станува досадна задача. Ова е местото каде што операторите на Kubernetes доаѓаат во рака - тие ви дозволуваат да го автоматизирате создавањето на ресурси на Kubernetes врз основа на промени во ресурсите. Во нашиот случај сакаме да создадеме RoleBinding додека создавате Namespace.

Прво, да ја дефинираме функцијата mainкој го прави потребното поставување за да ја изврши изјавата, а потоа го нарекува дејство на изјавата:

(Забелешка. превод.: овде и подолу коментарите во кодот се преведени на руски. Дополнително, вовлекувањето е поправено на празни места наместо табовите [препорачано во Go] исклучиво со цел подобра читливост во распоредот на Habr. По секоја листа, има линкови до оригиналот на GitHub, каде што се чуваат коментарите и картичките на англиски јазик.)

func main() {
  // Устанавливаем вывод логов в консольный STDOUT
  log.SetOutput(os.Stdout)

  sigs := make(chan os.Signal, 1) // Создаем канал для получения сигналов ОС
  stop := make(chan struct{})     // Создаем канал для получения стоп-сигнала

  // Регистрируем получение SIGTERM в канале sigs
  signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 

  // Goroutines могут сами добавлять себя в WaitGroup,
 // чтобы завершения их выполнения дожидались
  wg := &sync.WaitGroup{} 

  runOutsideCluster := flag.Bool("run-outside-cluster", false, "Set this flag when running outside of the cluster.")
  flag.Parse()
  // Создаем clientset для взаимодействия с кластером Kubernetes
  clientset, err := newClientSet(*runOutsideCluster)

  if err != nil {
    panic(err.Error())
  }

  controller.NewNamespaceController(clientset).Run(stop, wg)

  <-sigs // Ждем сигналов (до получения сигнала более ничего не происходит)
  log.Printf("Shutting down...")

  close(stop) // Говорим goroutines остановиться
  wg.Wait()   // Ожидаем, что все остановлено
}

(главен.одиво суровини)

Ние го правиме следново:

  1. Конфигурираме управувач за специфични сигнали на оперативниот систем за да предизвикаме грациозно прекинување на операторот.
  2. Ние користиме WaitGroupблагодатно да ги прекинете сите горутини пред да ја прекинете апликацијата.
  3. Обезбедуваме пристап до кластерот со креирање clientset.
  4. Лансира NamespaceController, во кој ќе биде сместена сета наша логика.

Сега ни треба основа за логика, а во нашиот случај ова е споменатата NamespaceController:

// NamespaceController следит через Kubernetes API за изменениями
// в пространствах имен и создает RoleBinding для конкретного namespace.
type NamespaceController struct {
  namespaceInformer cache.SharedIndexInformer
  kclient           *kubernetes.Clientset
}

// NewNamespaceController создает новый NewNamespaceController
func NewNamespaceController(kclient *kubernetes.Clientset) *NamespaceController {
  namespaceWatcher := &NamespaceController{}

  // Создаем информер для слежения за Namespaces
  namespaceInformer := cache.NewSharedIndexInformer(
    &cache.ListWatch{
      ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
        return kclient.Core().Namespaces().List(options)
      },
      WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
        return kclient.Core().Namespaces().Watch(options)
      },
    },
    &v1.Namespace{},
    3*time.Minute,
    cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
  )

  namespaceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc: namespaceWatcher.createRoleBinding,
  })

  namespaceWatcher.kclient = kclient
  namespaceWatcher.namespaceInformer = namespaceInformer

  return namespaceWatcher
}

(контролор.одиво суровини)

Тука конфигурираме SharedIndexInformer, кој ефективно (со користење на кеш) ќе чека промени во именските простори (прочитајте повеќе за информаторите во статијата“Како всушност функционира распоредувачот на Kubernetes?"- прибл. превод). По ова се поврзуваме EventHandler на информаторот, така што при додавање именски простор (Namespace) се повикува функцијата createRoleBinding.

Следниот чекор е да се дефинира оваа функција createRoleBinding:

func (c *NamespaceController) createRoleBinding(obj interface{}) {
  namespaceObj := obj.(*v1.Namespace)
  namespaceName := namespaceObj.Name

  roleBinding := &v1beta1.RoleBinding{
    TypeMeta: metav1.TypeMeta{
      Kind:       "RoleBinding",
      APIVersion: "rbac.authorization.k8s.io/v1beta1",
    },
    ObjectMeta: metav1.ObjectMeta{
      Name:      fmt.Sprintf("ad-kubernetes-%s", namespaceName),
      Namespace: namespaceName,
    },
    Subjects: []v1beta1.Subject{
      v1beta1.Subject{
        Kind: "Group",
        Name: fmt.Sprintf("ad-kubernetes-%s", namespaceName),
      },
    },
    RoleRef: v1beta1.RoleRef{
      APIGroup: "rbac.authorization.k8s.io",
        Kind:     "ClusterRole",
        Name:     "edit",
    },
  }

  _, err := c.kclient.Rbac().RoleBindings(namespaceName).Create(roleBinding)

  if err != nil {
    log.Println(fmt.Sprintf("Failed to create Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
  }
}

(контролор.одиво суровини)

Именскиот простор го добиваме како obj и претворете го во објект Namespace. Потоа дефинираме RoleBinding, врз основа на датотеката YAML спомната на почетокот, користејќи го дадениот објект Namespace и создавање RoleBinding. Конечно, евидентираме дали создавањето е успешно.

Последната функција што треба да се дефинира е Run:

// Run запускает процесс ожидания изменений в пространствах имён
// и действия в соответствии с этими изменениями.
func (c *NamespaceController) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
  // Когда эта функция завершена, пометим как выполненную
  defer wg.Done()

  // Инкрементируем wait group, т.к. собираемся вызвать goroutine
  wg.Add(1)

  // Вызываем goroutine
  go c.namespaceInformer.Run(stopCh)

  // Ожидаем получения стоп-сигнала
  <-stopCh
}

(контролор.одиво суровини)

Тука зборуваме WaitGroupдека го лансираме горутинот и потоа се јавуваме namespaceInformer, што е претходно дефинирано. Кога ќе пристигне сигналот за стоп, ќе ја заврши функцијата, информирајте WaitGroup, што повеќе не се извршува и оваа функција ќе излезе.

Информации за градење и извршување на оваа изјава на кластерот Kubernetes може да се најдат во складишта на GitHub.

Тоа е тоа за операторот што создава RoleBinding кога Namespace во кластерот Kubernetes, подготвен.

Извор: www.habr.com

Додадете коментар