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
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
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 main
Dadurch 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() // Ожидаем, что все остановлено
}
Wir machen Folgendes:
- Wir konfigurieren einen Handler für bestimmte Betriebssystemsignale, um eine ordnungsgemäße Beendigung des Operators zu bewirken.
- Wir gebrauchen
WaitGroup
um alle Goroutinen ordnungsgemäß zu stoppen, bevor die Anwendung beendet wird. - Wir ermöglichen den Zugriff auf den Cluster durch Erstellen
clientset
. - 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
}
(
Hier konfigurieren wir SharedIndexInformer
, das effektiv (unter Verwendung eines Caches) auf Änderungen in Namespaces wartet (Lesen Sie mehr über Informanten im Artikel „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))
}
}
(
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
}
(
Hier reden wir WaitGroup
dass 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
Das ist alles für den Betreiber, der erstellt RoleBinding
Wann Namespace
im Kubernetes-Cluster, fertig.
Source: habr.com