<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matheus Kocotem</title>
    <description>The latest articles on DEV Community by Matheus Kocotem (@m_kocotem_1b69865766c653).</description>
    <link>https://dev.to/m_kocotem_1b69865766c653</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4050237%2F535ee3e3-dd2a-4fc2-a42f-c1a6063c2939.png</url>
      <title>DEV Community: Matheus Kocotem</title>
      <link>https://dev.to/m_kocotem_1b69865766c653</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/m_kocotem_1b69865766c653"/>
    <language>en</language>
    <item>
      <title>Construindo uma plataforma GitOps do zero: Kubernetes, ArgoCD, Terraform e Observabilidade</title>
      <dc:creator>Matheus Kocotem</dc:creator>
      <pubDate>Mon, 27 Jul 2026 22:53:33 +0000</pubDate>
      <link>https://dev.to/m_kocotem_1b69865766c653/construindo-uma-plataforma-gitops-do-zero-kubernetes-argocd-terraform-e-observabilidade-34jh</link>
      <guid>https://dev.to/m_kocotem_1b69865766c653/construindo-uma-plataforma-gitops-do-zero-kubernetes-argocd-terraform-e-observabilidade-34jh</guid>
      <description>&lt;p&gt;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 &lt;em&gt;usar&lt;/em&gt; uma ferramenta e &lt;em&gt;entender&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;Este artigo é o registro dessa construção. Não é um tutorial de "cole esse comando"; é uma explicação de &lt;em&gt;por que&lt;/em&gt; 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 &lt;code&gt;git push&lt;/code&gt;, e métricas de uma aplicação Java fluírem até um dashboard tudo versionado, tudo reproduzível.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que vamos construir
&lt;/h2&gt;

&lt;p&gt;Antes do código, o mapa mental. A plataforma tem quatro camadas que se encaixam.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Terraform&lt;/strong&gt; provisiona o cluster e instala o motor de GitOps. É a fundação como código.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;kind&lt;/strong&gt; roda um cluster Kubernetes real dentro de containers Docker, localmente.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;ArgoCD&lt;/strong&gt; observa um repositório Git e garante que o cluster reflita exatamente o que está versionado.&lt;/p&gt;

&lt;p&gt;Por fim, &lt;strong&gt;Prometheus e Grafana&lt;/strong&gt; coletam e visualizam métricas, incluindo métricas customizadas de uma API Spring Boot.&lt;/p&gt;

&lt;p&gt;O fio que costura tudo é uma ideia só: &lt;strong&gt;o Git é a fonte da verdade.&lt;/strong&gt; Você não muda o cluster na mão; você muda arquivos no Git, e o cluster se ajusta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que GitOps?
&lt;/h2&gt;

&lt;p&gt;Vale parar um instante nessa ideia, porque ela é o coração de tudo.&lt;/p&gt;

&lt;p&gt;No modelo tradicional, você aplica mudanças no cluster diretamente — um &lt;code&gt;kubectl apply&lt;/code&gt; aqui, um &lt;code&gt;kubectl scale&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;GitOps inverte isso. O estado desejado do cluster vive num repositório Git. Uma ferramenta (aqui, o ArgoCD) fica continuamente comparando o que &lt;em&gt;deveria&lt;/em&gt; estar rodando (o Git) com o que &lt;em&gt;está&lt;/em&gt; rodando (o cluster) e corrige qualquer diferença.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;git revert&lt;/code&gt; e que ninguém precisa de acesso direto ao cluster para fazer deploy: basta commitar.&lt;/p&gt;

&lt;p&gt;Guarde essa ideia, porque vamos vê-la acontecer na prática.&lt;/p&gt;

