Kubernetese operaatori kirjutamine Golangis

Märge. tõlge: operaatorid on Kubernetese abitarkvara, mis on loodud teatud sündmuste korral klastri objektidel rutiinsete toimingute täitmise automatiseerimiseks. Oleme juba kirjutanud operaatoritest aastal see artikkel, kus räägiti oma töö põhiideedest ja põhimõtetest. Aga kui see materjal oli pigem Kubernetese valmiskomponentide käitamise vaade, siis nüüd pakutud uue artikli tõlge on juba uue operaatori juurutamisest hämmingus arendaja/DevOpsi inseneri nägemus.

Kubernetese operaatori kirjutamine Golangis

Otsustasin kirjutada selle postituse päriselust pärit näitega pärast seda, kui püüdsin leida Kubernetese operaatori loomise dokumentatsiooni, mis läbis koodi uurimise.

Kirjeldatav näide on järgmine: meie Kubernetese klastris igaüks Namespace esindab meeskonna liivakastikeskkonda ja soovisime neile juurdepääsu piirata, et meeskonnad saaksid mängida ainult oma liivakastides.

Saate saavutada selle, mida soovite, määrates kasutajale rühma, millel on olemas RoleBinding konkreetseks Namespace и ClusterRole toimetamisõigusega. YAML-i esitus näeb välja selline:

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

(rollide sidumine.yamlSisse toores)

Looge üks RoleBinding Saate seda teha käsitsi, kuid pärast saja nimeruumi piiri ületamist muutub see tüütuks ülesandeks. Siin tulevad appi Kubernetese operaatorid – need võimaldavad automatiseerida Kubernetese ressursside loomist ressursside muudatuste põhjal. Meie puhul tahame luua RoleBinding loomise ajal Namespace.

Kõigepealt määratleme funktsiooni mainmis teeb avalduse käitamiseks vajaliku seadistuse ja kutsub seejärel välja avalduse toimingu:

(Märge. tõlge: siin ja allpool on koodi kommentaarid tõlgitud vene keelde. Lisaks on taane muudetud vahekaartide [soovitatud Go] asemel tühikuteks ainult Habri paigutuse parema loetavuse huvides. Pärast iga kirjet on GitHubis lingid originaalile, kuhu salvestatakse ingliskeelsed kommentaarid ja vahelehed.)

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.goSisse toores)

Teeme järgmist:

  1. Konfigureerime käitleja konkreetsete operatsioonisüsteemi signaalide jaoks, et põhjustada operaatori graatsilist lõpetamist.
  2. Me kasutame WaitGroupet peatada graatsiliselt kõik gorutiinid enne rakenduse lõpetamist.
  3. Luues pakume juurdepääsu klastrile clientset.
  4. Käivitamine NamespaceController, milles asub kogu meie loogika.

Nüüd vajame loogika alust ja meie puhul on see mainitud 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.goSisse toores)

Siin me konfigureerime SharedIndexInformer, mis jääb tõhusalt (vahemälu kasutades) ootama muudatusi nimeruumides (loe informaatorite kohta lähemalt artiklist “Kuidas Kubernetese ajakava tegelikult töötab?"- u. tõlge). Pärast seda ühendame EventHandler informaatorile, nii et nimeruumi lisamisel (Namespace) kutsutakse funktsioon createRoleBinding.

Järgmine samm on selle funktsiooni määratlemine 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.goSisse toores)

Nimeruumi saame kui obj ja teisendada see objektiks Namespace. Seejärel määratleme RoleBinding, põhinedes alguses mainitud YAML-failil, kasutades antud objekti Namespace ja loomine RoleBinding. Lõpuks logime, kas loomine õnnestus.

Viimane määratletav funktsioon on 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.goSisse toores)

Siin me räägime WaitGroupet käivitame gorutiini ja siis helistame namespaceInformer, mis on eelnevalt määratletud. Kui stoppsignaal saabub, lõpetab see funktsiooni, teavita WaitGroup, mida enam ei käivitata, ja see funktsioon sulgub.

Teavet selle avalduse loomise ja käitamise kohta Kubernetese klastris leiate aadressilt hoidlad GitHubis.

See on see operaatori jaoks, kes loob RoleBinding millal Namespace Kubernetese klastris, valmis.

Allikas: www.habr.com

Lisa kommentaar