Deployment и ReplicaSet: управляем репликами

Рейтинг: 48.7% · 7 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 26
Зарегистрирован: 12 май 2026, 03:23

Deployment и ReplicaSet: управляем репликами

Сообщение anton_k8s »

АкадемияKubernetes на практикеГлава 4 из 19
Оглавление курса (19)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами (вы здесь)
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
В прошлой главе мы запускали поды руками и выяснили неприятную вещь: если под умер, никто его не поднимет. В реальной работе поды напрямую почти не создают. Вместо этого описывают Deployment, а он следит, чтобы нужное число реплик всегда было живо, и умеет обновлять приложение без простоя.

Как это устроено:

Deployment сам подами не управляет. Он создает ReplicaSet, и уже тот держит заданное количество подов: если под упал, ReplicaSet тут же создает замену. С отказом ноды чуть сложнее: когда kubelet перестает отвечать, нода получает taint, и поды с нее выселяются через taint-based eviction только по истечении tolerationSeconds, по умолчанию это 300 секунд. То есть при смерти ноды замена появится примерно через 5 минут, не мгновенно, имейте это в виду. Зачем тогда два уровня? Из-за обновлений. Когда вы меняете шаблон пода (например, версию образа), Deployment создает новый ReplicaSet и постепенно переносит реплики со старого на новый. Старый ReplicaSet остается с нулем реплик, к нему можно откатиться. Руками ReplicaSet вы создавать не будете, считайте его внутренней деталью.

Минимальный рабочий манифест, файл backend.yaml:

Код: Выделить всё

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: nginx:1.27
        ports:
        - containerPort: 80
Два момента, на которых спотыкаются. Первое: selector должен матчить labels в template. Совпадать один в один они не обязаны, в template могут быть и дополнительные метки, но каждая пара из selector.matchLabels обязана присутствовать среди labels шаблона, иначе apply вернет ошибку. Второе: selector после создания изменить нельзя, поле иммутабельное, только пересоздавать Deployment целиком.

Применяем в кластер из второй главы и смотрим:

Код: Выделить всё

kubectl apply -f backend.yaml
kubectl get deploy,rs,pods
В выводе видна цепочка: Deployment backend, ReplicaSet backend-7d4b9c8f6d и три пода вида backend-7d4b9c8f6d-x2lqp. Средняя часть имени это хэш шаблона пода, по нему ReplicaSet отличает свои поды от чужих. Удалите любой под через kubectl delete pod, и через пару секунд kubectl get pods покажет новый, с другим суффиксом. Это и есть самовосстановление.

Масштабирование:

Быстрый способ: kubectl scale deployment backend --replicas=5. Но если манифесты лежат в гите (а они должны там лежать), правильнее поменять replicas в файле и снова сделать apply. Иначе следующий apply из CI молча вернет старое значение, и вы будете гадать, куда делись реплики.

Обновление без простоя:

Меняем в манифесте образ на nginx:1.28, применяем и следим:

Код: Выделить всё

kubectl apply -f backend.yaml
kubectl rollout status deployment/backend
По умолчанию работает стратегия RollingUpdate с параметрами maxSurge 25% и maxUnavailable 25%: кластер может временно создать на четверть больше подов и одновременно убрать не больше четверти. Для трех реплик это значит: плюс один новый под, минус ноль старых (maxSurge округляется вверх, maxUnavailable вниз), обновление идет по одному поду.

RollingUpdate подходит не всем. Если две версии приложения не могут работать одновременно (несовместимая миграция схемы БД, эксклюзивный доступ к файлу или внешнему ресурсу), задайте spec.strategy.type: Recreate. Тогда Deployment сначала погасит все старые поды и только потом поднимет новые. Простой будет, зато не будет двух версий, конкурирующих за одни данные.

Если новая версия оказалась битой, откат делается одной командой:

Код: Выделить всё

kubectl rollout history deployment/backend
kubectl rollout undo deployment/backend
undo без флагов откатывает на предыдущую ревизию. В нашем сценарии после обновления на 1.28 текущая ревизия 2, предыдущая это ревизия 1 с nginx:1.27, на нее и вернемся. Когда ревизий накопилось много, сначала смотрите history и указывайте цель явно: kubectl rollout undo deployment/backend --to-revision=1. Частая ошибка: передать в --to-revision номер текущей, то есть сломанной ревизии, и удивляться, почему ничего не изменилось.

В выводе history колонка CHANGE-CAUSE по умолчанию пустая, и понять, что было в каждой ревизии, невозможно. Заполняется она аннотацией kubernetes.io/change-cause:

Код: Выделить всё

kubectl annotate deployment/backend kubernetes.io/change-cause="upgrade nginx 1.27 -> 1.28"
Удобнее держать эту аннотацию прямо в манифесте рядом с версией образа, тогда она будет меняться тем же коммитом, что и образ.

