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-ов
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.
Это 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-ах.
Если labels в spec.template.metadata.labels НЕ матчатся spec.selector — API server отклонит создание ReplicaSet с ошибкой selector does not match template labels. Это защита от очевидной ошибки.
Но если вручную поменять selector на уже существующем RS — старые Pod-ы становятся orphan (RS их больше не “видит”), а новые Pod-ы создаются под новый selector.
Это причина, по которой 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 входит вvaluesNotIn— значение НЕ входит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).
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.
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.
Прямое использование 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