Operatory Kubernetes: CRD, Controller Pattern i Operator SDK
Jak zarządzać klastrem PostgreSQL w środowisku produkcyjnym? Musisz monitorować główny, wykrywać awarii, promuj replikę, aktualizuj konfiguracje, wykonuj kopie zapasowe planujesz i zarządzasz rotacją certyfikatów. Są to operacje, które wykonuje ekspert DBA wie, jak to zrobić na pamięć, ale które wymagają godzin pracy fizycznej za każdym razem, gdy coś pójdzie nie tak krzywy o 3 nad ranem.
Wzór Operator Kubernetesa pozwala na kodyfikację tej wiedzy działający w oprogramowaniu natywnym Kubernetes: kontroler obserwujący stan klastra, porównuje go ze stanem pożądanym i podejmuje niezbędne działania, aby je pogodzić. Automatycznie. Ciągle. Bez interwencji człowieka. W tym artykule będziemy budować kompletny Operator korzystający z Operator SDK i Kubebuilder, dokładnie rozumiejący kontroler wzór i pętla uzgadniania.
Czego się nauczysz
- Co to jest operator Kubernetes i kiedy warto go zbudować
- Niestandardowa definicja zasobu (CRD): schemat, wersjonowanie, walidacja
- Wzorzec kontrolera i pętla uzgadniania
- Operator SDK vs Kubebuilder: różnice i kiedy którego używać
- Zaimplementuj kompletny operator za pomocą Kubebuilder
- Centrum operatora i menedżer cyklu życia operatora (OLM)
- Testowanie operatora za pomocą envtest
- Operator produkcji: Operator Zalando Postgres, Strimzi dla Kafki
Co to jest operator Kubernetes
Termin „Operator” został wprowadzony przez CoreOS w 2016 roku w celu opisania wzorca: a oprogramowanie, które zawiera wiedzę operacyjną dotyczącą konkretnej aplikacji, np zestaw kontrolerów Kubernetes. Formalna definicja Google:
Operator i metoda pakowania, wdrażania i zarządzania aplikacją Kubernetes. Operator wdraża i automatyzuje typowe zadania człowieka podczas zarządzania tego typu aplikacji: wdrażanie, aktualizacje, tworzenie kopii zapasowych, przełączanie awaryjne, skalowanie.
Operator rozszerza model deklaratywny Kubernetes na określone domeny. Zamiast „daj mi Poda” możesz powiedzieć „daj mi klaster PostgreSQL z 3 replikami, codzienną kopią zapasową na S3, automatyczne przełączanie awaryjne i certyfikaty TLS”. Operator wie, jak to przekształcić specyfikacja wysokiego poziomu w konkretnych zasobach Kubernetes.
Model dojrzałości operatora
Model Możliwości Operatora definiuje 5 poziomów rosnącej dojrzałości:
| Poziom | Nazwa | Pojemność |
|---|---|---|
| 1 | Podstawowa instalacja | Zautomatyzowane udostępnianie aplikacji |
| 2 | Bezproblemowe aktualizacje | Poprawki i mniejsze aktualizacje wersji |
| 3 | Pełny cykl życia | Kopia zapasowa, odzyskiwanie po awarii, rekonfiguracja |
| 4 | Głębokie spostrzeżenia | Metryki, alerty, przetwarzanie logów, analiza obciążenia |
| 5 | Autopilota | Automatyczne skalowanie, automatyczna konfiguracja, wykrywanie anomalii |
Niestandardowa definicja zasobu (CRD)
CRD rozszerza API Kubernetes o niestandardowe typy zasobów. Zamiast po prostu używać
zasoby natywne (Pod, Wdrożenie, Usługa), możesz zdefiniować konkretne zasoby domeny
jak PostgresCluster, KafkaTopic, MLModel.
Zdefiniuj CRD
# postgres-cluster-crd.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresclusters.database.example.com
spec:
group: database.example.com
scope: Namespaced
names:
plural: postgresclusters
singular: postgrescluster
kind: PostgresCluster
shortNames:
- pgc
versions:
- name: v1alpha1
served: true
storage: true
# Schema di validazione OpenAPI v3
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- replicas
- version
properties:
replicas:
type: integer
minimum: 1
maximum: 5
description: "Numero di repliche PostgreSQL"
version:
type: string
enum: ["14", "15", "16"]
description: "Versione PostgreSQL"
storage:
type: object
properties:
size:
type: string
pattern: "^[0-9]+Gi$"
default: "10Gi"
storageClass:
type: string
default: "fast-ssd"
backup:
type: object
properties:
enabled:
type: boolean
default: false
schedule:
type: string
description: "Cron expression per backup schedulato"
s3Bucket:
type: string
resources:
type: object
properties:
requests:
type: object
properties:
memory:
type: string
cpu:
type: string
limits:
type: object
properties:
memory:
type: string
cpu:
type: string
status:
type: object
properties:
phase:
type: string
enum: ["Pending", "Creating", "Running", "Degraded", "Failed"]
readyReplicas:
type: integer
primaryEndpoint:
type: string
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
reason:
type: string
message:
type: string
lastTransitionTime:
type: string
format: date-time
# Stampa colonne aggiuntive in kubectl get
additionalPrinterColumns:
- name: Replicas
type: integer
jsonPath: .spec.replicas
- name: Version
type: string
jsonPath: .spec.version
- name: Status
type: string
jsonPath: .status.phase
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
# Subresource status (necessario per UpdateStatus)
subresources:
status: {}
Niestandardowy zasób w akcji
# my-postgres-cluster.yaml
apiVersion: database.example.com/v1alpha1
kind: PostgresCluster
metadata:
name: myapp-db
namespace: production
spec:
replicas: 3
version: "16"
storage:
size: "100Gi"
storageClass: fast-ssd
backup:
enabled: true
schedule: "0 2 * * *" # ogni notte alle 2:00
s3Bucket: "my-postgres-backups"
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
Wzorzec kontrolera i pętla uzgadniania
Sercem Operatora jest kontroler: proces, który trwa w sposób ciągły obserwuje aktualny stan zasobów w klastrze i porównuje go ze stanem pożądanym zadeklarowany w zasobie niestandardowym. W przypadku wystąpienia różnicy (dryftu) sterownik wykonuje operację działań niezbędnych do pojednania obu państw. Cykl ten nazywa się pętla uzgodnień.
// Pseudocodice del reconcile loop
for {
desiredState = getDesiredState(customResource)
currentState = getCurrentState(cluster)
if currentState != desiredState {
actions = computeActions(desiredState, currentState)
execute(actions)
}
// Attendi il prossimo trigger (evento API server o requeueing)
waitForTrigger()
}
Kontroler nie stosuje podejścia opartego wyłącznie na zdarzeniach (gdzie każde zdarzenie uruchamia akcję specyfikacja), ale podejście oparte na poziomie: obserwuj stan całkowity i pogodzić. Dzięki temu kontrolery są bardziej niezawodne: jeśli brakuje zdarzeń (awaria, ponowne uruchomienie), kontroler i tak uruchomi się ponownie i osiągnie prawidłowy stan.
Kubebuilder: budowanie operatora
Kubebuilder to oficjalny framework CNCF do budowania Operatora w Go. Wygeneruj rusztowanie projektu, zarządza komunikacją z serwerem API i udostępnia pomocników dla pętli uzgadniania. Operator SDK jest oparty na Kubebuilderze i dodaje obsługę Operatory Helma i Ansible.
Konfiguracja projektu
# Installa Kubebuilder
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder
sudo mv kubebuilder /usr/local/bin/
# Crea un nuovo progetto Operator
mkdir postgres-operator && cd postgres-operator
kubebuilder init \
--domain database.example.com \
--repo github.com/myorg/postgres-operator
# Genera l'API e il controller per PostgresCluster
kubebuilder create api \
--group database \
--version v1alpha1 \
--kind PostgresCluster \
--resource \
--controller
# Struttura generata:
# api/v1alpha1/
# postgrescluster_types.go <- Definizione della struct CRD
# groupversion_info.go
# internal/controller/
# postgrescluster_controller.go <- Logica del reconcile loop
# config/crd/ <- Manifest YAML della CRD
Zdefiniuj typ interfejsu API
// api/v1alpha1/postgrescluster_types.go
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
)
// PostgresClusterSpec definisce lo stato desiderato
type PostgresClusterSpec struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=5
Replicas int32 `json:"replicas"`
// +kubebuilder:validation:Enum={"14","15","16"}
Version string `json:"version"`
Storage PostgresStorageSpec `json:"storage,omitempty"`
Backup PostgresBackupSpec `json:"backup,omitempty"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
type PostgresStorageSpec struct {
// +kubebuilder:default="10Gi"
Size string `json:"size,omitempty"`
StorageClass string `json:"storageClass,omitempty"`
}
type PostgresBackupSpec struct {
Enabled bool `json:"enabled,omitempty"`
Schedule string `json:"schedule,omitempty"`
S3Bucket string `json:"s3Bucket,omitempty"`
}
// PostgresClusterStatus descrive lo stato osservato
type PostgresClusterStatus struct {
Phase string `json:"phase,omitempty"`
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
PrimaryEndpoint string `json:"primaryEndpoint,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=".spec.replicas"
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
type PostgresCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PostgresClusterSpec `json:"spec,omitempty"`
Status PostgresClusterStatus `json:"status,omitempty"`
}
Pętla uzgadniania: wdrożenie
// internal/controller/postgrescluster_controller.go
package controller
import (
"context"
"fmt"
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"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
databasev1alpha1 "github.com/myorg/postgres-operator/api/v1alpha1"
)
type PostgresClusterReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=database.example.com,resources=postgresclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=database.example.com,resources=postgresclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
func (r *PostgresClusterReconciler) Reconcile(
ctx context.Context,
req ctrl.Request,
) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// 1. Ottieni la Custom Resource
pgCluster := &databasev1alpha1.PostgresCluster{}
if err := r.Get(ctx, req.NamespacedName, pgCluster); err != nil {
if errors.IsNotFound(err) {
// CR eliminata, pulizia gia gestita dai finalizer
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
logger.Info("Reconciling PostgresCluster",
"name", pgCluster.Name,
"namespace", pgCluster.Namespace,
"replicas", pgCluster.Spec.Replicas)
// 2. Reconcilia il Service headless
if err := r.reconcileHeadlessService(ctx, pgCluster); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to reconcile headless service: %w", err)
}
// 3. Reconcilia lo StatefulSet
sts, err := r.reconcileStatefulSet(ctx, pgCluster)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to reconcile statefulset: %w", err)
}
// 4. Aggiorna lo status della CR
pgCluster.Status.ReadyReplicas = sts.Status.ReadyReplicas
pgCluster.Status.PrimaryEndpoint = fmt.Sprintf(
"%s-0.%s.%s.svc.cluster.local:5432",
pgCluster.Name,
pgCluster.Name,
pgCluster.Namespace,
)
if sts.Status.ReadyReplicas == pgCluster.Spec.Replicas {
pgCluster.Status.Phase = "Running"
} else if sts.Status.ReadyReplicas > 0 {
pgCluster.Status.Phase = "Degraded"
} else {
pgCluster.Status.Phase = "Creating"
}
if err := r.Status().Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update status: %w", err)
}
logger.Info("Reconciliation complete",
"phase", pgCluster.Status.Phase,
"readyReplicas", pgCluster.Status.ReadyReplicas)
return ctrl.Result{}, nil
}
func (r *PostgresClusterReconciler) reconcileStatefulSet(
ctx context.Context,
pgCluster *databasev1alpha1.PostgresCluster,
) (*appsv1.StatefulSet, error) {
desired := r.buildStatefulSet(pgCluster)
// Imposta il owner reference per la garbage collection automatica
if err := ctrl.SetControllerReference(pgCluster, desired, r.Scheme); err != nil {
return nil, err
}
existing := &appsv1.StatefulSet{}
err := r.Get(ctx, client.ObjectKeyFromObject(desired), existing)
if errors.IsNotFound(err) {
// StatefulSet non esiste: crealo
if err := r.Create(ctx, desired); err != nil {
return nil, fmt.Errorf("failed to create StatefulSet: %w", err)
}
return desired, nil
}
if err != nil {
return nil, err
}
// StatefulSet esiste: aggiornalo se necessario
existing.Spec.Replicas = desired.Spec.Replicas
existing.Spec.Template = desired.Spec.Template
if err := r.Update(ctx, existing); err != nil {
return nil, fmt.Errorf("failed to update StatefulSet: %w", err)
}
return existing, nil
}
func (r *PostgresClusterReconciler) buildStatefulSet(
pgCluster *databasev1alpha1.PostgresCluster,
) *appsv1.StatefulSet {
image := fmt.Sprintf("postgres:%s", pgCluster.Spec.Version)
return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: pgCluster.Name,
Namespace: pgCluster.Namespace,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &pgCluster.Spec.Replicas,
ServiceName: pgCluster.Name,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": pgCluster.Name},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": pgCluster.Name},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "postgres",
Image: image,
Resources: pgCluster.Spec.Resources,
},
},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
StorageClassName: &pgCluster.Spec.Storage.StorageClass,
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: pgCluster.Spec.Storage.ParsedSize(),
},
},
},
},
},
},
}
}
// SetupWithManager registra il controller con il manager
func (r *PostgresClusterReconciler) SetupWithManager(
mgr ctrl.Manager,
) error {
return ctrl.NewControllerManagedBy(mgr).
For(&databasev1alpha1.PostgresCluster{}).
Owns(&appsv1.StatefulSet{}). // Reconcilia quando cambia lo StatefulSet owned
Owns(&corev1.Service{}).
Complete(r)
}
Finalizator: Oczyszczanie zasobów
Finalizatory umożliwiają wykonanie operacji czyszczenia przed zwolnieniem zasobu wyeliminowane. Bez finalizatora usunięcie CR PostgresCluster spowodowałoby usunięcie CR, ale niekoniecznie dane na S3 lub kopie zapasowe. Dzięki finalizatorom możesz zarządzać tym czyszczeniem:
// Aggiungi finalizer handling al Reconcile
const pgClusterFinalizer = "database.example.com/finalizer"
func (r *PostgresClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
pgCluster := &databasev1alpha1.PostgresCluster{}
if err := r.Get(ctx, req.NamespacedName, pgCluster); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Gestione eliminazione
if !pgCluster.DeletionTimestamp.IsZero() {
if controllerutil.ContainsFinalizer(pgCluster, pgClusterFinalizer) {
// Esegui cleanup
if err := r.cleanupExternalResources(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
// Rimuovi il finalizer
controllerutil.RemoveFinalizer(pgCluster, pgClusterFinalizer)
if err := r.Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// Aggiungi finalizer se non presente
if !controllerutil.ContainsFinalizer(pgCluster, pgClusterFinalizer) {
controllerutil.AddFinalizer(pgCluster, pgClusterFinalizer)
if err := r.Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
}
// ... resto della logica di reconcile
return ctrl.Result{}, nil
}
Testowanie operatora za pomocą envtest
Kubebuilder zapewnia zatwierdź, środowisko testowe, które uruchamia serwer API Prawdziwy Kubernetes (bez kubeletów i węzłów) do zintegrowanego testowania kontrolerów:
// internal/controller/postgrescluster_controller_test.go
package controller
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
databasev1alpha1 "github.com/myorg/postgres-operator/api/v1alpha1"
)
var _ = Describe("PostgresCluster Controller", func() {
const (
timeout = time.Second * 10
interval = time.Millisecond * 250
)
Context("Quando crea un PostgresCluster", func() {
It("Deve creare lo StatefulSet corrispondente", func() {
ctx := context.Background()
pgCluster := &databasev1alpha1.PostgresCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-postgres",
Namespace: "default",
},
Spec: databasev1alpha1.PostgresClusterSpec{
Replicas: 1,
Version: "16",
Storage: databasev1alpha1.PostgresStorageSpec{
Size: "10Gi",
StorageClass: "standard",
},
},
}
Expect(k8sClient.Create(ctx, pgCluster)).Should(Succeed())
// Verifica che lo StatefulSet venga creato
stsLookupKey := types.NamespacedName{
Name: "test-postgres",
Namespace: "default",
}
createdSts := &appsv1.StatefulSet{}
Eventually(func() bool {
err := k8sClient.Get(ctx, stsLookupKey, createdSts)
return err == nil
}, timeout, interval).Should(BeTrue())
// Verifica le specifiche dello StatefulSet
Expect(*createdSts.Spec.Replicas).Should(Equal(int32(1)))
Expect(createdSts.Spec.Template.Spec.Containers[0].Image).
Should(Equal("postgres:16"))
// Cleanup
Expect(k8sClient.Delete(ctx, pgCluster)).Should(Succeed())
})
})
})
Kompilacja i wdrożenie operatora
# Build dell'immagine
make docker-build docker-push IMG="myregistry/postgres-operator:v0.1.0"
# Deploy dell'Operator nel cluster
make deploy IMG="myregistry/postgres-operator:v0.1.0"
# Verifica il deployment
kubectl get pods -n postgres-operator-system
kubectl logs -n postgres-operator-system deployment/postgres-operator-controller-manager
# Applica una CR
kubectl apply -f my-postgres-cluster.yaml
kubectl get postgresclusters -n production
kubectl describe postgrescluster myapp-db -n production
Operator produkcji: prawdziwe przykłady
Nie jest konieczne budowanie operatora dla każdej popularnej aplikacji. Ekosystem Kubernetes oferuje dojrzałe operatory dla głównych aplikacji stanowych:
Operator Zalando Postgres
# Installa il Postgres Operator di Zalando (level 5 maturity)
helm repo add postgres-operator-charts \
https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm install postgres-operator \
postgres-operator-charts/postgres-operator \
-n postgres-operator --create-namespace
# Crea un cluster PostgreSQL con HA e backup su S3
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: myapp-postgres
namespace: production
spec:
teamId: "myteam"
volume:
size: 100Gi
storageClass: fast-ssd
numberOfInstances: 3
users:
myapp:
- superuser
- createdb
databases:
myapp: myapp
postgresql:
version: "16"
parameters:
shared_buffers: "1GB"
max_connections: "200"
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
patroni:
failsafe_mode: false
# Backup automatico su S3 con WAL-G
enableLogicalBackup: true
logicalBackupSchedule: "00 02 * * *"
Strimzi: Kafka na Kubernetesie
# Kafka cluster con Strimzi (level 5 maturity)
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: production-cluster
namespace: kafka
spec:
kafka:
version: 3.7.0
replicas: 3
listeners:
- name: plain
port: 9092
type: internal
tls: false
- name: tls
port: 9093
type: internal
tls: true
config:
offsets.topic.replication.factor: 3
transaction.state.log.replication.factor: 3
transaction.state.log.min.isr: 2
default.replication.factor: 3
min.insync.replicas: 2
inter.broker.protocol.version: "3.7"
storage:
type: jbod
volumes:
- id: 0
type: persistent-claim
size: 200Gi
class: fast-ssd
deleteClaim: false
resources:
requests:
memory: 4Gi
cpu: 2000m
limits:
memory: 8Gi
cpu: 4000m
zookeeper:
replicas: 3
storage:
type: persistent-claim
size: 10Gi
class: fast-ssd
deleteClaim: false
entityOperator:
topicOperator: {}
userOperator: {}
Menedżer cyklu życia operatora (OLM)
OLM zarządza instalacją, aktualizacją i cyklem życia oprogramowania Operator w klastrze. Oraz mechanizm używany przez OperatorHub.io do dystrybucji Operatorów.
# Installa OLM nel cluster
curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.28.0/install.sh | bash -s v0.28.0
# Installa un Operator da OperatorHub tramite OLM
kubectl create -f https://operatorhub.io/install/postgres-operator.yaml
# Verifica gli Operator installati
kubectl get csv -n operators # ClusterServiceVersion
kubectl get subscription -n operators
Najlepsze praktyki dla operatorów
Lista kontrolna dla operatorów produkcji
- Użyj finalizatora: Zawsze dla zasobów, które mają zewnętrzne skutki uboczne (wiadro S3, rekordy DNS itp.)
- Zaimplementuj warunki statusu: Postępuj zgodnie ze wzorcem warunku Kubernetes (typ, status, przyczyna, komunikat)
- Idempotencja: Pętla uzgadniająca musi być bezpieczna i można ją wykonać wiele razy z tym samym skutkiem
- Obsługa błędów przy ponownej próbie: USA
ctrl.Result{RequeueAfter: time.Minute}dla błędów przejściowych - Nie wykonuj operacji blokujących: Pojednanie nie może blokować; używaj goroutines do długich operacji
- Minimalna liczba RBAC: W adnotacjach używaj wyłącznie niezbędnych uprawnień
+kubebuilder:rbac - Testowanie za pomocą envtest: Napisz testy integracyjne dla każdego scenariusza uzgadniania
- Wersjonowanie API: Użyj wersjonowania (v1alpha1 -> v1beta1 -> v1) i webhooków konwersji na potrzeby migracji
Kiedy NIE budować operatora
Operatorzy ponoszą znaczne koszty rozwoju i utrzymania. Zbuduj jeden ma to sens tylko wtedy, gdy: (1) w OperatorHub znajduje się dojrzały operator dla aplikacji, która zarządzasz, korzystaj z tego; (2) aplikacja jest złożona, stanowa i wymaga wiedzy operacje specjalistyczne, które mają zostać zautomatyzowane; (3) masz dedykowane zespoły, które mogą utrzymać Koduj z czasem. W przypadku prostego wdrożenia nie jest to konieczne.
Wnioski i dalsze kroki
Wzorzec Operatora Kubernetesa jest naturalnym rozwinięciem deklaratywnej filozofii Kubernetes do złożonych domen aplikacji. Zamiast ręcznie zarządzać bazami danych, systemy przesyłania wiadomości i usługi stanowe, kodyfikujesz wiedzę operacyjną w formacie a kontroler pracujący 24 godziny na dobę, 7 dni w tygodniu, aby utrzymać system w pożądanym stanie.
Kubebuilder i Operator SDK zapewniają podstawę: rusztowanie projektu, zarządzanie API serwera, uzgodnij framework. Ale logika biznesowa – jak zarządzać Przełączanie awaryjne PostgreSQL, jak skalować klaster Kafki, jak obracać certyfikaty TLS - muszą być wdrażane z głęboką wiedzą na temat konkretnego zastosowania.
Nadchodzące artykuły z serii Kubernetes at Scale
Powiązane zasoby i serie
- Kubernetes Networking: CNI, Cilium z eBPF
- Pamięć trwała w Kubernetes
- MLOps: skalowanie ML w Kubernetes — Operator rurociągu szkoleniowego







