Translate

середа, 1 жовтня 2025 р.

Argo Rollouts. Part I: Intro And BlueGreen Strategy

За замовчуванням Kubernetes управляє стратегією виливки нового коду за допомогою Deployment об'єкта. Він забезпечує доволі простий функціонал, котрий, тим не менш, задовільнить більшість потреб: rolling update чи recreate стратегії виливки; наявність історії, котра може забезпечити безпечний rollback на попередні версії та інші. Deployment забезпечує поступову заміну старіших подів на новіші, перевіривши проходження readiness-проби кожного перед включенням його в трафік, або ж, у випадку recreate стратегії, видаляє старі поди і водночас за раз замінює їх на нові.

Будь-який новий реліз йде із додатковими ризиками, що можуть вплинути на доступність сервісу. І тому простих стратегій, що з коробки надає Kubernetes, буває недостатньо. Скажімо, може бути бажання перемкнути на нову версію (тег імеджа) лише певний відсоток трафіку чи протестувати нову версію прямо перед перемиканням на неї. Тож такі прогресивні стратегії виливки потенційно зменшать ризики та допоможуть заздалегідь знайти критичні недоліки, не вплинувши на значну кількість запитів.

Увесь подальший код доступний за посиланням.


1. PROGRESSIVE DELIVERY

Як вже було сказано, progressive delivery - це набір технік доставки коду, що зменшують ймовірність простою роботи сервісу чи його некоректну роботу. Найпопулярнішими варіантами таких доставок є:

  • Blue-Green. Виливається додаткова версія коду, після чого працює одночасно дві: blue (стара) та green (нова). Перемикання production трафіку на новий код зазвичай не миттєве, а лиш в тому разі, коли є впевненість у коректності його роботи, наприклад після прогону додаткових тестів. Дуже часто це робиться на рівні DNS, коли на нову версію прив'язують основне доменне ім'я сервісу. У разі проблем є швидка можливість повернення на попередню версію, адже старий сервіс про всяк випадок не вимикають одразу.
  • Canary. На нову версію коду спрямовується лише якийсь відсоток трафіку. У разі відсутності помилок цей відсоток збільшується і доходить до 100. Є різні варіації цієї техніки: наприклад може виливатись якась частина сервісу на новій версії без включення його в трафік, для того щоб додатково прогнати на ній тести. Такий собі варіант Blue-Green іn Canary. Ця техніка також зменшує ризики виливки проблемного коду, адже є ймовірність, що клієнти на новій версії повідомлять, що щось пішло не так допоки цю версію отримають всі користувачі сервісу. Немає якихось чітких часових рамок того як швидко має бути досягнуто завершення виливки нової версії. Для деяких компаній це години, а для інших - дні чи навіть тижні.
  • Feature Flags/Toggles. Способи активації додаткового (нового) функціоналу для групи користувачів у межах однієї версії коду, що імплементується додатковими змінними в межах самого коду чи окремих систем із якими він інтегрується. Все заради поступової виливки цих змін.
  • A/B Тестування. У межах однієї версії коду клієнтам буде продемонстровано різний функціонал чи різний вигляд елементів сторінки. Якщо у разі такого "експерименту" над користувачами буде підтверджено бажаний результат - ці зміни можуть бути активовані для всієї бази користувачів.

У цій серії статей ми зосередимось на розгортанні коду, а саме на Canary та Blue-Green як техніках, котрі безпосередньо імплементуються на рівні інфраструктури, а не особливостях роботи кодової бази.


2. BASIC BLUE-GREEN EXAMPLE

Для імплементації Blue-Green чи Canary технік Deployment потребує додаткової конфігурації та додаткового управління релізним процесом. Виключно для цілей демонстрації розглянемо як би міг виглядати Blue-Green без допомоги додаткових операторів. У більшій чи меншій мірі ці ж техніки використовуються і в інших більш просунутих інструментах.

Отже, нехай у нас буде 2 деплойменти: теперішня версія blue та майбутня версія green.

$ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1-28
spec:
  selector:
    matchLabels:
      name: nginx
      version: "1-28"
  replicas: 2
  template:
    metadata:
      labels:
        name: nginx
        version: "1-28"
    spec:
      containers: 
        - name: nginx
          image: nginx:1.28
          ports:
            - name: http
              containerPort: 80
EOF

$ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1-29
spec:
  selector:
    matchLabels:
      name: nginx
      version: "1-29"
  replicas: 2
  template:
    metadata:
      labels:
        name: nginx
        version: "1-29"
    spec:
      containers: 
        - name: nginx
          image: nginx:1.29
          ports:
            - name: http
              containerPort: 80
EOF


Тут нічого особливого - 2 версії nginx, що працюють на порті 80. Тепер опишемо сервіс із типом LoadBalancer:

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata: 
  name: nginx
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "5"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "4"
  labels: 
    name: nginx
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
  selector: 
    name: nginx
    version: "1-28"
  type: LoadBalancer
