Psaní operátoru pro Kubernetes v Golangu

Poznámka. přel.: Operátoři jsou pomocný software pro Kubernetes, určený k automatizaci provádění rutinních akcí na objektech clusteru, když nastanou určité události. O operátorech jsme již psali tento článek, kde hovořili o zásadních myšlenkách a principech své práce. Ale pokud by tento materiál byl spíše pohledem ze strany provozu hotových komponent pro Kubernetes, pak překlad nového článku, který je nyní navržen, je již vizí vývojáře/DevOps inženýra zmateného implementací nového operátora.

Psaní operátoru pro Kubernetes v Golangu

Rozhodl jsem se napsat tento příspěvek s příkladem ze skutečného života po mých pokusech najít dokumentaci k vytvoření operátoru pro Kubernetes, které prošly studiem kódu.

Příklad, který bude popsán, je tento: v našem clusteru Kubernetes každý Namespace představuje prostředí sandboxu týmu a chtěli jsme k nim omezit přístup, aby týmy mohly hrát pouze ve svých vlastních sandboxech.

Můžete dosáhnout toho, co chcete, tím, že uživateli přiřadíte skupinu, která má RoleBinding na konkrétní Namespace и ClusterRole s právy na úpravy. Reprezentace YAML bude vypadat takto:

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

(rolebinding.yamlV syrový)

Vytvořit jeden RoleBinding Můžete to udělat ručně, ale po překročení stovky jmenných prostorů se z toho stává nudný úkol. Zde se hodí operátory Kubernetes – umožňují vám automatizovat vytváření zdrojů Kubernetes na základě změn zdrojů. V našem případě chceme tvořit RoleBinding při tvoření Namespace.

Nejprve si nadefinujme funkci mainkterý provede požadované nastavení pro spuštění příkazu a poté zavolá akci příkazu:

(Poznámka. přel.: zde a níže jsou komentáře v kódu přeloženy do ruštiny. Kromě toho bylo odsazení opraveno na mezery namísto [doporučeno v tabulkách Přejít] pouze za účelem lepší čitelnosti v rozložení Habr. Po každém výpisu jsou odkazy na originál na GitHubu, kde jsou uloženy komentáře a karty v angličtině.)

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

(main.goV syrový)

Provádíme následující:

  1. Nakonfigurujeme obslužnou rutinu pro konkrétní signály operačního systému tak, aby způsobila bezproblémové ukončení operátora.
  2. Používáme WaitGroupladně zastavit všechny goroutiny před ukončením aplikace.
  3. Přístup do clusteru poskytujeme vytvořením clientset.
  4. Běh NamespaceController, ve kterém se bude nacházet veškerá naše logika.

Nyní potřebujeme základ pro logiku a v našem případě je to ten zmíněný 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
}

(ovladač.goV syrový)

Zde konfigurujeme SharedIndexInformer, který bude efektivně (pomocí mezipaměti) čekat na změny ve jmenných prostorech (více o informátorech čtěte v článku “Jak vlastně funguje plánovač Kubernetes?„- Cca. přel.). Po tomto se spojíme EventHandler informátorovi, takže při přidávání jmenného prostoru (Namespace) je volána funkce createRoleBinding.

Dalším krokem je definování této funkce 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))
  }
}

(ovladač.goV syrový)

Dostaneme jmenný prostor jako obj a převést jej na objekt Namespace. Poté definujeme RoleBinding, na základě souboru YAML uvedeného na začátku pomocí poskytnutého objektu Namespace a tvoření RoleBinding. Nakonec zaprotokolujeme, zda bylo vytvoření úspěšné.

Poslední funkce, která má být definována, je 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
}

(ovladač.goV syrový)

Tady mluvíme WaitGroupže spustíme goroutinu a pak zavoláme namespaceInformer, který byl dříve definován. Když dorazí signál stop, ukončí funkci, inform WaitGroup, která se již neprovádí, a tato funkce se ukončí.

Informace o sestavení a spuštění tohoto příkazu v clusteru Kubernetes naleznete v úložiště na GitHubu.

To je vše pro operátora, který vytváří RoleBinding když Namespace v clusteru Kubernetes, připraveno.

Zdroj: www.habr.com

Přidat komentář