24 Grundlagen von Kubernetes Operatoren

Ein Kubernetes Operator ist ein spezifisches Stück Software, das das Wissen eines menschlichen Operators in einer Software automatisiert. Ein Operator erweitert Kubernetes, um Anwendungen und deren Lebenszyklus auf Kubernetes zu verwalten. Dabei handelt es sich um ein Controller-Pattern, das von Kubernetes CRDs (Custom Resource Definitions) und Controllern verwendet wird.

24.0.1 Grundprinzipien eines Operators

  1. Custom Resource Definition (CRD): Erweitert die Kubernetes-API um benutzerdefinierte Ressourcen. Diese Ressourcen können wie native Kubernetes-Ressourcen (Pods, Services, etc.) verwaltet werden.
  2. Controller: Überwacht den Zustand der benutzerdefinierten Ressourcen und ergreift Maßnahmen, um den gewünschten Zustand zu erreichen.

24.1 Vorteile von Kubernetes Operatoren

  1. Automatisierung: Operatoren automatisieren komplexe, häufige und repetitive Aufgaben wie Backups, Upgrades und Skalierung.
  2. Konsistenz: Operatoren sorgen für Konsistenz in der Verwaltung von Anwendungen, indem sie Best Practices und vordefinierte Abläufe implementieren.
  3. Erweiterbarkeit: Mit Operatoren können Sie die Funktionalität von Kubernetes erweitern, um spezifische Anforderungen Ihrer Anwendungen zu erfüllen.

24.2 Aufbau eines Kubernetes Operators

Ein Kubernetes Operator besteht in der Regel aus drei Hauptkomponenten:

  1. Custom Resource Definitions (CRDs): Definieren die benutzerdefinierten Ressourcen, die der Operator verwalten wird.
  2. Custom Controller: Implementiert die Logik, um den gewünschten Zustand der benutzerdefinierten Ressourcen zu erreichen.
  3. Deployment: Stellt den Controller als Deployment auf einem Kubernetes-Cluster bereit.

24.2.1 Beispiel: Custom Resource Definition

Eine CRD definiert die Struktur und Eigenschaften einer benutzerdefinierten Ressource. Hier ein Beispiel für eine einfache CRD:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myresources.example.com
spec:
  group: example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                name:
                  type: string
  scope: Namespaced
  names:
    plural: myresources
    singular: myresource
    kind: MyResource
    shortNames:
    - mr

In diesem Beispiel definieren wir eine benutzerdefinierte Ressource MyResource innerhalb der Gruppe example.com. Die Ressource hat ein Feld name vom Typ string.

24.2.2 Beispiel: Controller-Logik

Ein Controller überwacht die benutzerdefinierte Ressource und führt Aktionen durch, um den gewünschten Zustand zu erreichen. Hier ein vereinfachtes Beispiel in Go:

package main

import (
    "context"
    "fmt"
    "os"

    "sigs.k8s.io/controller-runtime/pkg/client/config"
    "sigs.k8s.io/controller-runtime/pkg/controller"
    "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    "sigs.k8s.io/controller-runtime/pkg/manager"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type MyResourceReconciler struct {
    client client.Client
    scheme *runtime.Scheme
}

func (r *MyResourceReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    fmt.Println("Reconciling MyResource:", req.Name)
    return reconcile.Result{}, nil
}

func main() {
    cfg, err := config.GetConfig()
    if err != nil {
        fmt.Println("Error getting config:", err)
        os.Exit(1)
    }

    mgr, err := manager.New(cfg, manager.Options{})
    if err != nil {
        fmt.Println("Error creating manager:", err)
        os.Exit(1)
    }

    r := &MyResourceReconciler{
        client: mgr.GetClient(),
        scheme: mgr.GetScheme(),
    }

    c, err := controller.New("myresource-controller", mgr, controller.Options{
        Reconciler: r,
    })
    if err != nil {
        fmt.Println("Error creating controller:", err)
        os.Exit(1)
    }

    fmt.Println("Starting manager")
    if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
        fmt.Println("Error starting manager:", err)
        os.Exit(1)
    }
}

In diesem Beispiel:

  1. Wir erstellen einen Manager, der die Controller-Laufzeitumgebung verwaltet.
  2. Wir implementieren einen einfachen Reconciler, der die benutzerdefinierte Ressource überwacht.
  3. Wir erstellen einen Controller, der den Reconciler verwendet, um den Zustand der benutzerdefinierten Ressource zu überwachen und zu verwalten.