Écrire un opérateur pour Kubernetes dans Golang

Noter. trad.: Les opérateurs sont des logiciels auxiliaires pour Kubernetes, conçus pour automatiser l'exécution d'actions de routine sur les objets du cluster lorsque certains événements se produisent. Nous avons déjà parlé des opérateurs dans cet article, où ils ont parlé des idées et principes fondamentaux de leur travail. Mais si ce matériel était plutôt une vision du côté de l’exploitation de composants prêts à l’emploi pour Kubernetes, alors la traduction du nouvel article maintenant proposé est déjà la vision d’un développeur/ingénieur DevOps intrigué par la mise en œuvre d’un nouvel opérateur.

Écrire un opérateur pour Kubernetes dans Golang

J'ai décidé d'écrire cet article avec un exemple concret après mes tentatives de recherche de documentation sur la création d'un opérateur pour Kubernetes, qui sont passées par l'étude du code.

L'exemple qui va être décrit est le suivant : dans notre cluster Kubernetes, chaque Namespace représente l'environnement sandbox d'une équipe, et nous voulions en limiter l'accès afin que les équipes ne puissent jouer que dans leur propre sandbox.

Vous pouvez réaliser ce que vous voulez en attribuant à un utilisateur un groupe qui a RoleBinding à spécifique Namespace и ClusterRole avec droits d'édition. La représentation YAML ressemblera à ceci :

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

(liaison de rôle.yamldans brut)

Créer une RoleBinding Vous pouvez le faire manuellement, mais après avoir franchi la barre des cent espaces de noms, cela devient une tâche fastidieuse. C'est là que les opérateurs Kubernetes s'avèrent utiles : ils vous permettent d'automatiser la création de ressources Kubernetes en fonction des modifications apportées aux ressources. Dans notre cas, nous voulons créer RoleBinding en créant Namespace.

Tout d'abord, définissons la fonction mainqui effectue la configuration requise pour exécuter l'instruction, puis appelle l'action de l'instruction :

(Noter. trad.: ici et ci-dessous les commentaires dans le code sont traduits en russe. De plus, l'indentation a été corrigée en espaces au lieu des onglets [recommandés dans Go] uniquement dans le but d'une meilleure lisibilité dans la mise en page Habr. Après chaque liste, il y a des liens vers l'original sur GitHub, où les commentaires et les onglets en anglais sont stockés.)

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.godans brut)

Nous procédons comme suit :

  1. Nous configurons un gestionnaire pour des signaux spécifiques du système d'exploitation afin de provoquer la résiliation en douceur de l'opérateur.
  2. Nous utilisons WaitGrouppour arrêter gracieusement toutes les goroutines avant de terminer l'application.
  3. Nous donnons accès au cluster en créant clientset.
  4. Run NamespaceController, dans lequel se situera toute notre logique.

Nous avons maintenant besoin d'une base logique, et dans notre cas, c'est celle mentionnée 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
}

(contrôleur.godans brut)

Ici, nous configurons SharedIndexInformer, qui attendra effectivement (à l'aide d'un cache) les changements dans les espaces de noms (en savoir plus sur les informateurs dans l'article «Comment fonctionne réellement le planificateur Kubernetes ?"- environ. traduction). Après cela, nous nous connectons EventHandler à l'informateur, de sorte que lors de l'ajout d'un espace de noms (Namespace) la fonction est appelée createRoleBinding.

La prochaine étape consiste à définir cette fonction 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))
  }
}

(contrôleur.godans brut)

Nous obtenons l'espace de noms comme obj et convertissez-le en objet Namespace. Ensuite, nous définissons RoleBinding, basé sur le fichier YAML mentionné au début, en utilisant l'objet fourni Namespace et créer RoleBinding. Enfin, nous enregistrons si la création a réussi.

La dernière fonction à définir est 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
}

(contrôleur.godans brut)

Ici, nous parlons WaitGroupqu'on lance la goroutine puis qu'on appelle namespaceInformer, qui a été défini précédemment. Lorsque le signal d'arrêt arrive, il mettra fin à la fonction, informera WaitGroup, qui n'est plus exécuté, et cette fonction se terminera.

Des informations sur la création et l'exécution de cette instruction sur un cluster Kubernetes sont disponibles dans dépôts sur GitHub.

Voilà pour l'opérateur qui crée RoleBinding lorsque Namespace dans le cluster Kubernetes, prêt.

Source: habr.com

Ajouter un commentaire