Нещодавно я опублікував першу статтю про Argo Rollouts, де зупинився на прогресивних методах доставки та реалізацію BlueGreen із ALB балансувальником та без. Цього ж разу поговоримо про Canary та його імплементацію в Argo Rollouts.
Ця стаття потребує робочого EKS кластеру, AWS LB контролера і самого Argo Rollouts. Про все це можна почитати в попередніх статтях блогу, а останній Terraform-код знаходиться за наступною адресою.
1. CANARY W/O BALANCER
Офіційна документація пропонує різні варіанти, наприклад коли додаток виливається без сервіс-об'єктів взагалі, чого може бути достатньо для якихось воркерів, що працюють зі сторонньою базою та не мають входу через єдиний ендпоїнт/балансувальник. Чи варіант, коли група подів заводиться лише під один об'єкт сервісу, адже першим етапом такої виливки вже є включення відсотку нової версії коду в трафік. Існує також позиція, що першим етапом Canary має бути под/вузол нової версії, котрий ще не буде під трафіком, але його можна буде обкласти додатковими тестами. Власне варіацій багато, єдиного стандарту немає і все залежить від потреб продукту.
У цій секції ми ж розглянемо найпростіший варіант: єдиний об'єкт сервісу, за котрим будуть з'являтись лише деякі поди із новим кодом. Відсоток буде відраховуватись "вагою" нових подів: 1 пода із новим імеджом із 5 - це 20%, 2 поди - 40% тощо. Для більш просунутих варіантів буде потрібна інтеграція із AWS ALB, про яку поговоримо в наступній частині. На реальному прикладі це має бути більш зрозуміліше:
$ cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-canary
spec:
replicas: 5
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollout-canary
template:
metadata:
labels:
app: rollout-canary
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
imagePullPolicy: Always
ports:
- containerPort: 8080
strategy:
canary:
steps:
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: {duration: 10}
---
apiVersion: v1
kind: Service
metadata:
name: rollout-canary
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: rollout-canary
EOF
rollout.argoproj.io/rollout-canary created
service/rollout-canary created
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 6/6
SetWeight: 100
ActualWeight: 100
Images: argoproj/rollouts-demo:blue (stable)
Replicas:
Desired: 5
Current: 5
Updated: 5
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ✔ Healthy 3m55s
└──# revision:1
└──⧉ rollout-canary-679b8b5b4c ReplicaSet ✔ Healthy 3m55s stable
├──□ rollout-canary-679b8b5b4c-f8vzf Pod ✔ Running 3m55s ready:1/1
├──□ rollout-canary-679b8b5b4c-mgm6k Pod ✔ Running 3m55s ready:1/1
├──□ rollout-canary-679b8b5b4c-rk77m Pod ✔ Running 3m55s ready:1/1
├──□ rollout-canary-679b8b5b4c-txh55 Pod ✔ Running 3m55s ready:1/1
└──□ rollout-canary-679b8b5b4c-v482j Pod ✔ Running 3m55s ready:1/1
$ kubectl get rs rollout-canary-679b8b5b4c -o json | jq -r ".metadata.labels"
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "679b8b5b4c"
}
Сервіс rollout-canary буде постійно дивитись на групу по постійному селектору "app: rollout-canary" за котрим стоятимуть вже дві репліка сети і відповідно їхні поди.
Розпочнемо Canary-реліз, завдяки зміні імеджа в роллаут об'єкті:
$ kubectl argo rollouts set image rollout-canary rollouts-demo=argoproj/rollouts-demo:green
Переглянемо статус релізу:
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ॥ Paused
Message: CanaryPauseStep
Strategy: Canary
Step: 1/6
SetWeight: 20
ActualWeight: 20
Images: argoproj/rollouts-demo:blue (stable)
argoproj/rollouts-demo:green (canary)
Replicas:
Desired: 5
Current: 5
Updated: 1
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ॥ Paused 9m36s
├──# revision:2
│ └──⧉ rollout-canary-5584575b9d ReplicaSet ✔ Healthy 24s canary
│ └──□ rollout-canary-5584575b9d-tnmj2 Pod ✔ Running 24s ready:1/1
└──# revision:1
└──⧉ rollout-canary-679b8b5b4c ReplicaSet ✔ Healthy 9m36s stable
├──□ rollout-canary-679b8b5b4c-mgm6k Pod ✔ Running 9m36s ready:1/1
├──□ rollout-canary-679b8b5b4c-rk77m Pod ✔ Running 9m36s ready:1/1
├──□ rollout-canary-679b8b5b4c-txh55 Pod ✔ Running 9m36s ready:1/1
└──□ rollout-canary-679b8b5b4c-v482j Pod ✔ Running 9m36s ready:1/1
$ kubectl get rs rollout-canary-679b8b5b4c -o json | jq -r ".metadata.labels"
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "679b8b5b4c"
}
$ kubectl get rs rollout-canary-5584575b9d -o json | jq -r ".metadata.labels"
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "5584575b9d"
}
З'явилась одна нова пода в новому репліка-сеті і одна зникла зі старого. Тобто 1 нова нода із 5 це і буде 20%, як було вказано в стратегії.
Реліз наразі зупинився і чекає ручного втручання, завдяки параметру "pause: {}". Тому треба явно відправити promote:
$ kubectl argo rollouts promote rollout-canary
rollout 'rollout-canary' promoted
Після чого відбудеться виливка коду до кінця в 2 кроки із затримкою в 10 секунд:
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 6/6
SetWeight: 100
ActualWeight: 100
Images: argoproj/rollouts-demo:green (stable)
Replicas:
Desired: 5
Current: 5
Updated: 5
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ✔ Healthy 20m
├──# revision:2
│ └──⧉ rollout-canary-5584575b9d ReplicaSet ✔ Healthy 11m stable
│ ├──□ rollout-canary-5584575b9d-tnmj2 Pod ✔ Running 11m ready:1/1
│ ├──□ rollout-canary-5584575b9d-5274x Pod ✔ Running 40s ready:1/1
│ ├──□ rollout-canary-5584575b9d-6cfcx Pod ✔ Running 40s ready:1/1
│ ├──□ rollout-canary-5584575b9d-zzgv6 Pod ✔ Running 28s ready:1/1
│ └──□ rollout-canary-5584575b9d-xhm9b Pod ✔ Running 17s ready:1/1
└──# revision:1
└──⧉ rollout-canary-679b8b5b4c ReplicaSet • ScaledDown 20m
Дії по промоуту версії також можна виконувати із веб-панелі:
2. CANARY WITH BALANCER
Наявність провайдера трафіку, що у нашому випадку виконує роль AWS LB Controller, значно розширює можливості Canary в Argo Rollouts. Останній здатен маніпулювати анотаціями Ingress-ресурсів, котрі в свою чергу можуть встановлювати точний відсоток трафіку, що піде на кожну із версій сервісу. Також є можливість підняття нової версії без автоматичного включення її в трафік, наприклад задля попереднього її тестування тощо.
Розглянемо наступний варіант:
$ cat << EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-canary
spec:
replicas: 5
strategy:
canary:
canaryService: rollout-canary-canary
stableService: rollout-canary-stable
trafficRouting:
alb:
ingress: rollout-canary-stable
servicePort: 80
scaleDownDelaySeconds: 60
steps:
- setCanaryScale:
replicas: 2
- pause: {}
- setWeight: 20
- setCanaryScale:
matchTrafficWeight: true
- pause: {}
- setWeight: 40
- pause: {}
- setWeight: 60
- pause: {duration: 10}
- setWeight: 80
- pause: {duration: 10}
revisionHistoryLimit: 3
selector:
matchLabels:
app: rollout-canary
template:
metadata:
labels:
app: rollout-canary
spec:
containers:
- name: rollout-canary
image: argoproj/rollouts-demo:blue
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: rollout-canary-canary
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: rollout-canary
---
apiVersion: v1
kind: Service
metadata:
name: rollout-canary-stable
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: rollout-canary
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rollout-canary-stable
annotations:
alb.ingress.kubernetes.io/target-type: ip
# external-dns.alpha.kubernetes.io/hostname: canary.example.com
alb.ingress.kubernetes.io/group.name: canary-group
alb.ingress.kubernetes.io/success-codes: 200-302
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
spec:
ingressClassName: alb
rules:
- host: canary.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rollout-canary-stable
port:
name: use-annotation
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rollout-canary-canary
annotations:
alb.ingress.kubernetes.io/target-type: ip
# external-dns.alpha.kubernetes.io/hostname: canary-preview.example.com
alb.ingress.kubernetes.io/group.name: canary-group
alb.ingress.kubernetes.io/success-codes: 200-302
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
spec:
ingressClassName: alb
rules:
- host: canary-preview.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rollout-canary-canary
port:
number: 80
EOF
rollout.argoproj.io/rollout-canary created
service/rollout-canary-canary created
service/rollout-canary-stable created
ingress.networking.k8s.io/rollout-canary-stable created
ingress.networking.k8s.io/rollout-canary-canary created
Ми створили Rollout об'єкт, де вказали який сервіс буде стабільним (попередня версія коду до початку деплою), а який canary (нова версія коду, котру будуть поступово включати в загальний трафік). Як і у випадку із BlueGreen, Argo Rollouts оперуватиме додатковими селекторами сервіс-об'єктів задля того, щоб спрямувати їх до правильної групи подів (реплікасетів).
Також ми вилили два інгресс-об'єкта: один, rollout-canary-canary, буде постійно вказувати на одноіменний сервіс, а інший, rollout-canary-stable, буде мати дещо складнішу логіку. Для нього Argo Rollouts буде проставляти додатковий annotation, де буде вказано який відсоток і на який сервіс-об'єкт необхідно буде відсилати (port.name: use-annotation). Таким чином і буде досягатись відсоток трафіку, що має прямувати на нову версію коду.
Отже, початковий стан роллауту наступний:
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 11/11
SetWeight: 100
ActualWeight: 100
Images: argoproj/rollouts-demo:blue (stable)
Replicas:
Desired: 5
Current: 5
Updated: 5
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ✔ Healthy 99m
└──# revision:1
└──⧉ rollout-canary-7487c96d7b ReplicaSet ✔ Healthy 95m stable
├──□ rollout-canary-7487c96d7b-482gg Pod ✔ Running 95m ready:1/1
├──□ rollout-canary-7487c96d7b-4s895 Pod ✔ Running 95m ready:1/1
├──□ rollout-canary-7487c96d7b-bb42b Pod ✔ Running 95m ready:1/1
├──□ rollout-canary-7487c96d7b-kstlp Pod ✔ Running 95m ready:1/1
└──□ rollout-canary-7487c96d7b-vdh9v Pod ✔ Running 95m ready:1/1
Зі сторони AWS справи на балансувальнику виглядають наступним чином:
$ kubectl get targetgroupbinding
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE AGE
k8s-default-rolloutc-3563d42384 rollout-canary-canary 80 ip 116m
k8s-default-rolloutc-55889e344e rollout-canary-canary 80 ip 119m
k8s-default-rolloutc-7d29633734 rollout-canary-stable 80 ip 115m
$ kubectl get targetgroupbinding -o wide
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE ARN NAME AGE
k8s-default-rolloutc-3563d42384 rollout-canary-canary 80 ip arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutc-3563d42384/131cd3a0ef5445d9 117m
k8s-default-rolloutc-55889e344e rollout-canary-canary 80 ip arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutc-55889e344e/56e12919fd79b739 120m
k8s-default-rolloutc-7d29633734 rollout-canary-stable 80 ip arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutc-7d29633734/5415dfce8a8ada28 117m
Як бачимо, rollout-canary-canary сервіс має два TGB, серед яких і буде відбуватись магія відсотків.
На початку всі таргет групи будуть вказувати на ту ж групу:
Тепер почнемо сам реліз, котрий, нагадаю, також можна почати із web-панелі:
$ kubectl argo rollouts set image rollout-canary rollout-canary=argoproj/rollouts-demo:green
rollout "rollout-canary" image updated
І перевіримо, які зміни відбулись:
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ॥ Paused
Message: CanaryPauseStep
Strategy: Canary
Step: 1/11
SetWeight: 0
ActualWeight: 0
Images: argoproj/rollouts-demo:blue (stable)
argoproj/rollouts-demo:green (canary)
Replicas:
Desired: 5
Current: 7
Updated: 2
Ready: 7
Available: 7
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ॥ Paused 130m
├──# revision:2
│ └──⧉ rollout-canary-5497dcb47f ReplicaSet ✔ Healthy 57s canary
│ ├──□ rollout-canary-5497dcb47f-jnfrl Pod ✔ Running 56s ready:1/1
│ └──□ rollout-canary-5497dcb47f-vgsnh Pod ✔ Running 56s ready:1/1
└──# revision:1
└──⧉ rollout-canary-7487c96d7b ReplicaSet ✔ Healthy 126m stable
├──□ rollout-canary-7487c96d7b-482gg Pod ✔ Running 126m ready:1/1
├──□ rollout-canary-7487c96d7b-4s895 Pod ✔ Running 126m ready:1/1
├──□ rollout-canary-7487c96d7b-bb42b Pod ✔ Running 126m ready:1/1
├──□ rollout-canary-7487c96d7b-kstlp Pod ✔ Running 126m ready:1/1
└──□ rollout-canary-7487c96d7b-vdh9v Pod ✔ Running 126m ready:1/1
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
rollout-canary-canary ClusterIP 172.20.58.159 <none> 80/TCP 132m
rollout-canary-stable ClusterIP 172.20.165.229 <none> 80/TCP 128m
$ kubectl get svc rollout-canary-canary -o json | jq -r '.spec.selector'
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "5497dcb47f"
}
$ kubectl get svc rollout-canary-stable -o json | jq -r '.spec.selector'
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "7487c96d7b"
}
Проте по статусу роллауту, трафік наразі йде лише на rollout-canary-stable сервіс:
$ kubectl get rollout rollout-canary -o json | jq -r ".status.canary"
{
"weights": {
"canary": {
"podTemplateHash": "5497dcb47f",
"serviceName": "rollout-canary-canary",
"weight": 0
},
"stable": {
"podTemplateHash": "7487c96d7b",
"serviceName": "rollout-canary-stable",
"weight": 100
}
}
}
Це також підтверджує сам AWS, адже відсотковий розподіл на таргет групах лишився такий самий:
Проте preview-домен canary-preview.example.com наразі буде вказувати на нову версію коду:
$ kubectl get pod rollout-canary-5497dcb47f-jnfrl -o json | jq -r '.status.podIP'
10.0.25.90
$ kubectl get pod rollout-canary-5497dcb47f-vgsnh -o json | jq -r '.status.podIP'
10.0.18.113
Це робиться для можливості перевірити роботу нового коду перед фактичним його введенням в трафік. Звісно, якщо це необхідно.
Таргет група, на яку йде і досі 100% трафіку залишилась без змін:
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
rollout-canary-canary alb canary-preview.example.com internal-k8s-canarygroup-17ccba5dee-147664472.us-east-1.elb.amazonaws.com 80 173m
rollout-canary-stable alb canary.example.com internal-k8s-canarygroup-17ccba5dee-147664472.us-east-1.elb.amazonaws.com 80 173m
$ kubectl get ing rollout-canary-stable -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-canary-stable": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-canary-canary\",\"ServicePort\":\"80\",\"Weight\":0},{\"ServiceName\":\"rollout-canary-stable\",\"ServicePort\":\"80\",\"Weight\":100}]}}",
...
}
Анотація вказує на те, що 100% трафіку отримують саме поди під rollout-canary-stable сервісом, незалежно від того, що поди із новою версією були підняті також. Це можливо завдяки setCanaryScale опції роллаут-стратегії.
Наразі роллаут потребує ручного промоута наступного кроку завдяки параметру стратегії "pause: {}":
$ kubectl argo rollouts promote rollout-canary
rollout 'rollout-canary' promoted
Подивимось які зміни відбулись:
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ॥ Paused
Message: CanaryPauseStep
Strategy: Canary
Step: 4/11
SetWeight: 20
ActualWeight: 20
Images: argoproj/rollouts-demo:blue (stable)
argoproj/rollouts-demo:green (canary)
Replicas:
Desired: 5
Current: 6
Updated: 1
Ready: 6
Available: 6
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ॥ Paused 3h4m
├──# revision:2
│ └──⧉ rollout-canary-5497dcb47f ReplicaSet ✔ Healthy 55m canary
│ └──□ rollout-canary-5497dcb47f-jnfrl Pod ✔ Running 55m ready:1/1
└──# revision:1
└──⧉ rollout-canary-7487c96d7b ReplicaSet ✔ Healthy 3h1m stable
├──□ rollout-canary-7487c96d7b-482gg Pod ✔ Running 3h1m ready:1/1
├──□ rollout-canary-7487c96d7b-4s895 Pod ✔ Running 3h1m ready:1/1
├──□ rollout-canary-7487c96d7b-bb42b Pod ✔ Running 3h1m ready:1/1
├──□ rollout-canary-7487c96d7b-kstlp Pod ✔ Running 3h1m ready:1/1
└──□ rollout-canary-7487c96d7b-vdh9v Pod ✔ Running 3h1m ready:1/1
Завдяки кроку "setWeight: 20" було проставлено 20% трафіку на нову версію, а "setCanaryScale.matchTrafficWeight: true" повернув відповідність відсотку трафіку до відсотку под, що його обслуговують. Ця логіка активована по-замовчуванню, і її необхідно явно активувати після користування опцією ручного управління репліками setCanaryScale. Саме тому на новій версії залишилась лише 1 пода, котра кількісно і є 20-ма відсотками потужностей.
Балансувальник та таргет групи також все це відображають:$ kubectl get ing rollout-canary-stable -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-canary-stable": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-canary-canary\",\"ServicePort\":\"80\",\"Weight\":20},{\"ServiceName\":\"rollout-canary-stable\",\"ServicePort\":\"80\",\"Weight\":80}]}}",
...
}
Тож під основним доменом canary.example.com вже 20% трафіку піде на поди із новим кодом. Preview-домен canary-preview.example.com вже особливо не цікавить, бо він виконав свою роль:
Наступний крок збільшить відсоток трафіку нової версії до 40:
$ kubectl argo rollouts promote rollout-canary
$ kubectl get ing rollout-canary-stable -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-canary-stable": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-canary-canary\",\"ServicePort\":\"80\",\"Weight\":40},{\"ServiceName\":\"rollout-canary-stable\",\"ServicePort\":\"80\",\"Weight\":60}]}}",
...
}
$ kubectl argo rollouts get rollout rollout-canary
Name: rollout-canary
Namespace: default
Status: ॥ Paused
Message: CanaryPauseStep
Strategy: Canary
Step: 6/11
SetWeight: 40
ActualWeight: 40
Images: argoproj/rollouts-demo:blue (stable)
argoproj/rollouts-demo:green (canary)
Replicas:
Desired: 5
Current: 7
Updated: 2
Ready: 7
Available: 7
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ॥ Paused 3h28m
├──# revision:2
│ └──⧉ rollout-canary-5497dcb47f ReplicaSet ✔ Healthy 79m canary
│ ├──□ rollout-canary-5497dcb47f-jnfrl Pod ✔ Running 79m ready:1/1
│ └──□ rollout-canary-5497dcb47f-btdjf Pod ✔ Running 62s ready:1/1
└──# revision:1
└──⧉ rollout-canary-7487c96d7b ReplicaSet ✔ Healthy 3h25m stable
├──□ rollout-canary-7487c96d7b-482gg Pod ✔ Running 3h25m ready:1/1
├──□ rollout-canary-7487c96d7b-4s895 Pod ✔ Running 3h25m ready:1/1
├──□ rollout-canary-7487c96d7b-bb42b Pod ✔ Running 3h25m ready:1/1
├──□ rollout-canary-7487c96d7b-kstlp Pod ✔ Running 3h25m ready:1/1
└──□ rollout-canary-7487c96d7b-vdh9v Pod ✔ Running 3h25m ready:1/1
Після чого відсоток трафіку із двома паузами в 10 секунд буде доведено до 100%:
$ kubectl argo rollouts promote rollout-canary
$ kubectl get ing rollout-canary-stable -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-canary-stable": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-canary-canary\",\"ServicePort\":\"80\",\"Weight\":0},{\"ServiceName\":\"rollout-canary-stable\",\"ServicePort\":\"80\",\"Weight\":100}]}}",
...
}
$ kubectl argo rollouts get rollout rollout-canary Name: rollout-canary
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 11/11
SetWeight: 100
ActualWeight: 100
Images: argoproj/rollouts-demo:green (stable)
Replicas:
Desired: 5
Current: 5
Updated: 5
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-canary Rollout ✔ Healthy 3h34m
├──# revision:2
│ └──⧉ rollout-canary-5497dcb47f ReplicaSet ✔ Healthy 85m stable
│ ├──□ rollout-canary-5497dcb47f-jnfrl Pod ✔ Running 85m ready:1/1
│ ├──□ rollout-canary-5497dcb47f-btdjf Pod ✔ Running 7m15s ready:1/1
│ ├──□ rollout-canary-5497dcb47f-9jhct Pod ✔ Running 3m37s ready:1/1
│ ├──□ rollout-canary-5497dcb47f-wf5zl Pod ✔ Running 3m14s ready:1/1
│ └──□ rollout-canary-5497dcb47f-xwjq7 Pod ✔ Running 3m2s ready:1/1
└──# revision:1
└──⧉ rollout-canary-7487c96d7b ReplicaSet • ScaledDown 3h31m
На сервіс rollout-canary-stable буде знову прямувати 100% трафіку, проте за ним (як і за preview звісно) вже стоятимуть поди із новою версією коду:
$ kubectl get svc rollout-canary-stable -o json | jq -r '.spec.selector'
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "5497dcb47f"
}
$ kubectl get svc rollout-canary-canary -o json | jq -r '.spec.selector'
{
"app": "rollout-canary",
"rollouts-pod-template-hash": "5497dcb47f"
}
Тобто на останньому етапі Argo Rollouts переписав селектор для rollout-canary-stable на поди із новою версією коду 5497dcb47f, хоча раніше він був на 7487c96d7b.
Логіка хоч і не проста, але доволі зрозуміла. У кінці релізу, як і до його початку, сервіси rollout-canary-stable та rollout-canary-canary будуть вказувати на ті ж поди, а із початком релізу сервіс rollout-canary-canary буде "збирати" під собою поди нової версії. Недоліком цієї схеми є те, що із деякими контролерами (наприклад AWS LB Controller) управління трафіком на останньому етапі зміни селекторів можуть з'являтись 500-ті помилки для клієнтів. Причиною цього є те, що поява подів в кожній групи управляється за допомогою Pod Readiness Gate (так, ліпше його активувати). Але він жодним чином не регулює зміну адрес подів в кожній AWS таргет групі на етапі зміни селекторів в сервіс-об'єктах.
Ця ж особливість присутня і у імплементації BlueGreen, про котру я писав у попередній статті.
Знову ж, це може не бути проблемою, адже клієнти повинні володіти retry-логікою у разі появи подібних помилок. Якщо ж це все таки критично - то ліпше користуватись Canary PingPong стратегією, де не відбувається зміни селекторів на останньому етапі.
3. CANARY PING-PONG STRATEGY
Ця стратегія можлива лише із залученням контролерів управління трафіком і має інші параметри ніж попередня, хоч дещо схожу логіку:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-pingpong
spec:
replicas: 5
strategy:
canary:
pingPong:
pingService: rollout-ping
pongService: rollout-pong
trafficRouting:
alb:
ingress: rollout-pingpong
rootService: rollout-ping
servicePort: 80
scaleDownDelaySeconds: 60
steps:
- setWeight: 10
- pause: {}
- setWeight: 60
- pause: {duration: 10}
revisionHistoryLimit: 3
selector:
matchLabels:
app: rollout-pingpong
template:
metadata:
labels:
app: rollout-pingpong
spec:
containers:
- name: rollout-pingpong
image: argoproj/rollouts-demo:blue
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: rollout-ping
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: rollout-pingpong
---
apiVersion: v1
kind: Service
metadata:
name: rollout-pong
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: rollout-pingpong
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rollout-pingpong
annotations:
alb.ingress.kubernetes.io/target-type: ip
# external-dns.alpha.kubernetes.io/hostname: pingpong.example.com
alb.ingress.kubernetes.io/group.name: pingpong-group
alb.ingress.kubernetes.io/success-codes: 200-302
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
spec:
ingressClassName: alb
rules:
- host: pingpong.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rollout-ping
port:
name: use-annotation
EOF
rollout.argoproj.io/rollout-pingpong created
service/rollout-ping created
service/rollout-pong created
ingress.networking.k8s.io/rollout-pingpong created
До початку релізу на стороні AWS маємо одне правило в listener на 2 таргет групи, котрі із самого початку вказують на ті ж поди:
Традиційно відсотком в правилі керує AWS LB Controller через анотацію в Ingress:
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
rollout-pingpong alb pingpong.example.com internal-k8s-pingponggroup-7eed16b808-473890389.us-east-1.elb.amazonaws.com 80 17m
$ kubectl get ing rollout-pingpong -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-ping": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-pong\",\"ServicePort\":\"80\",\"Weight\":0},{\"ServiceName\":\"rollout-ping\",\"ServicePort\":\"80\",\"Weight\":100}]}}"
...
}
Тобто наразі 100% трафіку прямує на сервіс rollout-ping, а rollout-pong, котрий буде майбутнім Canary (тобто новою версією коду), наразі не отримує трафіку.
$ kubectl argo rollouts get rollout rollout-pingpong
Name: rollout-pingpong
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 4/4
SetWeight: 100
ActualWeight: 100
Images: argoproj/rollouts-demo:blue (stable, ping)
Replicas:
Desired: 5
Current: 5
Updated: 5
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-pingpong Rollout ✔ Healthy 21m
└──# revision:1
└──⧉ rollout-pingpong-86468fd556 ReplicaSet ✔ Healthy 21m stable,ping
├──□ rollout-pingpong-86468fd556-2s7tl Pod ✔ Running 21m ready:1/1
├──□ rollout-pingpong-86468fd556-97x92 Pod ✔ Running 21m ready:1/1
├──□ rollout-pingpong-86468fd556-ltwfl Pod ✔ Running 21m ready:1/1
├──□ rollout-pingpong-86468fd556-rthjw Pod ✔ Running 21m ready:1/1
└──□ rollout-pingpong-86468fd556-x6kn2 Pod ✔ Running 21m ready:1/1
Ну і селектори на сервісах однакові, що доводить вже попередню ситуацію за таргет групами:
$ kubectl get svc rollout-ping -o json | jq -r '.spec.selector'
{
"app": "rollout-pingpong",
"rollouts-pod-template-hash": "86468fd556"
}
$ kubectl get svc rollout-pong -o json | jq -r '.spec.selector'
{
"app": "rollout-pingpong"
}
Почнемо реліз, після чого подивимось, що станеться із об'єктами AWS та Kubernetes:
$ kubectl argo rollouts set image rollout-pingpong rollout-pingpong=argoproj/rollouts-demo:green
$ kubectl argo rollouts get rollout rollout-pingpong
Name: rollout-pingpong
Namespace: default
Status: ॥ Paused
Message: CanaryPauseStep
Strategy: Canary
Step: 1/4
SetWeight: 10
ActualWeight: 10
Images: argoproj/rollouts-demo:blue (stable, ping)
argoproj/rollouts-demo:green (canary, pong)
Replicas:
Desired: 5
Current: 6
Updated: 1
Ready: 6
Available: 6
NAME KIND STATUS AGE INFO
⟳ rollout-pingpong Rollout ॥ Paused 26m
├──# revision:2
│ └──⧉ rollout-pingpong-6b5d458dd7 ReplicaSet ✔ Healthy 16s canary,pong
│ └──□ rollout-pingpong-6b5d458dd7-khbsw Pod ✔ Running 15s ready:1/1
└──# revision:1
└──⧉ rollout-pingpong-86468fd556 ReplicaSet ✔ Healthy 26m stable,ping
├──□ rollout-pingpong-86468fd556-2s7tl Pod ✔ Running 26m ready:1/1
├──□ rollout-pingpong-86468fd556-97x92 Pod ✔ Running 26m ready:1/1
├──□ rollout-pingpong-86468fd556-ltwfl Pod ✔ Running 26m ready:1/1
├──□ rollout-pingpong-86468fd556-rthjw Pod ✔ Running 26m ready:1/1
└──□ rollout-pingpong-86468fd556-x6kn2 Pod ✔ Running 26m ready:1/1
$ kubectl get ing rollout-pingpong -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-ping": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-pong\",\"ServicePort\":\"80\",\"Weight\":10},{\"ServiceName\":\"rollout-ping\",\"ServicePort\":\"80\",\"Weight\":90}]}}",
...
}
$ kubectl get svc rollout-ping -o json | jq -r '.spec.selector'
{
"app": "rollout-pingpong",
"rollouts-pod-template-hash": "86468fd556"
}
$ kubectl get svc rollout-pong -o json | jq -r '.spec.selector'
{
"app": "rollout-pingpong",
"rollouts-pod-template-hash": "6b5d458dd7"
}
Цього разу 10% трафіку прямує на нову версію rollout-pong, а 90% на rollout-ping (стабільна версія).
$ kubectl argo rollouts promote rollout-pingpong
rollout 'rollout-pingpong' promoted
$ kubectl argo rollouts get rollout rollout-pingpong
Name: rollout-pingpong
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 4/4
SetWeight: 100
ActualWeight: 100
Images: argoproj/rollouts-demo:green (stable, pong)
Replicas:
Desired: 5
Current: 5
Updated: 5
Ready: 5
Available: 5
NAME KIND STATUS AGE INFO
⟳ rollout-pingpong Rollout ✔ Healthy 39m
├──# revision:2
│ └──⧉ rollout-pingpong-6b5d458dd7 ReplicaSet ✔ Healthy 13m stable,pong
│ ├──□ rollout-pingpong-6b5d458dd7-khbsw Pod ✔ Running 13m ready:1/1
│ ├──□ rollout-pingpong-6b5d458dd7-6455q Pod ✔ Running 2m39s ready:1/1
│ ├──□ rollout-pingpong-6b5d458dd7-bjk8w Pod ✔ Running 2m39s ready:1/1
│ ├──□ rollout-pingpong-6b5d458dd7-h2p6x Pod ✔ Running 2m17s ready:1/1
│ └──□ rollout-pingpong-6b5d458dd7-q65zw Pod ✔ Running 2m17s ready:1/1
└──# revision:1
└──⧉ rollout-pingpong-86468fd556 ReplicaSet • ScaledDown 39
$ kubectl get ing rollout-pingpong -o json | jq -r '.metadata.annotations'
{
"alb.ingress.kubernetes.io/actions.rollout-ping": "{\"Type\":\"forward\",\"ForwardConfig\":{\"TargetGroups\":[{\"ServiceName\":\"rollout-ping\",\"ServicePort\":\"80\",\"Weight\":0},{\"ServiceName\":\"rollout-pong\",\"ServicePort\":\"80\",\"Weight\":100}]}}",
...
}
Тут цікавий момент, що після завершення релізу 100% запитів так і потрапляють на сервіс rollout-pong без змін селекторів на сервісах:
$ kubectl get svc rollout-ping -o json | jq -r '.spec.selector'
{
"app": "rollout-pingpong",
"rollouts-pod-template-hash": "86468fd556"
}
$ kubectl get svc rollout-pong -o json | jq -r '.spec.selector'
{
"app": "rollout-pingpong",
"rollouts-pod-template-hash": "6b5d458dd7"
}
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
rollout-pingpong-6b5d458dd7-6455q 1/1 Running 0 6m14s
rollout-pingpong-6b5d458dd7-bjk8w 1/1 Running 0 6m14s
rollout-pingpong-6b5d458dd7-h2p6x 1/1 Running 0 5m52s
rollout-pingpong-6b5d458dd7-khbsw 1/1 Running 0 17m
rollout-pingpong-6b5d458dd7-q65zw 1/1 Running 0 5m52s
$ kubectl get targetgroupbinding -o wide
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE ARN NAME AGE
k8s-default-rolloutp-1ca7b121b6 rollout-ping 80 ip arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutp-1ca7b121b6/820015d99b453aa0 44m
k8s-default-rolloutp-7ddac9b339 rollout-pong 80 ip arn:aws:elasticloadbalancing:us-east-1:789248082627:targetgroup/k8s-default-rolloutp-7ddac9b339/c2213b9429d2690a 44mТобто останній етап зміни селекторів на стабільний хеш відстуній, а отже це унеможливлює 500-ті помилки в цей час.
Після завершення наступного реліза стабільною версію вже буде rollout-ping і так далі по колу. Тож саме тому відсутня можливість прив'язки додаткового інгреса на canary версію, адже її сервіс не має фіксованого імені. Знову ж, навряд це потрібно всім.
Детальніше про ці проблеми тут:
https://github.com/argoproj/argo-rollouts/issues/1283
https://github.com/argoproj/argo-rollouts/issues/1453
https://github.com/kubernetes-sigs/aws-load-balancer-controller/issues/2061
https://docs.google.com/presentation/d/1JnvlE-oKL7HPErwFnBBhH2pfWUf0kSoFRLUDt2Glc6E
Немає коментарів:
Дописати коментар