← 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
КомпонентРоль
SharedInformerwatch stream → in-memory cache всех объектов
Event handlerпри Add/Update/Delete кладёт ключ в workqueue
Workqueuerate-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 strategyrolling update невозможен
ReplicaSetactual pod count == desired replicasscale-up/down не работает
Node lifecycleстатус nodes, taints при unreachableмёртвые nodes не помечаются, Pod-ы не перемещаются
EndpointSlicemapping Service → Pod IPsService traffic не доходит до Pod-ов
Jobзавершение и retry Jobsbatch workloads зависают
ServiceAccountсоздание default SA в каждом namespacePod-ы не получают identity
Namespacecleanup при удалении 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 crashstandby перехватывает lease через ~15-20sleader_election_master_status, pod restart count
Reconcile lagcontroller работает, но не успевает за rate of changeworkqueue_depth растёт
Stuck workqueueitems в очереди, worker не обрабатываетworkqueue_depth растёт, workqueue_work_duration = 0
Repeated retriesaction fail-ится, backoff растётworkqueue_retries_total rate > baseline
API server недоступенinformer теряет watch, cache устареваетrest_client_requests_total{code="5xx"}
etcd slowcontroller видит расхождение, но write виситetcd_request_duration_seconds растёт

Три пути: от запроса до результата

PathКомпонентЧто доказываетЧто НЕ доказывает
Request pathkube-apiserverзапрос принят и сохранён в etcdчто workload создан
Placement pathkube-schedulerPod назначен на nodeчто Pod реально запущен
Reconciliation pathcontroller-managerdesired → actual stateчто результат стабилен

Healthy API + successful scheduling не доказывают convergence. Reconciliation path требует отдельного мониторинга.


Сигналы мониторинга

МетрикаЧто показываетПорог
workqueue_depthitems, ожидающие обработки
0 sustained → lag
workqueue_queue_duration_secondsвремя ожидания в очередиP99 > 5s → degradation
workqueue_work_duration_secondsдлительность обработки itemP99 > 1s → slow reconciliation
workqueue_retries_totalколичество re-enqueuerate > baseline → repeated failures
workqueue_unfinished_work_secondsкак долго work in progress
60s → stuck
leader_election_master_statusкто leader0 на всех → no leader, critical
node_collector_evictions_totalpod evictions из-за node failurespike → 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

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