O que é o Argo Rollouts?
Argo Rollouts é um controlador para Kubernetes e um conjunto de CRDs que oferecem capacidades avançadas de deploy, como blue-green, canary, análise de canary, experimentação e recursos de entrega progressiva para o Kubernetes.
O Argo Rollouts pode se integrar (opcionalmente) com controladores de ingress e service meshes, aproveitando suas habilidades de manipulação de tráfego para direcionar gradualmente o tráfego para a nova versão durante uma atualização. Além disso, o Rollouts pode consultar e interpretar métricas de diversos provedores para verificar KPIs importantes e automatizar a promoção ou o rollback durante uma atualização.
Repositórios
Diversas seções dos manifestos presentes no repositório acima, foram portadas ou inspiradas a partir do repositório msc-research-stuff, do Matheus Fidelis. Se você leva Engenharia a sério, recomendo fortemente que veja https://fidelissauro.dev/.
Este outro repositório pode te ajudar a provisionar, com Terraform, um cluster EKS funcional com a maior parte dos componentes utilizados no lab.
Componentes do cluster
Para esse lab, foram utilizados essencialmente os componentes abaixo em um cluster EKS:
- ArgoCD
- Argo Rollouts
- Kube-prometheus-stack
- Istio (base, istiod e gateway)
Projects e Applications no ArgoCD
Foram criados dois Projects e quatro Applications, uma para cada cenário explorado:
Project | Application |
---|---|
lab-apps-blue-green | lab-apps-blue-green-simple |
lab-apps-blue-green | lab-apps-blue-green-automated |
lab-apps-canary | lab-apps-canary-simple |
lab-apps-canary | lab-apps-canary-metrics |
Exemplo de um manifesto de AppProject e Application no ArgoCD:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: lab-apps-canary
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: Lab Apps with Argo Rollouts
sourceRepos:
- 'https://github.com/paulofponciano/lab-argo-rollouts'
- 'oci://registry-1.docker.io/paulofponciano'
- 'registry-1.docker.io/paulofponciano'
destinations:
- namespace: '*'
server: 'https://kubernetes.default.svc'
name: 'in-cluster'
clusterResourceWhitelist:
- group: '*'
kind: '*'
namespaceResourceWhitelist:
- group: '*'
kind: '*'
orphanedResources:
warn: false
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: lab-apps-canary-simple
namespace: argocd
labels:
type: application
spec:
project: lab-apps-canary
source:
repoURL: https://github.com/paulofponciano/lab-argo-rollouts
path: argo-apps/canary/simple
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Cenário com Blue/Green simples
Usamos uma app demo bem simples que já possui um helm chart criado. Com um ApplicationSet do Argo, passamos alguns valores para configuração:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: demo-ms-2
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: https://kubernetes.default.svc
template:
metadata:
name: demo-ms-2
spec:
project: lab-apps-blue-green
source:
repoURL: 'registry-1.docker.io/paulofponciano'
chart: demo-ms-intercomunicacao
targetRevision: 0.1.18
helm:
valuesObject:
# ...omitted for brevity...
strategy:
type: blueGreen
blueGreen:
activeService: demo-ms-2
previewService: demo-ms-2-green
autoPromotionEnabled: false
# ...omitted for brevity...
destination:
server: '{{ cluster }}'
namespace: argocd
syncPolicy:
automated: {}
No manifesto acima, veja que informamos dois services para a estratégia Blue/Green: activeService e previewService. Enquanto não existir uma réplica green, ambos os services direcionam o tráfego para réplica estável (blue). No entanto, o tráfego de "produção" chegará através do Virtual Service do Istio, que por sua vez roteia apenas para o activeService:
Recursos criados pelo ApplicationSet:
No pod que, atualmente, é a réplica estável, podemos ver que existe uma label rollouts-pod-template-hash: 667677c457
(além da label app):
Essa label é utilizada como selector
tanto no activeService quanto no previewService. Isso é controlado pelo Argo Rollouts:
Como mencionado anteriormente, o Virtual Service do Istio possui uma rota com destination
apenas para demo-ms-2.demo.svc.cluster.local
, que é o activeService:
Enviando requisições para o endpoint /version
tanto pelo activeService (que passa pelo VS do Istio) quanto pelo previewService, vemos que a resposta é a mesma, pois estamos acessando a mesma réplica (blue):
while true; do curl http://demo-ms-2.demo.svc.cluster.local:5000/version; done
while true; do curl http://demo-ms-2-green.demo.svc.cluster.local:5000/version; done
No ApplicationSet, alteramos a APP_VERSION
de "0.1.1"
para "0.1.2"
, essa alteração vai gerar um Blue/Green para essa app:
Agora com duas réplicas, uma continua sendo a estável (blue) recebendo tráfego de produção e a outra green com a nova versão da app. O Argo Rollouts altera o selector
no previewService para que o endpoint seja a réplica green:
Com isso, temos a chance de validar se a nova versão está se comportando como o esperado antes de enviar tráfego de produção. Em alguns casos, é interessante também para aplicações que possuem particularidades em relação a warm-up:
Uma vez que decidimos que está tudo certo com a nova versão (green), podemos promovê-la. No Rollout, selecionamos 'Promote-Full':
Desta vez, o selector
do activeService é alterado para que o endpoint seja a réplica da nova versão rollouts-pod-template-hash: 65dcC64c86
(que agora passa a ser blue):
Voltamos ao cenário inicial, mas desta vez com a nova versão "0.1.2"
já recebendo tráfego de produção. A réplica que antes era blue na versão "0.1.1"
é terminada:
Cenário com Blue/Green automatizado
Para aumentar a confiabilidade, no processo de rollout podemos incluir testes automatizados na réplica green, tornando a promoção da nova versão condicionada ao sucesso desses testes. Ainda assim, neste cenário, o parâmetro autoPromotionEnabled
permanece configurado como false
, o que significa que a promoção completa (Promote-Full) continuará sendo realizada manualmente, assim como no cenário anterior. Essa abordagem é uma escolha para manter maior controle sobre o processo.
No ApplicationSet adicionamos os templates de prePromotionAnalysis
bem como values para o Helm renderizar esses templates:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: demo-ms-4
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: https://kubernetes.default.svc
template:
metadata:
name: demo-ms-4
spec:
project: lab-apps-blue-green
source:
repoURL: 'registry-1.docker.io/paulofponciano'
chart: demo-ms-intercomunicacao
targetRevision: 0.1.18
helm:
valuesObject:
# ...omitted for brevity...
strategy:
type: blueGreen
blueGreen:
activeService: demo-ms-4
previewService: demo-ms-4-green
autoPromotionEnabled: false
prePromotionAnalysis:
templates:
- templateName: demo-ms-4-http-bench-analysis
- templateName: demo-ms-4-check-success
analysisTemplates:
- name: demo-ms-4-http-bench-analysis
spec:
metrics:
- name: http-bench-analysis
failureLimit: 1
provider:
job:
spec:
backoffLimit: 1
template:
metadata:
labels:
istio-injection: disabled
sidecar.istio.io/inject: "false"
spec:
containers:
- name: http-bench-analysis
image: rogerw/cassowary:v0.14.0
command: ["cassowary"]
args: ["run", "-u", "http://demo-ms-4-green.demo.svc.cluster.local:5000/healthz", "-c", "3", "-n", "1000"]
restartPolicy: Never
count: 1
- name: demo-ms-4-check-success
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95
failureLimit: 1
provider:
prometheus:
address: http://kube-prometheus-stack-prometheus.o11y.svc.cluster.local:9090
query: |
sum(irate(
istio_requests_total{destination_service=~"demo-ms-4-green.demo.svc.cluster.local",response_code!~"5.*"}[1m]
)) /
sum(irate(
istio_requests_total{destination_service=~"demo-ms-4-green.demo.svc.cluster.local"}[1m]
))
count: 1
# ...omitted for brevity...
destination:
server: '{{ cluster }}'
namespace: argocd
syncPolicy:
automated: {}
Para gerar o Blue/Green alteramos no ApplicationSet a APP_VERSION
de "0.1.3"
para "0.1.4"
:
Testes http-bench-analysis
(Job) e success-rate
com métricas do Prometheus:
Como os testes passaram com sucesso, a réplica green fica disponível para ser promovida:
while true; do curl http://demo-ms-4.demo.svc.cluster.local:5000/version; done
while true; do curl http://demo-ms-4-green.demo.svc.cluster.local:5000/version; done
A forma como o Argo Rollouts manipula o selector
tanto no activeService quanto no previewService é a mesma do cenário anterior, e o Virtual Service do Istio continua apenas com uma rota, tendo como destination
o activeService.
Após o 'Promote-Full', a réplica com a versão "0.1.3"
é removida e a nova versão "0.1.4"
agora é a blue:
Cenário com Canary simples
Com a estratégia de canary releases, podemos realizar um rollout gradual em ambiente de produção, avaliando a "aceitação estruturada do usuário". Caso sejam identificados problemas ou a aceitação não seja satisfatória, é fundamental que seja possível realizar um rollback (Abort) imediato para a versão anterior.
Nesse cenário, a progressão do rollout será manual entre os steps. Os values para o helm são informados no ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: demo-ms-7
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: https://kubernetes.default.svc
template:
metadata:
name: demo-ms-7
spec:
project: lab-apps-canary
source:
repoURL: 'registry-1.docker.io/paulofponciano'
chart: demo-ms-intercomunicacao
targetRevision: 0.1.18
helm:
valuesObject:
# ...omitted for brevity...
strategy:
type: canary
canary:
steps:
- setWeight: 0
- pause: {}
- setWeight: 10
- pause: {}
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: {}
- setWeight: 80
- pause: {}
- setWeight: 100
# ...omitted for brevity...
destination:
server: '{{ cluster }}'
namespace: argocd
syncPolicy:
automated: {}
Para iniciar o processo, alteramos no ApplicationSet a APP_VERSION
de "0.1.5"
para "0.1.6"
. Como o primeiro step é setWeight: 0
nada acontecerá até avançarmos manualmente o canary:
while true; do curl http://demo-ms-7.demo.svc.cluster.local:5000/version; done
while true; do curl http://demo-ms-7-canary.demo.svc.cluster.local:5000/version; done
Semelhante ao que vimos nos cenários de Blue/Green, aqui os dois services estão com o selector
na mesma réplica (réplica estável):
No entanto, o Virtual Service do Istio possui uma rota com dois destination
e com pesos (weight) distintos. Como ainda não avançamos para o próximo step, o peso para a versão canary é 0
, ou seja, sem tráfego (até mesmo porque não existe ainda uma réplica canary):
Seguindo, podemos selecionar 'Resume' no rollout e avançar para o segundo step:
Agora a réplica canary é criada:
O Argo Rollouts modifica o selector
do canaryService para selecionar a réplica criada:
No Virtual Service do Istio, o peso no destination do canary é alterado para atender o step setWeight: 10
:
Exemplo visual do canaryService e stableService:
Podemos ver que, as requisições enviadas para o Virtual Service do Istio, que responde pelo host
do stableService (demo-ms-7.demo.svc.cluster.local) já estão sendo direcionadas também para a versão canary, seguindo o setWeight: 10
até que avancemos mais um step manualmente:
Com apoio visual do Kiali, vemos a distribuição do tráfego:
Já com o canary mais avançado, no step setWeight: 80
:
Manualmente, iremos progredir o canary até que o tráfego seja totalmente direcionado para a nova versão, no step setWeight: 100
. Depois disso, o Argo Rollouts ajustará o selector
do stableService para que o endpoint seja a réplica com a versão "0.1.6"
e no Virtual Service do Istio os pesos serão ajustados para os valores que vimos inicialmente, ou seja, 100
para réplica estável e 0
para réplica canary (do próximo rollout no caso).
Cenário com Canary automatizado
Neste cenário, vamos deixar a progressão do canary condicionada ao sucesso dos testes automatizados. Assim como no cenário anterior, cada step irá direcionar uma parcela maior do tráfego de produção para réplica canary, até que seja totalmente concluído o rollout.
No ApplicationSet, adicionamos o template de análise na seção analysis
de cada step:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: demo-ms-10
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: https://kubernetes.default.svc
template:
metadata:
name: demo-ms-10
spec:
project: lab-apps-canary
source:
repoURL: 'registry-1.docker.io/paulofponciano'
chart: demo-ms-intercomunicacao
targetRevision: 0.1.18
helm:
valuesObject:
# ...omitted for brevity...
strategy:
type: canary
canary:
steps:
- setWeight: 10
- pause: { duration: 30s }
- analysis:
templates:
- templateName: demo-ms-10-check-success
- setWeight: 20
- pause: { duration: 1m }
- analysis:
templates:
- templateName: demo-ms-10-check-success
- setWeight: 40
- pause: { duration: 1m }
- analysis:
templates:
- templateName: demo-ms-10-check-success
- setWeight: 80
- pause: { duration: 1m }
- analysis:
templates:
- templateName: demo-ms-10-check-success
- setWeight: 100
analysisTemplates:
- name: demo-ms-10-check-success
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95
failureLimit: 1
provider:
prometheus:
address: http://kube-prometheus-stack-prometheus.o11y.svc.cluster.local:9090
query: |
sum(irate(
istio_requests_total{destination_service=~"demo-ms-10.demo.svc.cluster.local",response_code!~"5.*"}[1m]
)) /
sum(irate(
istio_requests_total{destination_service=~"demo-ms-10.demo.svc.cluster.local"}[1m]
))
count: 1
# ...omitted for brevity...
destination:
server: '{{ cluster }}'
namespace: argocd
syncPolicy:
automated: {}
Para iniciar o processo, alteramos no ApplicationSet a APP_VERSION
de "0.1.7"
para "0.1.8"
. Depois disso, não esperamos mais nenhuma ação "manual", ficamos aguardando os testes acontecerem em cada step e o canary progredir:
Argo Rollouts ajustando os pesos (weight) no Virtual Service do Istio:
while true; do curl http://demo-ms-10.demo.svc.cluster.local:5000/version; done
while true; do curl http://demo-ms-10-canary.demo.svc.cluster.local:5000/version; done
Conclusão do rollout e remoção da réplica com a versão "0.1.7"
:
Os pesos são reajustados para os valores iniciais, 100
para a réplica estável:
Importante!
Utilizar Canary releases e Blue/Green deployments é sensacional. Mas não devemos esquecer do que pode (e muitas vezes vai) inviabilizar esse tipo de estratégia (e outros tipos também): a CAMADA DE DADOS. É necessário investir um esforço de Engenharia considerável nesse ponto.
Referências e recomendações:
- DIA 4 - CLOUD #SEMANADEVOPS LINUXtips - Track com Matheus Fidelis
- Argo Rollouts - Kubernetes Progressive Delivery Controller
- Argo Rollouts - Istio traffic-management
- Istio - Virtual Service
- Matheus Fidelis - Technical Blog
Keep shipping! 🐳
Top comments (0)