Durante muito tempo, meu contato com Kubernetes foi do tipo "já mexi": subi um pod aqui, apliquei um manifesto ali, vi um deploy acontecer. Mas existe uma distância enorme entre usar uma ferramenta e entender o que ela faz por baixo. Eu venho de backend Java e automação de testes, e decidi atravessar essa distância de propósito construindo, do zero, uma plataforma GitOps completa e local.
Este artigo é o registro dessa construção. Não é um tutorial de "cole esse comando"; é uma explicação de por que cada peça existe e como elas se encaixam. Ao final, você terá visto uma infraestrutura nascer de um único comando, deploys acontecerem a partir de um git push, e métricas de uma aplicação Java fluírem até um dashboard tudo versionado, tudo reproduzível.
O que vamos construir
Antes do código, o mapa mental. A plataforma tem quatro camadas que se encaixam.
O Terraform provisiona o cluster e instala o motor de GitOps. É a fundação como código.
O kind roda um cluster Kubernetes real dentro de containers Docker, localmente.
O ArgoCD observa um repositório Git e garante que o cluster reflita exatamente o que está versionado.
Por fim, Prometheus e Grafana coletam e visualizam métricas, incluindo métricas customizadas de uma API Spring Boot.
O fio que costura tudo é uma ideia só: o Git é a fonte da verdade. Você não muda o cluster na mão; você muda arquivos no Git, e o cluster se ajusta.
Por que GitOps?
Vale parar um instante nessa ideia, porque ela é o coração de tudo.
No modelo tradicional, você aplica mudanças no cluster diretamente — um kubectl apply aqui, um kubectl scale ali. O problema é que o cluster vira uma caixa-preta. Ninguém sabe ao certo por que ele está do jeito que está, quem mudou o quê ou como reproduzir aquele estado em outro ambiente.
GitOps inverte isso. O estado desejado do cluster vive num repositório Git. Uma ferramenta (aqui, o ArgoCD) fica continuamente comparando o que deveria estar rodando (o Git) com o que está rodando (o cluster) e corrige qualquer diferença.
Na prática, isso significa que toda mudança passa a ter auditoria automática, porque ela é registrada como um commit com autor, data e motivo. Também significa que qualquer ambiente pode ser reproduzido a partir do mesmo repositório, que voltar atrás é tão simples quanto executar um git revert e que ninguém precisa de acesso direto ao cluster para fazer deploy: basta commitar.
Guarde essa ideia, porque vamos vê-la acontecer na prática.
Camada 1: o cluster como código com Terraform
O primeiro instinto de quem começa é criar o cluster na mão. O comando existe e é simples. Mas isso já quebra a promessa da reprodutibilidade — amanhã você não lembra exatamente como criou.
Por isso, desde o início, o cluster nasce de Terraform. O trecho central declara três providers e o cluster:
terraform {
required_providers {
kind = { source = "tehcyx/kind", version = "~> 0.9" }
helm = { source = "hashicorp/helm", version = "~> 2.17" }
kubernetes = { source = "hashicorp/kubernetes", version = "~> 2.35" }
}
}
resource "kind_cluster" "this" {
name = "gitops-lab"
wait_for_ready = true
kind_config {
kind = "Cluster"
api_version = "kind.x-k8s.io/v1alpha4"
node { role = "control-plane" }
node { role = "worker" }
node { role = "worker" }
}
}
Repare em duas decisões. Primeiro, o cluster tem três nós (um control-plane e dois workers) em vez de um só. Isso não é firula: com múltiplos nós, você vê o Kubernetes distribuir cargas entre eles, o que torna conceitos como alta disponibilidade concretos em vez de teóricos.
Segundo, as versões dos providers estão fixadas. Isso é o que garante que um terraform apply daqui a seis meses produza o mesmo resultado de hoje. Reprodutibilidade não é acidente; é uma escolha.
A partir daí, o mesmo Terraform instala o ArgoCD via Helm, já apontando os providers para o cluster recém-criado. Um único terraform apply entrega o cluster e o motor de GitOps prontos.
Camada 2: ensinando o ArgoCD a observar o Git
Com o ArgoCD instalado, ele está de pé, mas ocioso — não sabe o que observar. É preciso apresentá-lo a um repositório. Isso se faz com um objeto chamado Application.
A Application é a ponte. Ela diz três coisas: de onde puxar, para onde aplicar e como se comportar.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx
namespace: argocd
spec:
source:
repoURL: https://github.com/usuario/gitops-manifests.git
targetRevision: main
path: apps/nginx
destination:
server: https://kubernetes.default.svc
namespace: nginx-demo
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
As duas linhas mais importantes deste arquivo são prune: true e selfHeal: true. Elas são o que torna o GitOps realmente vivo.
Com selfHeal, se alguém alterar o cluster manualmente, o ArgoCD detecta a divergência em relação ao Git e desfaz a mudança. O Git vence.
Já prune garante que, se um recurso for removido do repositório, ele também será removido do cluster. O cluster espelha exatamente o conteúdo do Git, nada mais.
O momento GitOps: escalar com um commit
Aqui a teoria vira prática, e é o momento que fixa o conceito.
Com o nginx rodando com uma réplica, fiz uma mudança que normalmente exigiria um comando no cluster: aumentar para três réplicas. Só que, em GitOps, isso não é um comando. É uma edição de arquivo:
spec:
replicas: 3 # antes era 1
Um git commit e um git push depois, o ArgoCD detectou a diferença e criou dois novos pods — sozinho. Eu nunca rodei kubectl scale. O cluster simplesmente convergiu para o que o Git passou a dizer.
O teste que mais ensina veio a seguir. Executei um kubectl scale manual, forçando o cluster de volta a uma réplica. Por um instante, os pods começaram a ser removidos. Então o selfHeal entrou em ação: o ArgoCD percebeu que o cluster havia divergido do Git, que ainda dizia três réplicas, e recriou automaticamente os pods. Minha alteração manual foi desfeita em poucos segundos.
Essa é a garantia que dá segurança a ambientes de produção reais: não existe "conserta rápido no cluster e esquece". Toda mudança passa pelo Git ou acaba sendo revertida.
Camada 3: observabilidade e o diferencial da métrica de negócio
Uma plataforma que você não consegue enxergar é uma plataforma que você não controla. Por isso a última camada é observabilidade.
Instalei o kube-prometheus-stack, um pacote que traz Prometheus, Grafana e Alertmanager já integrados, mantendo o padrão GitOps: ele entra no cluster como mais uma Application do ArgoCD.
Um detalhe técnico importante aqui é que charts Helm muito grandes, como esse, exigem a opção ServerSideApply=true no ArgoCD, porque seus CRDs ultrapassam o limite de tamanho da aplicação tradicional. É o tipo de detalhe que normalmente só aparece durante a prática.
Mas coletar métricas genéricas de CPU e memória é apenas o básico. O que realmente diferencia uma plataforma é medir lógica de negócio. E foi aqui que meu background em Java entrou como vantagem.
Criei uma API Spring Boot simples que expõe uma métrica customizada: um contador que incrementa a cada chamada de um endpoint.
@RestController
public class HelloController {
private final Counter helloCounter;
public HelloController(MeterRegistry registry) {
this.helloCounter = Counter.builder("demo_hello_requests_total")
.description("Total de chamadas ao endpoint /hello")
.register(registry);
}
@GetMapping("/hello")
public String hello() {
helloCounter.increment();
return "Olá do GitOps Lab!";
}
}
Com Spring Boot Actuator e Micrometer, expor essa métrica no formato que o Prometheus entende exige muito pouca configuração.
A ponte final entre a aplicação e o Prometheus é um recurso chamado ServiceMonitor, responsável por informar ao Prometheus quais pods devem ser monitorados. Existe, porém, um detalhe que costuma bloquear muita gente: o ServiceMonitor precisa possuir um label específico (release: monitoring) para ser descoberto pelo Prometheus. Sem esse label, a coleta simplesmente não acontece e, pior, não existe uma mensagem de erro evidente indicando o motivo.
Com tudo conectado, o fluxo finalmente se fecha. Cada chamada ao endpoint incrementa o contador, o Prometheus coleta esse valor periodicamente e o Grafana o exibe em tempo real. Pela primeira vez, vi uma métrica escrita por mim, em Java, aparecer em um dashboard.
Houve ainda um detalhe interessante: o gráfico mostrava duas séries diferentes, uma para cada pod, com valores distintos. Não era um bug. Era o balanceamento de carga do Kubernetes se tornando visível através das métricas, mostrando que o tráfego estava sendo distribuído de forma desigual entre as instâncias da aplicação.
Os detalhes que só a prática ensina
Se eu tivesse que resumir o valor de construir tudo isso manualmente, em vez de apenas ler sobre o assunto, seria nos detalhes que dificilmente aparecem em diagramas.
Descobri que imagens carregadas localmente no kind exigem imagePullPolicy: IfNotPresent; caso contrário, o Kubernetes tentará buscá-las em um registry remoto e falhará.
Também percebi que separar o repositório de infraestrutura do repositório de manifestos evita acoplar mudanças na plataforma aos deploys das aplicações, permitindo que cada um siga seu próprio ciclo de vida.
E, talvez o hábito mais importante de todos, aprendi que ler cuidadosamente o resultado de um terraform plan antes do terraform apply é a diferença entre operar com confiança e simplesmente torcer para que tudo funcione.
Conclusão
No fim, a sensação é quase decepcionante de tão tranquila: você muda um número no Git, faz um git push e o cluster inteiro se ajusta sozinho.
Mas essa simplicidade aparente é justamente o objetivo. Uma boa plataforma esconde a complexidade atrás de um simples git push. Toda a engenharia — o cluster multi-nó, os componentes do ArgoCD conversando entre si, a reconciliação contínua e toda a cadeia de observabilidade — existe para que operar seja simples. Construir a transmissão automática é difícil; dirigir um carro automático é fácil. Eu quis construir a transmissão.
Se você também está atravessando a jornada de backend para plataforma, meu conselho é simples: não leia apenas. Construa. Conceitos como reconciliação, estado desejado e fonte da verdade deixam de ser abstratos no momento em que você vê o ArgoCD desfazer uma alteração manual e restaurar exatamente o que o Git determina.
O código completo, com instruções para executar tudo do zero, está disponível no repositório. E este é apenas o começo: os próximos capítulos incluem alertas, um pipeline de validação de manifestos e a migração para uma cloud gerenciada.
Até a próxima.
Top comments (1)
I love how you walk through building a GitOps platform from the ground up, especially the clear integration of ArgoCD with Terraform for seamless Kubernetes deployments. Have you thought about cross‑posting this to ZyVOP (zyvop.com) to share it with an even broader community of engineers?