DEV Community

Cover image for Blue/Green e Canary no Kubernetes com Argo Rollouts [Lab Session]
Paulo Ponciano for AWS Community Builders

Posted on • Edited on

Blue/Green e Canary no Kubernetes com Argo Rollouts [Lab Session]

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.

Fonte: https://argoproj.github.io/rollouts/


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

Tela do ArgoCD

  • Argo Rollouts

Tela do Argo Rollouts

  • Kube-prometheus-stack

Dashboard do Kube-prometheus-stack

  • Istio (base, istiod e gateway)

Componentes do Istio instalados no cluster

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

Projects e Applications no ArgoCD

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
Enter fullscreen mode Exit fullscreen mode

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: {}
Enter fullscreen mode Exit fullscreen mode

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 a 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:

Diagrama do roteamento Blue/Green com activeService e previewService

Recursos criados pelo ApplicationSet:

Recursos criados pelo ApplicationSet no ArgoCD

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):

Pod estável exibindo a label rollouts-pod-template-hash

Essa label é utilizada como selector tanto no activeService quanto no previewService. Isso é controlado pelo Argo Rollouts:

activeService usando a label como selector

previewService usando a mesma label como selector

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:

Virtual Service do Istio roteando apenas para 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
Enter fullscreen mode Exit fullscreen mode

Respostas iguais do activeService e do previewService apontando para a réplica blue

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:

Alteração da APP_VERSION no ApplicationSet disparando o Blue/Green

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:

previewService apontando para 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:

Réplica green disponível para validação antes do tráfego de produção

Uma vez que decidimos que está tudo certo com a nova versão (green), podemos promovê-la. No Rollout, selecionamos 'Promote-Full':

Opção Promote-Full no Rollout

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):

activeService apontando para a nova versão, agora 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:

Réplica antiga 0.1.1 sendo 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: {}
Enter fullscreen mode Exit fullscreen mode

Para gerar o Blue/Green alteramos no ApplicationSet a APP_VERSION de "0.1.3" para "0.1.4":

Alteração da APP_VERSION disparando o Blue/Green automatizado

Testes http-bench-analysis (Job) e success-rate com métricas do Prometheus:

Job http-bench-analysis em execução

Análise 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
Enter fullscreen mode Exit fullscreen mode

Respostas das réplicas durante a análise automatizada

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:

Nova versão 0.1.4 como blue após o Promote-Full

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: {}
Enter fullscreen mode Exit fullscreen mode

Configuração do canary no Argo Rollouts

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:

Rollout canary aguardando avanço manual no setWeight 0

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
Enter fullscreen mode Exit fullscreen mode

Respostas apontando para a réplica estável antes do avanço do canary

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):

stableService e canaryService apontando para a mesma réplica

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):

Virtual Service do Istio com peso 0 para a versão canary

Seguindo, podemos selecionar 'Resume' no rollout e avançar para o segundo step:

Opção Resume no rollout para avançar de step

Agora a réplica canary é criada:

Réplica canary criada

O Argo Rollouts modifica o selector do canaryService para selecionar a réplica criada:

canaryService apontando para a réplica canary

No Virtual Service do Istio, o peso no destination do canary é alterado para atender o step setWeight: 10:

Virtual Service do Istio com peso 10 para o canary

Exemplo visual do canaryService e stableService:

Visualização do canaryService e do 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:

Tráfego dividido entre estável e canary conforme o setWeight 10

Com apoio visual do Kiali, vemos a distribuição do tráfego:

Distribuição do tráfego visualizada no Kiali

Já com o canary mais avançado, no step setWeight: 80:

Tráfego do canary 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 a réplica estável e 0 para a 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 a 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: {}
Enter fullscreen mode Exit fullscreen mode

Configuração do canary automatizado com análise por step

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:

Rollout canary automatizado aguardando os testes em cada step

Progresso do canary automatizado entre os steps

Argo Rollouts ajustando os pesos (weight) no Virtual Service do Istio:

Argo Rollouts ajustando os pesos 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
Enter fullscreen mode Exit fullscreen mode

Respostas das réplicas durante o canary automatizado

Conclusão do rollout e remoção da réplica com a versão "0.1.7":

Conclusão do rollout e remoção da réplica 0.1.7

Os pesos são reajustados para os valores iniciais, 100 para a réplica estável:

Pesos reajustados para 100 na réplica estável

Estado final do Virtual Service após o rollout


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:


Keep shipping! 🐳

Top comments (0)