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:
- Declarativo: o sistema gerenciado é descrito declarativamente (manifests Kubernetes, não scripts imperativos)
- Versionado e imutável: o estado desejado é armazenado no git — histórico completo, auditável, e reversível
- Pull automático: agentes dentro do sistema puxam as declarações do git automaticamente
- 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:
- source-controller: observa repositórios git, Helm repositories, e OCI registries — produz artefatos
- kustomize-controller: aplica Kustomizations no cluster a partir dos artefatos
- helm-controller: aplica HelmReleases
- notification-controller: envia alertas para Slack/Teams quando sync falha
- image-automation-controller: atualiza automaticamente a tag de imagem no repositório git
# 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:
- Alguém fez
kubectl editem produção durante um incidente e esqueceu de commitar - Um Operator (como HPA) modificou um campo que o GitOps operator sobrescreve
- Uma mudança manual foi feita via console do cloud provider
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 é:
- Desabilitar auto-sync temporariamente:
argocd app set meu-app --sync-policy none - Fazer a mudança manual necessária
- Commitar a mudança no repositório git (com contexto no commit message)
- 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 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
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.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
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.
kubectl edit é revertida em menos de 3 minutos sem intervenção.
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.
apps/ no repositório resulta em uma nova Application sendo criada no ArgoCD automaticamente dentro de 2 minutos, sem nenhuma configuração adicional.
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.
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.
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).
Referências
- docs OpenGitOps — GitOps Principles v1.0
- docs ArgoCD Documentation — Core Concepts
- docs Flux Documentation — Image Update Guide
- article Weaveworks — GitOps — Operations by Pull Request
- book Billy Yuen et al. — GitOps and Kubernetes
- docs Flux CNCF — Getting Started
- docs ArgoCD — ApplicationSet Controller
- docs Kustomize — The Kubernetes native configuration management
- article CNCF — GitOps Working Group — Best Practices
- article Codefresh — GitOps Best Practices
- video KubeCon 2023 — ArgoCD vs Flux: A Practical Comparison
- article ArgoCD — ignoreDifferences and Resource Customization