Сам Kubernetes «из коробки» предоставляет два типа .spec.strategy.type
— Recreate и RollingUpdate, который явлется типом по-умолчанию.
Также, Kubernetes позволяет реализовать аналоги Canary и Blue-Green deployments, хотя с ограничениями.
См. документацию тут>>>.
Recreate
Тут всё достаточно просто: при этой стратегии, Kubernetes останавливает все запущенные в Deployment поды, и на их месте запускает новые.
Очевидно, что при таком подходе будет определённый даунтайм : пока остановятся старые поды, пока запустятся новые, пока они пройдут все Readiness проверки — приложение будет недоступно для пользователей.
Имеет смысл использовать его, если ваше приложение не может функционировать с двумя разными версиями одновременно, например из-за ограничений работы с базой данных.
Пример такого деплоймента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
apiVersion: apps/v1 kind: Deployment metadata: name: hello-deploy spec: replicas: 2 selector: matchLabels: app: hello-pod version: "1.0" strategy: type: Recreate template: metadata: labels: app: hello-pod spec: containers: - name: hello-pod image: nginxdemos/hello ports: - containerPort: 80 |
Деплоим version: "1.0"
:
Проверяем поды:
Обновляем label
на version: "2.0"
, передеплоиваем, проверяем снова:
Оба пода убиваются, и затем создаются новые:
Rolling Update
С RollingUpdate всё немного интереснее: тут Kubernetes запускает новые поды параллельно с запущенными старыми, а затем убивает старые, и оставляет только новые. Таким образом, в процессе деплоя некоторое время одновременно работают две версии приложения — и старое, и новое. Является типом по-умолчанию.
При таком подходе получаем zero downtime, так как в процессе обновления часть подов со старой версией остаётся жива.
Из недостатков — могут быть ситуации, когда такой подход неприменим. Например, если при старте подов выполняются MySQL-миграции, которые меняют схему базы данных таким образом, что предыдущая версия приложения не сможет её использовать.
Пример такого деплоймента:
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 |
apiVersion: apps/v1 kind: Deployment metadata: name: hello-deploy spec: replicas: 2 selector: matchLabels: app: hello-pod strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 template: metadata: labels: app: hello-pod version: "1.0" spec: containers: - name: hello-pod image: nginxdemos/hello ports: - containerPort: 80 |
Тут мы изменили strategy.type: Recreate
на type: RollingUpdate
, и добавили два опциональных поля, которые определяют поведение Деплоймента во время выполнения обновления:
maxUnavailable
: как много подов изreplicas
могут быть уничтожены для запуска новых. Может быть задано явно в количестве или в процентах.maxSurge
: как много подов может быть создано сверх значения, заданного вreplicas
. Может быть задано явно в количестве или в процентах.
В примере выше мы задали 0 в maxUnavailable
, т.е. не останаливать запущенные поды, пока не будут созданы новые, и maxSurge
указали в 1, т.е. во время обновления должен быть создан один дополнительный новый под, и только после того, как он перейдёт в статус Running — будет остановлен один из старых подов.
Деплоим с версией 1.0:
Обновляем версию на 2.0, деплоим ещё раз, и проверяем:
Получили один под сверх заданного replicas
на время обновления.
Kubernetes Canary Deployment
Тип Canary подразумевает запуск новых подов одновременно с запущенными старыми, по аналогии с RollingUpdate, но даёт больше контроля над процессом обновления.
После запуска новой версии приложения — часть запросов переключается на неё, а часть продолжает использовать старую версию.
Если с новой версией всё хорошо — то оставшиеся пользователи тоже переключаются на новую версию, а старая удаляется.
Canary тип не включён в .spec.strategy.type
, но его можно реализовать без дополнительных контроллеров механизмами самого Kubernetes.
При этом, решение получается достаточно примитивным и сложным в реализации и управлении.
Для того, что бы реализовать Canary потребуется два Deployment, в которых мы зададим разные версии приложения, но в Service, который направляет трафик к подам, используем один и тот же набор labels
в его selector
.
Создаём два Deployment и Service с типом LoadBalancer.
В Deployment-1 указываем replicas=2, а для Deployment-2 — 0:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
apiVersion: apps/v1 kind: Deployment metadata: name: hello-deploy-1 spec: replicas: 2 selector: matchLabels: app: hello-pod template: metadata: labels: app: hello-pod version: "1.0" spec: containers: - name: hello-pod image: nginxdemos/hello ports: - containerPort: 80 lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo 1 > /usr/share/nginx/html/index.html"] --- apiVersion: apps/v1 kind: Deployment metadata: name: hello-deploy-2 spec: replicas: 0 selector: matchLabels: app: hello-pod template: metadata: labels: app: hello-pod version: "2.0" spec: containers: - name: hello-pod image: nginxdemos/hello ports: - containerPort: 80 lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo 2 > /usr/share/nginx/html/index.html"] --- apiVersion: v1 kind: Service metadata: name: hello-svc spec: type: LoadBalancer ports: - port: 80 targetPort: 80 protocol: TCP selector: app: hello-pod |
С помощью postStart
перезапишем значение индексного файла, что бы потом иметь возможность увидеть, к какому именно поду мы обращаемся.
Деплоим:
Проверяем поды:
Сейчас Service отправляет весь трафик на поды из первого деплоймента:
Теперь можем обновить Deployment-2, задав ему replicas: 1
:
Получаем три пода с одинаковой label app=hello-pod
:
1 2 3 4 5 |
kubectl get pod -l app=hello-pod NAME READY STATUS RESTARTS AGE hello-deploy-1-dd584d88d-25rbx 1/1 Running 0 3m2s hello-deploy-1-dd584d88d-9xsng 1/1 Running 0 3m2s hello-deploy-2-d6c989569-x2lsb 1/1 Running 0 6s |
Соответственно, Service будет отправлять 70% запросов на поды из первого деплоймента, а 30 — на поды второго деплоймента:
После проверки version 2.0 — можно удалить старый деплоймент, а новый заскейлить до 2 подов.
Kubernetes Blue/Green Deployment
Используя этот же механизм — можем реализовать и аналог blue-green деплоймента, когда у нас одновременно работает и старая (green), и новая (blue) версия, но весь трафик направляет на новую, а если с ней возникают проблемы — то можно переключиться обратно на первую.
Для этого в .spec.selector
Сервиса добавим выборку подов первого, «green», деплоймента , используя label
version:
1 2 3 4 |
... selector: app: hello-pod version: "1.0" |
Передеплоиваем, проверяем:
Меняем selector
на version: 2
— переключаем трафик на blue-версию:
После того, как всё заработало — удаляется старая версия, а blue-приложение становится green.
При использовании и Canary, и Blue-Green по схемам, описанным выше, мы получаем целую пачку проблем — и необходимость самим менеджить разные Deployments, и отслеживать статус новых версий, анализировать трафик к новым сервисам на предмет ошибок, и т.д.