Einen Operator für Kubernetes in Golang schreiben

Notiz. übersetzen: Operatoren sind Hilfssoftware für Kubernetes, die die Ausführung von Routineaktionen an Clusterobjekten automatisieren soll, wenn bestimmte Ereignisse auftreten. Über Operatoren in haben wir bereits geschrieben Dieser Artikel, wo sie über die grundlegenden Ideen und Prinzipien ihrer Arbeit sprachen. Aber wenn es sich bei diesem Material eher um die Sichtweise des Betriebs vorgefertigter Komponenten für Kubernetes handelte, dann ist die jetzt vorgeschlagene Übersetzung des neuen Artikels bereits die Vision eines Entwicklers/DevOps-Ingenieurs, der über die Implementierung eines neuen Operators verwirrt ist.

Einen Operator für Kubernetes in Golang schreiben

Ich habe beschlossen, diesen Beitrag mit einem realen Beispiel zu schreiben, nachdem ich versucht hatte, eine Dokumentation zum Erstellen eines Operators für Kubernetes zu finden, bei der ich den Code studiert hatte.

Das beschriebene Beispiel ist folgendes: in unserem Kubernetes-Cluster, jeweils Namespace stellt die Sandbox-Umgebung eines Teams dar und wir wollten den Zugriff darauf beschränken, sodass Teams nur in ihren eigenen Sandboxes spielen können.

Sie können das erreichen, was Sie wollen, indem Sie einem Benutzer eine Gruppe zuweisen RoleBinding zu konkret Namespace и ClusterRole mit Bearbeitungsrechten. Die YAML-Darstellung sieht folgendermaßen aus:

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

(Rollenbindung.yamlIn roh)

Erstelle einen RoleBinding Sie können dies manuell tun, aber sobald die Marke von hundert Namespaces überschritten ist, wird es zu einer mühsamen Aufgabe. Hier kommen Kubernetes-Operatoren zum Einsatz – sie ermöglichen es Ihnen, die Erstellung von Kubernetes-Ressourcen basierend auf Änderungen an Ressourcen zu automatisieren. In unserem Fall wollen wir erschaffen RoleBinding beim Erstellen Namespace.

Definieren wir zunächst die Funktion mainDadurch werden die erforderlichen Einstellungen zum Ausführen der Anweisung vorgenommen und dann die Anweisungsaktion aufgerufen:

(Notiz. übersetzen: Hier und unten werden die Kommentare im Code ins Russische übersetzt. Darüber hinaus wurde die Einrückung auf Leerzeichen anstelle von [in Go empfohlenen] Tabulatoren korrigiert, nur um die Lesbarkeit im Habr-Layout zu verbessern. Nach jeder Auflistung gibt es Links zum Original auf GitHub, wo englischsprachige Kommentare und Tabs hinterlegt sind.)

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

Wir machen Folgendes:

  1. Wir konfigurieren einen Handler für bestimmte Betriebssystemsignale, um eine ordnungsgemäße Beendigung des Operators zu bewirken.
  2. Wir gebrauchen WaitGroupum alle Goroutinen ordnungsgemäß zu stoppen, bevor die Anwendung beendet wird.
  3. Wir ermöglichen den Zugriff auf den Cluster durch Erstellen clientset.
  4. Rennen NamespaceController, in dem sich unsere gesamte Logik befinden wird.

Jetzt brauchen wir eine Grundlage für die Logik, und in unserem Fall ist dies die erwähnte 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 roh)

Hier konfigurieren wir SharedIndexInformer, das effektiv (unter Verwendung eines Caches) auf Änderungen in Namespaces wartet (Lesen Sie mehr über Informanten im Artikel „Wie funktioniert eigentlich der Kubernetes-Scheduler?"- ca. Übersetzung). Danach verbinden wir uns EventHandler an den Informer, sodass beim Hinzufügen eines Namespace (Namespace) Funktion aufgerufen wird createRoleBinding.

Der nächste Schritt besteht darin, diese Funktion zu definieren 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 roh)

Wir erhalten den Namensraum als obj und wandeln Sie es in ein Objekt um Namespace. Dann definieren wir RoleBinding, basierend auf der eingangs erwähnten YAML-Datei, unter Verwendung des bereitgestellten Objekts Namespace und erschaffen RoleBinding. Abschließend protokollieren wir, ob die Erstellung erfolgreich war.

Die letzte zu definierende Funktion ist 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 roh)

Hier reden wir WaitGroupdass wir die Goroutine starten und dann aufrufen namespaceInformer, die zuvor definiert wurde. Wenn das Stoppsignal eintrifft, wird die Funktion beendet, informieren WaitGroup, die nicht mehr ausgeführt wird, und diese Funktion wird beendet.

Informationen zum Erstellen und Ausführen dieser Anweisung auf einem Kubernetes-Cluster finden Sie in Repositorys auf GitHub.

Das ist alles für den Betreiber, der erstellt RoleBinding Wann Namespace im Kubernetes-Cluster, fertig.

Source: habr.com

Kommentar hinzufügen