EOF


Тип LoadBalancer має забезпечувати контролер, що працює в інсталяції Kubernetes. У моєму випадку це AWS Load Balancer Controller. Цей вибір не принциповий, проте опис сервісу цього ж типу із іншим контролером, особливо в секції анотацій, може бути інакшим.

Балансувальник для сервісу буде представлений NLB і на його фактичне створення може піти до 1.5 хвилини. 

$ EXTERNAL_IP=$(kubectl get svc nginx -o jsonpath="{.status.loadBalancer.ingress[*].hostname}")


$ curl -s http://$EXTERNAL_IP/version | grep nginx
<hr><center>nginx/1.28.0</center>


Тепер відредагуємо секцію selector сервісу де виставимо новішу (green) версію:

$ kubectl edit svc nginx
...
  selector: 
    name: nginx
    version: "1-29"
...


І перевіримо чи відбулось перемикання:

$ while true; do curl -s http://$EXTERNAL_IP/version | grep nginx; sleep 1; date; done
<hr><center>nginx/1.28.0</center>
Wed Sep 18 15:41:07 EEST 2025
...
Wed Sep 18 15:41:19 EEST 2025
<hr><center>nginx/1.29.1</center>


Це також не миттєвий процес, на час його виконання впливають хелсчеки балансувальника, котрі описані в анотаціях. Щось подібне також присутнє і для Canary, проте не будемо довго на цьому зупинятись, адже це занадто мануальний процес і не вартий подальшого розвитку.

Links:
https://github.com/ianlewis/kubernetes-bluegreen-deployment-tutorial


3. ARGO ROLLOUTS

Argo Rollouts - це один із інструментів Argo Project. Вони всі доволі гарно інтегруються, хоча і не є залежними один від одного.

Argo Rollouts - це Kubernetes контролер та набір додаткових CRD, що імплементують вже згадані вище прогресивні техніки доставки коду, тестування та експерименти. Більш того він може інтегруватись із різноманітними ingress, mesh та gateway api контролерами для більш гранулярного управління трафіком.

На відміну від вбудованого Deployment об'єкту, що надає інсталяція Kubernetes, Argo Rollouts має такі переваги:

  • Blue-Green та Canary стратегії
  • Більш гранулярне управління трафіком між версіями (якщо є підтримка ingress/service mesh контролерів в Argo Rollouts) 
  • Автоматичні роллбеки чи промоушени версій (базуючись на результатах прогону тестів чи значень сервісів збору метрик). Або ж за потреби ручні, якщо цього вимагають процедури

Надалі ми установимо Argo Rollouts в AWS EKS кластер та розберемо декілька базових варіантів організації доставки нової версії та більш складну.


4. ARGO ROLLOUTS INSTALLATION

Щоб рухатись далі нам необхідні працюючий EKS кластер із AWS LB контролером, про установку яких можна почитати тут. Останні версії публічних модулів, котрі були у використанні, зазнали помітних змін, тож я оновив попередній код. Для його застосування скористаємось Тераформом версії 1.13.3:

$ git clone git@github.com:ipeacocks/terraform-aws-example.git

$ cd terraform-aws-example/eks-infra/vpc
$ terraform init && terraform apply --auto-approve

$ cd ../eks
$ terraform init && terraform apply --auto-approve

$ cd ../addons/lb-controller
$ terraform init && terraform apply --auto-approve


На установці Argo Rollouts зупинимось детальніше.

$ cd ../argo-rollouts
$ cat main.tf


data "aws_region" "this" {}

data "terraform_remote_state" "eks" {
  backend = "s3"

  config = {
    bucket = "my-tf-state-2023-06-01"
    key    = "my-eks.tfstate"
    region = "us-east-1"
  }
}

data "aws_iam_policy_document" "this" {

  statement {
    sid    = "LBAccess"
    effect = "Allow"
    actions = [
      "elasticloadbalancing:DescribeTargetGroups",
      "elasticloadbalancing:DescribeLoadBalancers",
      "elasticloadbalancing:DescribeListeners",
      "elasticloadbalancing:DescribeRules",
      "elasticloadbalancing:DescribeTags",
      "elasticloadbalancing:DescribeTargetHealth"
    ]
    resources = ["*"]
  }
}

resource "helm_release" "this" {
  name             = "argo-rollouts"
  repository       = "https://argoproj.github.io/argo-helm"
  chart            = "argo-rollouts"
  version          = var.helm_package_version
  namespace        = var.namespace
  create_namespace = true

  values = [
    templatefile("${path.module}/templates/helm/values.yaml.tpl", {
      region = data.aws_region.this.region
    })
  ]
}

module "custom_pod_identity" {

