Scrivere un operatore per Kubernetes in Golang

Nota. trad.: Gli operatori sono software ausiliario per Kubernetes, progettati per automatizzare l'esecuzione di azioni di routine sugli oggetti del cluster quando si verificano determinati eventi. Abbiamo già scritto degli operatori in questo articolo, dove hanno parlato delle idee e dei principi fondamentali del loro lavoro. Ma se quel materiale fosse più una visione dal lato operativo di componenti già pronti per Kubernetes, allora la traduzione del nuovo articolo ora proposto è già la visione di uno sviluppatore/ingegnere DevOps perplesso dall'implementazione di un nuovo operatore.

Scrivere un operatore per Kubernetes in Golang

Ho deciso di scrivere questo post con un esempio di vita reale dopo i miei tentativi di trovare documentazione sulla creazione di un operatore per Kubernetes, che sono passati attraverso lo studio del codice.

L'esempio che verrà descritto è questo: nel nostro cluster Kubernetes, ciascuno Namespace rappresenta l'ambiente sandbox di una squadra e volevamo limitarne l'accesso in modo che le squadre potessero giocare solo nei propri sandbox.

Puoi ottenere ciò che desideri assegnando a un utente un gruppo che ha RoleBinding a specifico Namespace и ClusterRole con diritti di modifica. La rappresentazione YAML sarà simile a questa:

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

Crearne uno RoleBinding Puoi farlo manualmente, ma dopo aver superato il limite dei cento spazi dei nomi, diventa un compito noioso. È qui che gli operatori Kubernetes tornano utili: ti consentono di automatizzare la creazione di risorse Kubernetes in base alle modifiche alle risorse. Nel nostro caso vogliamo creare RoleBinding durante la creazione Namespace.

Prima di tutto definiamo la funzione mainche esegue la configurazione richiesta per eseguire l'istruzione e quindi chiama l'azione dell'istruzione:

(Nota. trad.: qui e sotto i commenti nel codice sono tradotti in russo. Inoltre, il rientro è stato corretto in spazi anziché in schede [consigliato in Go] al solo scopo di una migliore leggibilità all'interno del layout Habr. Dopo ogni elenco sono presenti collegamenti all'originale su GitHub, dove sono archiviati commenti e schede in lingua inglese.)

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

Facciamo quanto segue:

  1. Configuriamo un gestore per segnali specifici del sistema operativo per causare la terminazione regolare dell'operatore.
  2. Noi usiamo WaitGroupper interrompere con garbo tutte le goroutine prima di terminare l'applicazione.
  3. Forniamo l'accesso al cluster creando clientset.
  4. Lanciamo NamespaceController, in cui si troverà tutta la nostra logica.

Ora abbiamo bisogno di una base logica, e nel nostro caso è quella menzionata 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 crudo)

Qui configuriamo SharedIndexInformer, che effettivamente (utilizzando una cache) attenderà le modifiche negli spazi dei nomi (leggi di più sugli informatori nell’articolo “Come funziona effettivamente lo scheduler Kubernetes?»- ca. traduzione). Dopodiché ci connettiamo EventHandler all'informatore, in modo che quando si aggiunge uno spazio dei nomi (Namespace) viene chiamata la funzione createRoleBinding.

Il passo successivo è definire questa 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 crudo)

Otteniamo lo spazio dei nomi come obj e convertirlo in un oggetto Namespace. Quindi definiamo RoleBinding, basato sul file YAML menzionato all'inizio, utilizzando l'oggetto fornito Namespace e creare RoleBinding. Infine, registriamo se la creazione ha avuto successo.

L'ultima funzione da definire è 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 crudo)

Qui stiamo parlando WaitGroupche lanciamo la goroutine e poi chiamiamo namespaceInformer, che è stato precedentemente definito. Quando arriva il segnale di arresto, la funzione terminerà, informa WaitGroup, che non viene più eseguito, e questa funzione uscirà.

È possibile trovare informazioni sulla creazione e l'esecuzione di questa istruzione su un cluster Kubernetes in repository su GitHub.

Questo è tutto per l'operatore che crea RoleBinding all'apparenza Namespace nel cluster Kubernetes, pronto.

Fonte: habr.com

Aggiungi un commento