🐳 Розділ 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 з мітками sel.  │
                  └──────────────────┬─────────────────────┘
                                     │
                 ┌───────────────────┴───────────────────┐
                 │                                       │
           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).

Пов’язані питання