  source  = "terraform-aws-modules/eks-pod-identity/aws"
  version = "2.0.0"

  name            = "eks-argo-rollouts-${data.terraform_remote_state.eks.outputs.cluster_name}-${var.region}"
  use_name_prefix = false

  attach_custom_policy = true
  source_policy_documents = [
    data.aws_iam_policy_document.this.json
  ]

  associations = {
    one = {
      cluster_name    = data.terraform_remote_state.eks.outputs.cluster_name
      namespace       = var.namespace
      service_account = "argo-rollouts"
    }
  }
}


Тут ми вичитали стейт створеного EKS кластеру, дані із якого необхідні для pod identity, що застосовує роль для "--aws-verify-target-group". Із її допомогою Argo Rollouts зможе робити додаткові перевірки AWS таргет груп (чи відповідає перелік адрес подів в сервісі тому, що додано в таргет групу) перед переходом до наступного етапу доставки. Це не обов'язковий функціонал, але завдяки йому можна мінімізувати кількість 5xx-помилок на етапі виливки. Його можна прибрати, якщо активований Pod Readiness Gate.

Далі ми встановили офіційний helm-чарт Argo Rollouts, що використовує templates/helm/values.yaml.tpl у якості параметрів:

$ cat templates/helm/values.yaml.tpl


serviceAccount:
  create: true

controller:
  extraArgs: ["--aws-verify-target-group"]
  extraEnv:
    - name: AWS_REGION
      value: ${region}
  replicas: 2
  metrics:
    enabled: true
  logging:
    format: "json"

keepCRDs: false

dashboard:
  enabled: true
  replicas: 2

  # Uncomment if you wish to use ALB/Ingress for external access
  # ingress:
  #   enabled: true
  #   annotations:
  #     alb.ingress.kubernetes.io/group.name: eks-addons
  #     alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
  #     alb.ingress.kubernetes.io/scheme: internet-facing
  #     alb.ingress.kubernetes.io/success-codes: "200-302"

  #     alb.ingress.kubernetes.io/target-type: ip
  #     # externalDNS is needed

  #     external-dns.alpha.kubernetes.io/hostname: rollouts-dashboard.example.com
  #     alb.ingress.kubernetes.io/healthcheck-port: traffic-port
  #   ingressClassName: "alb"
  #   hosts:
  #     - rollouts-dashboard.example.com
  #   paths:
  #     - /
  #   pathType: Prefix


Задля роботи із web-панеллю ми будемо користуватись форвардингом порту, але є можливість активувати Ingress/ALB прямо із хелм чарту (необхідно розкоментувати відповідну секцію, мати власний домен та встановлений ExternalDNS).

Проведемо установку всього написаного вище:

$ cd terraform-aws-example/eks-infra/addons/argo-rollouts
$ terraform init && terraform apply --auto-approve


Буде піднято сам контролер та його веб-панель:

$ aws eks update-kubeconfig --region us-east-1 --name my-eks

$ kubectl get pods -n argo-rollouts
NAME                                       READY   STATUS    RESTARTS   AGE
argo-rollouts-7466f98bb9-knd96             1/1     Running   0          6m45s
argo-rollouts-7466f98bb9-zcr8v             1/1     Running   0          6m46s
argo-rollouts-dashboard-6bc9fff6fc-79dl5   1/1     Running   0          6m46s
argo-rollouts-dashboard-6bc9fff6fc-wk2h8   1/1     Running   0          6m45s


