DEV Community

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

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

  • 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

projetcs

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

app

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-blue-green-simple

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

svc-primary-blue-green-simple

svc-green-blue-green-simple

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:

istio-vs-blue-green-simple

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: {}
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":

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

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

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:


Keep shipping! 🐳

Top comments (0)