Еще одна штатная операция: перекатить поды без смены образа, например после обновления ConfigMap или Secret, которые приложение читает при старте. Для этого есть kubectl rollout restart deployment/backend: команда меняет аннотацию в шаблоне пода, и Deployment делает обычный rolling update с тем же образом.

История по умолчанию хранит 10 ревизий (revisionHistoryLimit), это те самые старые ReplicaSet с нулем реплик.

Типичные грабли:

Тег latest. Вы пересобрали образ, сделали apply, а Kubernetes ничего не обновил: шаблон пода не изменился, значит и катить нечего. Вторая половина проблемы это imagePullPolicy: для тега latest он по умолчанию Always, для любого фиксированного тега IfNotPresent. Поэтому при очередном пересоздании пода (вылет, переезд на другую ноду) kubelet скачает свежий latest, и версия обновится сама собой, в случайный момент и не на всех репликах сразу. В итоге вы никогда не знаете, какая версия сейчас крутится. Фиксируйте тег: номер версии, git sha, дата сборки, что угодно, кроме latest.

Правка пода напрямую. kubectl edit pod что-то поменяет, но при первом же пересоздании ReplicaSet вернет все по шаблону. Любые изменения только через Deployment.

Зависший rollout. Новые поды не проходят readiness-пробу (про пробы будет глава 10), и rollout status не двигается. Вечно он висеть не будет: по умолчанию progressDeadlineSeconds равен 600, через 10 минут Deployment получит condition ProgressDeadlineExceeded, и rollout status завершится с ошибкой. Все это время старые поды продолжают обслуживать трафик, это защитное поведение, а не баг. Смотрите kubectl describe deployment и логи новых подов, к отладке мы плотно вернемся в главе 11. Чтобы CI не ждал лишние 10 минут, уменьшите progressDeadlineSeconds до разумного для вашего приложения значения.

Итог:

Deployment это основной способ запускать stateless-приложения: он держит реплики через ReplicaSet, катит обновления без простоя (или через Recreate, когда две версии не должны жить одновременно) и умеет откатываться. Осталась проблема: у каждого пода свой IP, и при каждом пересоздании он меняется. Как обращаться к трем репликам по одному стабильному адресу, разберем в следующей главе про Service.
👍 ❤️1 🔥2 😄 🤔1
✔ Лучший ответ сформирован автоматически — furnitureman
anton_k8s писал(а):Для трех реплик это значит: плюс один новый под, минус ноль старых а если реплика всего одна? по этой логике maxSurge округлится до 1, maxUnavailable до 0, то есть сначала поднимется второй под и только потом грохнется старый? или с одной репликой все равно будет простой
Перейти к ответу →
Аватара пользователя
furnitureman
Сообщения: 1
Зарегистрирован: 22 май 2026, 17:13

Re: Deployment и ReplicaSet: управляем репликами

Сообщение furnitureman »

✔ Лучший ответ — сформирован автоматически
anton_k8s писал(а):Для трех реплик это значит: плюс один новый под, минус ноль старых
а если реплика всего одна? по этой логике maxSurge округлится до 1, maxUnavailable до 0, то есть сначала поднимется второй под и только потом грохнется старый? или с одной репликой все равно будет простой
👍 ❤️2 🔥1 😄 🤔
Аватара пользователя
mon100
Сообщения: 5
Зарегистрирован: 12 май 2026, 09:32

Re: Deployment и ReplicaSet: управляем репликами

Сообщение mon100 »

у нас на проекте был ровно этот кейс с latest, еще и imagePullPolicy Always стоял, думали прокатит. не прокатило: apply молчит, образ в registry новый, никто ничего не катит. перешли на теги с git sha и все ожило. жаль этой главы год назад не было
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
pandas77
Сообщения: 1
Зарегистрирован: 15 май 2026, 13:04

Re: Deployment и ReplicaSet: управляем репликами

Сообщение pandas77 »

спасибо за хэш в имени пода, я был уверен что это просто рандом. вопрос по rollout undo: откатились, ок, но в гите то лежит манифест со сломанной версией, и следующий деплой из CI накатит ее обратно. получается undo это только чтобы потушить пожар, а потом все равно revert в гите?
👍1 ❤️ 🔥 😄 🤔1
Аватара пользователя
tusk
Сообщения: 1
Зарегистрирован: 11 май 2026, 08:07

Re: Deployment и ReplicaSet: управляем репликами

Сообщение tusk »

а масштабировать по нагрузке автоматом можно, или только руками replicas дергать? в программе курса такого не вижу, но хотелось бы хотя бы название механизма, чтобы погуглить
👍 ❤️1 🔥2 😄 🤔1
Ответить
← Предыдущая глава
Поды: базовая единица запуска
Следующая глава →
Service: сетевой доступ к подам

Все главы курса «Kubernetes на практике»

Поделиться темой: ✈ Telegram VK
  • Похожие темы

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость