🐳 Раздел 14 · Вопрос #13

Что такое ReplicaSet

Если в спецификации указано replicas: 3:


🟢 Junior Level

30-секундный ответ

ReplicaSet (RS) — это системный контроллер Kubernetes, единственная задача которого — гарантировать постоянное наличие заданного количества идентичных копий (реплик) пода в кластере в любой момент времени.

Если в спецификации указано replicas: 3:

  • Контроллер непрерывно следит: «Сколько подов работает прямо сейчас?»
  • Если один из подов упал с ошибкой или сервер под ним сгорел, ReplicaSet немедленно запускает новый под взамен выбывшего.
  • Если вы вручную подняли лишний под с такими же метками, ReplicaSet удалит лишний, чтобы сохранить точное число 3.

Наглядная аналогия из жизни

Представьте старшего смены на кассах супермаркета:

  • По регламенту в зале должно всегда работать строго 4 открытые кассы.
  • Если один кассир заболел или ушёл на обед, старший смены немедленно сажает на кассу свободного сотрудника из резерва.
  • Если наплыв покупателей схлынул и кассиров стало 5, старший смены отправляет лишнего сотрудника на склад.

Базовый манифест ReplicaSet

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: order-service-rs
spec:
  replicas: 3 # Желаемое количество реплик
  selector:
    matchLabels:
      app: order-service # Контролировать поды с этой меткой
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-app
        image: order-service:1.0
        ports:
        - containerPort: 8080

[!IMPORTANT] Золотое правило: в реальном продакшене ReplicaSet практически никогда не создаётся вручную! Вместо него всегда создаётся Deployment, который автоматически управляет жизненным циклом ReplicaSet под капотом.


🟡 Middle Level

Внутренний механизм: Цикл согласования (Reconciliation Loop)

ReplicaSet работает по принципу бесконечного автомата контроля с обратной связью:

                  ┌────────────────────────────────────────┐
                  │ Чтение desired count из spec.replicas  │
                  └──────────────────┬─────────────────────┘
                                     │
                                     ▼
                  ┌────────────────────────────────────────┐
                  │ Подсчёт actual Pods с метками selector │
                  └──────────────────┬─────────────────────┘
                                     │
                 ┌───────────────────┴───────────────────┐
                 │                                       │
           actual < desired                        actual > desired
                 │                                       │
                 ▼                                       ▼
    [ API Call: Создать Pod ]               [ API Call: Удалить лишний Pod ]

ReplicaSet vs Deployment: В чём разница?

Почему индустрия использует Deployment, а не «чистый» ReplicaSet?

Возможность ReplicaSet Deployment
Поддержание $N$ реплик 🟢 Да 🟢 Да (через RS)
Самоисцеление при падении пода 🟢 Да 🟢 Да (через RS)
Rolling Update (Обновление без простоя) ❌ Нет 🟢 Да
Автоматический откат (Rollback) ❌ Нет 🟢 Да
Хранение истории релизов (Revisions) ❌ Нет 🟢 Да

Как Deployment использует ReplicaSet при обновлении

Когда вы меняете версию образа в Deployment с v1 на v2:

  1. Deployment НЕ переписывает старый ReplicaSet. Он создаёт второй новый ReplicaSet для версии v2.
  2. Постепенно Deployment уменьшает реплики у RS-v1 ($3 \to 2 \to 1 \to 0$) и параллельно наращивает реплики у RS-v2 ($0 \to 1 \to 2 \to 3$).
  3. Старый RS-v1 остаётся в кластере со значением replicas: 0. Благодаря этому при команде kubectl rollout undo откат происходит мгновенно: RS-v1 масштабируется обратно до 3, а сбойный RS-v2 гасится в 0.

Set-Based Selectors vs ReplicationController

Исторический предшественник ReplicationController (из K8s v1.0) поддерживал только простое равенство меток (app = order-service). Современный ReplicaSet поддерживает селекторы на основе множеств (Set-based Selectors):

selector:
  matchExpressions:
  - key: env
    operator: In
    values: [production, staging]
  - key: tier
    operator: NotIn
    values: [frontend]
  - key: release
    operator: Exists

🔴 Senior Level

Усыновление (Adoption) и Сиротство (Orphaning) Подов

ReplicaSet связывается с подами не через жёсткие ссылки, а через динамическое сопоставление меток:

  1. Adoption (Усыновление):
    • Если вы создадите под вручную (kubectl run standalone-pod --labels="app=order-service"), а затем примените манифест ReplicaSet с селектором app: order-service, контроллер обнаружит этот под, посчитает его частью своей квоты и пропишет в метаданные пода поле metadata.ownerReferences (указав свой UID и флаг controller: true).
  2. Orphaning (Осиротение):
    • Если удалить ReplicaSet с флагом каскадного удаления kubectl delete rs my-rs --cascade=orphan, сам объект ReplicaSet будет стёрт из etcd, но все запущенные поды останутся работать как независимые поды-сироты!

Хэш-метки Deployment (pod-template-hash)

Чтобы несколько ReplicaSet одного приложения не конфликтовали между собой из-за одинаковых меток app: order-service, контроллер Deployment автоматически вычисляет 32-битный хэш от содержимого spec.template (алгоритм FNV-1a) и инжектирует его как обязательную метку:

labels:
  app: order-service
  pod-template-hash: "6876878b9b" # Инжектировано автоматически!

Благодаря этой метке селектор каждого ReplicaSet изолирован, и контроллеры не пытаются удалить поды соседней версии.


Изменение шаблона: Ловушка spec.template

Критический вопрос на собеседованиях Senior уровня:

