DEV Community

Cover image for Construindo uma plataforma GitOps do zero: Kubernetes, ArgoCD, Terraform e Observabilidade
Matheus Kocotem
Matheus Kocotem

Posted on

Construindo uma plataforma GitOps do zero: Kubernetes, ArgoCD, Terraform e Observabilidade

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

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

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.

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

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!";
    }
}
Enter fullscreen mode Exit fullscreen mode

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 (0)