← Kubernetes: рабочие нагрузки
8 мин · средне · Урок 11 из 11

Canary деплой

Доля трафика через соотношение реплик: как это устроено на голом Service, где точность врёт и когда пора брать service mesh.

Что такое Canary

Canary - деплой на растущей доле трафика. Новую версию раскатывают сначала на малый процент (5-10%), смотрят метрики, ошибки, задержку p99 (время ответа, в которое укладываются 99% запросов). Если всё хорошо, долю увеличивают до 25%, 50%, 100%. Если что-то сломалось, откат быстрый: пострадала только канареечная доля пользователей.

Название пришло из шахт. Шахтёры брали клетку с канарейкой. Птица переставала петь при утечке газа, и это был сигнал уходить. В деплое тот же смысл: малая часть нагрузки ловит проблему раньше остальных.

Без service mesh canary делают через соотношение реплик. Service mesh - отдельный слой управления трафиком поверх кластера. Один Service цепляет все Pod приложения, и stable, и canary. Service - объект, который раздаёт трафик на набор Pod по меткам. Pod - наименьшая единица запуска, внутри неё работает контейнер.

kube-proxy (компонент на каждой ноде, который раздаёт трафик Service между Pod) раскидывает запросы примерно поровну между конечными точками. endpoints - список IP-адресов Pod, на которые Service шлёт трафик. Чтобы дать canary около 40%, держат 2 канареечных Pod на 3 stable.

Service app   selector: app=app
  stable  ●●●   3 из 5  ~60%
  canary  ●●    2 из 5  ~40%

Минимальная конструкция

Нужны два Deployment и один Service. Deployment - объект, который держит нужное число одинаковых Pod.

Stable:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-stable
spec:
  replicas: 3
  selector:
    matchLabels: {app: app, version: stable}
  template:
    metadata:
      labels: {app: app, version: stable}
    spec:
      containers:
        - name: app
          image: nginxinc/nginx-unprivileged:1.25
          ports:
            - containerPort: 8080

Canary, одна реплика с новой версией:

metadata:
  name: app-canary
spec:
  replicas: 1
  selector:
    matchLabels: {app: app, version: canary}
  template:
    metadata:
      labels: {app: app, version: canary}
    spec:
      containers:
        - name: app
          image: nginxinc/nginx-unprivileged:1.25
          ports:
            - containerPort: 8080

Service цепляет оба Deployment по общей метке:

apiVersion: v1
kind: Service
metadata:
  name: app
spec:
  selector:
    app: app           # без version, поэтому ловит обе версии
  ports:
    - port: 80
      targetPort: 8080

Как сдвигают пропорцию

Команда kubectl scale меняет число реплик:

kubectl scale deploy app-canary --replicas=2   # 2 из 5, ~40%
kubectl scale deploy app-stable --replicas=3

Доля чистая и метрики в норме - canary увеличивают до полного размера, а stable обнуляют. После этого canary становится новым stable.

Незакрытый canary живёт вечно. Через месяц один Pod со старым образом всё ещё ловит свою долю запросов, и про него уже никто не помнит.

Гранулярность и точность

Доля трафика равна доле Pod. Чтобы дать 1%, нужен 1 Pod на 99. Чтобы дать 0.1%, нужен 1 на 999. На маленьких установках шаг грубый: 1 к 4 - это сразу 20%.

Балансировка не идеальна. На малом трафике 90/10 легко уезжает в 85/15. Причина в том, как клиенты держат соединения. Чтобы статистика выровнялась, нужны тысячи запросов.

Есть и нюанс kube-proxy. В режиме iptables (правила в ядре Linux) он не ходит по endpoints по кругу: каждое новое соединение попадает в правило -m statistic --mode random и получает случайный endpoint с равной вероятностью. Настоящий round-robin делает режим IPVS. На практике разницы нет: весов всё равно нет, доли на длинной дистанции равные, поэтому соотношение реплик и есть соотношение трафика.

Когда нужен service mesh

Без service mesh управление сводится к числу Pod. Нельзя направить canary 10% пользователей по userID. Нельзя послать на canary только запросы с заголовком x-canary: true. Такое умеют отдельные инструменты:

Раньше в этот список попадали canary-аннотации ingress-nginx (nginx.ingress.kubernetes.io/canary). Проект сняли с поддержки в марте 2026, так что на новых кластерах этот путь закрыт.

Все они дороже в обслуживании, чем kubectl scale. Поэтому в небольших проектах начинают с канарейки на уровне Pod.

Связанные команды

kubectl scale deploy app-canary --replicas=2
kubectl get pods -l app=app --show-labels
kubectl get endpoints app
kubectl rollout status deploy/app-canary
kubectl logs -l app=app,version=canary --tail=20

Объект v1 Endpoints deprecated с 1.33, и kubectl get endpoints печатает про это предупреждение. Тот же список адресов через актуальный объект:

kubectl get endpointslice -l kubernetes.io/service-name=app

Что часто ломается

Домашка

Теорию закрепляет задание на своём кластере: 2 шага с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.

./run.sh start k8s-canary     # развернуть стартовое состояние
./run.sh verify k8s-canary    # проверить текущий шаг

Коротко

Прошли материал до конца? Отметьте урок пройденным.