← Back to notes

kube-controller-manager: reconciliation и
desired state


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

kube-controller-manager: reconciliation и desired state | Aleksandr Suprun