← 21: Scheduler | 23: etcd →
Роль controller-manager
Controller-manager — компонент, который превращает desired state в actual state. API server сохраняет намерение, scheduler назначает node, но именно controllers делают реальную работу: создают Pod-ы, обновляют endpoints, помечают мёртвые nodes.
После kubectl apply -f deployment.yaml API server отвечает 200 OK — это означает, что Deployment сохранён в etcd. Контейнеры ещё не существуют. Всё дальнейшее — асинхронная цепочка reconciliation.
[source: kubernetes.io/docs/concepts/architecture/controller/]
Reconciliation loop
Каждый controller работает по одному алгоритму:
┌──────────────────────────────┐
│ │
▼ │
Observe desired state │
(из informer cache) │
│ │
▼ │
Compare с actual state │
│ │
├── совпадают → ничего не делать ┘
│
└── расходятся
│
▼
Execute corrective action
(create/update/delete через API)
│
▼
Observe again ─────────────────┘
Это level-triggered design: controller реагирует не на событие ("Pod удалён"), а на состояние ("desired replicas=3, actual replicas=2"). Если событие пропущено (рестарт, failover), следующий цикл reconcile увидит расхождение и исправит — упущенные события воспроизводить не нужно.
Informer + Workqueue architecture
API server
│
│ watch stream (events: Add, Update, Delete)
│
▼
SharedInformer
│ поддерживает in-memory cache всех объектов
│ вызывает event handlers при изменениях
│
▼
Event Handler
│ извлекает ключ объекта (namespace/name)
│ кладёт ключ в workqueue
│
▼
Workqueue (rate-limited)
│ дедупликация: один ключ в очереди только раз
│ exponential backoff при retry
│
▼
Worker goroutine(s)
│ берёт ключ из очереди
│ читает desired state из cache
│ сравнивает с actual
│ выполняет corrective action
│
├── успех → done
└── ошибка → re-enqueue с backoff
SharedInformer общий для всех controllers, работающих с одним типом ресурса. Это экономит watch connections к API server.
[source: kubernetes.io/docs/concepts/architecture/controller/#controller-pattern]
Встроенные controllers
kube-controller-manager — один бинарник с десятками controllers:
Все controllers запущены в одном процессе, зависят от одного leader election.
Пример: Deployment rollout пошагово
Deployment (replicas: 3, image: v2, RollingUpdate strategy)
│
▼
Deployment controller:
1. Видит: текущий RS имеет image: v1
2. Создаёт новый ReplicaSet с image: v2, replicas: 1
3. Уменьшает старый RS: replicas: 2
│
▼
ReplicaSet controller (новый RS):
1. Видит: desired=1, actual=0
2. Создаёт Pod с image: v2
│
▼
(Pod scheduled, запущен, прошёл readiness probe)
│
▼
Deployment controller:
1. Видит: новый RS ready=1
2. Увеличивает новый RS: replicas: 2
3. Уменьшает старый RS: replicas: 1
│
... повторяется до полного rollout ...
│
▼
Deployment controller:
Новый RS: replicas=3, ready=3
Старый RS: replicas=0
Rollout complete.
Каждый шаг — отдельный цикл reconciliation. Если controller-manager упал посередине rollout, новый leader продолжит с текущего состояния (level-triggered).
# Следить за rollout в реальном времени
kubectl rollout status deployment/my-app -n app-ns
# Посмотреть историю rollouts
kubectl rollout history deployment/my-app -n app-ns
# Откатиться к предыдущей версии
kubectl rollout undo deployment/my-app -n app-ns
Leader election
В HA-кластерах запущены несколько экземпляров kube-controller-manager. Активен только один (lease holder). Остальные в standby.
controller-manager-1 (leader) ← выполняет reconciliation
controller-manager-2 (standby) ← ждёт
controller-manager-3 (standby) ← ждёт
controller-manager-1 падает:
t=0s lease не renew-ится
t=15s lease истекает
t=15-20s standby перехватывает lease
t=20s новый leader начинает reconciliation
Во время паузы (~15-20s):
- Pod-ы продолжают работать (kubelet независим от controller-manager)
- Новые Deployment-ы не создают ReplicaSet
- Scale events не обрабатываются
- Мёртвые nodes не помечаются
Level-triggered design: после failover новый leader видит все расхождения и исправляет их.
# Посмотреть кто текущий leader
kubectl get lease kube-controller-manager -n kube-system -o yaml
[source: kubernetes.io/docs/concepts/architecture/leases/]
Что может пойти не так
Три пути: от запроса до результата
Healthy API + successful scheduling не доказывают convergence. Reconciliation path требует отдельного мониторинга.
Сигналы мониторинга
Что мониторить в первую очередь:
# Reconciliation работает?
leader_election_master_status == 1 (хотя бы на одном instance)
# Reconciliation успевает?
rate(workqueue_depth) не растёт
workqueue_queue_duration_seconds P99 < 5s
# Reconciliation не ломается?
rate(workqueue_retries_total) стабилен
workqueue_unfinished_work_seconds < 60
Когда использовать
Custom controllers / операторы:
- Когда стандартных Kubernetes primitives недостаточно для управления приложением.
- Паттерн Operator — CRD + custom controller, реализующий reconciliation для domain-specific logic.
- Примеры: управление PostgreSQL cluster, Kafka topics, TLS certificates (cert-manager).
Понимание reconciliation важно при:
- Отладке зависших rollouts: смотреть
kubectl describe replicaset,kubectl describe deployment, events. - Troubleshooting scale issues: ReplicaSet controller не создаёт Pod-ы → смотреть workqueue metrics и events.
- Node failures: Node lifecycle controller должен пометить unreachable node в течение ~40s по умолчанию (
node-monitor-grace-period).
[source: kubernetes.io/docs/concepts/extend-kubernetes/operator/]
Типичные ошибки
1. Считать 200 OK от API признаком работающего workload
API server ответил на CREATE Deployment. Controllers ещё не запустились. Реальный статус — kubectl rollout status. Для автоматизации: проверяйте .status.readyReplicas == .spec.replicas.
2. Не понимать, почему Scale не работает мгновенно
Scale Deployment через kubectl scale → API write → ReplicaSet controller видит изменение → создаёт Pod-ы → scheduler размещает → kubelet запускает. Это цепочка, не атомарная операция. При деградации любого звена — задержка.
3. Паника при коротком отсутствии leader
15-20 секунд без controller-manager leader во время failover — нормально для HA кластера. Запущенные workloads продолжают работать. Новые deployment/scale операции подождут.
4. Не мониторить workqueue_depth
Растущий workqueue означает, что controller не успевает за нагрузкой. Типичная причина: слишком много объектов в кластере при недостаточных ресурсах controller-manager. Решение: увеличить --concurrent-*-syncs флаги.
5. Игнорировать node lifecycle при node failures
По умолчанию node-monitor-grace-period = 40s, затем node помечается NotReady. После ещё ~5 минут (pod-eviction-timeout) — Pod-ы evict-ируются с проблемной ноды. Без мониторинга node_collector_evictions_total — этот процесс невидим.
Альтернативы
Kubebuilder / controller-runtime: фреймворк для custom controllers на Go (Manager, Reconciler, informer caches). Де-факто стандарт для операторов, те же паттерны что и встроенные controllers.
Operator SDK: надстройка над kubebuilder с scaffolding и packaging. Поддерживает Ansible и Helm для несложных случаев.
Metacontroller: controller logic как HTTP-сервис на любом языке. Менее производительно, проще для старта без Go.
[source: kubernetes.io/docs/concepts/architecture/garbage-collection/]
← 21: Scheduler | 23: etcd →