$ kubectl -n argo-rollouts logs -f deploy/argo-rollouts
Found 2 pods, using pod/argo-rollouts-7466f98bb9-zcr8v
{"level":"info","msg":"Argo Rollouts starting","time":"2025-09-08T20:30:01Z","version":{"Version":"v1.8.3+49fa151","BuildDate":"2025-06-04T22:18:04Z","GitCommit":"49fa1516cf71672b69e265267da4e1d16e1fe114","GitTag":"","GitTreeState":"clean","GoVersion":"go1.23.9","Compiler":"gc","Platform":"linux/arm64"}}
{"level":"info","msg":"Creating event broadcaster","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"Setting up event handlers","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"Setting up experiments event handlers","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"Setting up analysis event handlers","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"Leaderelection get id argo-rollouts-7466f98bb9-zcr8v_d00332ed-6ec2-47f5-9346-c1abad7f654b","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"Starting Healthz Server at 0.0.0.0:8080","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"Starting Metric Server at 0.0.0.0:8090","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"attempting to acquire leader lease argo-rollouts/argo-rollouts-controller-lock...","time":"2025-09-08T20:30:01Z"}
{"level":"info","msg":"New leader elected: argo-rollouts-7466f98bb9-knd96_eedcd00f-68c1-4258-b565-5c382f128a64","time":"2025-09-08T20:30:01Z}


Переглянемо чи працює web-панель:

$ kubectl -n argo-rollouts port-forward deployment/argo-rollouts-dashboard 3100:3100

Форвардинг панелі можна виконати простіше, але для цього буде необхідний офіційний плагін до kubectl:

$ curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
$ chmod +x ./kubectl-argo-rollouts-linux-amd64
$ sudo mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

$ kubectl argo rollouts version
kubectl-argo-rollouts: v1.8.3+49fa151
  BuildDate: 2025-06-04T22:15:54Z
  GitCommit: 49fa1516cf71672b69e265267da4e1d16e1fe114
  GitTreeState: clean
  GoVersion: go1.23.9
  Compiler: gc
  Platform: linux/amd6


$ kubectl argo rollouts dashboard -n argo-rollouts


Цей плагін буде корисним і надалі для управління етапами реліз-процесу, тож його обов'зково треба буде встановити.

Argo Rollouts сетап привносить додаткові CRD, що імплементують додаткову логіку:

$ kubectl get crds | grep argo
analysisruns.argoproj.io                     2025-09-09T17:45:42Z
analysistemplates.argoproj.io                2025-09-09T17:45:42Z
clusteranalysistemplates.argoproj.io         2025-09-09T17:45:42Z
experiments.argoproj.io                      2025-09-09T17:45:42Z
rollouts.argoproj.io                         2025-09-09T17:
45:42Z

Наприклад Rollout забезпечує виконання самої логіки доставки, тим самим замінюючи офіційний Deployment, Analysis додає можливість вбудовувати тести між етапами доставки коду, Experiment вміє створювати додаткові репліки для перевірки коду тощо. Власне все для покращення якості релізів та зменшення ризиків.

Загальна схема роботи Argo Rollouts має наступний вигляд:

Тут показаний Canary реліз із залученням сумісного Ingress-контроллера для гранулярного управління трафіком. Як і в базовому прикладі, що був наведений вище, перемикання версії коду відбувається зміною селекторів на об'єкті сервісу за яким йде дві репліки, що управляються Rollout ресурсом. Про всі деталі поговоримо далі.


5. BLUE-GREEN W/O BALANCER

Маючи робочий EKS кластер та всі необхідні контролери, можемо переходити до організації самих процесів доставки коду на Argo Rollouts. У цій частині поговоримо про базовий Blue-Green, без залучення управлінням трафіку на AWS балансувальнику.

Спершу розглянемо сам Blue-Green, для чого візьмемо приклад від офіційного проекту:

$ cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bluegreen
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bluegreen
  template:
    metadata:
      labels:
        app: rollout-bluegreen
    spec:
      containers:
      - name: rollouts-demo
        image: argoproj/rollouts-demo:blue
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
  strategy:
    blueGreen: 
      activeService: rollout-bluegreen-active
      previewService: rollout-bluegreen-preview
      autoPromotionEnabled: false
---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-active
spec:
  selector:
    app: rollout-bluegreen
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-preview
spec:
  selector:
    app: rollout-bluegreen
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
EOF
rollout.argoproj.io/rollout-bluegreen created
service/rollout-bluegreen-active created
service/rollout-bluegreen-preview created


Було створено 2 сервіси rollout-bluegreen-active та rollout-bluegreen-preview, що дивляться на ту ж групу подів. Назви цих сервісів прописані в стратегії об'єкту rollout. OCI-образи argoproj/rollouts-demo зібрані лише під x86_64 архітектуру, тож у EKS кластері мають бути відповідні воркери.

$ kubectl argo rollouts get rollout rollout-bluegreen
Name:            rollout-bluegreen
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                           KIND        STATUS     AGE  INFO
⟳ rollout-bluegreen                            Rollout     ✔ Healthy  19m  
└──# revision:1                                                            
   └──⧉ rollout-bluegreen-5ffd47b8d4           ReplicaSet  ✔ Healthy  19m  stable,active
      ├──□ rollout-bluegreen-5ffd47b8d4-7hp7h  Pod         ✔ Running  18m  ready:1/1
      └──□ rollout-bluegreen-5ffd47b8d4-nl4n6  Pod         ✔ Running  18m  ready:1/1


Сервіс має статус Healthy, імедж версії rollouts-demo:blue, кількість подів 2. Все як і планувалось. 

$ kubectl get svc 
                                        
NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
rollout-bluegreen-active    ClusterIP   172.20.179.62    <none>        80/TCP    49m
rollout-bluegreen-preview   ClusterIP   172.20.225.120   <none>        80/TCP    49m


До релізу наступної версії імеджа, обидва сервіси мають такі ж селектори, тобто вказують на ті ж поди:

$ kubectl get svc rollout-bluegreen-active -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "5ffd47b8d4"
}

$ kubectl get svc rollout-bluegreen-preview -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "5ffd47b8d4"
}


