Learning Platform
Глоссарий Troubleshooting
Урок 06.01 · 18 мин
Средний
ReplicaSetController reconcileLabel selectorownerReferencesWorkloads

ReplicaSet: контроллер репликации Pods

Pod сам по себе — одноразовый объект: упал — никто его не пересоздаст. Чтобы получить self-healing — нужен контроллер, который следит за желаемым количеством Pod-replicas и поднимает новые взамен упавших. Самый низкоуровневый такой контроллер — ReplicaSet.

ReplicaSet редко создают напрямую (для этого есть Deployment), но понимать его обязательно — Deployment под капотом управляет именно ReplicaSet-ами, и при rollout-ах, отладке, rollback-ах вы будете видеть и работать с ReplicaSet объектами напрямую.


scale и restart_policy в Compose: горизонтальное масштабирование

Что такое ReplicaSet

ReplicaSet — это API-объект группы apps/v1, который гарантирует, что в кластере в любой момент времени работает ровно столько Pod-ов, сколько указано в spec.replicas, причём эти Pod-ы матчатся по spec.selector.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      tier: frontend
  template:
    metadata:
      labels:
        app: web
        tier: frontend
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80

Три обязательных поля:

  • spec.replicas — желаемое число Pod-ов (по умолчанию 1, если не указано)
  • spec.selector — label selector, по которому RS находит “свои” Pod-ы
  • spec.template — Pod template, который RS использует для создания новых Pod-ов
NOTE

ReplicaSet — это эволюция старого ReplicationController (v1). Главное отличие: ReplicationController поддерживает только equality-based selectors (key=value), а ReplicaSet — ещё и set-based через matchExpressions (In, NotIn, Exists, DoesNotExist). С v1.16 ReplicationController считается deprecated в пользу ReplicaSet и Deployment.


Reconcile loop: как ReplicaSetController работает

ReplicaSet — это всего лишь декларативный объект в etcd. Всю работу делает ReplicaSetController, который живёт в kube-controller-manager.

Reconcile loop ReplicaSetController
watch ReplicaSetsController подписан через watch на изменения всех ReplicaSet-объектов в кластере. Каждое создание/обновление/удаление RS вызывает reconcile в очереди для этого объекта.
watch PodsПараллельно controller подписан на все Pods. Когда Pod падает или удаляется — controller ищет владеющий RS через ownerReferences и кладёт его в reconcile-очередь.
reconcile тик
1. Список владенийController листает все Pods в namespace и фильтрует те, что матчатся spec.selector ЭТОГО ReplicaSet. Это owned Pods.
сравнение
2. diffactual = len(ownedPods), desired = spec.replicas. Если actual < desired — создать (desired - actual) Pod-ов. Если actual > desired — удалить (actual - desired) Pod-ов.
действие
3a. CreateДля каждого недостающего Pod: создать новый Pod-объект на основе spec.template, добавить labels из template.metadata.labels, проставить ownerReference на этот RS, POST на API server. Дальше scheduler и kubelet делают своё.
3b. DeleteЕсли actual > desired (например, кто-то добавил label на чужой Pod и он стал матчиться) — controller удаляет лишние Pods. Выбирает кандидатов: сначала те, что Pending и без node assignment, потом newest-first.

Это level-triggered reconciliation. Controller не реагирует на конкретное событие — он смотрит на актуальное состояние мира и приводит его к желаемому. Если controller упадёт, поднимется, ребутнётся — он перечитает state и продолжит.

# Создать RS
kubectl apply -f rs.yaml

# Посмотреть
kubectl get rs
# NAME   DESIRED   CURRENT   READY   AGE
# web    3         3         3       2m

# Удалить один Pod вручную
kubectl delete pod -l app=web --limit=1

# Controller тут же создаст новый
kubectl get pods -l app=web
# web-abc12   Running   2m
# web-def34   Running   2m
# web-xyz56   Running   3s   # ← новый

Selector — это не template.labels

Здесь живёт самый коварный момент ReplicaSet, который многие проваливают на собеседовании.

ReplicaSet НЕ привязан к labels Pod template. Он связывает Pod-ы через spec.selector. Template — это всего лишь шаблон для создания новых Pod-ов; владение определяется матчингом selector против actual labels на Pod-ах.

WARNING

Если labels в spec.template.metadata.labels НЕ матчатся spec.selector — API server отклонит создание ReplicaSet с ошибкой selector does not match template labels. Это защита от очевидной ошибки.

Но если вручную поменять selector на уже существующем RS — старые Pod-ы становятся orphan (RS их больше не “видит”), а новые Pod-ы создаются под новый selector.

Что произойдёт при смене selector
Состояние T0RS с selector {app: web}, replicas=3. Controller видит 3 owned Pods с label app=web.
меняем selector через kubectl edit
Состояние T1Selector теперь {app: web, version: v2}. Старые 3 Pods имеют только app=web — НЕ матчатся новый selector. Они становятся orphan: ownerReference на RS остаётся, но controller считает их чужими.
reconcile тик
3 orphan + 3 newController видит 0 матчингов под новый selector → создаёт 3 новых Pods с правильными labels из template. Старые 3 Pods продолжают жить — но не управляются этим RS. Итого в кластере 6 Pods.

Это причина, по которой spec.selector в ReplicaSet помечен как immutable в более новых API (Deployment уже не даёт его менять без force). Но в чистом RS — можно изменить через kubectl edit, и это породит хаос.


