Scriem un operator pentru Kubernetes în Golang

Nota traducătorului.: Operatoarele (operators) sunt software auxiliar pentru Kubernetes, destinat automatizării execuției acțiunilor rutiniere asupra obiectelor cluster-ului în anumite evenimente. Am scris deja despre operatoare în această articole, unde am discutat despre ideile fundamentale și principiile lor de funcționare. Dar dacă acel material a fost mai degrabă o privire din partea utilizării componentelor gata existente pentru Kubernetes, traducerea noului articol propus este deja viziunea unui dezvoltator / inginer DevOps, preocupat de realizarea unui nou operator.

Scriem un operator pentru Kubernetes în Golang

Acest post cu un exemplu din viața reală l-am decis să-l scriu după eforturile mele de a găsi documentația pentru crearea unui operator pentru Kubernetes, care a trecut prin studierea codului.

Exemplul care va fi descris este următorul: în cluster-ul nostru Kubernetes fiecare Namespace reprezintă un mediu sandbox pentru o anumită echipă, iar noi am dorit să limităm accesul la acestea astfel încât echipele să poată juca doar în sandbox-ul lor.

Acest lucru dorit poate fi realizat prin atribuirea utilizatorului către un grup care are RoleBinding la un anumit Namespace și ClusterRole cu drept de editare. Reprezentarea YAML va arăta astfel:

---
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.yaml, pe raw)

Crearea unui astfel de RoleBinding se poate face și manual, dar după ce a fost depășită limita de o sută de spații de nume, devine o activitate obositoare. Aici intervin operatoarele Kubernetes - ele permit automatizarea creării resurselor Kubernetes, bazându-se pe modificările din resurse. În cazul nostru, dorim să creăm RoleBinding atunci când se creează Namespace.

Mai întâi vom defini funcția main, care efectuează configurarea necesară pentru a lansa operatorul și apoi apelează acțiunea operatorului:

(Nota traducătorului.: aici și mai departe comentariile din cod sunt traduse în limba română. În plus, indentările au fost corectate în spații în loc de [recomandatele în Go] tab-uri exclusiv în scopul unei mai bune lizibilități în cadrul layout-ului Habr. După fiecare listare sunt linkuri către originalul de pe GitHub, unde sunt păstrate comentariile în engleză și tab-urile.)

func main() {
  // Stabilim ieșirea logurilor în STDOUT-ul consolei
  log.SetOutput(os.Stdout)

  sigs := make(chan os.Signal, 1) // Cream un canal pentru a primi semnalele OS
  stop := make(chan struct{})     // Cream un canal pentru a primi semnalul de oprire

  // Înregistrăm primirea SIGTERM în canalul sigs
  signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 

  // Goroutines se pot adăuga singure în WaitGroup,
  // pentru a aștepta finalizarea lor
  wg := &sync.WaitGroup{} 

  runOutsideCluster := flag.Bool("run-outside-cluster", false, "Setați acest flag când rulați în afara cluster-ului.")
  flag.Parse()
  // Cream clientset pentru interacțiunea cu cluster-ul Kubernetes
  clientset, err := newClientSet(*runOutsideCluster)

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

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

  <-sigs // Așteptăm semnalele (până la primirea unui semnal, nimic altceva nu se întâmplă)
  log.Printf("Se închide...")

  close(stop) // Spunem goroutines să se oprească
  wg.Wait()   // Așteptăm ca toate să se oprească
}

(main.go, pe raw)

Facem următoarele:

  1. Configurăm handler-ul pentru semnalele specifice ale sistemului de operare, pentru a provoca o oprire corectă (graceful) a operatorului.
  2. Vom folosi WaitGroup, pentru a opri corect toate goroutines înainte de a termina aplicația.
  3. Oferim acces la cluster prin crearea clientset.
  4. Lansăm NamespaceController, în care va fi amplasată toată logica noastră.

Acum ne trebuie baza pentru logică, iar în cazul nostru aceasta este menționată 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, pe raw)

Aici configurăm SharedIndexInformer, care va aștepta eficient (folosind cache) modificările în spațiile de nume (mai multe despre informers citiți în articolul „Cum funcționează cu adevărat scheduler-ul Kubernetes?» — notă de traducere.). După aceasta, conectăm EventHandler la informer, astfel încât, atunci când se adaugă un spațiu de nume (Namespace), se apelează funcția createRoleBinding.

Următorul pas este să definim această funcție 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("A eșuat crearea Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("A fost creat AD RoleBinding pentru Namespace: %s", roleBinding.Name))
  }
}

(controller.go, pe raw)

Obținem spațiul de nume ca obj și îl transformăm într-un obiect Namespace. Apoi definim RoleBinding, bazându-ne pe ceea ce a fost menționat la început în fișierul YAML, utilizând obiectul furnizat Namespace și creând RoleBinding. În cele din urmă, înregistrăm dacă crearea a fost realizată cu succes.

Ultima funcție care trebuie definită este 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, pe raw)

Aici discutăm WaitGroup, că vom rula goroutine și apoi apelăm namespaceInformer, care a fost definit anterior. Când primește un semnal de oprire, va finaliza funcția, va informa WaitGroup, că nu mai este în execuție, iar această funcție își va termina activitatea.

Informațiile despre construirea și rularea acestui operator în clusterul Kubernetes pot fi găsite în repositorii pe GitHub.

Astfel, operatorul care creează RoleBinding la apariția Namespace în clusterul Kubernetes, este gata.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster