MÓDULO 12 · CONCEITO 05 DE 12

GitOps — ArgoCD, Flux e o repositório como fonte da verdade

O princípio GitOps: estado desejado da infra vive no git, operator garante convergência. ArgoCD: Applications, ApplicationSets, sync policies. Flux: kustomize-controller, helm-controller. Drift detection e multi-cluster.

Tempo de leitura ~22 min Pré-requisito 04 · CI/CD Pipelines · 03 · Kubernetes em Produção Próximo 06 · FinOps e Custo de Cloud →

Em 2017, Alexis Richardson (CEO da Weaveworks) cunhou o termo "GitOps" descrevendo como o time operava clusters Kubernetes internamente: toda mudança de infraestrutura era um pull request, um operador dentro do cluster sincronizava o estado real com o estado desejado no git, e qualquer divergência era detectada automaticamente. O insight central era inverter o fluxo de deploy: em vez do pipeline de CI empurrar mudanças para o cluster, o cluster puxava do repositório git.

Essa inversão resolve um problema de segurança sutil: no modelo push, o pipeline de CI tem credenciais de produção (uma kubeconfig ou IAM role com permissão de deploy). No modelo pull, o cluster tem credenciais para ler o repositório git — ninguém de fora tem credenciais de produção. O raio de explosão de um comprometimento do pipeline de CI é eliminado.

Os quatro princípios GitOps

A OpenGitOps (CNCF) define GitOps por quatro princípios:

  1. Declarativo: o sistema gerenciado é descrito declarativamente (manifests Kubernetes, não scripts imperativos)
  2. Versionado e imutável: o estado desejado é armazenado no git — histórico completo, auditável, e reversível
  3. Pull automático: agentes dentro do sistema puxam as declarações do git automaticamente
  4. Reconciliação contínua: os agentes comparam continuamente o estado real com o desejado e corrigem divergências

O princípio da reconciliação contínua é o que diferencia GitOps de simplesmente "armazenar manifests no git": um GitOps operator detecta drift (quando alguém modifica um recurso diretamente no cluster sem passar pelo git) e reverte automaticamente. O cluster sempre converge para o estado declarado no repositório.

ArgoCD — o operator mais popular

ArgoCD é o GitOps operator mais adotado, com UI web, CLI, e API. O conceito central é o objeto Application:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: meu-servico
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/minha-org/k8s-manifests
    targetRevision: main
    path: apps/meu-servico/producao   # pasta com os manifests Kustomize
  destination:
    server: https://kubernetes.default.svc  # cluster local
    namespace: meu-servico
  syncPolicy:
    automated:
      prune: true      # remove recursos que foram deletados do git
      selfHeal: true   # reverte mudanças manuais no cluster (drift correction)
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground

Com automated.selfHeal: true, qualquer mudança feita diretamente no cluster (via kubectl edit ou kubectl apply) é revertida automaticamente em até 3 minutos. Isso pode parecer restritivo — e é. Em emergências, você pode desabilitar o auto-sync temporariamente via argocd app set meu-servico --sync-policy none, fazer a mudança manual, e depois documentar e commitar a mudança no git.

ApplicationSets — gerenciando múltiplos clusters

Para gerenciar muitas Applications de forma programática — por exemplo, um Application por ambiente ou por cluster — o ArgoCD tem ApplicationSet:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: meu-servico-todos-ambientes
spec:
  generators:
    - list:
        elements:
          - cluster: dev
            url: https://dev.k8s.internal
          - cluster: staging
            url: https://staging.k8s.internal
          - cluster: producao
            url: https://prod.k8s.internal
  template:
    metadata:
      name: 'meu-servico-{{cluster}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/minha-org/k8s-manifests
        path: 'apps/meu-servico/{{cluster}}'
        targetRevision: main
      destination:
        server: '{{url}}'
        namespace: meu-servico

Outros generators: git (detecta pastas no repositório), cluster (todos os clusters registrados no ArgoCD), e matrix (produto cartesiano de dois generators). ApplicationSets são a forma de escalar GitOps de 5 para 50 clusters sem duplicar configuração.

Flux — a alternativa composável

Flux (Weaveworks) implementa GitOps através de um conjunto de controllers separados que podem ser compostos. Ao contrário do ArgoCD (que tem uma única instalação com múltiplos componentes), o Flux segue o princípio Unix de ferramentas pequenas e focadas:

# GitRepository — de onde Flux observa o estado desejado
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: k8s-manifests
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/minha-org/k8s-manifests
  ref:
    branch: main
  secretRef:
    name: github-credentials

---
# Kustomization — o que aplicar e onde
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: meu-servico
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/meu-servico/producao
  prune: true
  sourceRef:
    kind: GitRepository
    name: k8s-manifests
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: meu-servico
      namespace: meu-servico

Image Update Automation — GitOps para imagens

Um problema do GitOps puro: quando o CI faz push de uma nova imagem, como o repositório git é atualizado com o novo digest? Sem automação, alguém precisa fazer commit manualmente. O Flux resolve com Image Automation:

# ImageRepository — qual registry observar
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: meu-servico
  namespace: flux-system
spec:
  image: ghcr.io/minha-org/meu-servico
  interval: 5m

---
# ImagePolicy — qual tag selecionar automaticamente
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: meu-servico
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: meu-servico
  policy:
    semver:
      range: '>=1.0.0'   # ou regex para selecionar a última

---
# ImageUpdateAutomation — commitar a nova tag no git
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m
  sourceRef:
    kind: GitRepository
    name: k8s-manifests
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@github.com
        name: fluxcdbot
      messageTemplate: 'chore: update {{range .Updated.Images}}{{.}}{{end}}'
    push:
      branch: main

Com image update automation, o fluxo completo é: CI faz push de nova imagem → Flux detecta nova tag → Flux commita o manifest atualizado no git → Flux aplica o manifest no cluster. O git sempre reflete o estado real do cluster.

Drift detection — quando o cluster diverge do git

Drift acontece quando o estado real do cluster diverge do estado declarado no git. Causas comuns:

Com selfHeal: true no ArgoCD ou prune: true no Flux, drift é corrigido automaticamente. O problema é quando a mudança manual foi intencional — como ajustar réplicas temporariamente durante um incidente. Para esse caso, o processo correto é:

  1. Desabilitar auto-sync temporariamente: argocd app set meu-app --sync-policy none
  2. Fazer a mudança manual necessária
  3. Commitar a mudança no repositório git (com contexto no commit message)
  4. Reabilitar auto-sync: argocd app set meu-app --sync-policy automated

Ferramentas como kubeconform e kube-score podem ser integradas no pipeline de CI para validar manifests antes que entrem no repositório — evitando que manifests inválidos causem falha de sync no cluster.

Repositório de manifests — estrutura recomendada

k8s-manifests/
├── infrastructure/         # infra compartilhada (ingress, cert-manager, ESO)
│   ├── base/
│   └── overlays/
│       ├── staging/
│       └── producao/
├── apps/
│   ├── api-gateway/
│   │   ├── base/           # manifests base com Kustomize
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── staging/    # patches específicos de staging
│   │       └── producao/   # patches específicos de prod (mais réplicas, etc)
│   └── meu-servico/
│       └── ...
└── clusters/
    ├── staging/            # FluxCD Kustomizations por cluster
    │   └── flux-system/
    └── producao/
        └── flux-system/

A separação entre repositório de código (onde o CI roda) e repositório de manifests (onde o GitOps operator observa) é opcional mas recomendada para organizações maiores. A vantagem: audit trail separado para mudanças de infra, permissões separadas (nem todo dev pode mudar produção), e histórico de mudanças de configuração independente do código.

ArgoCD vs Flux — quando escolher cada um

ArgoCD tem UI web rica, RBAC nativo, e uma curva de aprendizado menor — é a escolha natural para times que querem visibilidade imediata do estado de sync. Flux é mais composável e cabe melhor em clusters de produção onde você não quer uma UI adicional para operar, e quando image update automation é importante. Para times que já usam Helm extensivamente, o helm-controller do Flux é mais maduro. Para multi-cluster com ApplicationSets, o ArgoCD tem vantagem. A escolha final é menos importante que a decisão de usar GitOps.

Decisões de engenharia

ArgoCD vs Flux
ArgoCD quando o time valoriza visibilidade operacional: UI web mostra o estado de sync de todos os clusters em tempo real, RBAC nativo controla quem pode sincronizar manualmente, e ApplicationSets escalam multi-cluster facilmente. Flux quando a filosofia é "tudo como código" sem UIs extras: cada controller é um CRD versionado, a composabilidade é maior, e image update automation é mais madura. Times que migram de CI push para GitOps pela primeira vez geralmente adotam ArgoCD pela visibilidade; times com cultura de operação por terminal preferem Flux.
Mono-repo vs multi-repo para manifests
Mono-repo (todos os serviços no mesmo repositório de manifests) simplifica a descoberta e garante que uma mudança de infra compartilhada atinge todos os serviços atomicamente — ideal para times menores e plataformas homogêneas. Multi-repo (repositório separado por serviço ou domínio) isola permissões (um time não pode acidentalmente modificar manifests de outro), reduz o blast radius de um merge acidentalmente equivocado, e escala melhor para dezenas de times. O anti-pattern é manter os manifests no mesmo repositório que o código da aplicação — mistura os audit trails e as permissões de code review com as de deploy em produção.
selfHeal: true vs false — quando usar cada um
selfHeal: true é o padrão correto para produção estável: qualquer desvio do git é revertido automaticamente, criando um invariante forte de que "o cluster sempre está como o git diz". Desabilitar selfHeal quando o cluster tem operadores que modificam campos legítimos (HPA modifica replicas, VPA modifica resources) e esses campos estão declarados nos manifests — o ArgoCD sobrescreveria as mudanças do HPA. A solução correta não é desabilitar selfHeal, mas excluir esses campos do diff usando ignoreDifferences, preservando o invariante para os campos que importam.
GitOps push vs pull — quando o modelo pull não se aplica
O modelo pull (cluster observa o git) é o padrão GitOps e o mais seguro. O modelo push (pipeline de CI aplica direto no cluster) é necessário em dois casos: clusters isolados de rede que não conseguem acessar o repositório git externo (air-gapped), e deploys que requerem orquestração complexa entre múltiplos sistemas heterogêneos antes e depois do deploy (ex: migração de banco de dados que deve ocorrer entre o deploy do serviço antigo e o novo). Nesses casos, use OIDC do CI para assume roles de curta duração em vez de kubeconfigs permanentes.

Perguntas de entrevista

Por que GitOps usa modelo pull em vez de push, e qual é o benefício de segurança central?

No modelo push (CI/CD tradicional), o pipeline tem credenciais de produção: uma kubeconfig com permissão de apply, ou uma IAM role com acesso ao cluster. Se o sistema de CI for comprometido — por um supply chain attack, por credenciais vazadas, ou por um plugin malicioso — o atacante tem acesso direto a produção.

No modelo pull, o cluster tem credenciais para ler o repositório git (geralmente read-only SSH key ou token). Ninguém externo ao cluster tem credenciais de produção. Um atacante que comprometer o CI pode, no máximo, modificar o repositório git — o que é detectável (histórico de commits), revertível (git revert), e sujeito a code review (via PRs com branch protection). O cluster só aplica o que está no git depois de aprovado.

O segundo benefício é auditabilidade: toda mudança no cluster tem um commit correspondente no git com autor, timestamp, e mensagem. Isso torna o histórico de mudanças de infra auditável da mesma forma que o histórico de código — essencial para compliance e investigação de incidentes.

O que é drift em um contexto GitOps e como um engenheiro deve lidar com drift intencional durante um incidente?

Drift é a divergência entre o estado real do cluster e o estado declarado no repositório git. Causas: alguém fez kubectl edit durante um incidente sem commitar depois, um operator (HPA, VPA) modificou campos que o GitOps operator monitora, ou alguém fez mudanças via console da cloud.

Com selfHeal: true, o ArgoCD reverte drift automaticamente em até 3 minutos. Isso resolve drift acidental, mas cria um problema durante incidentes quando você precisa fazer uma mudança urgente que ainda não está no git.

O processo correto para drift intencional: (1) argocd app set meu-app --sync-policy none para desabilitar auto-sync; (2) fazer a mudança necessária via kubectl; (3) verificar que a mudança resolveu o incidente; (4) commitar a mudança equivalente no repositório git com mensagem descritiva de por que foi necessário; (5) argocd app set meu-app --sync-policy automated para reabilitar. A regra é: drift intencional deve durar minutos, não horas — e sempre deve resultar em um commit no git.

Como funciona o image update automation do Flux e como ele resolve o problema do "último mile" do GitOps?

O "último mile" do GitOps é um problema prático: o princípio é que o git é a fonte da verdade, então o manifests no git deve sempre refletir o que está rodando em produção. Mas quando o CI faz push de uma nova versão da imagem, alguém precisa atualizar o manifest no git com o novo tag ou digest — caso contrário o git fica desatualizado.

O Flux resolve isso com três CRDs: ImageRepository (observa o container registry em busca de novas tags), ImagePolicy (define qual tag selecionar — semver, regex, ou sempre a última), e ImageUpdateAutomation (commita o manifest atualizado diretamente no repositório git). O resultado é que o Flux fecha o loop: CI push image → Flux detecta → Flux commita manifest atualizado → Flux aplica no cluster. O git sempre reflete o que está rodando.

Uma anotação especial no manifest indica ao Flux qual campo atualizar: # {"$imagepolicy": "flux-system:meu-servico"} próximo ao campo de imagem. Isso evita que o Flux modifique campos incorretos.

Qual é a diferença entre ArgoCD Application e ApplicationSet, e quando cada um é apropriado?

Application é a unidade básica do ArgoCD: define uma fonte (repositório git + path + branch) e um destino (cluster + namespace). É gerenciado manualmente — para adicionar um novo ambiente ou cluster, você cria uma nova Application. Para 3-5 serviços em 2-3 ambientes, isso é gerenciável.

ApplicationSet é um controller que gera Applications de forma programática a partir de generators. O generator list usa uma lista estática de parâmetros; o generator git detecta automaticamente pastas no repositório (cada pasta vira uma Application); o generator cluster usa todos os clusters registrados no ArgoCD; o generator matrix faz o produto cartesiano entre dois generators (ex: todos os serviços × todos os clusters).

ApplicationSets são necessários quando a duplicação manual de Applications se torna impraticável — tipicamente com mais de 10 serviços ou mais de 3 clusters. Com o generator git, adicionar um novo serviço ao repositório de manifests automaticamente cria a Application correspondente no ArgoCD — sem nenhuma configuração manual adicional.

Como o ArgoCD lida com recursos que são modificados por operators nativos do Kubernetes, como HPA alterando o campo replicas?

Este é um conflito real e frequente: o HPA modifica dinamicamente o campo spec.replicas do Deployment com base em métricas. Se o manifest no git declara replicas: 3 e o HPA escala para replicas: 7, o ArgoCD com selfHeal: true vai reverter para 3 — quebrando o HPA.

A solução correta é ignoreDifferences na Application: instrui o ArgoCD a ignorar campos específicos no diff, sem desabilitar selfHeal para o restante do manifest:

spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas   # ArgoCD ignora diferenças neste campo
  syncPolicy:
    automated:
      selfHeal: true       # ainda corrige drift em todos os outros campos

Com isso, o ArgoCD monitora e corrige drift em todos os campos do Deployment exceto replicas, que é gerenciado pelo HPA. O mesmo padrão se aplica ao VPA (que modifica resources) e a outros operators que mutam objetos gerenciados pelo GitOps operator.

Exercícios práticos

01 · Instalar ArgoCD e criar primeira Application

Em um cluster Kubernetes local (kind ou k3d), instale o ArgoCD via manifests oficiais. Crie um repositório de manifests no GitHub com um Deployment e Service simples (nginx) em uma pasta apps/nginx/. Configure uma Application no ArgoCD apontando para esse repositório com automated.selfHeal: true e prune: true. Depois da sincronização automática, faça uma mudança manual diretamente no cluster via kubectl e observe o ArgoCD reverter automaticamente.

Critério: A Application fica em estado Synced automaticamente após qualquer mudança no repositório; uma edição manual via kubectl edit é revertida em menos de 3 minutos sem intervenção.
02 · ApplicationSet com generator git para múltiplos serviços

Estruture um repositório de manifests com três serviços em pastas separadas: apps/api/, apps/worker/, apps/frontend/ — cada uma com um Deployment e Service básico. Configure um ApplicationSet usando o generator git com directories que detecta automaticamente cada pasta dentro de apps/ e cria uma Application correspondente. Adicione um quarto serviço (apps/cache/) ao repositório e verifique que o ApplicationSet cria a Application automaticamente.

Critério: Adicionar uma nova pasta em apps/ no repositório resulta em uma nova Application sendo criada no ArgoCD automaticamente dentro de 2 minutos, sem nenhuma configuração adicional.
03 · Estrutura Kustomize com overlays de ambiente

Para um serviço com Deployment e ConfigMap, crie a estrutura Kustomize com base/ (manifests base com 1 réplica, recursos mínimos) e dois overlays: overlays/staging/ (patch para 2 réplicas, variáveis de ambiente de staging) e overlays/producao/ (patch para 5 réplicas, requests/limits maiores, variáveis de produção). Configure o ArgoCD com duas Applications — uma para staging e uma para produção — cada uma apontando para seu overlay. Valide que kubectl kustomize overlays/producao/ gera os manifests corretos antes de commitar.

Critério: Os dois ambientes rodam configurações diferentes (réplicas e variáveis distintas) a partir do mesmo base, sem duplicação de YAML; uma mudança no base se propaga para ambos os ambientes na próxima sincronização.
04 · Configurar Flux com image update automation

Instale o Flux em um cluster local. Configure um GitRepository apontando para seu repositório de manifests, uma Kustomization aplicando um Deployment com imagem do Docker Hub (nginx). Configure ImageRepository para observar o registry, ImagePolicy para selecionar a tag mais recente via semver, e ImageUpdateAutomation para commitar automaticamente atualizações. Adicione a anotação # {"$imagepolicy": "flux-system:nginx"} no campo de imagem do Deployment. Simule um "novo release" atualizando a tag no registry e observe o commit automático.

Critério: Uma nova tag que satisfaça a ImagePolicy resulta em um commit automático no repositório git atualizando o campo de imagem, sem intervenção humana.
05 · ignoreDifferences para campos gerenciados por HPA

Configure um Deployment com HPA (horizontal pod autoscaler) no ArgoCD. Observe o problema: o ArgoCD fica em estado OutOfSync ou reverte as réplicas que o HPA modificou. Configure ignoreDifferences na Application para ignorar /spec/replicas no Deployment. Depois, simule carga no serviço para acionar o HPA (ou ajuste manualmente as réplicas), verifique que o ArgoCD não reverte, e confirme que selfHeal ainda funciona para outros campos (ex: edite manualmente uma label e verifique que é revertida).

Critério: O HPA pode escalar réplicas sem que o ArgoCD reverta; ao mesmo tempo, edições manuais em outros campos do Deployment são revertidas automaticamente pelo selfHeal; a Application fica em estado Synced mesmo com o HPA ativo.

Referências

  1. docs OpenGitOps — GitOps Principles v1.0 opengitops.dev · definição oficial dos quatro princípios GitOps pela CNCF Working Group
  2. docs ArgoCD Documentation — Core Concepts argo-cd.readthedocs.io · guia completo de Applications, ApplicationSets, RBAC e multi-cluster
  3. docs Flux Documentation — Image Update Guide fluxcd.io/flux/guides/image-update · guia completo do image update automation com ImagePolicy e ImageRepository
  4. article Weaveworks — GitOps — Operations by Pull Request weaveworks.com · 2017 · o artigo original que cunhou o termo GitOps por Alexis Richardson
  5. book Billy Yuen et al. — GitOps and Kubernetes Manning · 2021 · guia prático de GitOps com ArgoCD e Flux em clusters Kubernetes
  6. docs Flux CNCF — Getting Started fluxcd.io · documentação oficial do Flux como projeto graduado da CNCF
  7. docs ArgoCD — ApplicationSet Controller argo-cd.readthedocs.io/applicationset · generators, templates e uso em multi-cluster
  8. docs Kustomize — The Kubernetes native configuration management kustomize.io · overlays, patches e estrutura de repositórios para múltiplos ambientes
  9. article CNCF — GitOps Working Group — Best Practices github.com/cncf/tag-app-delivery · repositório de estrutura e decisões do GitOps Working Group
  10. article Codefresh — GitOps Best Practices codefresh.io/blog · mono-repo vs multi-repo, separação de repositórios de código e manifests
  11. video KubeCon 2023 — ArgoCD vs Flux: A Practical Comparison youtube.com · comparação hands-on de features, trade-offs e casos de uso de cada ferramenta
  12. article ArgoCD — ignoreDifferences and Resource Customization argo-cd.readthedocs.io/user-guide/resource-customization · configuração de campos excluídos do diff como HPA replicas