Deployment и ReplicaSet

Уровень: beginner ~55 мин Практика: Homer (Apache-2.0)

Что нужно знать перед уроком

  • M1.01 — Pod и контейнеры — нужно понимать Pod, restartPolicy, requests/limits

Что нужно знать перед уроком

M1 урок 1 — что такое Pod, фазы и состояния, graceful shutdown. Здесь мы разбираем, кто и как поддерживает нужное количество Pod’ов и управляет их обновлением.

Теория

Зачем нужен ReplicaSet, если есть Pod

Если создать Pod напрямую (как в прошлом уроке) и он упадёт (узел отказал, процесс убит OOM Killer’ом) — Kubernetes не создаст его заново. Голый Pod — это одноразовый объект без самовосстановления. Чтобы гарантировать «у меня всегда N работающих копий этого контейнера», нужен контроллер, который:

  1. Непрерывно сверяет фактическое число Pod’ов, подходящих под selector, с желаемым replicas;
  2. При недостатке — создаёт новые Pod’ы по шаблону (template);
  3. При избытке — удаляет лишние.

Это и есть ReplicaSet. Но ReplicaSet сам по себе не умеет декларативно обновлять версию приложения (поменять образ) без даунтайма — для этого нужен ещё один уровень.

Зачем нужен Deployment поверх ReplicaSet

Deployment — контроллер, управляющий ReplicaSet’ами. При изменении template (например, новый тег образа) Deployment не редактирует старый ReplicaSet «на месте», а создаёт новый ReplicaSet с обновлённым шаблоном и постепенно переключает трафик со старого на новый по стратегии обновления. Это даёт: историю ревизий (можно откатиться), нулевой (или контролируемый) даунтайм при обновлении, и явную декларативность («хочу версию v2» вместо императивных шагов).

flowchart LR Dev["Вы: kubectl apply (новый образ)"] --> Deploy["Deployment"] Deploy -->|"создаёт новый"| RSNew["ReplicaSet (новая версия)"] Deploy -->|"постепенно уменьшает"| RSOld["ReplicaSet (старая версия)"] RSNew -->|"создаёт Pod'ы"| PodsNew["Pod'ы v2"] RSOld -->|"удаляет Pod'ы"| PodsOld["Pod'ы v1"]

Иммутабельность spec.selector

Частая ошибка новичков: попытка поменять spec.selector.matchLabels у уже существующего Deployment. Kubernetes запрещает это на уровне API-валидации — selector иммутабелен после создания. Причина — селектор однозначно определяет, какие Pod’ы «принадлежат» этому Deployment (через созданные им ReplicaSet’ы); если бы селектор можно было менять на лету, Deployment мог бы внезапно «потерять» или «захватить чужие» Pod’ы. Если действительно нужно поменять лейблы, определяющие принадлежность, — единственный путь — пересоздать Deployment целиком (удалить и создать новый с новым selector), явно продумав, как это повлияет на доступность.

RollingUpdate: maxUnavailable/maxSurge

Стратегия обновления по умолчанию — RollingUpdate (альтернатива — Recreate, убивает все старые Pod’ы перед созданием новых — используется редко, когда старая и новая версия не могут работать одновременно). У RollingUpdate два параметра, оба — абсолютное число или процент от replicas:

  • maxUnavailable (по умолчанию 25%) — сколько Pod’ов может быть недоступно относительно желаемого числа реплик в процессе обновления;
  • maxSurge (по умолчанию 25%) — сколько «лишних» Pod’ов сверх replicas может быть создано одновременно.
1
2
3
4
5
6
7
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

Жёсткое правило API: maxUnavailable не может быть 0, если maxSurge тоже 0 — иначе обновление физически не сможет продвинуться (нельзя ни создать новый Pod сверх лимита, ни временно уменьшить число доступных).

Чтение kubectl describe deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Name:                   homer
...
StrategyType:           RollingUpdate
RollingUpdateStrategy:  1 max unavailable, 1 max surge
OldReplicaSets:         homer-5f9b7c6d8 (2/2 replicas created)
NewReplicaSet:          homer-7d4c9f8b6 (2/2 replicas created)
Events:
  Normal  ScalingReplicaSet  2m   deployment-controller  Scaled up replica set homer-7d4c9f8b6 to 1
  Normal  ScalingReplicaSet  2m   deployment-controller  Scaled down replica set homer-5f9b7c6d8 to 1
  Normal  ScalingReplicaSet  1m   deployment-controller  Scaled up replica set homer-7d4c9f8b6 to 2
  Normal  ScalingReplicaSet  1m   deployment-controller  Scaled down replica set homer-5f9b7c6d8 to 0

Видно буквально то, что показано на диаграмме выше: новый ReplicaSet «надувается» шаг за шагом, старый — «сдувается», в пределах maxSurge/maxUnavailable на каждом шаге.

revisionHistoryLimit и откат

Deployment хранит историю старых ReplicaSet’ов (по умолчанию 10 последних, настраивается revisionHistoryLimit) именно для возможности откатиться:

1
2
3
kubectl rollout history deployment/homer
kubectl rollout undo deployment/homer                  # откат на предыдущую ревизию
kubectl rollout undo deployment/homer --to-revision=3   # откат на конкретную ревизию