Rollout об'єкт в секції status відображає які репліки вважаються active та preview на даний момент часу:

$ kubectl get rollout rollout-bluegreen -o json | jq -r ".status.blueGreen"
{
  "activeSelector": "5ffd47b8d4",
  "previewSelector": "5ffd47b8d4"
}


Та власне сама репліка:

$ kubectl get replicaset
NAME                           DESIRED   CURRENT   READY   AGE
rollout-bluegreen-5ffd47b8d4   2         2         2       73m

$ kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
rollout-bluegreen-5ffd47b8d4-7hp7h   1/1     Running   0          73m
rollout-bluegreen-5ffd47b8d4-nl4n6   1/1     Running   0          73m


Друга поки відсутня, адже ще не було релізу, і всюди активна лише репліка 5ffd47b8d4.

Новий реліз починається тоді, коли з'явились зміни в секції .spec.template об'єкту Rollout. Логіка абсолютно така ж як і в Deployment. Установимо нову версію OCI імеджа через kubectl та argo плагін:

$ kubectl argo rollouts set image rollout-bluegreen rollouts-demo=argoproj/rollouts-demo:green

де rollout-bluegreen - це ім'я об'єкту Rollout сервісу, а rollouts-demo - ім'я контейнера.

$ kubectl argo rollouts get rollout rollout-bluegreen   
Name:            rollout-bluegreen
Namespace:       default
Status:          ॥ Paused
Message:         BlueGreenPause
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
                 argoproj/rollouts-demo:green (preview)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                           KIND        STATUS     AGE    INFO
⟳ rollout-bluegreen                            Rollout     ॥ Paused   3h16m  
├──# revision:2                                                              
│  └──⧉ rollout-bluegreen-75695867f            ReplicaSet  ✔ Healthy  30s    preview
│     ├──□ rollout-bluegreen-75695867f-92fdg   Pod         ✔ Running  30s    ready:1/1
│     └──□ rollout-bluegreen-75695867f-bkcfd   Pod         ✔ Running  30s    ready:1/1
└──# revision:1                                                              
   └──⧉ rollout-bluegreen-5ffd47b8d4           ReplicaSet  ✔ Healthy  3h16m  stable,active
      ├──□ rollout-bluegreen-5ffd47b8d4-7hp7h  Pod         ✔ Running  170m   ready:1/1
      └──□ rollout-bluegreen-5ffd47b8d4-nl4n6  Pod         ✔ Running  170m   ready:1/1


Завдяки раніше вказаній опції autoPromotionEnabled: false перемикання на нову версію не відбулось автоматично і процес чекає нашого мануального рішення. Але спочатку подивимось що відбулось:

$ kubectl get svc rollout-bluegreen-active -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "5ffd47b8d4"
}

$ kubectl get svc rollout-bluegreen-preview -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}

$ kubectl get rollout rollout-bluegreen -o json | jq -r ".status.blueGreen
"
{
  "activeSelector": "5ffd47b8d4",
  "previewSelector": "75695867f"
}

$ kubectl get replicaset
NAME                           DESIRED   CURRENT   READY   AGE
rollout-bluegreen-5ffd47b8d4   2         2         2       3h20m
rollout-bluegreen-75695867f    2         2         2       4m45s

$ kubectl get pods                                         
NAME                                 READY   STATUS    RESTARTS   AGE
rollout-bluegreen-5ffd47b8d4-7hp7h   1/1     Running   0          174m
rollout-bluegreen-5ffd47b8d4-nl4n6   1/1     Running   0          174m
rollout-bluegreen-75695867f-92fdg    1/1     Running   0          5m12s
rollout-bluegreen-75695867f-bkcfd    1/1     Running   0          5m12s


Selector у сервіса rollout-bluegreen-active не змінився, rollout-bluegreen-preview додав новий rollouts-pod-template-hash, значення якого вказує на новий реплікасет rollout-bluegreen-75695867f. Цей реплікасет піднятий в кількості подів, що дорівнює 2 та має відповідні лейбли:

$ kubectl get pod rollout-bluegreen-75695867f-92fdg -o json | jq -r ".metadata.labels"
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}

$ kubectl get pod rollout-bluegreen-75695867f-92fdg -o json | jq -r ".spec.containers.[].image"

argoproj/rollouts-demo:green


Якщо коротко, то з'явилась нова група подів під сервісом rollout-bluegreen-preview із новим імеджем, але трафік поки йде на стару версію. Цей новий сервіс можна додатково потестувати чи щось таке, допоки не відбудеться ручний promote:

$ kubectl argo rollouts promote rollout-bluegreen
rollout 'rollout-bluegreen' promoted

