Kubernetes Operators sind eine fortgeschrittene Erweiterung der Kubernetes-Plattform, die es ermöglicht, anwendungsspezifische Betriebsabläufe zu automatisieren und zu standardisieren. Sie stellen eine Methode dar, komplexe, zustandsbehaftete Anwendungen in Kubernetes zu verwalten, indem sie das Domänenwissen eines menschlichen Operators in Software kodieren. Dieses Kapitel erklärt das Konzept der Operators, ihre Funktionsweise, Anwendungsfälle und wie man eigene Operators entwickeln kann.
Ein Kubernetes Operator ist ein anwendungsspezifischer Controller, der das Kubernetes API erweitert, um komplexe Anwendungen zu erstellen, zu konfigurieren und zu verwalten. Operators folgen dem Kubernetes-Prinzip der Kontrolle durch Beobachtung des aktuellen Zustands und Arbeiten in Richtung eines gewünschten Zustands.
Traditionelle Kubernetes-Ressourcen wie Deployments und StatefulSets reichen oft nicht aus, um komplexe, zustandsbehaftete Anwendungen zu verwalten, die spezifische Kenntnisse für Operationen wie:
Operators schließen diese Lücke, indem sie solche anwendungsspezifischen Prozesse automatisieren.
Ein Kubernetes Operator besteht typischerweise aus mehreren Komponenten:
CRDs erweitern die Kubernetes API um benutzerdefinierte Ressourcentypen. Sie definieren das Schema für neue Objekttypen, die die zu verwaltende Anwendung darstellen.
Beispiel einer CRD für eine PostgreSQL-Datenbank:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresclusters.database.example.com
spec:
group: database.example.com
names:
kind: PostgresCluster
listKind: PostgresClusterList
plural: postgresclusters
singular: postgrescluster
shortNames:
- pgc
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
version:
type: string
replicas:
type: integer
minimum: 1
storage:
type: object
properties:
size:
type: string
storageClass:
type: stringEine Custom Resource ist eine Instanz der durch die CRD definierten Ressource, die den gewünschten Zustand der Anwendung beschreibt.
Beispiel einer PostgreSQL-CR:
apiVersion: database.example.com/v1
kind: PostgresCluster
metadata:
name: example-postgres
spec:
version: "13.3"
replicas: 3
storage:
size: "10Gi"
storageClass: "ssd"Der Controller ist der Kern des Operators. Er überwacht kontinuierlich Custom Resources und führt die notwendigen Aktionen aus, um den gewünschten Zustand zu erreichen.
Typische Aufgaben eines Controllers:
Die Operator-Fähigkeitsstufen beschreiben, wie ausgereift und leistungsfähig ein Operator ist:
Der Operator kann eine Anwendung installieren und eine grundlegende Konfiguration vornehmen. Dies entspricht in etwa den Fähigkeiten eines Deployment mit einigen zusätzlichen ConfigMaps oder Secrets.
Der Operator kann Upgrades der verwalteten Anwendung durchführen, einschließlich Datenmigration und Dienstkontinuität während des Upgrade-Prozesses.
Der Operator übernimmt zusätzliche Lebenszyklusaufgaben wie Backup, Restore, Skalierung und Monitoring mit Anpassungen für spezifische Anwendungsereignisse.
Der Operator sammelt und exportiert detaillierte Metriken und Status-Informationen, bietet spezifische Alarme und Warnungen für anwendungsspezifische Probleme.
Der Operator kann automatisch auf komplexe Probleme reagieren, wie Lastspitzen, Knotenausfälle oder andere Störungen, ohne menschliches Eingreifen.
Es gibt zahlreiche Open-Source-Operators für verbreitete Anwendungen:
Verwaltet Prometheus-Monitoring-Stacks, automatisiert die Konfiguration von Prometheus, Alertmanager und Grafana.
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
spec:
serviceAccountName: prometheus
replicas: 2
serviceMonitorSelector:
matchLabels:
team: frontendVerwaltet Elasticsearch-Cluster, einschließlich Skalierung, Updates und Monitoring.
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: logging
spec:
version: 7.12.0
nodeSets:
- name: masters
count: 3
config:
node.roles: ["master"]
- name: data
count: 5
config:
node.roles: ["data"]Automatisiert die Verwaltung von etcd-Clustern, einschließlich Erstellung, Skalierung, Upgrades und Disaster Recovery.
apiVersion: etcd.database.coreos.com/v1beta2
kind: EtcdCluster
metadata:
name: example-etcd-cluster
spec:
size: 3
version: "3.4.13"Verwaltet PostgreSQL-Cluster mit Funktionen wie Hochverfügbarkeit, automatischen Backups und Point-in-Time-Recovery.
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: acid-postgres-cluster
spec:
teamId: "acid"
volume:
size: 10Gi
numberOfInstances: 3
postgresql:
version: "13"Es gibt mehrere Frameworks und Tools, die die Entwicklung von Operators erleichtern:
Das Operator SDK ist ein Framework zur Beschleunigung der Operator-Entwicklung. Es unterstützt mehrere Programmiersprachen und Abstraktionsebenen:
# Operator SDK installieren
export RELEASE_VERSION=v1.16.0
curl -LO https://github.com/operator-framework/operator-sdk/releases/download/${RELEASE_VERSION}/operator-sdk_linux_amd64
# Ein neues Operator-Projekt erstellen
operator-sdk init --domain example.com --repo github.com/example/nginx-operator
# Eine neue API (CRD) erstellen
operator-sdk create api --group demo --version v1 --kind NginxService --resource --controller
# Code implementieren und Operator bauen
make docker-build docker-push
# Operator im Cluster deployen
make deployKUDO vereinfacht die Operator-Entwicklung durch einen deklarativen Ansatz ohne Programmierung.
Ein Framework zum Erstellen von Kubernetes APIs mit Go, das eine solide Grundlage für Controller bietet.
Lassen Sie uns genauer betrachten, wie ein einfacher Operator implementiert wird. Dies ist ein vereinfachtes Go-Beispiel für einen Nginx-Operator:
package controllers
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
webserverv1 "github.com/example/nginx-operator/api/v1"
)
// NginxServiceReconciler reconciles a NginxService object
type NginxServiceReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *NginxServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// Fetch the NginxService instance
nginxService := &webserverv1.NginxService{}
err := r.Get(ctx, req.NamespacedName, nginxService)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted
return ctrl.Result{}, nil
}
// Error reading the object
return ctrl.Result{}, err
}
// Check if the deployment already exists, if not create a new one
found := &appsv1.Deployment{}
err = r.Get(ctx, types.NamespacedName{Name: nginxService.Name, Namespace: nginxService.Namespace}, found)
if err != nil && errors.IsNotFound(err) {
// Define a new deployment
dep := r.deploymentForNginx(nginxService)
log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
err = r.Create(ctx, dep)
if err != nil {
log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
return ctrl.Result{}, err
}
// Deployment created successfully - return and requeue
return ctrl.Result{Requeue: true}, nil
} else if err != nil {
log.Error(err, "Failed to get Deployment")
return ctrl.Result{}, err
}
// Ensure the deployment replicas is the same as the spec
replicas := nginxService.Spec.Replicas
if *found.Spec.Replicas != replicas {
found.Spec.Replicas = &replicas
err = r.Update(ctx, found)
if err != nil {
log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
return ctrl.Result{}, err
}
// Spec updated - return and requeue
return ctrl.Result{Requeue: true}, nil
}
// Update the NginxService status
nginxService.Status.AvailableReplicas = found.Status.AvailableReplicas
err = r.Status().Update(ctx, nginxService)
if err != nil {
log.Error(err, "Failed to update NginxService status")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// deploymentForNginx returns a NginxService Deployment object
func (r *NginxServiceReconciler) deploymentForNginx(m *webserverv1.NginxService) *appsv1.Deployment {
ls := labelsForNginx(m.Name)
replicas := m.Spec.Replicas
dep := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: m.Name,
Namespace: m.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: ls,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: ls,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Image: "nginx:" + m.Spec.Version,
Name: "nginx",
Ports: []corev1.ContainerPort{{
ContainerPort: 80,
}},
}},
},
},
},
}
// Set NginxService instance as the owner and controller
ctrl.SetControllerReference(m, dep, r.Scheme)
return dep
}
// labelsForNginx returns the labels for selecting the resources
func labelsForNginx(name string) map[string]string {
return map[string]string{"app": "nginx", "webserver_cr": name}
}
func (r *NginxServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&webserverv1.NginxService{}).
Owns(&appsv1.Deployment{}).
Complete(r)
}In diesem Code:
Reconcile-Methode ist das Herzstück des
Controllers, die für jede Änderung am NginxService CR aufgerufen
wird.Es gibt verschiedene Wege, Operators in Kubernetes-Clustern bereitzustellen:
Manuelles Anwenden der CRDs und Deployment des Operator-Controllers.
OLM ist ein Werkzeug zur Installation, Aktualisierung und Verwaltung von Operators in einem Kubernetes-Cluster. Es bietet:
Beispiel für ein OLM-Installationspaket:
apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
name: postgresql-operator.v0.9.4
spec:
displayName: PostgreSQL Operator
version: 0.9.4
description: Manages PostgreSQL databases on Kubernetes
install:
strategy: deployment
spec:
deployments:
- name: postgresql-operator
spec:
replicas: 1
...OperatorHub.io ist ein zentraler Ort, an dem die Community Operators teilen kann. Viele Operators können direkt von hier installiert werden.
Operators sollten ressourcenschonend sein und nicht unnötig Cluster-Ressourcen verbrauchen.
Ein Operator sollte sich auf eine spezifische Anwendung oder einen spezifischen Dienst konzentrieren.
Gute Operators sollten robust auf Fehler reagieren können und transparentes Logging bieten.
Der Operator sollte detaillierte Statusinformationen bereitstellen, damit Benutzer den Zustand der verwalteten Ressourcen leicht verstehen können.
Operators sollten gründlich getestet werden, einschließlich Unit-Tests und End-to-End-Tests.
Operators eignen sich besonders für:
Datenbanken wie MongoDB, PostgreSQL, MySQL, Elasticsearch haben komplexe Betriebsabläufe wie Sharding, Replikation und Backup/Restore.
Systeme wie Prometheus und Grafana erfordern spezielle Konfiguration und Integration.
Istio, Linkerd und andere Service-Meshes benötigen komplexe Konfiguration und Verwaltung.
Anwendungen, die für Cloud-Umgebungen entwickelt wurden und spezifische Skalierungs- oder Resilienzmuster implementieren.
Traditionelle Anwendungen, die nicht für Kubernetes entwickelt wurden, können durch Operators mit notwendiger Betriebslogik ausgestattet werden.
Die Entwicklung von Operators kann komplex sein und erfordert ein tiefes Verständnis sowohl der Kubernetes-API als auch der zu verwaltenden Anwendung.
Das Testen von Operators ist herausfordernd, da reale Cluster-Umgebungen und komplexe Zustandsänderungen simuliert werden müssen.
Operators müssen mit Änderungen der Anwendung und der Kubernetes-API Schritt halten.
Die Aktualisierung von Operators selbst kann komplex sein, besonders wenn sie kritische zustandsbehaftete Anwendungen verwalten.