matchLabels vs matchExpressions

Selector в ReplicaSet поддерживает два формата.

matchLabels — equality-based, простой словарь:

selector:
  matchLabels:
    app: web
    tier: frontend

Match если у Pod-а ВСЕ указанные labels с указанными значениями (AND).

matchExpressions — set-based, более гибкий:

selector:
  matchExpressions:
    - key: app
      operator: In
      values: ["web", "api"]
    - key: tier
      operator: NotIn
      values: ["debug"]
    - key: legacy
      operator: DoesNotExist

Операторы:

  • In — значение label входит в values
  • NotIn — значение НЕ входит
  • Exists — label с таким key существует (значение неважно, values не указывается)
  • DoesNotExist — label с таким key отсутствует

Можно использовать оба формата одновременно — они комбинируются по AND.


ownerReferences и cascade delete

Когда ReplicaSetController создаёт Pod, он проставляет на него ownerReference:

apiVersion: v1
kind: Pod
metadata:
  name: web-abc12
  ownerReferences:
    - apiVersion: apps/v1
      kind: ReplicaSet
      name: web
      uid: 8c4d9a1e-...
      controller: true
      blockOwnerDeletion: true

controller: true означает что этот owner — основной (Pod может иметь несколько ownerReferences, но только один с controller: true).

Граф owner references
ReplicaSetКорень цепочки в случае прямого использования RS. При Deployment — корнем будет Deployment, и RS получит ownerReference на Deployment.
ownerReferences
Pod 1ownerReferences указывает на rs/web. blockOwnerDeletion=true — нельзя удалить RS не удалив Pod (без --cascade=orphan).
Pod 2Каждый Pod создан этим RS получает такой же ownerReference. Это позволяет garbage collector выстроить дерево объектов.
Pod 3При удалении RS garbage collector обходит дерево по ownerReferences и удаляет все Pods с этим owner.

Cascade delete:

# Удалить RS и все его Pods (default — cascade=background)
kubectl delete rs web

# Удалить RS, но оставить Pods (они станут orphan)
kubectl delete rs web --cascade=orphan

Когда --cascade=orphan, garbage collector НЕ удаляет Pods. Они остаются жить, но без управляющего контроллера — превращаются в обычные одноразовые Pods.

TIP

Cascade delete — это работа отдельного компонента kube-controller-manager под именем garbage collector. Он watch-ит owner references и при удалении owner-объекта чистит зависимые. Поэтому удаление RS не происходит “мгновенно” — это асинхронный процесс.


Scaling

ReplicaSet легко scale-ить:

# Imperative
kubectl scale rs/web --replicas=5

# Declarative (через edit или apply)
kubectl edit rs/web   # изменить spec.replicas

Scaling — это просто изменение spec.replicas в API server. Controller увидит diff и создаст/удалит Pods. Никакого rolling update нет — все новые реплики появляются сразу же (как только scheduler найдёт ноды).


Почему ReplicaSet напрямую — anti-pattern

В production вы почти никогда не создаёте ReplicaSet напрямую. Используется Deployment, который под капотом создаёт ReplicaSet-ы.

Причины:

  • Нет revision history. Если вы обновите spec.template.spec.containers[0].image в RS, controller просто пересоздаст Pods на новый образ — и старого RS больше не будет. Откатиться некуда.
  • Нет rolling update. Изменение template в RS не даёт rolling update — он просто меняет шаблон для будущих созданий, существующие Pods не пересоздаются.
  • Deployment даёт всё это бесплатно. Он создаёт новый RS на каждую ревизию, постепенно scale-ит новый up и старый down, хранит historry для rollback.
WARNING

Прямое использование ReplicaSet оправдано только в очень специфичных сценариях (например, при написании оператора, который сам управляет лайфциклом ревизий). В 99% случаев CKAD-таска требует Deployment, а не RS.


kubectl: типичные команды

# Создать
kubectl apply -f rs.yaml

# Посмотреть список
kubectl get rs
kubectl get rs -o wide   # с image и selector

# Описать
kubectl describe rs/web

# Изменить число реплик
kubectl scale rs/web --replicas=5

# Удалить (cascade — Pods тоже удалятся)
kubectl delete rs/web

# Удалить, но сохранить Pods
kubectl delete rs/web --cascade=orphan

Просмотр через describe покажет события controller-а:

Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  2m    replicaset-controller  Created pod: web-abc12
  Normal  SuccessfulCreate  2m    replicaset-controller  Created pod: web-def34
  Normal  SuccessfulCreate  2m    replicaset-controller  Created pod: web-xyz56

Проверка знанийKnowledge check
Что произойдёт, если вручную через kubectl edit изменить spec.selector существующего ReplicaSet так, чтобы он перестал матчиться с labels уже работающих Pods?
ОтветAnswer
Существующие Pods станут orphan: у них останется ownerReference на этот RS, но controller перестанет их 'видеть' через selector. Controller увидит actual=0 owned Pods под новый selector и создаст spec.replicas новых Pods с labels из template. В итоге в кластере останутся: старые Pods (живут, но без управления), плюс новые Pods (создал RS). Это распространённый источник 'zombie' Pods. Именно поэтому в Deployment selector сделан immutable — нельзя изменить без force. Лучшая практика: не менять selector работающего workload, создать новый workload и удалить старый.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Что произойдёт, если в манифесте ReplicaSet labels в spec.template.metadata.labels НЕ матчатся spec.selector?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5