Operátor írása a Kubernetes számára Golangban

Jegyzet. ford.: Az operátorok a Kubernetes segédszoftverei, amelyek bizonyos események bekövetkezésekor automatizálják a rutinműveletek végrehajtását a fürtobjektumokon. Az operátorokról már írtunk ben ezt a cikket, ahol munkájuk alapvető gondolatairól és elveiről beszélgettek. De ha ez az anyag inkább a Kubernetes kész komponenseinek üzemeltetését szemlélteti, akkor a most javasolt új cikk fordítása már egy fejlesztő/DevOps mérnök víziója, aki értetlenül áll az új operátor bevezetése miatt.

Operátor írása a Kubernetes számára Golangban

Úgy döntöttem, hogy ezt a bejegyzést egy valós példával írom meg, miután megpróbáltam dokumentációt találni a Kubernetes operátor létrehozásához, amely során a kód tanulmányozása ment keresztül.

A leírandó példa a következő: Kubernetes-fürtünkben mindegyik Namespace egy csapat homokozó környezetét képviseli, és szerettük volna korlátozni a hozzáférést, hogy a csapatok csak a saját homokozójukban játszhassanak.

Elérheti, amit akar, ha hozzárendel egy felhasználóhoz egy csoportot RoleBinding konkréthoz Namespace и ClusterRole szerkesztési joggal. A YAML-ábrázolás így fog kinézni:

---
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

(szerepkötés.yaml-Ban nyers)

Csinálni egyet RoleBinding Ezt manuálisan is megteheti, de a száz névtér határának átlépése után unalmas feladattá válik. Itt jönnek jól a Kubernetes-operátorok – lehetővé teszik a Kubernetes-erőforrások létrehozásának automatizálását az erőforrások változásai alapján. A mi esetünkben alkotni akarunk RoleBinding alkotás közben Namespace.

Először is definiáljuk a függvényt mainamely elvégzi az utasítás futtatásához szükséges beállításokat, majd meghívja az utasításműveletet:

(Jegyzet. ford.: itt és lent a kód megjegyzései oroszra vannak fordítva. Ezenkívül a behúzást szóközökre javították a [ajánlott a Go] tabulátorok helyett, kizárólag a jobb olvashatóság érdekében a Habr-elrendezésen belül. Minden lista után hivatkozások találhatók az eredetire a GitHubon, ahol az angol nyelvű megjegyzések és lapok tárolódnak.)

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()   // Ожидаем, что все остановлено
}

(fő.go-Ban nyers)

A következőket tesszük:

  1. Konfigurálunk egy kezelőt az adott operációs rendszer jeleihez, hogy az operátor kecses leállítását idézzük elő.
  2. Használjuk WaitGrouphogy az alkalmazás befejezése előtt kecsesen állítson le minden gorutint.
  3. Létrehozással hozzáférést biztosítunk a klaszterhez clientset.
  4. Dob NamespaceController, amelyben minden logikánk elhelyezkedik.

Most logikai alapra van szükségünk, és esetünkben ez az, amit említettünk 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
}

(controller.go-Ban nyers)

Itt konfiguráljuk SharedIndexInformer, amely gyakorlatilag (gyorsítótár segítségével) várja a névterek változásait (az informátorokról bővebben a cikkben olvashatHogyan működik valójában a Kubernetes ütemező?"- kb. ford.). Ezek után csatlakozunk EventHandler az informátorhoz, így névtér hozzáadásakor (Namespace) függvényt hívjuk meg createRoleBinding.

A következő lépés ennek a függvénynek a meghatározása 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))
  }
}

(controller.go-Ban nyers)

A névteret így kapjuk obj és alakítsa át tárggyá Namespace. Aztán meghatározzuk RoleBinding, az elején említett YAML fájl alapján, a megadott objektum használatával Namespace és létrehozása RoleBinding. Végül naplózzuk, hogy a létrehozás sikeres volt-e.

Az utolsó definiálandó függvény a 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
}

(controller.go-Ban nyers)

Itt beszélünk WaitGrouphogy elindítjuk a gorutint, majd hívjuk namespaceInformer, amelyet korábban meghatároztunk. Ha megérkezik a stop jelzés, leállítja a funkciót, értesít WaitGroup, amely már nem hajtódik végre, és ez a funkció kilép.

Ennek az utasításnak a Kubernetes-fürtön való felépítésével és futtatásával kapcsolatos információk itt találhatók adattárak a GitHubon.

Ez az üzemeltető számára, aki létrehoz RoleBinding mikor Namespace a Kubernetes klaszterben, kész.

Forrás: will.com

Hozzászólás