← 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 | watch stream → in-memory cache всех объектов |
| Event handler | при Add/Update/Delete кладёт ключ в workqueue |
| Workqueue | rate-limited очередь с retry и exponential backoff |
| Worker goroutine | берёт ключ, reconcile-ит |
SharedInformer общий для всех controllers, работающих с одним типом ресурса. Это экономит watch connections к API server.
source: kubernetes.io/docs/concepts/architecture/controller/#controller-pattern
Встроенные controllers
kube-controller-manager — один бинарник с десятками controllers:
| Controller | Что reconcile-ит | Что ломается без него |
|---|---|---|
| Deployment | управляет ReplicaSet-ами, rollout strategy | rolling update невозможен |
| ReplicaSet | actual pod count == desired replicas | scale-up/down не работает |
| Node lifecycle | статус nodes, taints при unreachable | мёртвые nodes не помечаются, Pod-ы не перемещаются |
| EndpointSlice | mapping Service → Pod IPs | Service traffic не доходит до Pod-ов |
| Job | завершение и retry Jobs | batch workloads зависают |
| ServiceAccount | создание default SA в каждом namespace | Pod-ы не получают identity |
| Namespace | cleanup при удалении namespace | ресурсы orphaned namespace висят вечно |
| Node CIDR | выделение podCIDR для nodes | новые nodes не получают сеть |
Все 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.
| Параметр | Типичное значение |
|---|---|
| Lease duration | ~15s |
| Renew deadline | ~10s |
| Retry period | ~2s |
| Worst-case failover | ~15-20s пауза reconciliation |
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/
Что может пойти не так
| Проблема | Что происходит | Как обнаружить |
|---|---|---|
| Leader crash | standby перехватывает lease через ~15-20s | leader_election_master_status, pod restart count |
| Reconcile lag | controller работает, но не успевает за rate of change | workqueue_depth растёт |
| Stuck workqueue | items в очереди, worker не обрабатывает | workqueue_depth растёт, workqueue_work_duration = 0 |
| Repeated retries | action fail-ится, backoff растёт | workqueue_retries_total rate > baseline |
| API server недоступен | informer теряет watch, cache устаревает | rest_client_requests_total{code="5xx"} |
| etcd slow | controller видит расхождение, но write висит | etcd_request_duration_seconds растёт |
Три пути: от запроса до результата
| Path | Компонент | Что доказывает | Что НЕ доказывает |
|---|---|---|---|
| Request path | kube-apiserver | запрос принят и сохранён в etcd | что workload создан |
| Placement path | kube-scheduler | Pod назначен на node | что Pod реально запущен |
| Reconciliation path | controller-manager | desired → actual state | что результат стабилен |
Healthy API + successful scheduling не доказывают convergence. Reconciliation path требует отдельного мониторинга.
Сигналы мониторинга
| Метрика | Что показывает | Порог |
|---|---|---|
workqueue_depth | items, ожидающие обработки | 0 sustained → lag |
workqueue_queue_duration_seconds | время ожидания в очереди | P99 > 5s → degradation |
workqueue_work_duration_seconds | длительность обработки item | P99 > 1s → slow reconciliation |
workqueue_retries_total | количество re-enqueue | rate > baseline → repeated failures |
workqueue_unfinished_work_seconds | как долго work in progress | 60s → stuck |
leader_election_master_status | кто leader | 0 на всех → no leader, critical |
node_collector_evictions_total | pod evictions из-за node failure | spike → node lifecycle active |
Что мониторить в первую очередь:
# 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 в течение ~50s по умолчанию (
node-monitor-grace-period; дефолт изменён с 40s на 50s в Kubernetes 1.32).
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 = 50s (дефолт изменён с 40s в 1.32), затем node помечается NotReady и получает taints not-ready/unreachable (NoExecute). После ещё ~5 минут Pod-ы evict-ируются с проблемной ноды — это даёт DefaultTolerationSeconds (toleration 300s на эти taints), а не флаг pod-eviction-timeout: он не работает с переходом на taint-based eviction (default с 1.18), eviction идёт через taints per-pod. С Kubernetes 1.34 сам eviction вынесен в отдельный taint-eviction-controller (GA), отделённый от пометки taint-ов node lifecycle-контроллером. Без мониторинга 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 →