Písanie operátora pre Kubernetes v Golang

Poznámka. preklad.: Operátori sú pomocný softvér pre Kubernetes, určený na automatizáciu vykonávania rutinných akcií na objektoch klastra, keď nastanú určité udalosti. O operátoroch v r sme už písali v tomto článku, kde hovorili o zásadných myšlienkach a princípoch svojej práce. Ak by však tento materiál bol skôr pohľadom zo strany prevádzky hotových komponentov pre Kubernetes, potom preklad nového článku, ktorý je teraz navrhnutý, je už víziou vývojára / inžiniera DevOps, ktorý je zmätený implementáciou nového operátora.

Písanie operátora pre Kubernetes v Golang

Rozhodol som sa napísať tento príspevok so skutočným príkladom po mojich pokusoch nájsť dokumentáciu o vytvorení operátora pre Kubernetes, ktoré prešli štúdiom kódu.

Príklad, ktorý bude popísaný, je tento: v našom klastri Kubernetes každý Namespace predstavuje tímové sandboxové prostredie a chceli sme k nim obmedziť prístup, aby tímy mohli hrať iba vo svojich vlastných karanténách.

Môžete dosiahnuť to, čo chcete, priradením skupiny používateľovi, ktorá má RoleBinding na konkrétne Namespace и ClusterRole s právami na úpravu. Reprezentácia YAML bude vyzerať 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 surový)

Vytvor jednu RoleBinding Môžete to urobiť ručne, ale po prekročení hranice sto menných priestorov sa to stáva únavnou úlohou. Tu sa hodia operátori Kubernetes – umožňujú vám automatizovať vytváranie zdrojov Kubernetes na základe zmien zdrojov. V našom prípade chceme tvoriť RoleBinding pri tvorbe Namespace.

Najprv si definujme funkciu mainčo vykoná požadované nastavenie na spustenie príkazu a potom zavolá akciu príkazu:

(Poznámka. preklad.: tu a nižšie sú komentáre v kóde preložené do ruštiny. Okrem toho bolo odsadenie opravené na medzery namiesto kariet [odporúča sa v Ísť] iba za účelom lepšej čitateľnosti v rozložení Habr. Po každom výpise sú odkazy na originál na GitHub, kde sú uložené komentáre a karty v anglickom jazyku.)

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 surový)

Robíme nasledovné:

  1. Nakonfigurujeme obslužnú rutinu pre špecifické signály operačného systému, aby spôsobilo elegantné ukončenie operátora.
  2. Používame WaitGrouppred ukončením aplikácie ladne zastaviť všetky goroutiny.
  3. Prístup do klastra poskytujeme vytvorením clientset.
  4. Spustiť NamespaceController, v ktorom sa bude nachádzať celá naša logika.

Teraz potrebujeme základ pre logiku a v našom prípade je to ten, ktorý sme spomenuli 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
}

(ovládač.goV surový)

Tu konfigurujeme SharedIndexInformer, ktorý bude efektívne (pomocou vyrovnávacej pamäte) čakať na zmeny v menných priestoroch (viac o informátoroch si prečítajte v článku “Ako vlastne funguje plánovač Kubernetes?„- približne. preklad). Po tomto sa spojíme EventHandler informátorovi, takže pri pridávaní menného priestoru (Namespace) sa volá funkcia createRoleBinding.

Ďalším krokom je definovanie tejto funkcie 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))
  }
}

(ovládač.goV surový)

Dostaneme menný priestor ako obj a previesť ho na objekt Namespace. Potom definujeme RoleBinding, na základe súboru YAML uvedeného na začiatku pomocou poskytnutého objektu Namespace a tvorenie RoleBinding. Nakoniec zalogujeme, či bolo vytvorenie úspešné.

Posledná funkcia, ktorú treba definovať, 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
}

(ovládač.goV surový)

Tu hovoríme WaitGroupže spustíme goroutín a potom zavoláme namespaceInformer, ktorý bol definovaný skôr. Keď príde signál stop, ukončí funkciu, inform WaitGroup, ktorá sa už nevykoná a táto funkcia sa ukončí.

Informácie o vytvorení a spustení tohto príkazu v klastri Kubernetes nájdete v úložiská na GitHub.

To je všetko pre operátora, ktorý vytvára RoleBinding kedy Namespace v klastri Kubernetes, pripravený.

Zdroj: hab.com

Pridať komentár