Kubernetes: типы Deployment Strategies

Thank you for reading this post, don't forget to subscribe!

Сам Kubernetes «из короб­ки» предо­став­ля­ет два типа .spec.strategy.type — Recreate и RollingUpdate, кото­рый явлет­ся типом по-умолчанию.

Так­же, Kubernetes поз­во­ля­ет реа­ли­зо­вать ана­ло­ги Canary и Blue-Green deployments, хотя с ограничениями.

См. доку­мен­та­цию тут>>>.

Recreate

Тут всё доста­точ­но про­сто: при этой стра­те­гии, Kubernetes оста­нав­ли­ва­ет все запу­щен­ные в Deployment поды, и на их месте запус­ка­ет новые.

Оче­вид­но, что при таком под­хо­де будет опре­де­лён­ный даун­тайм : пока оста­но­вят­ся ста­рые поды, пока запу­стят­ся новые, пока они прой­дут все Readiness про­вер­ки — при­ло­же­ние будет недо­ступ­но для пользователей.

Име­ет смысл исполь­зо­вать его, если ваше при­ло­же­ние не может функ­ци­о­ни­ро­вать с дву­мя раз­ны­ми вер­си­я­ми одно­вре­мен­но, напри­мер из-за огра­ни­че­ний рабо­ты с базой данных.

При­мер тако­го деплоймента:

Деп­ло­им version: "1.0":

kubectl apply -f deployment.yaml
deployment.apps/hello-deploy created

Про­ве­ря­ем поды:

kubectl get pod -l app=hello-pod
NAME                            READY   STATUS    RESTARTS   AGE
hello-deploy-77bcf495b7-b2s2x   1/1     Running   0          9s
hello-deploy-77bcf495b7-rb8cb   1/1     Running   0          9s

Обнов­ля­ем label на version: "2.0", пере­де­п­ло­и­ва­ем, про­ве­ря­ем снова:

kubectl get pod -l app=hello-pod
NAME                           READY   STATUS        RESTARTS   AGE
hello-deploy-dd584d88d-vv5bb   0/1     Terminating   0          51s
hello-deploy-dd584d88d-ws2xp   0/1     Terminating   0          51s

Оба пода уби­ва­ют­ся, и затем созда­ют­ся новые:

kubectl get pod -l app=hello-pod
NAME                           READY   STATUS    RESTARTS   AGE
hello-deploy-d6c989569-c67vt   1/1     Running   0          27s
hello-deploy-d6c989569-n7ktz   1/1     Running   0          27s

Rolling Update

С RollingUpdate всё немно­го инте­рес­нее: тут Kubernetes запус­ка­ет новые поды парал­лель­но с запу­щен­ны­ми ста­ры­ми, а затем уби­ва­ет ста­рые, и остав­ля­ет толь­ко новые. Таким обра­зом, в про­цес­се деп­лоя неко­то­рое вре­мя одно­вре­мен­но рабо­та­ют две вер­сии при­ло­же­ния — и ста­рое, и новое. Явля­ет­ся типом по-умолчанию.

При таком под­хо­де полу­ча­ем zero downtime, так как в про­цес­се обнов­ле­ния часть подов со ста­рой вер­си­ей оста­ёт­ся жива.

Из недо­стат­ков — могут быть ситу­а­ции, когда такой под­ход непри­ме­ним. Напри­мер, если при стар­те подов выпол­ня­ют­ся MySQL-мигра­ции, кото­рые меня­ют схе­му базы дан­ных таким обра­зом, что преды­ду­щая вер­сия при­ло­же­ния не смо­жет её использовать.

При­мер тако­го деплоймента:

Тут мы изме­ни­ли strategy.type: Recreate на type: RollingUpdate, и доба­ви­ли два опци­о­наль­ных поля, кото­рые опре­де­ля­ют пове­де­ние Деп­лой­мен­та во вре­мя выпол­не­ния обновления:

  • maxUnavailable: как мно­го подов из replicas могут быть уни­что­же­ны для запус­ка новых. Может быть зада­но явно в коли­че­стве или в процентах.
  • maxSurge: как мно­го подов может быть созда­но сверх зна­че­ния, задан­но­го в replicas. Может быть зада­но явно в коли­че­стве или в процентах.

В при­ме­ре выше мы зада­ли 0 в maxUnavailable, т.е. не оста­на­ли­вать запу­щен­ные поды, пока не будут созда­ны новые, и maxSurge ука­за­ли в 1, т.е. во вре­мя обнов­ле­ния дол­жен быть создан один допол­ни­тель­ный новый под, и толь­ко после того, как он перей­дёт в ста­тус Running — будет оста­нов­лен один из ста­рых подов.