«Что произойдёт с работающими подами, если мы выполним kubectl edit rs и заменим образ контейнера с app:1.0 на app:2.0 прямо в ReplicaSet?»

Ответ: Абсолютно ничего не произойдёт!

  • Шаблон spec.template в ReplicaSet используется контроллером ТОЛЬКО в момент создания новых подов (когда actual < desired).
  • Уже запущенные поды НЕ перезапускаются и НЕ обновляются, так как контроллер ReplicaSet проверяет только КОЛИЧЕСТВО подов, а не их внутреннее соответствие шаблону!
  • Новые поды с образом 2.0 появятся только тогда, когда старый под упадёт или будет удалён вручную. Именно поэтому для обновлений кода всегда используют Deployment, а не ReplicaSet.

4 Tricky Questions

1. Если изменить spec.template.spec.containers[0].image напрямую в запущенном ReplicaSet, обновятся ли текущие работающие Pod’ы?

Ответ: Нет, текущие поды продолжат работать на старом образе. Контроллер ReplicaSet следит исключительно за счётчиком количества живых подов (spec.replicas), соответствующих селектору matchLabels. Он не отслеживает drift (расхождение) конфигурации уже запущенных подов с текущим шаблоном template. Пока количество работающих подов равно числу replicas, ReplicaSet не предпринимает никаких действий. Новый образ применится только к тем подам, которые будут созданы в будущем (например, при масштабировании или взамен случайно удалённого пода).


2. Что произойдёт, если два разных манифеста ReplicaSet будут иметь пересекающиеся селекторы меток (Overlapping Selectors), например app: myapp?

Ответ: Возникнет опасное состояние гонки (Race Condition / Flapping Loop):

  • Если у обоих RS задано replicas: 2, а в кластере запущено 2 пода с меткой app: myapp, оба контроллера будут считать эти 2 пода «своими».
  • Но если контроллеры имеют разные ownerReferences, контроллер A может попытаться «усыновить» поды контроллера B, удаляя и создавая их заново.
  • Если же подов станет 4, каждый из RS решит, что в кластере превышен лимит реплик, и оба контроллера одновременно начнут удалять поды, снижая их число до нуля, затем одновременно создавать новые, вводя поды в бесконечный шторм пересозданий. Kubernetes не блокирует дублирующиеся селекторы, ответственность лежит на инженере.

3. Чем отличаются стратегии каскадного удаления Foreground, Background и Orphan при удалении ReplicaSet?

Ответ: При выполнении команды kubectl delete rs <name> --cascade=<policy>:

  • Background (по умолчанию): Kubernetes немедленно удаляет объект ReplicaSet из базы etcd. Затем сборщик мусора K8s (Garbage Collector) в фоновом режиме находит и удаляет все дочерние поды с соответствующим ownerReference.
  • Foreground: ReplicaSet переходит в статус deletionTimestamp, получает финализатор foregroundDeletion и остаётся в etcd до тех пор, пока все дочерние поды не будут полностью остановлены и удалены. Только после этого стирается сам ReplicaSet.
  • Orphan: ReplicaSet удаляется из etcd, но с дочерних подов предварительно снимаются метки ownerReferences. Все поды продолжают спокойно работать в кластере как неуправляемые одиночные поды.

4. В каком порядке ReplicaSet выбирает, какой именно Pod удалить, если мы уменьшаем число реплик с 5 до 2 (Scale Down)?

Ответ: Контроллер ReplicaSet использует строгий детерминированный приоритетный алгоритм сортировки кандидатов на удаление:

  1. Поды в фазе Pending (ещё не запустившиеся) удаляются в первую очередь.
  2. Поды в фазе Unknown или Failed (нездоровые).
  3. Поды с непрошедшей проверкой готовности (Ready=False).
  4. Поды, работающие на нодах с наибольшим числом подов этого же ReplicaSet (для сохранения равномерного распределения по серверам кластера).
  5. Если все поды одинаково здоровы: удаляется самый молодой под (с самым поздним временем создания creationTimestamp), так как более старые поды уже прогреты (JIT-компиляция в Java) и закэшированы.

🎯 Шпаргалка для интервью

Главное о ReplicaSet

  • Суть: низкоуровневый контроллер, гарантирующий наличие $N$ реплик подов.
  • Reconciliation Loop: сверяет spec.replicas с фактическим числом подов по селектору.
  • Deployment vs ReplicaSet: Deployment управляет ReplicaSet’ами для Rolling Updates и Rollbacks.
  • Шаблон template: изменения шаблона в RS не перезапускают текущие поды.
  • Селекторы: поддерживает Set-based выражения (In, NotIn, Exists).
  • Связь с подами: динамическая через metadata.ownerReferences.
  • Каскадное удаление: флаг --cascade=orphan оставляет поды жить после удаления RS.

Scale-Down приоритет (Кого убивают первым?)

1. Pending / CrashLoopBackOff поды
2. Поды с Ready=False
3. Поды с перегруженных нод (балансировка)
4. Самые «молодые» поды (сберегая прогретую JVM в старых подах)

Красные флаги на собеседовании (Чего говорить нельзя)

  • ❌ «Для обновления версии микросервиса я редактирую образ в ReplicaSet» — поды не обновятся, нужно использовать Deployment.
  • ❌ «В продакшене мы деплоим приложения манифестами с kind: ReplicaSet» — грубая ошибка, всегда используется Deployment.
  • ❌ «ReplicaSet удаляет поды случайным образом при scale down» — существует строгий приоритетный алгоритм (Pending $\to$ Unready $\to$ Youngest).

Связанные вопросы