&lt;h2&gt;
  
  
  Camada 1: o cluster como código com Terraform
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Por isso, desde o início, o cluster nasce de Terraform. O trecho central declara três providers e o cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;kind&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tehcyx/kind"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.9"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;helm&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/helm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.17"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/kubernetes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.35"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kind_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gitops-lab"&lt;/span&gt;
  &lt;span class="nx"&gt;wait_for_ready&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;kind_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;kind&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Cluster"&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kind.x-k8s.io/v1alpha4"&lt;/span&gt;

    &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"control-plane"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"worker"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"worker"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare em duas decisões. Primeiro, o cluster tem &lt;strong&gt;três nós&lt;/strong&gt; (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.&lt;/p&gt;

&lt;p&gt;Segundo, as &lt;strong&gt;versões dos providers estão fixadas&lt;/strong&gt;. Isso é o que garante que um &lt;code&gt;terraform apply&lt;/code&gt; daqui a seis meses produza o mesmo resultado de hoje. Reprodutibilidade não é acidente; é uma escolha.&lt;/p&gt;

&lt;p&gt;A partir daí, o mesmo Terraform instala o ArgoCD via Helm, já apontando os providers para o cluster recém-criado. Um único &lt;code&gt;terraform apply&lt;/code&gt; entrega o cluster e o motor de GitOps prontos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Camada 2: ensinando o ArgoCD a observar o Git
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;Application&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Application é a ponte. Ela diz três coisas: de onde puxar, para onde aplicar e como se comportar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/usuario/gitops-manifests.git&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/nginx&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-demo&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As duas linhas mais importantes deste arquivo são &lt;code&gt;prune: true&lt;/code&gt; e &lt;code&gt;selfHeal: true&lt;/code&gt;. Elas são o que torna o GitOps realmente vivo.&lt;/p&gt;

&lt;p&gt;Com &lt;code&gt;selfHeal&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;Já &lt;code&gt;prune&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  O momento GitOps: escalar com um commit
&lt;/h2&gt;

&lt;p&gt;Aqui a teoria vira prática, e é o momento que fixa o conceito.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;   &lt;span class="c1"&gt;# antes era 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um &lt;code&gt;git commit&lt;/code&gt; e um &lt;code&gt;git push&lt;/code&gt; depois, o ArgoCD detectou a diferença e criou dois novos pods — sozinho. Eu nunca rodei &lt;code&gt;kubectl scale&lt;/code&gt;. O cluster simplesmente convergiu para o que o Git passou a dizer.&lt;/p&gt;

&lt;p&gt;O teste que mais ensina veio a seguir. Executei um &lt;code&gt;kubectl scale&lt;/code&gt; manual, forçando o cluster de volta a uma réplica. Por um instante, os pods começaram a ser removidos. Então o &lt;code&gt;selfHeal&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Camada 3: observabilidade e o diferencial da métrica de negócio
&lt;/h2&gt;

&lt;p&gt;Uma plataforma que você não consegue enxergar é uma plataforma que você não controla. Por isso a última camada é observabilidade.&lt;/p&gt;

&lt;p&gt;Instalei o &lt;code&gt;kube-prometheus-stack&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;Um detalhe técnico importante aqui é que charts Helm muito grandes, como esse, exigem a opção &lt;code&gt;ServerSideApply=true&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Mas coletar métricas genéricas de CPU e memória é apenas o básico. O que realmente diferencia uma plataforma é medir &lt;strong&gt;lógica de negócio&lt;/strong&gt;. E foi aqui que meu background em Java entrou como vantagem.&lt;/p&gt;

&lt;p&gt;Criei uma API Spring Boot simples que expõe uma métrica customizada: um contador que incrementa a cada chamada de um endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="n"&gt;helloCounter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HelloController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MeterRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;helloCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"demo_hello_requests_total"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Total de chamadas ao endpoint /hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;helloCounter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Olá do GitOps Lab!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com Spring Boot Actuator e Micrometer, expor essa métrica no formato que o Prometheus entende exige muito pouca configuração.&lt;/p&gt;

&lt;p&gt;A ponte final entre a aplicação e o Prometheus é um recurso chamado &lt;code&gt;ServiceMonitor&lt;/code&gt;, responsável por informar ao Prometheus quais pods devem ser monitorados. Existe, porém, um detalhe que costuma bloquear muita gente: o &lt;code&gt;ServiceMonitor&lt;/code&gt; precisa possuir um label específico (&lt;code&gt;release: monitoring&lt;/code&gt;) 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Os detalhes que só a prática ensina
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Descobri que imagens carregadas localmente no kind exigem &lt;code&gt;imagePullPolicy: IfNotPresent&lt;/code&gt;; caso contrário, o Kubernetes tentará buscá-las em um registry remoto e falhará.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;E, talvez o hábito mais importante de todos, aprendi que ler cuidadosamente o resultado de um &lt;code&gt;terraform plan&lt;/code&gt; antes do &lt;code&gt;terraform apply&lt;/code&gt; é a diferença entre operar com confiança e simplesmente torcer para que tudo funcione.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;No fim, a sensação é quase decepcionante de tão tranquila: você muda um número no Git, faz um &lt;code&gt;git push&lt;/code&gt; e o cluster inteiro se ajusta sozinho.&lt;/p&gt;

&lt;p&gt;Mas essa simplicidade aparente é justamente o objetivo. Uma boa plataforma esconde a complexidade atrás de um simples &lt;code&gt;git push&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Até a próxima.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>gitops</category>
    </item>
  </channel>
</rss>
