Anmerkung des Übersetzers.: Operatoren (Operators) sind Hilfssoftware für Kubernetes, die darauf abzielt, die Ausführung routinemäßiger Aufgaben im Zusammenhang mit Clusterobjekten bei bestimmten Ereignissen zu automatisieren. Wir haben bereits über Operatoren in , wo wir die grundlegenden Ideen und Prinzipien ihrer Funktionsweise erläutert haben, geschrieben. Während das Material damals eher einen Blick aus der Perspektive der Nutzung fertiger Kubernetes-Komponenten bot, ist die jetzt vorgeschlagene Übersetzung eines neuen Artikels eine Sichtweise eines Entwicklers/DevOps-Ingenieurs, der mit der Implementierung eines neuen Operators beschäftigt ist.

Ich habe diesen Beitrag mit einem Beispiel aus dem echten Leben nach meinen Versuchen verfasst, Dokumentationen zur Erstellung eines Operators für Kubernetes zu finden, die durch das Studium des Codes gingen.
Das Beispiel, das beschrieben wird, ist: In unserem Kubernetes-Cluster stellt jeder Namespace eine Sandbox-Umgebung für ein Team dar, und wir wollten den Zugriff so beschränken, dass die Teams nur in ihren eigenen Sandkästen spielen konnten.
Das gewünschte Ziel kann erreicht werden, indem einem Benutzer eine Gruppe zugewiesen wird, die RoleBinding zu den Namespace und Clusterrolle mit Bearbeitungsrechten hat. Die YAML-Darstellung würde folgendermaßen aussehen:
---
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(, in )
Einen solchen Operator kann man auch manuell erstellen, aber nach der Erreichung von über hundert Namespaces wird dies mühsam. Hier kommen Kubernetes-Operatoren ins Spiel – sie ermöglichen die Automatisierung der Erstellung von Kubernetes-Ressourcen basierend auf Änderungen an Ressourcen. In unserem Fall wollen wir erstellen RoleBinding bei der Erstellung RoleBinding Zuerst definieren wir die Funktion Namespace.
, die die erforderliche Konfiguration zum Starten des Operators vornimmt und dann die Aktion des Operators aufruft: main: Hier und im Folgenden sind die Kommentare im Code ins Deutsche übersetzt. Außerdem wurden die Einrückungen aus Tabulatoren, die in Go empfohlen werden, aus Gründen der besseren Lesbarkeit in Leerzeichen umgewandelt. Nach jedem Listing werden Links zum Original auf GitHub bereitgestellt, wo die englischen Kommentare und Tabs erhalten bleiben.
(Anmerkung des Übersetzers.: Hier und im Folgenden sind die Kommentare im Code ins Russische übersetzt. Außerdem wurden die Einrückungen aus Gründen der besseren Lesbarkeit in der Habr-Layout auf Leerzeichen anstelle von [in Go empfohlenen] Tabs geändert. Nach jeder Auflistung finden Sie Links zum Original auf GitHub, wo die englischen Kommentare und Tabs beibehalten wurden.)
func main() {
// Wir richten die Protokollausgabe auf STDOUT in der Konsole ein
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Wir erstellen einen Kanal für das Empfangen von Betriebssystemsignalen
stop := make(chan struct{}) // Wir erstellen einen Kanal für das Empfangen des Stoppsignals
// Wir registrieren das Empfangen von SIGTERM im Kanal sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutinen können sich selbst zur WaitGroup hinzufügen,
// damit auf deren Abschluss gewartet werden kann
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Setzen Sie dieses Flag, wenn Sie außerhalb des Clusters arbeiten.")
flag.Parse()
// Wir erstellen ein Clientset zur Interaktion mit dem Kubernetes-Cluster
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Wir warten auf Signale (bis ein Signal empfangen wird, passiert nichts weiter)
log.Printf("Herunterfahren...")
close(stop) // Wir sagen den Goroutinen, dass sie anhalten sollen
wg.Wait() // Wir warten, bis alles gestoppt ist
}(, in )
Wir machen Folgendes:
- Wir konfigurieren Handler für spezifische Betriebssystemsignale, um eine korrekte (graceful) Beendigung des Operators auszulösen.
- Verwenden Sie
WaitGroup, um alle Goroutinen korrekt vor der Beendigung der Anwendung anzuhalten. - Wir bieten Zugang zum Cluster durch die Erstellung von
Clientset. - Starten
NamespaceController, in dem unsere gesamte Logik untergebracht ist.
Jetzt brauchen wir die Grundlage für die Logik, und in unserem Fall ist das der 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
}(, in )
Hier richten wir SharedIndexInformer, der effizient (unter Verwendung des Caches) auf Änderungen in den Namensräumen wartet (mehr über Informer erfahren Sie im Artikel „» — Anm. d. Ü.). Danach verbinden wir EventHandler mit dem Informer, sodass beim Hinzufügen eines Namensraums (Namespace) die Funktion createRoleBinding.
Der nächste Schritt ist, 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("Fehler beim Erstellen der Rollenbindung: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Erstellte AD Rolle-Bindung für Namespace: %s", roleBinding.Name))
}
}(, in )
Wir erhalten den Namensraum als obj und wandeln ihn in ein Objekt um Namespace. Dann definieren wir RoleBinding, basierend auf der im YAML-Datei zu Beginn erwähnten, unter Verwendung des bereitgestellten Objekts Namespace und erstellen RoleBinding. Schließlich loggen wir, ob die Erstellung erfolgreich war.
Die letzte Funktion, die definiert werden muss, ist Ausführen:
// 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
}(, in )
Hier sprechen wir WaitGroup, dass wir eine Goroutine starten und dann namespaceInformer, die im Voraus definiert wurde, aufrufen. Wenn ein Stoppsignal empfangen wird, wird die Funktion beendet, und es wird gemeldet WaitGroup, dass sie nicht mehr ausgeführt wird, und diese Funktion wird ihre Arbeit einstellen.
Informationen über den Aufbau und die Ausführung dieses Operators im Kubernetes-Cluster finden Sie in .
Dieser Operator, der erstellt wird, RoleBinding wird aktiviert, wenn Namespace im Kubernetes-Cluster erscheint, bereit.
Quelle: habr.com