Частые ошибки и подводные камни

  • Попытка изменить spec.selector у существующего Deployment — API вернёт ошибку валидации. Единственный путь — пересоздание объекта.
  • maxUnavailable: 0 и maxSurge: 0 одновременно — невалидная конфигурация, обновление зависнет/будет отклонено.
  • Не следить за revisionHistoryLimit — если поставить 0, откат kubectl rollout undo станет невозможен (нет сохранённой истории).
  • Путать масштабирование (kubectl scale) с обновлением версии — масштабирование меняет replicas у того же ReplicaSet, обновление версии создаёт новый.

Linux / Сети / Docker

RollingUpdate — это причина, по которой ваше приложение обязано штатно обрабатывать одновременную работу двух версий (старой и новой) в течение всего периода обновления: в момент rollout часть трафика идёт на старые Pod’ы, часть — на новые. Если версии несовместимы по схеме БД или формату API между собой — RollingUpdate приведёт к ошибкам у части пользователей. Это первое соприкосновение с темой обратной совместимости при деплое, к которой мы вернёмся в M6 уроке 4 (миграции БД перед деплоем новой версии).

Практика в кластере

🧩 Практика урока: Homer · лицензия Apache-2.0

Homer — статический дашборд-стартовая страница (чистый HTML/JS/YAML-конфиг), чуть сложнее Excalidraw за счёт конфигурации через volume — но volumes мы разберём подробно в уроке 6, здесь используем emptyDir для затравки, не углубляясь.

  1. Создайте Deployment:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
kubectl create namespace course-m1-deployment
kubectl apply -n course-m1-deployment -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: homer
spec:
  replicas: 3
  selector:
    matchLabels:
      app: homer
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 5
  template:
    metadata:
      labels:
        app: homer
    spec:
      containers:
        - name: homer
          image: b4bz/homer:v23.10.1
          ports:
            - containerPort: 8080
          resources:
            requests: { cpu: "50m", memory: "64Mi" }
            limits: { cpu: "200m", memory: "128Mi" }
EOF
  1. Проверьте ReplicaSet, созданный Deployment’ом, и его связь через ownerReferences:
1
2
kubectl get deployment,replicaset,pod -n course-m1-deployment -o wide
kubectl get pod -n course-m1-deployment -o jsonpath='{.items[0].metadata.ownerReferences}'
  1. Обновите версию образа и наблюдайте RollingUpdate в реальном времени:
1
2
3
kubectl set image deployment/homer homer=b4bz/homer:v23.10.2 -n course-m1-deployment
kubectl rollout status deployment/homer -n course-m1-deployment
kubectl describe deployment homer -n course-m1-deployment   # изучите Events и Old/NewReplicaSet
  1. Откатите обновление и убедитесь, что версия вернулась:
1
2
3
kubectl rollout history deployment/homer -n course-m1-deployment
kubectl rollout undo deployment/homer -n course-m1-deployment
kubectl rollout status deployment/homer -n course-m1-deployment
  1. Попробуйте (и получите закономерную ошибку) изменить selector:
1
2
kubectl patch deployment homer -n course-m1-deployment --type=merge -p '{"spec":{"selector":{"matchLabels":{"app":"homer-v2"}}}}'
# Ожидаемая ошибка: Forbidden: field is immutable

Практика разработки

Образ b4bz/homer — готовый, собственной сборки в этом уроке нет; первая сборка своими руками — в M3.

Шпаргалка команд урока

1
2
3
4
5
6
7
kubectl create deployment <name> --image=<image> --replicas=<n>
kubectl scale deployment/<name> --replicas=<n>
kubectl set image deployment/<name> <container>=<image>
kubectl rollout status deployment/<name>
kubectl rollout history deployment/<name>
kubectl rollout undo deployment/<name> [--to-revision=N]
kubectl rollout restart deployment/<name>

Вопросы для самопроверки

Почему Deployment создаёт новый ReplicaSet при обновлении версии, а не редактирует старый?

Новый ReplicaSet на каждое изменение template — основа механизма ревизий и откатов Deployment.

Источник: Kubernetes — deployment

Что произойдёт при попытке изменить spec.selector у существующего Deployment?

selector Deployment иммутабелен — единственный способ поменять принадлежность Pod'ов по лейблам — пересоздание объекта.

Источник: Kubernetes — deployment

Какая комбинация maxUnavailable/maxSurge невалидна?

Одновременный ноль у обоих параметров не позволяет обновлению продвигаться: нельзя ни создать Pod сверх лимита, ни временно снизить доступность.

Источник: Kubernetes — deployment

Что нужно для успешного kubectl rollout undo?

revisionHistoryLimit определяет, сколько старых ReplicaSet'ов (ревизий) хранится для возможного отката.

Источник: Kubernetes — deployment

Чем отличается kubectl scale от обновления образа через kubectl set image?

Масштабирование не меняет template Pod'а и работает в рамках текущего ReplicaSet; изменение образа меняет template, что запускает создание нового ReplicaSet.

Источник: Kubernetes — deployment

Рекомендуемая литература

Официальная документация

Статьи и блоги

Книги

  • Marko Lukša. Kubernetes in Action. 2nd ed., 2022. ISBN 978-1617297618.
  • Kelsey Hightower, Brendan Burns, Joe Beda. Kubernetes: Up and Running. 3rd ed., 2022. ISBN 978-1098110201.
  • Michael Hausenblas, Stefan Schimanski. Programming Kubernetes. 2019. ISBN 978-1492047107.

Связанные материалы