$ kubectl argo rollouts get rollout rollout-bluegreen                            
Name:            rollout-bluegreen
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:green (stable, active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                           KIND        STATUS     AGE    INFO
⟳ rollout-bluegreen                            Rollout     ✔ Healthy  3h38m  
├──# revision:2                                                              
│  └──⧉ rollout-bluegreen-75695867f            ReplicaSet  ✔ Healthy  22m    stable,active
│     ├──□ rollout-bluegreen-75695867f-92fdg   Pod         ✔ Running  22m    ready:1/1
│     └──□ rollout-bluegreen-75695867f-bkcfd   Pod         ✔ Running  22m    ready:1/1
└──# revision:1                                                              
   └──⧉ rollout-bluegreen-5ffd47b8d4           ReplicaSet  • ScaledDown  3h38m

Отже основною стала репліка 75695867f, поди попередної 5ffd47b8d4 вже видалені (затримку їх знищення можна конфігурувати, щоб швидко повернутись до неї за необхідності). Селектор сервісу rollout-bluegreen-active, як і preview, тепер буде вказувати на 75695867f (тобто на єдину репліку, як і до початку релізу):

$ kubectl get svc rollout-bluegreen-active -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}

$ kubectl get svc rollout-bluegreen-preview -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}

$ kubectl get rollout rollout-bluegreen -o json | jq -r ".status.blueGreen"
{
  "activeSelector": "75695867f",
  "previewSelector": "75695867f"
}

$ kubectl get replicaset
NAME                           DESIRED   CURRENT   READY   AGE
rollout-bluegreen-5ffd47b8d4   0         0         0       3h51m
rollout-bluegreen-75695867f    2         2         2       35m

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
rollout-bluegreen-75695867f-92fdg   1/1     Running   0          35m
rollout-bluegreen-75695867f-bkcfd   1/1     Running   0          35m

$ kubectl get pod rollout-bluegreen-75695867f-92fdg -o json | jq -r ".metadata.labels"
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}


Вітаю, реліз завершено! Promote релізу також можна робити із web-панелі, про котру я вже згадував:

Вона може бути в нагоді, якщо реліз процес у вашій компанії більш формалізований чи делегується менш кваліфікованим людям без доступу до Kubernetes API.

Додаток argoproj/rollouts-demo має відповідне забарвлення залежно від вказаного тегу, задля більш наочної демонстрації можливостей Argo Rollouts. Тому якщо перейти на порт сервісу можна побачити таку картину:

$ kubectl port-forward svc/rollout-bluegreen-active 8080:80

Колір зелений, адже це версія, котру було вилито.


6. BLUE-GREEN WITH BALANCER

Цього разу поглянемо на складніший варіант, що потребує залучення AWS LB Controller. Якщо ж ви оперуєте в іншому середовищі, то можна обрати інший зі списку, що підтримує Argo Rollouts. Інакше прийдеться задовольнятися базовим функціоналом, про котрий я писав вище.

У якості імеджа будемо використовувати той же rollouts-demo:

$ cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bluegreen
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bluegreen
  template:
    metadata:
      labels:
        app: rollout-bluegreen
    spec:
      containers:
      - name: rollouts-demo
        image: argoproj/rollouts-demo:blue
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
  strategy:
    blueGreen: 
      activeService: rollout-bluegreen-active
      previewService: rollout-bluegreen-preview
      autoPromotionEnabled: false
---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-active
spec:
  selector:
    app: rollout-bluegreen
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-preview
spec:
  selector:
    app: rollout-bluegreen
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rollout-bluegreen
  annotations:
    alb.ingress.kubernetes.io/target-type: ip
    # external-dns.alpha.kubernetes.io/hostname: bluegreen.example.com
    alb.ingress.kubernetes.io/group.name: blue-green-group
    alb.ingress.kubernetes.io/success-codes: 200-302
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
spec:
  ingressClassName: alb
  rules:
    - host: bluegreen.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: rollout-bluegreen-active
                port:
                  number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rollout-bluegreen-preview
  annotations:
    alb.ingress.kubernetes.io/target-type: ip
    # external-dns.alpha.kubernetes.io/hostname: bluegreen-preview.example.com
    alb.ingress.kubernetes.io/group.name: blue-green-group
    alb.ingress.kubernetes.io/success-codes: 200-302
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
spec:
  ingressClassName: alb
  rules:
    - host: bluegreen-preview.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: rollout-bluegreen-preview
                port:
                  number: 80
EOF


Цього разу опис майже аналогічний попередньому BlueGreen окрім двох Ingress-об'єктів, котрі, виходячи із анотацій, і обслуговує AWS ALB контролер. У початковій фазі традиційно обидва сервіси, а значить і інгреси, будуть дивитись на єдиний реплікасет/групу подів:

$ kubectl argo rollouts get rollout rollout-bluegreen

