GitOps & ArgoCD
Como o ArgoCD sincroniza o cluster contra o Git e como o ApplicationSet gera um cliente por arquivo
O princípio GitOps
GitOps é uma aplicação específica do control loop (ver orchestration-fundamentals): em vez de alguém rodar kubectl apply na mão contra o cluster, um agente dentro do cluster observa um repositório Git e aplica sozinho qualquer mudança que aparecer nele. O Git vira a única fonte de verdade do estado desejado — kubectl apply direto continua tecnicamente possível, mas o próprio agente desfaz a mudança na próxima sincronização, porque ela diverge do que está no Git.
Duas consequências práticas disso: todo o histórico de mudança de infraestrutura é um histórico de commit (quem mudou o quê, quando, revisável em PR antes de acontecer), e o cluster nunca fica de fato inacessível para auditoria — mesmo sem acesso a kubectl, o estado desejado inteiro está legível no repositório.
ArgoCD: reconciliation + self-heal
O ArgoCD é o agente que faz esse control loop. Cada Application do ArgoCD aponta para um caminho num repositório Git e um destino (cluster + namespace); o ArgoCD compara periodicamente o manifest renderizado daquele caminho contra o que está de fato rodando, e sincroniza.
selfHeal: true é o que faz a reconciliação valer também contra mudança manual: se alguém rodar kubectl edit num Deployment gerenciado por uma Application com self-heal, o ArgoCD detecta a divergência e reverte para o que está no Git, não o contrário. É por isso que spec.replicas é omitido do Deployment quando o HPA está ativo (ver network-and-scaling) — sem isso, toda vez que o HPA escalasse o pod, o self-heal desfaria o scale-up na sincronização seguinte, porque o Git “diz” que é 1 réplica.
ApplicationSet: um Application por arquivo
Uma Application do ArgoCD é estática — um caminho, um destino. Esse cluster tem N clientes, cada um precisando da própria Application (próprio namespace, próprio values file), e o número de clientes cresce sem redeploy da plataforma. O ApplicationSet resolve isso: é um gerador de Applications, que lê algo (aqui, uma lista de arquivos no Git) e cria uma Application por item encontrado.
# argocd-apps/clients-appset.yaml
generators:
- git:
repoURL: https://github.com/finatto-devops/k3s-platform.git
revision: HEAD
files:
- path: "clients/*.yaml"
template:
metadata:
name: '{{ .path.filename | trimSuffix ".yaml" }}'
spec:
sources:
- repoURL: https://github.com/finatto-devops/k3s-platform.git
path: charts/app
helm:
releaseName: '{{ .path.filename | trimSuffix ".yaml" }}'
valueFiles:
- $values/apps/{{ .platform.app }}.yaml
- $values/{{ .path.path }}/{{ .path.filename }}
destination:
namespace: '{{ .path.filename | trimSuffix ".yaml" }}'
O generator git files varre clients/*.yaml a cada sincronização do ApplicationSet — criar um cliente novo é só adicionar um arquivo em clients/, sem tocar no ApplicationSet em si. {{ .path.filename | trimSuffix ".yaml" }} usa o nome do arquivo como nome da Application, nome do release Helm e nome do namespace — é por isso que clients/acme.yaml vira exatamente o namespace acme, sem mapeamento extra em lugar nenhum.
Cada Application gerada tem duas fontes de values combinadas: apps/{{ .platform.app }}.yaml (o que é comum à aplicação, ex. apps/checklist.yaml — mesmo repositório, mesma imagem, para todo cliente que roda checklist) e clients/<slug>.yaml (o que é específico deste cliente — domínio, bucket S3). O Helm mescla os dois: o segundo sobrescreve o primeiro campo a campo. Uma terceira source (sealed/, filtrada pelo nome do arquivo) traz os SealedSecret do cliente — ver secrets-encryption.
ignoreDifferences na definição do ApplicationSet é o que impede o self-heal de brigar com o HPA: /spec/replicas do Deployment é explicitamente excluído da comparação, então o ArgoCD para de tentar “corrigir” a contagem de réplicas que o HPA está ajustando.
Helm: templating por trás disso
O ArgoCD sincroniza manifests — objetos YAML finais, prontos para kubectl apply. charts/app não é esse YAML final: é um template Helm, com placeholders ({{ .Values.algumaCoisa }}) que o Helm resolve contra um arquivo values.yaml antes de virar manifest. Um único chart (charts/app, versão fixada em Chart.yaml) serve toda aplicação Laravel da plataforma — o que muda de app para app, e de cliente para cliente, são só os valores injetados, não o template.
É esse mecanismo que faz apps/checklist.yaml (comum à aplicação) e clients/acme.yaml (específico do cliente) virarem, juntos, um Deployment, um Service, um Ingress e um Cluster de Postgres completos — sem que ninguém escreva manifest à mão por cliente.