Деп­ло­им с вер­си­ей 1.0:

kubectl apply -f deployment.yaml
deployment.apps/hello-deploy created
Полу­ча­ем два пода:
kubectl get pod -l app=hello-pod
NAME                           READY   STATUS              RESTARTS   AGE
hello-deploy-dd584d88d-84qk4   0/1     ContainerCreating   0          3s
hello-deploy-dd584d88d-cgc5v   1/1     Running             0          3s

Обнов­ля­ем вер­сию на 2.0, деп­ло­им ещё раз, и проверяем:

kubectl get pod -l app=hello-pod
NAME                           READY   STATUS              RESTARTS   AGE
hello-deploy-d6c989569-dkz7d   0/1     ContainerCreating   0          3s
hello-deploy-dd584d88d-84qk4   1/1     Running             0          55s
hello-deploy-dd584d88d-cgc5v   1/1     Running             0          55s

Полу­чи­ли один под сверх задан­но­го 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:

С помо­щью postStart пере­за­пи­шем зна­че­ние индекс­но­го фай­ла, что бы потом иметь воз­мож­ность уви­деть, к како­му имен­но поду мы обращаемся.

Деп­ло­им:

kubectl apply -f deployment.yaml
deployment.apps/hello-deploy-1 created
deployment.apps/hello-deploy-2 created
service/hello-svc created

Про­ве­ря­ем поды:

kubectl get pod -l app=hello-pod
NAME                             READY   STATUS    RESTARTS   AGE
hello-deploy-1-dd584d88d-25rbx   1/1     Running   0          71s
hello-deploy-1-dd584d88d-9xsng   1/1     Running   0          71s

Сей­час Service отправ­ля­ет весь тра­фик на поды из пер­во­го деплоймента:

curl adb469658008c41cd92a93a7adddd235-1170089858.us-east-2.elb.amazonaws.com
1
curl adb469658008c41cd92a93a7adddd235-1170089858.us-east-2.elb.amazonaws.com
1

 

Теперь можем обно­вить Deployment-2, задав ему replicas: 1:

kubectl patch deployment.v1.apps/hello-deploy-2 -p '{"spec":{"replicas": 1}}'
deployment.apps/hello-deploy-2 patched

Полу­ча­ем три пода с оди­на­ко­вой label app=hello-pod:


Соот­вет­ствен­но, Service будет отправ­лять 70% запро­сов на поды из пер­во­го деп­лой­мен­та, а 30 — на поды вто­ро­го деплоймента:
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
2
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
2

После про­вер­ки version 2.0 — мож­но уда­лить ста­рый деп­лой­мент, а новый заскей­лить до 2 подов.

Kubernetes Blue/Green Deployment

Исполь­зуя этот же меха­низм — можем реа­ли­зо­вать и ана­лог blue-green деп­лой­мен­та, когда у нас одно­вре­мен­но рабо­та­ет и ста­рая (green), и новая (blue) вер­сия, но весь тра­фик направ­ля­ет на новую, а если с ней воз­ни­ка­ют про­бле­мы — то мож­но пере­клю­чить­ся обрат­но на первую.

Для это­го в .spec.selector Сер­ви­са доба­вим выбор­ку подов пер­во­го, «green», деп­лой­мен­та , исполь­зуя label version:


Пере­де­п­ло­и­ва­ем, проверяем:
curl adb***858.us-east-2.elb.amazonaws.com
1
curl adb***858.us-east-2.elb.amazonaws.com
1

Меня­ем selector на version: 2 — пере­клю­ча­ем тра­фик на blue-версию:

kubectl patch services/hello-svc -p '{"spec":{"selector":{"version": "2.0"}}}'
service/hello-svc patched
про­ве­ря­ем:
curl adb***858.us-east-2.elb.amazonaws.com
2
curl adb***858.us-east-2.elb.amazonaws.com
2

После того, как всё зара­бо­та­ло — уда­ля­ет­ся ста­рая вер­сия, а blue-при­ло­же­ние ста­но­вит­ся green.

При исполь­зо­ва­нии и Canary, и Blue-Green по схе­мам, опи­сан­ным выше, мы полу­ча­ем целую пач­ку про­блем — и необ­хо­ди­мость самим менеджить раз­ные Deployments, и отсле­жи­вать ста­тус новых вер­сий, ана­ли­зи­ро­вать тра­фик к новым сер­ви­сам на пред­мет оши­бок, и т.д.