Operator pattern: CRD + custom controller
Operator — это паттерн расширения Kubernetes, который инкапсулирует «как управлять stateful-приложением в кластере» в коде кастомного контроллера. Если built-in Deployment controller знает, как разворачивать stateless приложения (rolling update, replicas, история), то Postgres operator знает, как разворачивать Postgres-кластер: primary + replicas, репликация, ежедневные бэкапы, failover, point-in-time recovery. Это знание DBA, перенесённое в код. Пользователь пишет 30-строчный YAML PostgresCluster, оператор делает всё остальное.
Производственные операции Kafka: метрики и управление
Что такое Operator формально
Operator состоит ровно из двух частей:
- CRD — описывает желаемое состояние domain-specific объекта. Например,
kind: PostgresClusterсо specinstances: 3, version: 16, backupSchedule: "0 2 * * *". - Controller — программа (обычно Deployment в namespace
<operator-name>-system), которая через Watch API подписана на CR этого типа. Когда CR создаётся / меняется / удаляется, controller вызывает свою функциюReconcile(req), которая:- читает текущее состояние мира (Pods, StatefulSets, Secrets, и т.д.),
- сравнивает с
specCR, - применяет diff: создаёт / обновляет / удаляет нужные built-in ресурсы.
Это тот же reconcile loop, что у built-in controllers (Deployment, ReplicaSet, StatefulSet) — просто живёт в Pod вне kube-controller-manager и работает над кастомными ресурсами.
Reconcile loop: контракт level-based, а не edge-based
Operator controller следует тому же контракту, что built-in controllers — это не accident, а сознательное design решение:
- Level-based, не edge-based. Reconcile получает CURRENT state мира и реагирует на него. Если он пропустил event (Pod упал, пока operator перезапускался), при следующем reconcile он всё равно увидит missing Pod в
currentReplicas != desiredReplicasи создаст его. Edge-based reagировал бы на change event и пропустил бы missed updates. - Идемпотентность.
Reconcile(req)можно вызвать 10 раз подряд для одного объекта — результат тот же. Это потому что внутри проверяется текущее состояние перед action, а не действия «делать +1 replica». - Retry с backoff. Если reconcile вернул error — controller-runtime автоматически requeue-ит работу с exponential backoff. Никаких «забыл, что failed».
- Watch + workqueue. Controller подписан на CR (свой kind) И на produced ресурсы (Pods, STS, etc) через
owns(...). Любое изменение в owned ресурсе тоже триггерит reconcile parent CR — self-healing.
// Контракт controller-runtime (Kubebuilder)
func (r *PostgresClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var cluster examplev1.PostgresCluster
if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil {
// Если deleted — игнорируем (ownerReferences сделают GC)
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 1. Прочитать текущее состояние мира
var sts appsv1.StatefulSet
err := r.Get(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, &sts)
// ...
// 2. Построить desired StatefulSet по spec CR
desired := buildStatefulSet(&cluster)
// 3. Применить (create или update)
if err := r.Patch(ctx, desired, client.Apply, client.ForceOwnership); err != nil {
return ctrl.Result{}, err
}
// 4. Обновить status
cluster.Status.ReadyReplicas = sts.Status.ReadyReplicas
if err := r.Status().Update(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
Operator frameworks: Kubebuilder и Operator SDK
Писать controller с нуля можно через client-go + informer factory, но это десятки тысяч строк boilerplate (queues, leader election, metrics). Поэтому используются framework-и:
- Kubebuilder — официальный SIG-API-machinery framework, основан на
controller-runtime. Лидирующий выбор. Генерирует scaffolding: PROJECT, Dockerfile, Makefile, CRD manifest из Go-структур (черезkubebuilder:objectmarkers), RBAC из markers, webhooks. Полностью Go. - Operator SDK (RedHat) — построен поверх Kubebuilder для Go-операторов, но также поддерживает Helm-operators (CRD → Helm chart) и Ansible-operators (CRD → Ansible playbook). Helm/Ansible операторы не требуют Go-кода, но менее гибкие.
- KUDO, Metacontroller — менее популярные alternatives с другой философией (declarative composition).
Если задача — обернуть существующий Helm chart в operator без кодирования (например, для UI lifecycle через OpenShift OperatorHub), Helm operator — самый быстрый путь. Если нужна сложная operational логика (custom backup workflow, rolling upgrade с health checks, in-place schema migration), нужен Go operator на Kubebuilder.
Реальные операторы экосистемы
| Operator | CRDs | Что делает |
|---|---|---|
| cert-manager | Certificate, Issuer, ClusterIssuer, CertificateRequest, Order, Challenge | Автоматический выпуск TLS-сертификатов (Let’s Encrypt, self-signed, CA). Watches Certificate, заказывает сертификат у issuer, кладёт в Secret. |
| Prometheus Operator | Prometheus, Alertmanager, ServiceMonitor, PodMonitor, PrometheusRule, Probe | Деплоит Prometheus / Alertmanager как StatefulSet. ServiceMonitor — декларативный scrape config. PrometheusRule — alerting rules. Используется через kube-prometheus-stack Helm chart. |
| PostgreSQL operators (Zalando postgres-operator, CloudNative-PG, Crunchy) | PostgresCluster, Backup, PoolerSchedule | Provisioning кластера, репликация, рестарт, бэкапы (WAL-G в S3), failover, point-in-time recovery, версионные апгрейды. |
| MongoDB operators (mongodb-kubernetes-operator, percona) | MongoDBCommunity, MongoDB, OpsManager | Replica sets, sharded clusters, automation. |
| ArgoCD | Application, AppProject, ApplicationSet | GitOps: следит за Git-репо, syncs манифесты в кластер, статусы deployments. |
| Argo Workflows | Workflow, WorkflowTemplate, CronWorkflow | DAG-based pipelines (CI/CD, data pipelines) as native K8s ресурсы. |
| Argo Rollouts | Rollout, Experiment, AnalysisRun | Canary и blue-green deployments с автоматической промоушн/откатом по метрикам. |
| Istio | VirtualService, DestinationRule, Gateway, AuthorizationPolicy, PeerAuthentication, Sidecar, и ещё ~20 | Service mesh: traffic management, mTLS, observability, policy. |
| External Secrets Operator | ExternalSecret, SecretStore, ClusterSecretStore | Sync секретов из Vault / AWS Secrets Manager / Azure Key Vault в нативные Kubernetes Secrets. |
| Crossplane | сотни CRDs (Bucket, Database, Cluster, …) | Provisioning cloud-ресурсов (AWS, GCP, Azure) как Kubernetes-native объектов. K8s превращается в universal control plane. |
| Strimzi | Kafka, KafkaTopic, KafkaUser, KafkaConnect | Apache Kafka в Kubernetes: brokers, topics, ACLs, MirrorMaker. |
| MinIO Operator | Tenant | S3-compatible object storage. |
Operator capability levels (OperatorHub)
OperatorHub описывает 5 уровней зрелости оператора:
- Level 1 — Basic install. Установка приложения и конфигурация через CR. Никакого upgrade, никакого backup.
- Level 2 — Seamless upgrades. Operator знает, как обновлять managed app: rolling restart, version migrations, schema updates.
- Level 3 — Full lifecycle. Backup, restore, scale-up/scale-down, failover, replication.
- Level 4 — Deep insights. Метрики Prometheus, alerts, custom events о health managed app. Logs централизованы.
- Level 5 — Auto-pilot. Auto-scaling по нагрузке, auto-healing после ошибок, auto-tuning параметров (cache sizes, connection pools). Самообслуживающий platform.
cert-manager — Level 4-5. Сложные DB-операторы (CloudNative-PG, Strimzi) — Level 4. Helm-operator чаще всего Level 1-2.
Поток жизни Operator-ом: Pod на Deployment
Operator физически работает как обычный Deployment в кластере:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cert-manager
namespace: cert-manager
spec:
replicas: 1 # обычно одна replica (leader election не нужно)
selector:
matchLabels: {app: cert-manager}
template:
spec:
serviceAccountName: cert-manager
containers:
- name: controller
image: quay.io/jetstack/cert-manager-controller:v1.16.0
args:
- --leader-elect=true # если несколько replicas — лидер один
ServiceAccount cert-manager имеет ClusterRole с правами на все Certificate, Issuer, CertificateRequest, плюс на нативные Secrets (создавать/обновлять). Без RBAC controller не сможет работать.
Leader election в операторах: если запустить два Pod-а одного controller параллельно — каждый будет реагировать на каждый CR независимо, и они начнут конкурировать (двойной reconcile, дубликаты, конфликты update). Поэтому в большинстве операторов включён leader election: один Pod забирает Lease object и работает; остальные ждут в hot standby. При его падении один из standby берёт Lease и продолжает.
CKAD scope: что нужно знать про операторы
CKAD не требует писать оператор. Типичные задачи:
- Понять, что в кластере установлен оператор:
kubectl get crd | grep <vendor>,kubectl get deployment -A | grep operator. - Создать CR через готовый оператор (например,
Certificateчерез cert-manager). - Проверить, что CR обработан:
kubectl describe certificate my-cert, посмотреть на.status.conditions. - Если CR долго не реконсилируется — найти Pod оператора и почитать его логи:
kubectl logs -n cert-manager deployment/cert-manager. - Объяснить, почему данный ресурс не Pod / Service / Deployment: «это custom resource из cert-manager оператора».
# Найти все CRDs от cert-manager
kubectl get crd -l app.kubernetes.io/name=cert-manager
# Создать Certificate
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: default
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames: [example.com, www.example.com]
EOF
# Проверить статус
kubectl describe certificate example-com
# State: Ready/NotReady, Events с reason=Generated/Issuing/Failed
# Найти оператор
kubectl get pods -n cert-manager
kubectl logs -n cert-manager deployment/cert-manager --tail=100
Killer-моменты
- Operator = CRD + controller. Без controller CRD — это просто etcd-storage; без CRD controller не знает, на что реагировать.
- Reconcile level-based и идемпотентен. Это позволяет ему быть устойчивым к network glitches, restart-ам, missed events. Тот же контракт у built-in controllers — Deployment-controller тот же level-based loop.
- Operator живёт как Deployment в кластере. Это не сторонний agent, не demon вне K8s; это обычный workload с RBAC и ServiceAccount.
- Owner references дают cascade GC. CR удаляют — все owned Pods/STS/Secrets каскадно удаляются. Никакого orphan state без явных усилий.
- Operators — это не «cool кодинг», а перенос operational knowledge в код. Postgres operator делает то, что десятилетиями делал DBA вручную. Это автоматизация по-настоящему.