Scrivite un operatore per Kubernetes in Golang

Nota. transl.: L'operatori sò software ausiliari per Kubernetes, cuncepitu per automatizà l'esekzione di l'azzioni di rutina nantu à l'oggetti di cluster quandu certi avvenimenti accadenu. Avemu digià scrittu annantu à l'operatori in stu articulu, induve si parlavanu di l'idee fundamentali è i principii di u so travagliu. Ma se quellu materiale era più di una vista da u latu di uperazione di cumpunenti pronti per Kubernetes, allora a traduzzione di u novu articulu avà prupostu hè digià a visione di un sviluppatore / ingegnere DevOps perplessu da l'implementazione di un novu operatore.

Scrivite un operatore per Kubernetes in Golang

Aghju decisu di scrive stu post cun un esempiu di a vita reale dopu à i mo tentativi di truvà documentazione nantu à a creazione di un operatore per Kubernetes, chì passava per studià u codice.

L'esempiu chì serà descrittu hè questu: in u nostru cluster Kubernetes, ognunu Namespace rapprisenta l'ambiente sandbox di una squadra, è vulemu limità l'accessu à elli in modu chì e squadre puderanu ghjucà solu in i so propii sandbox.

Pudete ottene ciò chì vulete assignendu un utilizatore un gruppu chì hà RoleBinding à specifichi Namespace и ClusterRole cù diritti di editazione. A rapprisintazioni YAML sarà cusì:

---
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.yamlin crudu)

Crea unu RoleBinding Pudete fà manualmente, ma dopu avè attraversatu a marca di centu spazii di nomi, diventa un compitu tedious. Hè quì chì l'operatori di Kubernetes sò utili - permettenu di automatizà a creazione di risorse Kubernetes basatu nantu à i cambiamenti à e risorse. In u nostru casu vulemu creà RoleBinding mentre crea Namespace.

Prima di tuttu, definiscemu a funzione mainchì face a configurazione necessaria per eseguisce a dichjarazione è poi chjama l'azzione di dichjarazione:

(Nota. transl.: quì è sottu i cumenti in u codice sò tradutti in Russu. Inoltre, l'indentazione hè stata curretta à spazii invece di tabulazioni [consigliate in Go] solu per u scopu di megliu leghjibilità in u layout Habr. Dopu ogni listinu ci sò ligami à l'uriginale in GitHub, induve i cumenti è e tabulazioni in lingua inglese sò almacenati.)

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.goin crudu)

Facemu i seguenti:

  1. Configuremu un gestore per signali specifichi di u sistema operatore per causà a fine grazia di l'operatore.
  2. Avemu aduprà WaitGroupper piantà graziamente tutte e goroutines prima di finisce l'applicazione.
  3. Avemu furnisce l'accessu à u cluster creendu clientset.
  4. Lanciari NamespaceController, in quale sarà situata tutta a nostra logica.

Avà avemu bisognu di una basa per a logica, è in u nostru casu questu hè quellu mintuatu 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.goin crudu)

Quì avemu cunfigurà SharedIndexInformer, chì in modu efficace (usendu una cache) aspittà per i cambiamenti in i namespaces (leghjite più nantu à l'informatori in l'articulu "Cumu funziona veramente u pianificatore Kubernetes?"- ca. transl.). Dopu questu avemu cunnette EventHandler à l'informatore, perchè quandu aghjunghje un spaziu di nomi (Namespace) a funzione hè chjamata createRoleBinding.

U prossimu passu hè di definisce sta funzione 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.goin crudu)

Avemu u spaziu di nomi cum'è obj è cunvertisce in un oggettu Namespace. Allora avemu definitu RoleBinding, basatu annantu à u schedariu YAML mintuatu à u principiu, utilizendu l'ughjettu furnitu Namespace è crea RoleBinding. Infine, registremu se a creazione hà successu.

L'ultima funzione per esse definita hè 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.goin crudu)

Quì avemu parlatu WaitGroupchì avemu lanciatu u goroutine è dopu chjamate namespaceInformer, chì hè statu definitu prima. Quandu u signale di stop arriva, finisce a funzione, informa WaitGroup, chì ùn hè più eseguitu, è sta funzione esce.

L'infurmazione nantu à a custruzzione è a gestione di sta dichjarazione nantu à un cluster Kubernetes pò esse truvata in repository nantu à GitHub.

Hè per l'operatore chì crea RoleBinding quandu Namespace in u cluster Kubernetes, pronta.

Source: www.habr.com

Add a comment