Scrierea unui operator pentru Kubernetes în Golang

Notă. transl.: Operatorii sunt software auxiliare pentru Kubernetes, concepute pentru a automatiza execuția acțiunilor de rutină pe obiectele cluster atunci când apar anumite evenimente. Am scris deja despre operatori în acest articol, unde au vorbit despre ideile și principiile fundamentale ale muncii lor. Dar dacă acel material a fost mai mult o vedere din partea operațiunilor de componente gata făcute pentru Kubernetes, atunci traducerea noului articol propus acum este deja viziunea unui dezvoltator/inginer DevOps nedumerit de implementarea unui nou operator.

Scrierea unui operator pentru Kubernetes în Golang

Am decis să scriu această postare cu un exemplu din viața reală după încercările mele de a găsi documentație despre crearea unui operator pentru Kubernetes, care a trecut prin studierea codului.

Exemplul care va fi descris este următorul: în clusterul nostru Kubernetes, fiecare Namespace reprezintă mediul sandbox al unei echipe și am vrut să limităm accesul la acestea, astfel încât echipele să poată juca doar în propriile lor sandbox.

Puteți obține ceea ce doriți alocand unui utilizator un grup care are RoleBinding la specific Namespace и ClusterRole cu drepturi 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

(legare de rol.yamlÎn crud)

Creeaza una RoleBinding O puteți face manual, dar după ce treceți marcajul sutei de spații de nume, devine o sarcină plictisitoare. Acesta este locul în care operatorii Kubernetes sunt la îndemână - vă permit să automatizați crearea resurselor Kubernetes pe baza modificărilor aduse resurselor. În cazul nostru dorim să creăm RoleBinding în timp ce creează Namespace.

În primul rând, să definim funcția maincare face configurarea necesară pentru a rula instrucțiunea și apoi apelează acțiunea instrucțiunii:

(Notă. transl.: aici și mai jos comentariile din cod sunt traduse în rusă. În plus, indentarea a fost corectată la spații în loc de file [recomandat în Go] numai în scopul unei mai bune lizibilități în aspectul Habr. După fiecare listă există link-uri către originalul de pe GitHub, unde sunt stocate comentariile și file-urile în limba engleză.)

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

(principal.goÎn crud)

Facem următoarele:

  1. Configuram un handler pentru semnalele specifice ale sistemului de operare pentru a provoca terminarea gratioasa a operatorului.
  2. Folosim WaitGrouppentru a opri cu grație toate goroutinele înainte de a termina aplicația.
  3. Oferim acces la cluster prin creare clientset.
  4. Lansa NamespaceController, în care se va localiza toată logica noastră.

Acum avem nevoie de o bază pentru logică, iar în cazul nostru aceasta este cea 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
}

(controlor.du-teÎn crud)

Aici configuram SharedIndexInformer, care va aștepta efectiv (folosind un cache) modificări în spațiile de nume (citiți mai multe despre informatori în articolul „Cum funcționează de fapt programatorul Kubernetes?"- aproximativ traducere). După aceasta ne conectăm EventHandler la informator, astfel încât atunci când adăugați un spațiu de nume (Namespace) este numită funcția createRoleBinding.

Următorul pas este definirea acestei funcții 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))
  }
}

(controlor.du-teÎn crud)

Obținem spațiul de nume ca obj și convertiți-l într-un obiect Namespace. Apoi definim RoleBinding, pe baza fișierului YAML menționat la început, folosind obiectul furnizat Namespace și creând RoleBinding. În cele din urmă, înregistrăm dacă crearea a avut 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
}

(controlor.du-teÎn crud)

Aici vorbim WaitGroupcă lansăm goroutina și apoi sunăm namespaceInformer, care a fost definit anterior. Când sosește semnalul de oprire, acesta va încheia funcția, informează WaitGroup, care nu mai este executată, iar această funcție va ieși.

Informații despre construirea și rularea acestei instrucțiuni pe un cluster Kubernetes pot fi găsite în depozite pe GitHub.

Asta este pentru operatorul care creează RoleBinding cand Namespace în clusterul Kubernetes, gata.

Sursa: www.habr.com

Adauga un comentariu