Name:            rollout-bluegreen
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                           KIND        STATUS     AGE  INFO
⟳ rollout-bluegreen                            Rollout     ✔ Healthy  21m  
└──# revision:1                                                            
   └──⧉ rollout-bluegreen-5ffd47b8d4           ReplicaSet  ✔ Healthy  21m  stable,active
      ├──□ rollout-bluegreen-5ffd47b8d4-89f6c  Pod         ✔ Running  21m  ready:1/1
      └──□ rollout-bluegreen-5ffd47b8d4-h6rmg  Pod         ✔ Running  21m  ready:1/1

$ kubectl get svc                                          
NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
rollout-bluegreen-active    ClusterIP   172.20.211.205   <none>        80/TCP    22m
rollout-bluegreen-preview   ClusterIP   172.20.164.253   <none>        80/TCP    22m

$ kubectl get rs 
NAME                           DESIRED   CURRENT   READY   AGE
rollout-bluegreen-5ffd47b8d4   2         2         2       22m

$ kubectl get svc rollout-bluegreen-active -o json | jq -r '.spec.selector'  
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "5ffd47b8d4"
}

$ kubectl get svc rollout-bluegreen-preview -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "5ffd47b8d4"
}

$ kubectl get pods                                                                
NAME                                 READY   STATUS    RESTARTS   AGE
rollout-bluegreen-5ffd47b8d4-89f6c   1/1     Running   0          25m
rollout-bluegreen-5ffd47b8d4-h6rmg   1/1     Running   0          25m

Завдяки AWS ALB контролеру буде створено ALB балансувальник, що буде прослуховувати 80 порт та матиме 2 правила для preview та active (тобто основного) доменів:

Наразі вони обидва будуть вказувати на ту ж групу подів:
А отже по доменах bluegreen-preview.example.com та bluegreen.example.com буде відкриватись та ж сама версія веб-застосунку.

Розпочнемо процес релізу:

$ kubectl argo rollouts set image rollout-bluegreen rollouts-demo=argoproj/rollouts-demo:green
rollout "rollout-bluegreen" image updated


І переглянемо ситуацію:

$ kubectl argo rollouts get rollout rollout-bluegreen
Name:            rollout-bluegreen
Namespace:       default
Status:          ॥ Paused
Message:         BlueGreenPause
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
                 argoproj/rollouts-demo:green (preview)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                           KIND        STATUS     AGE  INFO
⟳ rollout-bluegreen                            Rollout     ॥ Paused   36m  
├──# revision:2                                                            
│  └──⧉ rollout-bluegreen-75695867f            ReplicaSet  ✔ Healthy  23s  preview
│     ├──□ rollout-bluegreen-75695867f-bbcts   Pod         ✔ Running  22s  ready:1/1
│     └──□ rollout-bluegreen-75695867f-m8djd   Pod         ✔ Running  22s  ready:1/1
└──# revision:1                                                            
   └──⧉ rollout-bluegreen-5ffd47b8d4           ReplicaSet  ✔ Healthy  36m  stable,active
      ├──□ rollout-bluegreen-5ffd47b8d4-89f6c  Pod         ✔ Running  36m  ready:1/1
      └──□ rollout-bluegreen-5ffd47b8d4-h6rmg  Pod         ✔ Running  36m  ready:1/1

$ kubectl get svc                                          
NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes                  ClusterIP   172.20.0.1       <none>        443/TCP   93m
rollout-bluegreen-active    ClusterIP   172.20.211.205   <none>        80/TCP    37m
rollout-bluegreen-preview   ClusterIP   172.20.164.253   <none>        80/TCP    37m

$ kubectl get rs                                                       NAME                           DESIRED   CURRENT   READY   AGE
rollout-bluegreen-5ffd47b8d4   2         2         2       38m
rollout-bluegreen-75695867f    2         2         2       100s

$ kubectl get svc rollout-bluegreen-active -o json | jq -r '.spec.selector'  
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "5ffd47b8d4"
}

$ kubectl get svc rollout-bluegreen-preview -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}

$ kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
rollout-bluegreen-5ffd47b8d4-89f6c   1/1     Running   0          38m
rollout-bluegreen-5ffd47b8d4-h6rmg   1/1     Running   0          38m
rollout-bluegreen-75695867f-bbcts    1/1     Running   0          2m2s
rollout-bluegreen-75695867f-m8djd    1/1     Running   0          2m2s


Інгреси як і раніше вказують на ті ж сервіси (Argo Rollouts наразі не оперує ними), піднявся новий реплікасет rollout-bluegreen-75695867f, а отже і його група подів, і на цю групу подів вже дивиться preview сервіс. Знову поглянемо на балансувальник:

По IP-адресам помітно, що це різні поди, як і має бути. Kubectl це підтверджує: 

// preview
$ kubectl get pod rollout-bluegreen-75695867f-bbcts -o json | jq -r '.status.podIP'
10.0.27.77

$ kubectl get pod rollout-bluegreen-75695867f-m8djd -o json | jq -r '.status.podIP'
10.0.18.188

// active
$ kubectl get pod rollout-bluegreen-5ffd47b8d4-89f6c -o json | jq -r '.status.podIP'
10.0.29.141

$ kubectl get pod rollout-bluegreen-5ffd47b8d4-h6rmg -o json | jq -r '.status.podIP'
10.0.19.222

Сервіси пов'язуються із таргет групами за допомогою targetGroupBinding ресурсів:

$ kubectl get targetgroupbinding
NAME                              SERVICE-NAME                SERVICE-PORT   TARGET-TYPE   AGE
k8s-default-rolloutb-813be4e464   rollout-bluegreen-active    80             ip            50m
k8s-default-rolloutb-9cf520ca12   rollout-bluegreen-preview   80             ip            61m

$ kubectl get targetgroupbinding k8s-default-rolloutb-813be4e464 -o yaml
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  ...
  serviceRef:
    name: rollout-bluegreen-active
    port: 80
  targetGroupARN: arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutb-813be4e464/5c3694c4a9de62a8
  targetType: ip
  vpcID: vpc-09061fa28d48455cc
  ...

$ kubectl get targetgroupbinding k8s-default-rolloutb-9cf520ca12 -o yaml
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  ...
  serviceRef:
    name: rollout-bluegreen-preview
    port: 80
  targetGroupARN: arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutb-9cf520ca12/09d2943d1c395c7c
  targetType: ip
  vpcID: vpc-09061fa28d48455cc
  ...

Тож цього разу bluegreen-preview.example.com вже буде вказувати на новий реліз, а на bluegreen.example.com досі на попередній (стабільний). Все завдяки продемонстрованим вище AWS rules в відповідному litener-і.

Завершимо реліз, зробивши promote:

$ kubectl argo rollouts promote rollout-bluegreen 
rollout 'rollout-bluegreen' promoted


І через 30 секунд (значення за замовчуванням) реліз завершиться:

$ kubectl argo rollouts get rollout rollout-bluegreen
Name:            rollout-bluegreen
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:green (stable, active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                          KIND        STATUS        AGE  INFO
⟳ rollout-bluegreen                           Rollout     ✔ Healthy     73m  
├──# revision:2                                                              
│  └──⧉ rollout-bluegreen-75695867f           ReplicaSet  ✔ Healthy     36m  stable,active
│     ├──□ rollout-bluegreen-75695867f-bbcts  Pod         ✔ Running     36m  ready:1/1
│     └──□ rollout-bluegreen-75695867f-m8djd  Pod         ✔ Running     36m  ready:1/1
└──# revision:1                                                              
   └──⧉ rollout-bluegreen-5ffd47b8d4          ReplicaSet  • ScaledDown  73m


bluegreen.example.com буде вказувати вже на нову версію, аналогічно і bluegreen-preview.example.com (адже реліз завершився). Сервіси вказують на новий реплікасет, що став стабільним:

$ kubectl get svc rollout-bluegreen-active -o json | jq -r '.spec.selector'  
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}

$ kubectl get svc rollout-bluegreen-preview -o json | jq -r '.spec.selector'
{
  "app": "rollout-bluegreen",
  "rollouts-pod-template-hash": "75695867f"
}


Кожна таргет група вказує на ті ж адреси/поди:

 

7. ADDITIONAL HINTS

Для забезпечення виливки без 50X-помилок необхідно активувати Pod Readiness Gate, із котрим поди отримуватимуть статус Ready лише після реєстрації їх у відповідній таргет групі.

Також не варто занижувати значення scaleDownDelaySeconds нижче дефолтного 30 секунд, адже цей час необхідний для того, що AWS таргет групи встигли оновитись до необхідних значень об'єктів Kubernetes.

Офіційна документація попереджає, що повністю уникнути 50X-помилок (при наявності вхідного Ingress/балансувальника) для BlueGreen стратегії неможливо на етапі зміни селекторів, адже Pod Readiness Gate працює лише для створення подів. Загалом це не є проблемою, але клієнські дотатки мають вміти робити ретраї без впливу на кінцевих користувачів.

Якщо ж 50X-ті критичні - то варто використовувати Canary PingPong стратегію (чи Gateway API інтеграції), про яку в тому числі і піде мова у другій частині. 

Посилання:
https://argo-rollouts.readthedocs.io/en/stable/
https://rollouts-plugin-trafficrouter-gatewayapi.readthedocs.io/en/latest/
https://github.com/argoproj/argo-rollouts/tree/master/docs 
https://github.com/argoproj/argo-rollouts/tree/master/examples

Немає коментарів:

Дописати коментар