<?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: apsis-cc</title>
    <description>The latest articles on DEV Community by apsis-cc (apsis-cc).</description>
    <link>https://dev.to/apsis-cc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14122%2F536c6bfe-3d3a-4372-9fb1-7d8c6d80ccfd.webp</url>
      <title>DEV Community: apsis-cc</title>
      <link>https://dev.to/apsis-cc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/apsis-cc"/>
    <language>en</language>
    <item>
      <title>Dashboards no Grafana - combinando PromQL e LogQL para acompanhar uma aplicação de ponta a ponta</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Sat, 26 Sep 2026 09:30:37 +0000</pubDate>
      <link>https://dev.to/apsis-cc/dashboards-no-grafana-combinando-promql-e-logql-para-acompanhar-uma-aplicacao-de-ponta-a-ponta-4k02</link>
      <guid>https://dev.to/apsis-cc/dashboards-no-grafana-combinando-promql-e-logql-para-acompanhar-uma-aplicacao-de-ponta-a-ponta-4k02</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: dados prontos, falta visualizar
&lt;/h2&gt;

&lt;p&gt;A stack unificada do artigo anterior já coleta métricas das duas mini apps, recebe seus logs e processa seus traces. Mas até aqui, toda consulta foi feita manualmente — uma expressão PromQL de cada vez no Prometheus, uma query LogQL de cada vez no Explore do Grafana. Este artigo constrói um dashboard real, com vários painéis lado a lado, para acompanhar as duas mini apps sem precisar reabrir consultas soltas toda vez.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Criando o dashboard e o primeiro painel
&lt;/h2&gt;

&lt;p&gt;No Grafana, em &lt;strong&gt;Dashboards → New → New Dashboard → Add visualization&lt;/strong&gt;, escolhendo o datasource &lt;strong&gt;Prometheus&lt;/strong&gt;, o primeiro painel é a taxa de requisições da mini app Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rate(app_requests_total{job="python-app"}[1m])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configurações relevantes desse painel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visualization&lt;/strong&gt;: Time series (o padrão, adequado para qualquer métrica ao longo do tempo).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt;: "Taxa de Requisições — Python App".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit&lt;/strong&gt; (na aba Standard options): "requests/sec", para o eixo Y ficar legível sem precisar interpretar o número bruto.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Painel de latência com histogram_quantile
&lt;/h2&gt;

&lt;p&gt;Um segundo painel, usando o histogram de duração criado no artigo 3 para a mini app PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;histogram_quantile(0.50, rate(orders_duration_seconds_bucket{job="php-app"}[5m]))
histogram_quantile(0.95, rate(orders_duration_seconds_bucket{job="php-app"}[5m]))
histogram_quantile(0.99, rate(orders_duration_seconds_bucket{job="php-app"}[5m]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adicionando as três expressões como três queries (A, B, C) no mesmo painel de Time series, cada uma vira uma linha — p50, p95 e p99 sobrepostas no mesmo gráfico é o padrão mais comum para visualizar latência, porque a distância entre as três linhas já mostra visualmente o quão "cauda longa" (long tail) a latência está: linhas próximas indicam latência consistente, p99 muito acima do p50 indica que uma fração pequena de requisições está bem mais lenta que a maioria.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Painel de taxa de erro (gauge)
&lt;/h2&gt;

&lt;p&gt;Um painel do tipo &lt;strong&gt;Gauge&lt;/strong&gt; (ponteiro/velocímetro) para o tamanho atual da fila de pedidos da mini app PHP — útil por mostrar o valor instantâneo de forma proeminente, sem exigir interpretar um gráfico de linha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;orders_queue_size{job="php-app"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na aba &lt;strong&gt;Thresholds&lt;/strong&gt; desse painel, vale configurar faixas de cor — verde até 5, amarelo até 15, vermelho acima disso — para o painel comunicar "tudo bem" ou "atenção" de relance, sem precisar ler o número.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Painel de logs com LogQL
&lt;/h2&gt;

&lt;p&gt;Adicionando um novo painel, agora com o datasource &lt;strong&gt;Loki&lt;/strong&gt;, tipo &lt;strong&gt;Logs&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{app=~"python-app|php-app"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A expressão &lt;code&gt;=~&lt;/code&gt; faz correspondência por regex no valor do label — nesse caso, trazendo logs das duas aplicações no mesmo painel, empilhados por tempo. Um segundo painel de logs, filtrado só para eventos de erro (útil para manter visível mesmo quando o volume geral de log é alto):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{app=~"python-app|php-app", level="error"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Painel de volume de logs ao longo do tempo
&lt;/h2&gt;

&lt;p&gt;O LogQL também permite agregações numéricas sobre logs, não só listagem de linhas — útil para correlacionar visualmente um pico de volume de log de erro com um painel de métricas ao lado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sum(count_over_time({app=~"python-app|php-app", level="error"}[1m]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse painel, como &lt;strong&gt;Time series&lt;/strong&gt; (mesmo vindo de uma query LogQL), mostra quantas linhas de log de erro por minuto cada aplicação produziu — o equivalente, em logs, ao que &lt;code&gt;rate()&lt;/code&gt; faz para um counter do Prometheus.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Organizando o dashboard
&lt;/h2&gt;

&lt;p&gt;Com os cinco painéis criados — taxa de requisições, latência p50/p95/p99, tamanho da fila, logs recentes e volume de erro — a organização recomendada segue uma convenção comum em dashboards de observabilidade: do geral para o específico, de cima para baixo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────┬─────────────────────────┐
│ Taxa de Requisições      │ Latência p50/p95/p99     │
├─────────────────────────┼─────────────────────────┤
│ Tamanho da Fila (gauge)  │ Volume de Erros / min     │
├─────────────────────────┴─────────────────────────┤
│ Logs Recentes (filtrados por app e nível)           │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Painéis de métrica agregada (o "o quê") ficam no topo, e o painel de logs detalhado (o "por quê") fica embaixo — assim, o fluxo natural de investigação (ver o problema no topo, entender a causa embaixo) segue a ordem visual do próprio dashboard. Salvando o dashboard com um nome como "Mini Apps — Visão Geral" e uma tag &lt;code&gt;observabilidade&lt;/code&gt;, ele fica disponível na lista principal de dashboards do Grafana.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Variável de template para alternar entre apps
&lt;/h2&gt;

&lt;p&gt;Para não duplicar o dashboard inteiro entre Python e PHP, o Grafana permite criar uma &lt;strong&gt;variável&lt;/strong&gt; que filtra os painéis dinamicamente. Em &lt;strong&gt;Dashboard settings → Variables → Add variable&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;code&gt;app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type&lt;/strong&gt;: Query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data source&lt;/strong&gt;: Prometheus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query&lt;/strong&gt;: &lt;code&gt;label_values(app_requests_total, job)&lt;/code&gt; (ou, mais genérico, &lt;code&gt;label_values(job)&lt;/code&gt; para listar todos os jobs configurados no &lt;code&gt;prometheus.yml&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com a variável criada, cada query dos painéis passa a usar &lt;code&gt;$app&lt;/code&gt; no lugar do valor fixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rate(app_requests_total{job="$app"}[1m])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um seletor aparece no topo do dashboard, permitindo trocar entre &lt;code&gt;python-app&lt;/code&gt; e &lt;code&gt;php-app&lt;/code&gt; sem editar nenhum painel manualmente — o mesmo dashboard serve para as duas mini apps, e para qualquer aplicação nova que a stack venha a ganhar no futuro.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Construímos um dashboard real combinando métricas via PromQL (taxa de requisições, latência por percentil, tamanho de fila) e logs via LogQL (eventos recentes, volume de erro), organizados do geral para o específico e parametrizados com uma variável de template para reutilização entre aplicações. No último artigo desta série, esse dashboard vira ferramenta de investigação de verdade: vamos simular um incidente real nas mini apps e usar métricas, logs e traces juntos para encontrar a causa raiz.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/" rel="noopener noreferrer"&gt;Grafana — Build a Dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/grafana/latest/dashboards/variables/" rel="noopener noreferrer"&gt;Grafana — Add Template Variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/loki/latest/query/metric_queries/" rel="noopener noreferrer"&gt;Grafana Loki — Metric Queries (LogQL)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>Stack completa de observabilidade com Docker Compose - Python, PHP, Prometheus, Loki, OpenTelemetry e Grafana</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Fri, 25 Sep 2026 09:30:38 +0000</pubDate>
      <link>https://dev.to/apsis-cc/stack-completa-de-observabilidade-com-docker-compose-python-php-prometheus-loki-opentelemetry-4occ</link>
      <guid>https://dev.to/apsis-cc/stack-completa-de-observabilidade-com-docker-compose-python-php-prometheus-loki-opentelemetry-4occ</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: as peças soltas até aqui
&lt;/h2&gt;

&lt;p&gt;Cada artigo desta série subiu uma peça isolada: Prometheus + Loki + Grafana no artigo 1, a mini app Python no artigo 2, a mini app PHP no artigo 3, envio de logs para o Loki no artigo 4, e instrumentação de traces (por enquanto só impressos no console) no artigo 5. Este artigo une tudo em um único &lt;code&gt;docker-compose.yml&lt;/code&gt;, com uma peça nova: um &lt;strong&gt;coletor OpenTelemetry&lt;/strong&gt;, responsável por receber os traces das mini apps via OTLP e encaminhá-los adiante.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Por que um coletor no meio do caminho
&lt;/h2&gt;

&lt;p&gt;No artigo 5, as mini apps exportavam spans diretamente para o console (&lt;code&gt;ConsoleSpanExporter&lt;/code&gt;). Isso prova que a instrumentação funciona, mas não é assim que uma aplicação real se comunica com uma stack de observabilidade — cada aplicação teria que saber exportar diretamente para cada backend final, duplicando configuração de endpoint, autenticação e formato em todas elas.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;OpenTelemetry Collector&lt;/strong&gt; resolve isso ficando no meio: as aplicações exportam sempre para o mesmo lugar (o coletor, via OTLP), e é o coletor — configurado uma vez, centralmente — quem decide para onde os dados seguem depois: um backend de traces, processamento adicional (amostragem, enriquecimento de atributos), ou múltiplos destinos ao mesmo tempo. Trocar de backend passa a ser uma mudança de configuração do coletor, não um redeploy de cada aplicação instrumentada.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Trocando o exportador de console por OTLP
&lt;/h2&gt;

&lt;p&gt;Nas duas mini apps, o único ponto que muda em relação ao artigo 5 é o exportador — a forma como os spans &lt;strong&gt;saem&lt;/strong&gt; do processo — não a instrumentação em si (os &lt;code&gt;tracer.start_as_current_span&lt;/code&gt; e &lt;code&gt;spanBuilder(...)-&amp;gt;startSpan()&lt;/code&gt; continuam idênticos).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tracing.py (Python) — troca do ConsoleSpanExporter por OTLP
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.exporter.otlp.proto.http.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://otel-collector:4318/v1/traces&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-exporter-otlp-proto-http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// tracing.php (PHP) — troca do ConsoleSpanExporter por OTLP&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;OpenTelemetry\Contrib\Otlp\SpanExporter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;OpenTelemetry\SDK\Common\Transport\Http\PsrTransportFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PsrTransportFactory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'http://otel-collector:4318/v1/traces'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'application/x-protobuf'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$tracerProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exporter&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nv"&gt;$tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tracerProvider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php-app'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O hostname &lt;code&gt;otel-collector&lt;/code&gt; usado nos dois casos é o nome do serviço do Compose que aparece na seção 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Configurando o coletor
&lt;/h2&gt;

&lt;p&gt;O coletor precisa de um arquivo de configuração declarando três coisas: &lt;strong&gt;receivers&lt;/strong&gt; (como os dados entram), &lt;strong&gt;processors&lt;/strong&gt; (transformações opcionais no meio do caminho) e &lt;strong&gt;exporters&lt;/strong&gt; (para onde os dados saem):&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="c1"&gt;# otel-collector-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4318&lt;/span&gt;

&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;verbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este exemplo usa o exportador &lt;code&gt;debug&lt;/code&gt; (o sucessor do antigo &lt;code&gt;logging&lt;/code&gt; exporter), que imprime os traces recebidos no log do próprio coletor — suficiente para confirmar que o pipeline completo funciona de ponta a ponta: mini app → coletor → saída. Visualizar traces de forma gráfica, com uma árvore de spans navegável, exigiria um backend de tracing dedicado (como Grafana Tempo ou Jaeger) recebendo esse mesmo exportador — fora do escopo desta stack mínima, mas um próximo passo natural para quem quiser aprofundar depois desta série.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. O docker-compose.yml unificado
&lt;/h2&gt;

&lt;p&gt;Juntando tudo o que a série construiu até aqui em um único 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="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.55.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml:ro&lt;/span&gt;

  &lt;span class="na"&gt;loki&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/loki:3.2.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3100:3100"&lt;/span&gt;

  &lt;span class="na"&gt;otel-collector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel/opentelemetry-collector:0.112.0&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--config=/etc/otel-collector-config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4318:4318"&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:11.3.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GF_SECURITY_ADMIN_PASSWORD=admin&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;loki&lt;/span&gt;

  &lt;span class="na"&gt;python-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./python-app:/app:ro&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pip&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--no-cache-dir&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;opentelemetry-sdk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;opentelemetry-exporter-otlp-proto-http&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;python3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app.py"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otel-collector&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;loki&lt;/span&gt;

  &lt;span class="na"&gt;php-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php:8.3-cli&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./php-app:/app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-S"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0:8001"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.php"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8001:8001"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otel-collector&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;loki&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o &lt;code&gt;prometheus.yml&lt;/code&gt; reunindo os alvos dos artigos 2 e 3:&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="c1"&gt;# prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prometheus"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:9090"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app:8000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php-app"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php-app:8001"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subindo tudo de uma vez:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seis serviços devem aparecer no ar: &lt;code&gt;prometheus&lt;/code&gt;, &lt;code&gt;loki&lt;/code&gt;, &lt;code&gt;otel-collector&lt;/code&gt;, &lt;code&gt;grafana&lt;/code&gt;, &lt;code&gt;python-app&lt;/code&gt; e &lt;code&gt;php-app&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Configurando os datasources no Grafana
&lt;/h2&gt;

&lt;p&gt;Com todos os serviços na mesma rede do Compose, em &lt;strong&gt;Connections → Data sources&lt;/strong&gt; no Grafana, dois datasources apontam para os serviços pelo nome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt; — &lt;code&gt;http://prometheus:9090&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loki&lt;/strong&gt; — &lt;code&gt;http://loki:3100&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esses são os dois datasources que o artigo 7 desta série vai efetivamente usar em dashboards. O coletor OpenTelemetry não aparece como datasource do Grafana neste setup — ele só encaminha traces para o exportador &lt;code&gt;debug&lt;/code&gt; configurado na seção 4; ligar um datasource de traces é o passo seguinte caso um backend de tracing seja adicionado à stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Confirmando que o pipeline completo funciona
&lt;/h2&gt;

&lt;p&gt;Gerando tráfego nas duas aplicações e conferindo cada ponta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/
curl http://localhost:8001/

&lt;span class="c"&gt;# metricas&lt;/span&gt;
curl http://localhost:8000/metrics
curl http://localhost:8001/metrics

&lt;span class="c"&gt;# logs do coletor, onde os traces devem aparecer impressos&lt;/span&gt;
docker compose logs otel-collector &lt;span class="nt"&gt;--tail&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o &lt;code&gt;docker compose logs otel-collector&lt;/code&gt; mostrar blocos com &lt;code&gt;TraceID&lt;/code&gt;, &lt;code&gt;SpanID&lt;/code&gt; e &lt;code&gt;Name&lt;/code&gt; para as requisições feitas, o pipeline de ponta a ponta está funcionando: as mini apps geram spans, o coletor os recebe via OTLP e os processa.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Unimos as seis peças construídas ao longo da série em uma stack única e reproduzível: duas mini aplicações instrumentadas para métricas, logs e traces, Prometheus fazendo scraping, Loki recebendo logs, um coletor OpenTelemetry recebendo traces via OTLP, e Grafana com os datasources de Prometheus e Loki configurados. No próximo artigo, o foco volta para o Grafana: construir dashboards reais que combinam PromQL e LogQL para acompanhar as mini apps de ponta a ponta em um único painel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OpenTelemetry Collector — Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/collector/configuration/" rel="noopener noreferrer"&gt;OpenTelemetry Collector — Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/" rel="noopener noreferrer"&gt;OpenTelemetry — OTLP Exporter Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/how-tos/networking/" rel="noopener noreferrer"&gt;Docker Compose — Networking&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>OpenTelemetry - o que é, por que virou padrão e como instrumentar traces</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:30:37 +0000</pubDate>
      <link>https://dev.to/apsis-cc/opentelemetry-o-que-e-por-que-virou-padrao-e-como-instrumentar-traces-46d0</link>
      <guid>https://dev.to/apsis-cc/opentelemetry-o-que-e-por-que-virou-padrao-e-como-instrumentar-traces-46d0</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: métricas e logs prontos, falta conectar os pontos
&lt;/h2&gt;

&lt;p&gt;As mini apps desta série já expõem métricas (artigos 2 e 3) e enviam logs para o Loki (artigo 4). O que ainda falta é o terceiro pilar visto no artigo 1: traces, que mostram o caminho de uma requisição específica e onde exatamente o tempo foi gasto. Este artigo apresenta o &lt;strong&gt;OpenTelemetry&lt;/strong&gt;, o padrão atual para gerar esse tipo de dado, e instrumenta as duas mini apps para produzir traces reais.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. O que é o OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OpenTelemetry&lt;/strong&gt; (frequentemente abreviado "OTel") é um projeto da CNCF que define um conjunto de APIs, SDKs e um protocolo de transporte (o &lt;strong&gt;OTLP&lt;/strong&gt; — OpenTelemetry Protocol) para gerar, processar e exportar dados de telemetria — métricas, logs e, principalmente neste artigo, traces — de forma neutra em relação a qual backend vai efetivamente armazenar e visualizar esses dados.&lt;/p&gt;

&lt;p&gt;Na prática, isso significa que a aplicação se instrumenta &lt;strong&gt;uma vez&lt;/strong&gt;, contra a API do OpenTelemetry, e decide &lt;strong&gt;depois&lt;/strong&gt;, por configuração, para onde exportar — Prometheus, Loki, Jaeger, um vendor comercial, ou, como esta série vai fazer no próximo artigo, um coletor OpenTelemetry intermediário. Trocar de backend de observabilidade deixa de exigir reescrever a instrumentação.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Por que virou o padrão dominante
&lt;/h2&gt;

&lt;p&gt;Antes do OpenTelemetry, existiam dois projetos concorrentes e incompatíveis tentando resolver o mesmo problema: &lt;strong&gt;OpenTracing&lt;/strong&gt; (focado em traces) e &lt;strong&gt;OpenCensus&lt;/strong&gt; (traces e métricas, liderado pelo Google). Cada biblioteca de instrumentação precisava escolher um dos dois, fragmentando o ecossistema — uma biblioteca instrumentada para OpenTracing não conversava com um backend pensado para OpenCensus sem camadas de tradução.&lt;/p&gt;

&lt;p&gt;Em 2019, os dois projetos se fundiram no OpenTelemetry, sob a CNCF (a mesma fundação que abriga Kubernetes e Prometheus). Isso resolveu a fragmentação e, mais importante, deu ao projeto neutralidade de fornecedor: nenhum vendor de observabilidade controla o padrão, o que fez praticamente todos os grandes players (Datadog, New Relic, Grafana, AWS, Google Cloud, Azure) apoiarem OTLP como protocolo de entrada nativo. Hoje, instrumentar com OpenTelemetry é a opção que menos prende a aplicação a um fornecedor específico.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Conceitos de tracing: trace, span e contexto
&lt;/h2&gt;

&lt;p&gt;Retomando e aprofundando o que o artigo 1 introduziu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Span&lt;/strong&gt; — a unidade básica: representa uma operação com início, fim e duração (uma chamada HTTP, uma query, um cálculo). Carrega um nome, atributos (pares chave-valor arbitrários) e, opcionalmente, eventos pontuais dentro do seu intervalo de tempo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace&lt;/strong&gt; — o conjunto de todos os spans gerados para atender uma única requisição de ponta a ponta, conectados entre si por um &lt;code&gt;trace id&lt;/code&gt; compartilhado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Span pai/filho&lt;/strong&gt; — um span pode iniciar outros spans "dentro" dele (por exemplo, o span da requisição HTTP inicia um span filho para a query de banco que ela dispara). Essa relação de aninhamento é o que permite reconstruir a árvore de tempo vista no artigo 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagação de contexto&lt;/strong&gt; — quando uma requisição atravessa um limite de processo (chamada HTTP entre dois serviços), o &lt;code&gt;trace id&lt;/code&gt; e o &lt;code&gt;span id&lt;/code&gt; atual viajam como cabeçalhos HTTP (o formato padrão é o &lt;code&gt;traceparent&lt;/code&gt;, definido pela especificação W3C Trace Context), permitindo que o serviço seguinte continue o mesmo trace em vez de iniciar um novo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Instrumentando a mini app Python
&lt;/h2&gt;

&lt;p&gt;O SDK oficial do Python precisa ser instalado (não é parte da biblioteca padrão, diferente do que as mini apps fizeram até aqui para métricas e logs — gerar spans manualmente sem SDK é possível, mas reimplementar amostragem, contexto e propagação à mão perde o sentido de usar um padrão justamente feito para isso):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instrumentação manual, criando um span por requisição:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tracing.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integrando no &lt;code&gt;app.py&lt;/code&gt; do artigo 2, envolvendo o processamento da requisição em um span:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;

&lt;span class="c1"&gt;# dentro do do_GET, no branch que nao eh /metrics
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handle_request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http.path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;request_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;log_to_loki&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requisicao recebida em &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http.status_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por enquanto, o exportador usado é o &lt;code&gt;ConsoleSpanExporter&lt;/code&gt;, que apenas imprime cada span no stdout — suficiente para confirmar que a instrumentação está gerando dados, sem depender de nenhuma infraestrutura adicional ainda. Rodando a aplicação e fazendo uma requisição, o terminal deve mostrar um bloco JSON com &lt;code&gt;trace_id&lt;/code&gt;, &lt;code&gt;span_id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; e &lt;code&gt;start_time&lt;/code&gt;/&lt;code&gt;end_time&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Instrumentando a mini app PHP
&lt;/h2&gt;

&lt;p&gt;O equivalente em PHP usa o SDK oficial, instalado via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require open-telemetry/sdk open-telemetry/exporter-otlp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// tracing.php&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;OpenTelemetry\SDK\Trace\TracerProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$tracerProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tracerProvider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php-app'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integrando no &lt;code&gt;app.php&lt;/code&gt; do artigo 3, ao redor do processamento de um pedido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/tracing.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tracer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;spanBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'process_order'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;startSpan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$span&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... logica de processamento do pedido, ja existente ...&lt;/span&gt;
    &lt;span class="nv"&gt;$span&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order.duration_ms'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'durations'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'durations'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$span&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$scope&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;detach&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;O padrão &lt;code&gt;try/finally&lt;/code&gt; garante que o span é finalizado mesmo se o processamento lançar uma exceção no meio do caminho — um span que nunca termina (por um &lt;code&gt;end()&lt;/code&gt; que não é alcançado) é um erro comum de instrumentação manual, e polui o backend de traces com spans "presos".&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Vimos o que é o OpenTelemetry, por que a fusão de OpenTracing e OpenCensus em 2019 o tornou o padrão neutro de instrumentação, os conceitos de span, trace e propagação de contexto, e instrumentamos manualmente as duas mini apps para gerar spans — por enquanto, só visíveis no console de cada aplicação. No próximo artigo, a stack inteira é unificada em um único &lt;code&gt;docker-compose.yml&lt;/code&gt;: as mini apps trocam o &lt;code&gt;ConsoleSpanExporter&lt;/code&gt; por um exportador OTLP de verdade, apontando para um coletor OpenTelemetry, ao lado de Prometheus, Loki e Grafana já configurados.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/what-is-opentelemetry/" rel="noopener noreferrer"&gt;OpenTelemetry — What is OpenTelemetry?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/concepts/signals/traces/" rel="noopener noreferrer"&gt;OpenTelemetry — Traces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C Trace Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/languages/python/getting-started/" rel="noopener noreferrer"&gt;OpenTelemetry Python — Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/languages/php/getting-started/" rel="noopener noreferrer"&gt;OpenTelemetry PHP — Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>Loki na prática - enviando logs via API HTTP a partir das mini apps Python e PHP</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Wed, 23 Sep 2026 09:30:35 +0000</pubDate>
      <link>https://dev.to/apsis-cc/loki-na-pratica-enviando-logs-via-api-http-a-partir-das-mini-apps-python-e-php-5en5</link>
      <guid>https://dev.to/apsis-cc/loki-na-pratica-enviando-logs-via-api-http-a-partir-das-mini-apps-python-e-php-5en5</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: métricas prontas, falta o segundo pilar
&lt;/h2&gt;

&lt;p&gt;As duas mini aplicações desta série — a de Python (artigo 2) e a de PHP (artigo 3) — já expõem métricas consumidas pelo Prometheus. Métricas respondem "quantos pedidos falharam nos últimos 5 minutos", mas não respondem "por que o pedido 4821 falhou especificamente". Essa é a pergunta que logs respondem, e este artigo cobre como enviá-los para o Loki diretamente das mini apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Por que a indexação do Loki é diferente
&lt;/h2&gt;

&lt;p&gt;Ferramentas de log tradicionais, como o Elasticsearch, indexam o conteúdo completo de cada linha — todo termo de texto vira pesquisável, o que permite buscas ricas, mas exige estruturas de índice caras em disco e CPU, que crescem proporcionalmente ao volume de log.&lt;/p&gt;

&lt;p&gt;O Loki toma uma decisão deliberadamente diferente, inspirada diretamente no modelo do Prometheus: &lt;strong&gt;indexa só um conjunto pequeno de labels&lt;/strong&gt; — como &lt;code&gt;app="python-app"&lt;/code&gt;, &lt;code&gt;env="production"&lt;/code&gt; — e trata o conteúdo da linha de log como texto opaco, comprimido e armazenado em blocos (chunks), sem índice próprio. Quando uma consulta pede um filtro de texto (&lt;code&gt;|= "erro"&lt;/code&gt;, por exemplo), o Loki varre os chunks relevantes (já filtrados pelos labels) no momento da consulta, em vez de consultar um índice pré-construído.&lt;/p&gt;

&lt;p&gt;O resultado prático dessa troca:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Armazenamento muito mais barato&lt;/strong&gt; — sem índice invertido de texto completo, o custo de disco cresce bem mais devagar com o volume de logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buscas por label são muito rápidas&lt;/strong&gt; — "todos os logs do serviço X, no ambiente Y" é uma operação barata, porque os labels são de fato indexados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buscas por texto livre são mais lentas conforme o volume cresce&lt;/strong&gt; — porque envolvem varredura, não um lookup de índice. Na prática, isso empurra o usuário a sempre filtrar primeiro por labels (que reduzem drasticamente o volume a varrer) antes de aplicar um filtro de texto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cardinalidade de labels importa tanto quanto no Prometheus&lt;/strong&gt; — usar um label com alta cardinalidade (como um &lt;code&gt;user_id&lt;/code&gt; único por requisição) quebra a mesma premissa de eficiência nos dois sistemas, e é um erro comum de quem vem de ferramentas que indexam tudo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. A API HTTP de push do Loki
&lt;/h2&gt;

&lt;p&gt;Diferente do Prometheus (que faz pull), o Loki recebe logs por &lt;strong&gt;push&lt;/strong&gt;: a aplicação envia ativamente cada lote de linhas para um endpoint HTTP, &lt;code&gt;POST /loki/api/v1/push&lt;/code&gt;. O corpo da requisição é um JSON com o seguinte formato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"streams"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"1735689600000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"requisicao recebida em /"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pontos importantes desse formato:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stream&lt;/code&gt; carrega os &lt;strong&gt;labels&lt;/strong&gt; — o que o Loki de fato indexa. Poucos e de baixa cardinalidade, como visto acima.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;values&lt;/code&gt; é uma lista de &lt;code&gt;[timestamp, linha]&lt;/code&gt;, onde o timestamp é uma string em &lt;strong&gt;nanosegundos desde epoch&lt;/strong&gt; (não segundos, nem milissegundos — um detalhe fácil de errar).&lt;/li&gt;
&lt;li&gt;É possível enviar múltiplas linhas do mesmo stream de uma vez, o que é mais eficiente do que uma requisição HTTP por linha de log.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Enviando logs a partir da mini app Python
&lt;/h2&gt;

&lt;p&gt;Usando só a biblioteca padrão (&lt;code&gt;urllib&lt;/code&gt;, sem instalar um client HTTP externo), uma função de log que envia diretamente para o Loki:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# logger.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="n"&gt;LOKI_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://loki:3100/loki/api/v1/push&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_to_loki&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;timestamp_ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_ns&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;streams&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;timestamp_ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&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="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;LOKI_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# nunca deixar uma falha de log derrubar a aplicacao
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;falha ao enviar log para o Loki: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integrando no &lt;code&gt;app.py&lt;/code&gt; do artigo 2, chamando &lt;code&gt;log_to_loki&lt;/code&gt; a cada requisição de negócio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dentro do do_GET, no branch que nao eh /metrics
&lt;/span&gt;&lt;span class="nf"&gt;log_to_loki&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requisicao recebida em &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Enviando logs a partir da mini app PHP
&lt;/h2&gt;

&lt;p&gt;O mesmo endpoint, chamado a partir do PHP puro com &lt;code&gt;file_get_contents&lt;/code&gt; e um &lt;code&gt;stream context&lt;/code&gt; (sem precisar de cURL nem bibliotecas externas):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// logger.php&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;logToLoki&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'info'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'php-app'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// microtime(true) da segundos com fracao decimal; convertendo para&lt;/span&gt;
    &lt;span class="c1"&gt;// nanossegundos desde epoch, o formato que o Loki espera&lt;/span&gt;
    &lt;span class="nv"&gt;$timestampNs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%.0f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'streams'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
            &lt;span class="s1"&gt;'stream'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'app'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'level'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$level&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'values'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;$timestampNs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$message&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="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;stream_context_create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'http'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'header'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'ignore_errors'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&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="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://loki:3100/loki/api/v1/push'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&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;Integrando no &lt;code&gt;app.php&lt;/code&gt; do artigo 3, logo após processar um pedido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/logger.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;logToLoki&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pedido processado em &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'requests_total'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms de fila"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Consultando os logs no Grafana com LogQL
&lt;/h2&gt;

&lt;p&gt;Com o datasource do Loki já configurado no Grafana desde o artigo 1, em &lt;strong&gt;Explore&lt;/strong&gt;, selecionando o datasource Loki, uma consulta LogQL básica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{app="python-app"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso retorna todas as linhas de log com o label &lt;code&gt;app="python-app"&lt;/code&gt; — o equivalente a um &lt;code&gt;grep&lt;/code&gt; pré-filtrado pelos labels indexados. Para refinar com um filtro de texto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{app="python-app"} |= "erro"

{app="php-app", level="info"} != "healthcheck"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;|=&lt;/code&gt; filtra linhas que &lt;strong&gt;contêm&lt;/strong&gt; o texto; &lt;code&gt;!=&lt;/code&gt; filtra linhas que &lt;strong&gt;não contêm&lt;/strong&gt;. Esses filtros de texto rodam sobre o conteúdo já restrito pelos labels — daí a importância de sempre começar a query pelos labels certos, como discutido na seção 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Vimos por que o Loki indexa só labels e trata o conteúdo da linha como texto não indexado — uma troca deliberada de riqueza de busca por custo —, como enviar logs diretamente via API HTTP sem bibliotecas externas em Python e PHP, e como consultar esses logs com LogQL no Grafana. Com métricas e logs das duas mini apps já fluindo para a stack, falta o terceiro pilar: o próximo artigo introduz o OpenTelemetry e instrumenta as mesmas aplicações para gerar traces.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs" rel="noopener noreferrer"&gt;Grafana Loki — HTTP API (push)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/loki/latest/query/" rel="noopener noreferrer"&gt;Grafana Loki — LogQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/loki/latest/get-started/labels/bp-labels/" rel="noopener noreferrer"&gt;Grafana Loki — Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>Métricas customizadas - counters, gauges e histograms com uma mini app PHP</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Tue, 22 Sep 2026 09:30:28 +0000</pubDate>
      <link>https://dev.to/apsis-cc/metricas-customizadas-counters-gauges-e-histograms-com-uma-mini-app-php-2o8i</link>
      <guid>https://dev.to/apsis-cc/metricas-customizadas-counters-gauges-e-histograms-com-uma-mini-app-php-2o8i</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: de uma métrica a um conjunto completo
&lt;/h2&gt;

&lt;p&gt;No artigo anterior, a mini aplicação em Python expunha uma única métrica — uma contagem de requisições. Na prática, uma aplicação real quer responder perguntas de negócio mais específicas: quantos pedidos foram criados? Quanto tempo cada requisição levou? Quantas conexões estão abertas agora? Cada uma dessas perguntas pede um &lt;strong&gt;tipo diferente&lt;/strong&gt; de métrica. Este artigo cobre os três tipos que cobrem praticamente todo caso de uso — counter, gauge e histogram — com uma segunda mini aplicação, desta vez em PHP puro, expondo métricas de negócio.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Counter: só sobe (até o processo reiniciar)
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;counter&lt;/strong&gt; é um valor que só aumenta (ou zera, se o processo reinicia). É o tipo certo para "quantas vezes algo aconteceu": número de requisições, número de erros, número de pedidos criados. A mini app do artigo anterior já usava um counter (&lt;code&gt;app_requests_total&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;O sufixo &lt;code&gt;_total&lt;/code&gt; no nome é uma convenção do Prometheus para deixar explícito que a métrica é um counter cumulativo, não um valor instantâneo — vale seguir essa convenção mesmo implementando o texto à mão.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP orders_created_total Total de pedidos criados&lt;/span&gt;
&lt;span class="c"&gt;# TYPE orders_created_total counter&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_created_total&lt;/span&gt; &lt;span class="mi"&gt;187&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Gauge: sobe e desce livremente
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;gauge&lt;/strong&gt; representa um valor que pode aumentar ou diminuir a qualquer momento — é o tipo certo para "quanto existe agora": número de conexões abertas, itens numa fila, memória em uso, temperatura. Diferente do counter, não existe uma direção esperada.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP orders_queue_size Numero de pedidos aguardando processamento&lt;/span&gt;
&lt;span class="c"&gt;# TYPE orders_queue_size gauge&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_queue_size&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Histogram: distribuição de valores em buckets
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;histogram&lt;/strong&gt; captura a distribuição de uma medida contínua — o caso mais comum é tempo de resposta. Em vez de guardar cada valor individual (inviável em alta escala) ou só uma média (que esconde outliers), um histogram conta quantas observações caíram dentro de faixas pré-definidas, chamadas &lt;strong&gt;buckets&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP orders_duration_seconds Tempo de processamento de um pedido&lt;/span&gt;
&lt;span class="c"&gt;# TYPE orders_duration_seconds histogram&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0.1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0.5"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;195&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"+Inf"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_duration_seconds_sum&lt;/span&gt; &lt;span class="mf"&gt;42.7&lt;/span&gt;
&lt;span class="o"&gt;or&lt;/span&gt;&lt;span class="n"&gt;ders_duration_seconds_count&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada linha &lt;code&gt;_bucket{le="X"}&lt;/code&gt; é cumulativa: "quantas observações foram menores ou iguais a X segundos". &lt;code&gt;_sum&lt;/code&gt; é a soma de todos os valores observados, e &lt;code&gt;_count&lt;/code&gt; é o total de observações (igual ao último bucket, &lt;code&gt;+Inf&lt;/code&gt;). Com essas peças, o Prometheus consegue calcular percentis aproximados (p50, p95, p99) em tempo de consulta, via &lt;code&gt;histogram_quantile&lt;/code&gt; — sem que a aplicação precise calcular percentil nenhum sozinha.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mini app em PHP puro com métricas de negócio
&lt;/h2&gt;

&lt;p&gt;Sem nenhum framework, usando apenas o servidor embutido do PHP (&lt;code&gt;php -S&lt;/code&gt;), uma aplicação que simula processamento de pedidos e expõe as três métricas vistas acima:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// app.php&lt;/span&gt;

&lt;span class="nv"&gt;$stateFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/metrics_state.json'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;readState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'requests_total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'queue_size'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'durations'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[]];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;writeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stateFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_URI'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'/metrics'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$buckets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$durations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'durations'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$durations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$durations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: text/plain; version=0.0.4'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# HELP orders_created_total Total de pedidos criados&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# TYPE orders_created_total counter&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"orders_created_total &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'requests_total'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# HELP orders_queue_size Numero de pedidos aguardando processamento&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# TYPE orders_queue_size gauge&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"orders_queue_size &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'queue_size'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# HELP orders_duration_seconds Tempo de processamento de um pedido&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# TYPE orders_duration_seconds histogram&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$cumulative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$buckets&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$le&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$cumulative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$durations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nv"&gt;$le&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"orders_duration_seconds_bucket&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;le=\"{$le}\"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$cumulative&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"orders_duration_seconds_bucket&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;le=\"+Inf\"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"orders_duration_seconds_sum &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"orders_duration_seconds_count &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Simula o processamento de um novo pedido&lt;/span&gt;
&lt;span class="nv"&gt;$start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'queue_size'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;usleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400_000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// simula trabalho real, 20-400ms&lt;/span&gt;
&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'queue_size'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'requests_total'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'durations'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;writeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stateFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: text/plain'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pedido processado&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subindo com o servidor embutido do PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-S&lt;/span&gt; 0.0.0.0:8001 app.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gerando alguns pedidos e conferindo as métricas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 10&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8001/ &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done
&lt;/span&gt;curl http://localhost:8001/metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O estado é persistido em um arquivo JSON local só para manter o exemplo simples e sem dependências — em uma aplicação real, esses contadores normalmente vivem em memória do próprio processo (e cada worker/processo exporia os seus, deixando a agregação entre réplicas por conta do Prometheus).&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Adicionando a app PHP à stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (trecho adicionado)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;php-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php:8.3-cli&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./app.php:/app/app.php&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-S"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0:8001"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.php"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8001:8001"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prometheus.yml (job adicionado)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php-app"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;php-app:8001"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Consultando um histogram com histogram_quantile
&lt;/h2&gt;

&lt;p&gt;Com dados fluindo, a consulta mais útil sobre um histogram no PromQL é o percentil de latência:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# p95 de tempo de processamento nos ultimos 5 minutos
histogram_quantile(0.95, rate(orders_duration_seconds_bucket[5m]))

# tamanho atual da fila
orders_queue_size

# taxa de pedidos criados por segundo
rate(orders_created_total[1m])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;histogram_quantile&lt;/code&gt; reconstrói o percentil aproximado a partir da distribuição por buckets — quanto mais granulares os buckets ao redor do percentil de interesse, mais precisa a estimativa. É uma aproximação, não um cálculo exato sobre os valores individuais (que o Prometheus nunca armazena um a um), mas suficiente para a grande maioria dos casos de uso de observação de latência em produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Cobrimos os três tipos fundamentais de métrica — counter para contagens cumulativas, gauge para valores que sobem e descem, histogram para distribuições como latência — e construímos uma segunda mini aplicação, agora em PHP puro, expondo métricas de negócio reais. Com duas aplicações diferentes já enviando métricas para o Prometheus, o próximo artigo muda de pilar: como enviar &lt;strong&gt;logs&lt;/strong&gt; dessas mesmas aplicações para o Loki, e por que o modelo de indexação dele é tão diferente do Prometheus.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/concepts/metric_types/" rel="noopener noreferrer"&gt;Prometheus — Metric Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/practices/histograms/" rel="noopener noreferrer"&gt;Prometheus — Histograms and Summaries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile" rel="noopener noreferrer"&gt;PromQL — histogram_quantile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.php.net/manual/en/features.commandline.webserver.php" rel="noopener noreferrer"&gt;PHP Manual — Built-in web server&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>Prometheus na prática - scraping, o endpoint /metrics e uma mini app Python expondo métricas</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:16:08 +0000</pubDate>
      <link>https://dev.to/apsis-cc/prometheus-na-pratica-scraping-o-endpoint-metrics-e-uma-mini-app-python-expondo-metricas-2j08</link>
      <guid>https://dev.to/apsis-cc/prometheus-na-pratica-scraping-o-endpoint-metrics-e-uma-mini-app-python-expondo-metricas-2j08</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: da stack vazia à primeira fonte de dados
&lt;/h2&gt;

&lt;p&gt;No primeiro artigo desta série, subimos Prometheus, Loki e Grafana com Docker Compose, mas sem nenhuma aplicação real enviando dados — o Prometheus só monitorava a si mesmo. Este artigo resolve isso: vamos entender como o Prometheus efetivamente coleta métricas de uma aplicação (scraping) e construir uma mini aplicação em Python puro, sem nenhum framework, que expõe métricas nesse formato.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Pull vs push: o modelo de scraping do Prometheus
&lt;/h2&gt;

&lt;p&gt;Existem duas formas de uma ferramenta de métricas obter dados de uma aplicação:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Push&lt;/strong&gt; — a aplicação envia ativamente os valores para um servidor central sempre que quiser (ou em intervalos definidos por ela mesma). StatsD funciona assim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull (scraping)&lt;/strong&gt; — a aplicação apenas expõe seus valores atuais em um endpoint HTTP, e é o servidor de métricas que periodicamente visita esse endpoint e lê o estado atual. É o modelo do Prometheus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O Prometheus escolheu pull deliberadamente, por algumas razões práticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Descoberta centralizada de falhas&lt;/strong&gt; — se o Prometheus não consegue fazer scraping de um alvo, ele mesmo já sabe e registra isso (métrica &lt;code&gt;up == 0&lt;/code&gt;), sem depender da aplicação perceber que parou de enviar dados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilidade de testar manualmente&lt;/strong&gt; — como o endpoint é só HTTP, dá para abrir &lt;code&gt;/metrics&lt;/code&gt; num navegador ou com &lt;code&gt;curl&lt;/code&gt; e ver exatamente o que será coletado, sem precisar simular o lado do Prometheus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controle central do intervalo de coleta&lt;/strong&gt; — quem decide a frequência é quem consome os dados (o Prometheus), não cada aplicação individualmente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A exceção fica por conta de jobs de curta duração (um script batch que roda e termina em segundos, sem ficar no ar para ser "scrapeado") — para esses casos existe o &lt;strong&gt;Pushgateway&lt;/strong&gt;, um componente auxiliar que aceita push e fica exposto para o Prometheus fazer pull dele. Para aplicações de vida longa como as desta série (servidores HTTP), o modelo pull padrão é suficiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. O formato de exposição: o que vive em /metrics
&lt;/h2&gt;

&lt;p&gt;Um endpoint &lt;code&gt;/metrics&lt;/code&gt; do Prometheus é simplesmente texto plano, em um formato específico chamado &lt;strong&gt;exposition format&lt;/strong&gt;. Cada métrica segue esta estrutura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP app_requests_total Total de requisições recebidas&lt;/span&gt;
&lt;span class="c"&gt;# TYPE app_requests_total counter&lt;/span&gt;
&lt;span class="n"&gt;app_requests_total&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A linha &lt;code&gt;# HELP&lt;/code&gt; é uma descrição legível por humanos (opcional, mas boa prática).&lt;/li&gt;
&lt;li&gt;A linha &lt;code&gt;# TYPE&lt;/code&gt; declara o tipo da métrica — &lt;code&gt;counter&lt;/code&gt;, &lt;code&gt;gauge&lt;/code&gt;, &lt;code&gt;histogram&lt;/code&gt; ou &lt;code&gt;summary&lt;/code&gt; (o artigo 3 desta série detalha cada tipo).&lt;/li&gt;
&lt;li&gt;A linha de dados é &lt;code&gt;nome_da_metrica{labels_opcionais="valor"} valor&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nenhuma biblioteca é estritamente necessária para gerar esse texto — é só formatação de string. Isso torna o formato fácil de implementar em qualquer linguagem, mesmo sem um cliente oficial, o que é exatamente o que esta mini aplicação vai fazer.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Uma mini app em Python puro com http.server
&lt;/h2&gt;

&lt;p&gt;Usando apenas a biblioteca padrão do Python (módulo &lt;code&gt;http.server&lt;/code&gt;, sem Flask, Django ou qualquer outro framework), uma aplicação mínima que conta requisições e expõe essa contagem em &lt;code&gt;/metrics&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;http.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ThreadingHTTPServer&lt;/span&gt;

&lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;request_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;request_count&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;uptime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# HELP app_requests_total Total de requisicoes recebidas&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# TYPE app_requests_total counter&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_requests_total &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# HELP app_uptime_seconds Tempo desde que o processo iniciou&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# TYPE app_uptime_seconds gauge&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_uptime_seconds &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uptime&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/plain; version=0.0.4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;request_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/plain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ola do app em python&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# silencia o log padrao do http.server no stdout
&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ThreadingHTTPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app rodando em http://0.0.0.0:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rodando localmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada requisição a qualquer path diferente de &lt;code&gt;/metrics&lt;/code&gt; incrementa o contador &lt;code&gt;app_requests_total&lt;/code&gt; — importante notar que a própria requisição ao &lt;code&gt;/metrics&lt;/code&gt; &lt;strong&gt;não&lt;/strong&gt; conta como requisição de negócio, propositalmente, para não distorcer a métrica. Testando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/
curl http://localhost:8000/
curl http://localhost:8000/metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A última chamada deve mostrar &lt;code&gt;app_requests_total 2&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Configurando o prometheus.yml para coletar a nova app
&lt;/h2&gt;

&lt;p&gt;Com o endpoint funcionando, o próximo passo é adicionar um &lt;code&gt;job&lt;/code&gt; ao &lt;code&gt;prometheus.yml&lt;/code&gt; do artigo anterior, apontando para essa aplicação:&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="c1"&gt;# prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prometheus"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:9090"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python-app:8000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E incluir a aplicação no &lt;code&gt;docker-compose.yml&lt;/code&gt;, na mesma rede dos outros serviços (por isso o hostname &lt;code&gt;python-app&lt;/code&gt; funciona no target acima — é o nome do serviço no Compose):&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="c1"&gt;# docker-compose.yml (trecho adicionado ao do artigo anterior)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;python-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./app.py:/app/app.py:ro&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.py"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de &lt;code&gt;docker compose up -d&lt;/code&gt;, o Prometheus passa a fazer scraping da aplicação a cada 15 segundos (o &lt;code&gt;scrape_interval&lt;/code&gt; global). Para confirmar que o alvo está saudável, em &lt;code&gt;http://localhost:9090/targets&lt;/code&gt; o job &lt;code&gt;python-app&lt;/code&gt; deve aparecer com estado &lt;code&gt;UP&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Consultando os dados no Prometheus
&lt;/h2&gt;

&lt;p&gt;Com alguns minutos de coleta (gerando tráfego manualmente com &lt;code&gt;curl&lt;/code&gt; em loop, se necessário), já é possível consultar no campo de expressão do Prometheus (&lt;code&gt;http://localhost:9090/graph&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# valor bruto do contador, acumulado desde que o processo subiu
app_requests_total

# taxa de requisições por segundo, calculada sobre a janela de 1 minuto
rate(app_requests_total[1m])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rate()&lt;/code&gt; é a função mais usada sobre contadores no PromQL: como um contador só cresce (e zera se o processo reiniciar), o valor bruto não diz muito sozinho — o que importa é a &lt;strong&gt;velocidade&lt;/strong&gt; de crescimento, que é exatamente o que &lt;code&gt;rate()&lt;/code&gt; calcula, já lidando corretamente com o caso de reinício do contador.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Vimos por que o Prometheus escolhe pull em vez de push, como o formato de exposição em &lt;code&gt;/metrics&lt;/code&gt; é apenas texto simples — implementável sem biblioteca alguma —, construímos uma mini aplicação Python que expõe uma métrica real, e configuramos o Prometheus para coletá-la periodicamente. No próximo artigo, a mini aplicação passa a ser em PHP puro, e o foco vai para os três tipos de métrica — counters, gauges e histograms — aplicados a métricas de negócio, não só contagem de requisições.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/instrumenting/exposition_formats/" rel="noopener noreferrer"&gt;Prometheus — Exposition Formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/" rel="noopener noreferrer"&gt;Prometheus — Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/prometheus/latest/querying/basics/" rel="noopener noreferrer"&gt;Prometheus — Querying Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/http.server.html" rel="noopener noreferrer"&gt;Python Docs — http.server&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>Observabilidade - o que é, métricas vs logs vs traces e a stack Prometheus + Loki + Grafana</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:16:06 +0000</pubDate>
      <link>https://dev.to/apsis-cc/observabilidade-o-que-e-metricas-vs-logs-vs-traces-e-a-stack-prometheus-loki-grafana-2136</link>
      <guid>https://dev.to/apsis-cc/observabilidade-o-que-e-metricas-vs-logs-vs-traces-e-a-stack-prometheus-loki-grafana-2136</guid>
      <description>&lt;h2&gt;
  
  
  1. Por que "monitoramento" não é mais suficiente
&lt;/h2&gt;

&lt;p&gt;Por muito tempo, "monitorar" um sistema significava basicamente uma coisa: configurar alguns alertas (CPU alta, disco cheio, processo caiu) e torcer para que eles cubram os problemas que realmente vão acontecer. Isso funciona enquanto o sistema é simples e as formas de falhar são poucas e conhecidas de antemão.&lt;/p&gt;

&lt;p&gt;O problema aparece quando o sistema cresce: dezenas de serviços, comunicando-se entre si, rodando em múltiplos containers ou máquinas, com falhas que emergem de combinações imprevisíveis de fatores. Nesse cenário, a pergunta deixa de ser "esse alerta específico disparou?" e passa a ser "por que essa requisição específica ficou lenta, agora, nesse serviço, para esse usuário?" — uma pergunta que ninguém pensou em antecipar com um alerta configurado de antemão.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observabilidade&lt;/strong&gt; é a propriedade de um sistema que permite responder a esse tipo de pergunta nova, não prevista, olhando apenas para os dados que ele expõe externamente — sem precisar adicionar instrumentação nova ou fazer deploy de novo código toda vez que surge uma dúvida diferente. Ela se apoia em três tipos de dados, chamados informalmente de "os três pilares": métricas, logs e traces. Esta é a primeira parte de uma série sobre observabilidade que vai construir, artigo a artigo, uma stack completa e funcional com exemplos práticos em Docker Compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Métricas: números agregados ao longo do tempo
&lt;/h2&gt;

&lt;p&gt;Uma &lt;strong&gt;métrica&lt;/strong&gt; é um valor numérico associado a um timestamp, coletado repetidamente ao longo do tempo — uma série temporal. Exemplos: número de requisições por segundo, uso de memória, tempo de resposta médio, número de erros nos últimos 5 minutos.&lt;/p&gt;

&lt;p&gt;Características importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baratas de armazenar em volume e por longo prazo&lt;/strong&gt; — um número por intervalo de tempo ocupa muito menos espaço que um log de texto completo, o que permite guardar meses ou anos de histórico sem custo proibitivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ótimas para alertas e dashboards&lt;/strong&gt; — perguntas como "a taxa de erro passou de 5%?" ou "a latência p99 está subindo?" são naturalmente respondidas por métricas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agregadas, não individuais&lt;/strong&gt; — uma métrica de "tempo de resposta médio" não diz nada sobre qual requisição específica foi lenta, só que, em média, algo mudou. Para investigar o "qual" e o "por quê", é preciso recorrer aos outros dois pilares.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Logs: eventos discretos com contexto rico
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;log&lt;/strong&gt; é um registro textual e datado de um evento específico: uma requisição recebida, um erro capturado, uma conexão de banco aberta. Ao contrário de uma métrica, um log carrega contexto arbitrário — uma mensagem de erro completa, um stack trace, o payload de uma requisição.&lt;/p&gt;

&lt;p&gt;Essa riqueza tem um custo: logs são muito mais caros de armazenar e indexar do que métricas, principalmente se cada linha de log for indexada integralmente (busca full-text). É por isso que ferramentas modernas de log, como o Loki (artigo 4 desta série), tomam uma decisão deliberada de indexar só um conjunto pequeno de labels e tratar o conteúdo da linha como texto não indexado — um equilíbrio diferente do Elasticsearch tradicional, que indexa tudo.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Traces: o caminho de uma requisição entre serviços
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;trace&lt;/strong&gt; captura o caminho completo de uma requisição individual à medida que ela atravessa múltiplos serviços — quanto tempo foi gasto em cada etapa, em qual ordem, e onde exatamente uma falha ou lentidão aconteceu. Um trace é composto de &lt;strong&gt;spans&lt;/strong&gt;: cada span representa uma unidade de trabalho (uma chamada HTTP, uma query de banco, uma chamada RPC), com início, fim e metadados.&lt;/p&gt;

&lt;p&gt;Enquanto métricas respondem "algo mudou?" e logs respondem "o que aconteceu nesse evento específico?", traces respondem "onde exatamente, entre dez serviços, o tempo está sendo gasto para essa requisição em particular?" — uma pergunta que se torna essencial (e quase impossível de responder sem traces) assim que um sistema deixa de ser um monólito. O artigo 5 desta série cobre OpenTelemetry, o padrão atual para gerar traces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requisição do usuário
  │
  ├─ Span: API Gateway (2ms)
  │    │
  │    └─ Span: Serviço de Pedidos (45ms)
  │         │
  │         ├─ Span: Query no banco (38ms)  ← gargalo aqui
  │         └─ Span: Chamada ao Serviço de Estoque (5ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. A stack desta série: Prometheus + Loki + Grafana
&lt;/h2&gt;

&lt;p&gt;Esta série vai construir uma stack de observabilidade mínima, mas funcional, usando três ferramentas open source que se tornaram um padrão de fato:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt; — coleta e armazena métricas, usando um modelo de "scraping" (ele busca ativamente as métricas de cada aplicação, em vez de esperar que a aplicação as envie). Vem com o PromQL, uma linguagem de consulta específica para séries temporais.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loki&lt;/strong&gt; — agregação de logs, criado pela mesma empresa por trás do Grafana, com uma filosofia deliberada de "indexar como o Prometheus": poucos labels indexados, conteúdo da linha buscado por varredura em tempo de consulta. Isso o torna significativamente mais barato de operar que stacks tradicionais baseadas em Elasticsearch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana&lt;/strong&gt; — a camada de visualização, capaz de consultar tanto Prometheus (PromQL) quanto Loki (LogQL) — e, mais adiante nesta série, também dados de tracing — em dashboards unificados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nenhuma das três aparece isolada por acaso: métricas dizem "o quê", logs dizem "por quê" e traces dizem "onde" — e a stack só cumpre seu papel de observabilidade quando as três se completam.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Subindo a versão básica com Docker Compose
&lt;/h2&gt;

&lt;p&gt;Para experimentar a stack sem instalar nada localmente além do Docker, um &lt;code&gt;docker-compose.yml&lt;/code&gt; mínimo já sobe as três ferramentas:&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="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.55.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml:ro&lt;/span&gt;

  &lt;span class="na"&gt;loki&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/loki:3.2.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3100:3100"&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:11.3.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GF_SECURITY_ADMIN_PASSWORD=admin&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;loki&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Prometheus precisa de um arquivo de configuração mínimo para não falhar ao subir — por enquanto, só para ele monitorar a si mesmo:&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="c1"&gt;# prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prometheus"&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:9090"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subindo tudo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Prometheus fica acessível em &lt;code&gt;http://localhost:9090&lt;/code&gt; — vale abrir e rodar uma query como &lt;code&gt;up&lt;/code&gt; no campo de expressão, que deve retornar &lt;code&gt;1&lt;/code&gt; para o próprio Prometheus.&lt;/li&gt;
&lt;li&gt;Loki fica acessível em &lt;code&gt;http://localhost:3100&lt;/code&gt; (sem interface visual própria — ele é consultado através do Grafana ou de sua API HTTP).&lt;/li&gt;
&lt;li&gt;Grafana fica acessível em &lt;code&gt;http://localhost:3000&lt;/code&gt; (login &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;admin&lt;/code&gt;, conforme a variável de ambiente definida acima).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dentro do Grafana, em &lt;strong&gt;Connections → Data sources&lt;/strong&gt;, adicionar o Prometheus (&lt;code&gt;http://prometheus:9090&lt;/code&gt;, usando o nome do serviço do Compose como hostname, já que os containers estão na mesma rede) e o Loki (&lt;code&gt;http://loki:3100&lt;/code&gt;) como fontes de dados. A partir daqui, a stack está de pé — mas ainda vazia, sem nenhuma aplicação real enviando dados.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Vimos o problema que a observabilidade resolve — responder perguntas não antecipadas sobre um sistema em produção —, os papéis complementares de métricas, logs e traces, e subimos a versão mínima da stack desta série: Prometheus, Loki e Grafana rodando via Docker Compose. No próximo artigo, a stack ganha sua primeira fonte de dados real: uma mini aplicação em Python puro que expõe métricas em &lt;code&gt;/metrics&lt;/code&gt;, e o Prometheus configurado para fazer scraping delas.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Prometheus — repositório &lt;a href="https://github.com/prometheus/prometheus" rel="noopener noreferrer"&gt;prometheus/prometheus&lt;/a&gt;, licença Apache 2.0 (convertido para PNG via wsrv.nl)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/docs/introduction/overview/" rel="noopener noreferrer"&gt;Prometheus — Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/loki/latest/" rel="noopener noreferrer"&gt;Grafana Loki — Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/grafana/latest/" rel="noopener noreferrer"&gt;Grafana — Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/concepts/observability-primer/" rel="noopener noreferrer"&gt;OpenTelemetry — Observability Primer&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>observability</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>devops</category>
    </item>
    <item>
      <title>GitOps avançado - automação de deploy, rollback e boas práticas para produção</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:16:05 +0000</pubDate>
      <link>https://dev.to/apsis-cc/gitops-avancado-automacao-de-deploy-rollback-e-boas-praticas-para-producao-5cfa</link>
      <guid>https://dev.to/apsis-cc/gitops-avancado-automacao-de-deploy-rollback-e-boas-praticas-para-producao-5cfa</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: da sincronização manual à confiança em produção
&lt;/h2&gt;

&lt;p&gt;Esta série cobriu, até aqui, o suficiente para operar Kubernetes com GitOps no dia a dia: conceitos fundamentais de Pods, Deployments e Services, &lt;code&gt;kubectl&lt;/code&gt;, namespaces, ConfigMaps e Secrets, e uma introdução ao ArgoCD com uma &lt;code&gt;Application&lt;/code&gt; sincronizando um repositório ao cluster. Este último artigo fecha a lacuna entre "o ArgoCD está instalado" e "confio nesse pipeline para colocar mudanças em produção sem supervisão constante": automação completa de deploy, rollback automático, sync policies mais refinadas e as boas práticas que separam um setup de demonstração de um setup pronto para produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Automatizando o deploy de ponta a ponta
&lt;/h2&gt;

&lt;p&gt;Um pipeline GitOps completo normalmente combina CI (que constrói e testa a aplicação) com CD via ArgoCD (que aplica o resultado ao cluster). O CI não aplica nada diretamente ao cluster — sua única responsabilidade é atualizar o repositório de manifests com a nova versão, deixando o ArgoCD detectar e aplicar a mudança:&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="c1"&gt;# .github/workflows/deploy.yml&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;build-and-release&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Build e push da imagem&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker build -t registry.exemplo.com/minha-app:${{ github.sha }} .&lt;/span&gt;
          &lt;span class="s"&gt;docker push registry.exemplo.com/minha-app:${{ github.sha }}&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;Atualizar tag da imagem no repositório de manifests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git clone https://github.com/minha-org/minha-app-manifests.git&lt;/span&gt;
          &lt;span class="s"&gt;cd minha-app-manifests&lt;/span&gt;
          &lt;span class="s"&gt;kustomize edit set image minha-app=registry.exemplo.com/minha-app:${{ github.sha }}&lt;/span&gt;
          &lt;span class="s"&gt;git commit -am "deploy: minha-app@${{ github.sha }}"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse último passo — um commit automatizado no repositório de manifests, trocando a tag da imagem — é o gatilho que faz o ArgoCD (com &lt;code&gt;syncPolicy.automated&lt;/code&gt; configurado, como visto no artigo anterior) detectar a diferença e aplicar o rollout automaticamente, sem que o pipeline de CI precise de nenhuma credencial de acesso ao cluster. Essa separação — CI só escreve no Git, só o ArgoCD escreve no cluster — é o núcleo do que torna GitOps mais seguro que um pipeline de CI tradicional com &lt;code&gt;kubectl apply&lt;/code&gt; direto.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Rollback automático
&lt;/h2&gt;

&lt;p&gt;Um dos ganhos mais concretos de GitOps aparece quando um deploy dá errado. Como cada mudança é um commit, reverter um deploy problemático é, em princípio, tão simples quanto reverter o commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Reverter o último commit que trocou a tag da imagem&lt;/span&gt;
git revert HEAD
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O ArgoCD detecta essa reversão como qualquer outra mudança no repositório e aplica automaticamente, voltando o cluster para o estado anterior. Isso pode (e deve) ser combinado com verificações automáticas de saúde, para que o rollback aconteça sem esperar alguém perceber manualmente que algo quebrou:&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;minha-app&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="c1"&gt;# ...&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;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
        &lt;span class="na"&gt;factor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
        &lt;span class="na"&gt;maxDuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O bloco &lt;code&gt;retry&lt;/code&gt; faz o ArgoCD tentar novamente uma sincronização que falhou, com backoff exponencial — útil para falhas transitórias (um &lt;code&gt;readiness probe&lt;/code&gt; que demora um pouco mais que o normal, por exemplo) sem exigir intervenção manual. Para rollback automático baseado na saúde real da aplicação (não só no sucesso da sincronização), ferramentas complementares como o Argo Rollouts adicionam estratégias de deploy progressivo — canary ou blue-green — que promovem ou revertem uma nova versão automaticamente com base em métricas (taxa de erro, latência) coletadas durante o rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Sync policies mais refinadas
&lt;/h2&gt;

&lt;p&gt;Nem toda mudança deveria ser sincronizada automaticamente e sem revisão. O ArgoCD permite refinar isso por &lt;code&gt;Application&lt;/code&gt;:&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;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;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ApplyOutOfSyncOnly=true&lt;/span&gt;
  &lt;span class="na"&gt;ignoreDifferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps&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;Deployment&lt;/span&gt;
      &lt;span class="na"&gt;jsonPointers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/spec/replicas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CreateNamespace=true&lt;/code&gt; cria o namespace de destino automaticamente, se ainda não existir — evita um passo manual de setup antes do primeiro deploy de uma aplicação nova.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ApplyOutOfSyncOnly=true&lt;/code&gt; aplica só os recursos que de fato divergem do Git, reduzindo o "ruído" de reaplicar tudo a cada sincronização.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ignoreDifferences&lt;/code&gt; diz ao ArgoCD para ignorar divergências em um campo específico — no exemplo, &lt;code&gt;spec/replicas&lt;/code&gt; de Deployments, útil quando um Horizontal Pod Autoscaler (HPA) ajusta o número de réplicas dinamicamente e não se quer que o ArgoCD reverta esse ajuste para o valor fixo declarado no Git.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Em ambientes com múltiplos times, é comum também restringir permissões via &lt;code&gt;AppProject&lt;/code&gt; do ArgoCD — limitando quais repositórios, clusters e namespaces cada &lt;code&gt;Application&lt;/code&gt; pode usar como fonte ou destino, em vez de dar acesso irrestrito a todo o cluster para qualquer &lt;code&gt;Application&lt;/code&gt; criada.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Boas práticas para produção
&lt;/h2&gt;

&lt;p&gt;Algumas práticas que separam um cluster GitOps de demonstração de um pronto para produção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nunca commitar Secrets em texto plano&lt;/strong&gt; — retomando o ponto levantado no Artigo 2 desta série: use Sealed Secrets, SOPS ou integração com um cofre externo (Vault, AWS Secrets Manager) para versionar segredos com segurança dentro do fluxo GitOps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separar repositórios de aplicação e de manifests&lt;/strong&gt; — o repositório com o código-fonte da aplicação e o repositório com os manifests Kubernetes costumam ser diferentes, cada um com seu próprio controle de acesso; isso também evita que um push no código dispare acidentalmente uma sincronização do ArgoCD antes de a imagem estar pronta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revisão obrigatória para produção&lt;/strong&gt; — mesmo com sincronização automática ligada, proteger a branch/pasta que aponta para produção com exigência de pull request revisado, para que nenhuma mudança chegue ao cluster sem pelo menos um segundo par de olhos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitorar o próprio ArgoCD&lt;/strong&gt; — um controlador GitOps fora do ar (ou com a sincronização travada) significa que mudanças urgentes, incluindo rollbacks, não chegam ao cluster; alertas sobre a saúde do ArgoCD são tão importantes quanto alertas sobre a aplicação em si.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Começar com sync manual, migrar para automático aos poucos&lt;/strong&gt; — para aplicações críticas, é razoável começar sem &lt;code&gt;syncPolicy.automated&lt;/code&gt;, validar o processo com sincronizações manuais revisadas, e só ligar a automação depois que o time confia no pipeline de CI e nos testes que o precedem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Conclusão da série
&lt;/h2&gt;

&lt;p&gt;Ao longo desses quatro artigos, esta série foi do "o que é um Pod" a um pipeline GitOps completo: os conceitos fundamentais do Kubernetes (Pods, Deployments, Services), o &lt;code&gt;kubectl&lt;/code&gt; e a organização de recursos com namespaces, ConfigMaps e Secrets, a introdução ao GitOps e ao ArgoCD, e finalmente automação de deploy de ponta a ponta, rollback automático via Git e as boas práticas que tornam esse fluxo seguro o suficiente para produção. A partir daqui, a base está posta para explorar tópicos mais específicos — deploys progressivos com Argo Rollouts, gestão de segredos com Sealed Secrets ou Vault, observabilidade de clusters — mas o par Kubernetes + GitOps, bem implementado, já resolve a maior parte do desafio de operar aplicações em produção com confiança e rastreabilidade.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Kubernetes — repositório &lt;a href="https://github.com/kubernetes/kubernetes/tree/master/logo" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/" rel="noopener noreferrer"&gt;ArgoCD Documentation — Sync Policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-rollouts.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;Argo Rollouts Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitnami-labs/sealed-secrets" rel="noopener noreferrer"&gt;Bitnami Sealed Secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps — Princípios de GitOps&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gitops</category>
      <category>kubernetes</category>
      <category>argocd</category>
      <category>devops</category>
    </item>
    <item>
      <title>GitOps na prática - ArgoCD e estrutura de repositório</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:16:04 +0000</pubDate>
      <link>https://dev.to/apsis-cc/gitops-na-pratica-argocd-e-estrutura-de-repositorio-28j5</link>
      <guid>https://dev.to/apsis-cc/gitops-na-pratica-argocd-e-estrutura-de-repositorio-28j5</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: do &lt;code&gt;kubectl apply&lt;/code&gt; manual a um fluxo automatizado
&lt;/h2&gt;

&lt;p&gt;Nas duas primeiras partes desta série, todo Deployment foi aplicado com &lt;code&gt;kubectl apply -f arquivo.yaml&lt;/code&gt;, rodado à mão a partir da máquina de quem estava operando o cluster. Isso funciona para aprender e para clusters pequenos, mas expõe problemas reais em produção: não há registro confiável de quem aplicou o quê e quando, é fácil um manifesto aplicado manualmente divergir silenciosamente do que está versionado no Git, e cada pessoa do time precisa de acesso direto (e credenciais) ao cluster para fazer deploy. Este artigo introduz &lt;strong&gt;GitOps&lt;/strong&gt;, a prática que resolve exatamente isso, e o ArgoCD, uma das ferramentas mais usadas para implementá-la.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. O que é GitOps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitOps&lt;/strong&gt; é a aplicação da ideia de infraestrutura como código a um extremo específico: o &lt;strong&gt;Git como única fonte de verdade&lt;/strong&gt; do estado desejado de um cluster Kubernetes, com um agente rodando dentro do próprio cluster que observa o repositório continuamente e aplica qualquer mudança automaticamente — sem que ninguém precise rodar &lt;code&gt;kubectl apply&lt;/code&gt; manualmente.&lt;/p&gt;

&lt;p&gt;O fluxo típico é:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Alguém abre um pull request alterando um manifesto YAML (por exemplo, mudando &lt;code&gt;replicas: 2&lt;/code&gt; para &lt;code&gt;replicas: 4&lt;/code&gt;, ou atualizando a tag de uma imagem).&lt;/li&gt;
&lt;li&gt;O PR passa por revisão e CI, como qualquer outra mudança de código.&lt;/li&gt;
&lt;li&gt;Ao ser mesclado (&lt;code&gt;merge&lt;/code&gt;) na branch principal, um agente GitOps rodando no cluster detecta a diferença entre o que está no Git e o que está rodando, e aplica a mudança automaticamente.&lt;/li&gt;
&lt;li&gt;O cluster converge para o estado descrito no repositório — sem que ninguém tenha rodado um comando manual contra o cluster.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pull Request → Review/CI → Merge no Git
                                  │
                     (agente GitOps observa o repo)
                                  │
                                  ▼
                    Cluster Kubernetes converge
                    automaticamente para o estado
                          descrito no Git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Por que isso faz sentido em infraestrutura como código
&lt;/h2&gt;

&lt;p&gt;Comparado a aplicar manifestos manualmente (mesmo que a partir de um pipeline de CI que roda &lt;code&gt;kubectl apply&lt;/code&gt;), GitOps traz três ganhos concretos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auditoria natural:&lt;/strong&gt; o histórico do Git — quem propôs a mudança, quem revisou, quando foi mesclada — já é o registro de auditoria de tudo que aconteceu no cluster, sem precisar de uma ferramenta separada de log de mudanças.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconciliação contínua:&lt;/strong&gt; o agente GitOps não só aplica mudanças, mas continua comparando o estado real do cluster com o Git em intervalos regulares. Se alguém alterar algo manualmente no cluster (um &lt;code&gt;kubectl edit&lt;/code&gt; de emergência, por exemplo), o agente detecta a divergência e pode reverter automaticamente para o que está declarado no Git — ou pelo menos alertar que há um "drift".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Menos acesso direto ao cluster:&lt;/strong&gt; times não precisam mais de credenciais de &lt;code&gt;kubectl&lt;/code&gt; com permissão de escrita no cluster de produção para fazer deploy — a mudança passa pelo Git, e só o agente GitOps (com suas próprias credenciais, geridas separadamente) tem acesso de escrita direto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso é uma extensão natural do que a série já vinha construindo: Deployments e Services declarativos (Artigo 1), organizados com namespaces e configuração externa via ConfigMaps/Secrets (Artigo 2) — GitOps apenas formaliza que a fonte de verdade desses YAMLs é um repositório Git, e que a aplicação ao cluster é automática, não manual.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Introdução ao ArgoCD
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;ArgoCD&lt;/strong&gt; é um dos controladores GitOps mais usados para Kubernetes. Ele roda como uma aplicação dentro do próprio cluster e introduz um novo tipo de recurso, a &lt;code&gt;Application&lt;/code&gt;, que aponta para um caminho em um repositório Git e para um cluster/namespace de destino.&lt;/p&gt;

&lt;p&gt;Instalação básica em um cluster (local, como o criado com Minikube/Kind no Artigo 1, ou um cluster real):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de instalado, uma &lt;code&gt;Application&lt;/code&gt; do ArgoCD conecta um repositório a um destino no cluster:&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;minha-app&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;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&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/minha-org/minha-app-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;k8s/producao&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;minha-app&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;source&lt;/code&gt; diz &lt;strong&gt;onde&lt;/strong&gt; está o estado desejado: repositório, branch/tag e caminho dentro do repo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destination&lt;/code&gt; diz &lt;strong&gt;onde aplicar&lt;/strong&gt;: qual cluster (&lt;code&gt;https://kubernetes.default.svc&lt;/code&gt; é o próprio cluster onde o ArgoCD roda) e namespace.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;syncPolicy.automated&lt;/code&gt; liga a sincronização automática: &lt;code&gt;selfHeal: true&lt;/code&gt; faz o ArgoCD reverter mudanças manuais feitas fora do Git (o "drift" mencionado acima); &lt;code&gt;prune: true&lt;/code&gt; remove do cluster recursos que foram removidos do repositório.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sem &lt;code&gt;syncPolicy.automated&lt;/code&gt;, o ArgoCD ainda detecta divergências e mostra o diff entre Git e cluster, mas espera uma sincronização manual (via UI ou &lt;code&gt;argocd app sync&lt;/code&gt;) — uma opção mais conservadora para começar, antes de confiar totalmente no modo automático.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Alternativa: Flux
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;Flux&lt;/strong&gt; é a alternativa mais usada ao ArgoCD, com a mesma proposta — observar um repositório Git e reconciliar o cluster com o que está nele — mas com uma filosofia mais "nativa" ao modelo de controladores do Kubernetes (sem uma UI própria tão robusta quanto a do ArgoCD, focado em CRDs e composição via &lt;code&gt;kustomize&lt;/code&gt;). Este artigo usa o ArgoCD como exemplo principal por ter a curva de entrada mais suave (UI web incluída), mas os conceitos de GitOps — Git como fonte de verdade, reconciliação automática, sync policies — se aplicam igualmente às duas ferramentas, e a escolha entre elas costuma depender mais de preferência de equipe do que de uma diferença técnica decisiva.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Estruturando um repositório de manifests
&lt;/h2&gt;

&lt;p&gt;Uma dúvida comum ao adotar GitOps é como organizar o repositório de manifests. Uma estrutura comum, que separa aplicações e ambientes:&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="s"&gt;minha-app-manifests/&lt;/span&gt;
&lt;span class="s"&gt;├── base/&lt;/span&gt;
&lt;span class="s"&gt;│   ├── deployment.yaml&lt;/span&gt;
&lt;span class="s"&gt;│   ├── service.yaml&lt;/span&gt;
&lt;span class="s"&gt;│   └── kustomization.yaml&lt;/span&gt;
&lt;span class="s"&gt;└── overlays/&lt;/span&gt;
    &lt;span class="s"&gt;├── dev/&lt;/span&gt;
    &lt;span class="s"&gt;│   ├── kustomization.yaml&lt;/span&gt;
    &lt;span class="s"&gt;│   └── patch-replicas.yaml&lt;/span&gt;
    &lt;span class="s"&gt;├── staging/&lt;/span&gt;
    &lt;span class="s"&gt;│   └── kustomization.yaml&lt;/span&gt;
    &lt;span class="s"&gt;└── producao/&lt;/span&gt;
        &lt;span class="s"&gt;├── kustomization.yaml&lt;/span&gt;
        &lt;span class="s"&gt;└── patch-replicas.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa estrutura usa &lt;code&gt;kustomize&lt;/code&gt; (embutido no &lt;code&gt;kubectl&lt;/code&gt; e nativamente suportado por ArgoCD e Flux): a pasta &lt;code&gt;base/&lt;/code&gt; tem os manifestos comuns, e cada &lt;code&gt;overlay&lt;/code&gt; aplica só as diferenças daquele ambiente (número de réplicas, limites de recursos, variáveis específicas) por cima da base, evitando duplicar o YAML inteiro para cada ambiente. Uma &lt;code&gt;Application&lt;/code&gt; do ArgoCD apontando para &lt;code&gt;overlays/producao&lt;/code&gt; (como no exemplo da seção anterior) sempre aplica a base mais os ajustes específicos de produção.&lt;/p&gt;

&lt;p&gt;Para projetos que usam Helm em vez de manifestos YAML puros, o mesmo princípio se aplica: o repositório versiona o &lt;code&gt;values.yaml&lt;/code&gt; de cada ambiente, e a &lt;code&gt;Application&lt;/code&gt; do ArgoCD aponta para o chart e para o arquivo de valores correspondente ao ambiente de destino.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Neste artigo, vimos o que é GitOps e por que ele resolve problemas reais de auditoria, drift de configuração e acesso direto ao cluster que o &lt;code&gt;kubectl apply&lt;/code&gt; manual não resolve, uma introdução prática ao ArgoCD com uma &lt;code&gt;Application&lt;/code&gt; real, uma menção ao Flux como alternativa, e uma forma comum de estruturar um repositório de manifests com &lt;code&gt;kustomize&lt;/code&gt; para múltiplos ambientes. No próximo e último artigo desta série, os exemplos ficam mais avançados: automação completa de deploy com GitOps, rollback automático, sync policies mais refinadas e boas práticas para rodar isso tudo com confiança em produção.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Kubernetes — repositório &lt;a href="https://github.com/kubernetes/kubernetes/tree/master/logo" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;ArgoCD Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluxcd.io/flux/" rel="noopener noreferrer"&gt;Flux Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps — Princípios de GitOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/" rel="noopener noreferrer"&gt;Kustomize Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gitops</category>
      <category>kubernetes</category>
      <category>argocd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Kubernetes no dia a dia - kubectl, Namespaces, ConfigMaps e Secrets</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:16:03 +0000</pubDate>
      <link>https://dev.to/apsis-cc/kubernetes-no-dia-a-dia-kubectl-namespaces-configmaps-e-secrets-gd6</link>
      <guid>https://dev.to/apsis-cc/kubernetes-no-dia-a-dia-kubectl-namespaces-configmaps-e-secrets-gd6</guid>
      <description>&lt;h2&gt;
  
  
  1. Retomando: de conceitos a comandos
&lt;/h2&gt;

&lt;p&gt;Na primeira parte desta série vimos o que é o Kubernetes, o problema que ele resolve e os três conceitos fundamentais — Pods, Deployments e Services — aplicando-os em um Deployment mínimo de Nginx. Agora que a base teórica está posta, o foco deste artigo é prático: o &lt;code&gt;kubectl&lt;/code&gt; no dia a dia, como organizar recursos com namespaces, como gerenciar configuração e segredos com ConfigMaps e Secrets, e um primeiro deploy de uma aplicação real (não só um Nginx de exemplo).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;kubectl&lt;/code&gt; além do &lt;code&gt;apply&lt;/code&gt; e do &lt;code&gt;get&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O artigo anterior já usou &lt;code&gt;kubectl apply&lt;/code&gt; e &lt;code&gt;kubectl get&lt;/code&gt;. No dia a dia, alguns outros comandos aparecem o tempo todo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Descrever um recurso em detalhes (eventos, status, configuração completa)&lt;/span&gt;
kubectl describe pod nginx-7d9f8c6b4-x2z9k

&lt;span class="c"&gt;# Ver os logs de um container dentro de um Pod&lt;/span&gt;
kubectl logs nginx-7d9f8c6b4-x2z9k
kubectl logs &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-7d9f8c6b4-x2z9k    &lt;span class="c"&gt;# segue o log em tempo real&lt;/span&gt;

&lt;span class="c"&gt;# Abrir um shell dentro de um container em execução&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; nginx-7d9f8c6b4-x2z9k &lt;span class="nt"&gt;--&lt;/span&gt; bash

&lt;span class="c"&gt;# Remover um recurso&lt;/span&gt;
kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml

&lt;span class="c"&gt;# Encaminhar uma porta local para um Pod, sem precisar expor um Service externo&lt;/span&gt;
kubectl port-forward pod/nginx-7d9f8c6b4-x2z9k 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl describe&lt;/code&gt; costuma ser o primeiro comando rodado ao investigar um Pod que não sobe como esperado — a seção &lt;code&gt;Events&lt;/code&gt; no final da saída normalmente aponta a causa (imagem não encontrada, falta de recursos no cluster, falha no &lt;code&gt;readiness probe&lt;/code&gt;, etc.) antes mesmo de olhar os logs da aplicação.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Organizando recursos com Namespaces
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;namespace&lt;/strong&gt; é uma forma de dividir um cluster em espaços lógicos isolados — times, ambientes (&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;) ou aplicações diferentes podem viver em namespaces separados, com nomes de recursos que não colidem entre si e (com as políticas certas) sem visibilidade de rede umas nas outras.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Criar um namespace&lt;/span&gt;
kubectl create namespace minha-app

&lt;span class="c"&gt;# Listar todos os namespaces do cluster&lt;/span&gt;
kubectl get namespaces

&lt;span class="c"&gt;# Aplicar um manifesto em um namespace específico&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml &lt;span class="nt"&gt;-n&lt;/span&gt; minha-app

&lt;span class="c"&gt;# Listar recursos de um namespace específico&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; minha-app

&lt;span class="c"&gt;# Listar recursos de TODOS os namespaces&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem indicar &lt;code&gt;-n&lt;/code&gt;, todo comando &lt;code&gt;kubectl&lt;/code&gt; opera no namespace &lt;code&gt;default&lt;/code&gt; — o que funciona para experimentos, mas rapidamente vira confusão em um cluster real com várias aplicações. A prática recomendada é sempre criar um namespace por aplicação ou por time, e declará-lo explicitamente nos manifestos:&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;apps/v1&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;Deployment&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;api&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;minha-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. ConfigMaps: configuração fora da imagem
&lt;/h2&gt;

&lt;p&gt;Uma boa prática de containers (reforçada também na série de Docker deste blog) é nunca hardcodar configuração dentro da imagem — ela deve poder rodar em qualquer ambiente, recebendo a configuração de fora. O &lt;strong&gt;ConfigMap&lt;/strong&gt; é o objeto do Kubernetes para isso: um conjunto de pares chave-valor não sensíveis, injetados em Pods como variáveis de ambiente ou arquivos.&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;v1&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;ConfigMap&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;api-config&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;minha-app&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info"&lt;/span&gt;
  &lt;span class="na"&gt;FEATURE_FLAG_NOVO_CHECKOUT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consumindo o ConfigMap em um Deployment, como variáveis de ambiente:&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;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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;api&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minha-app/api:1.0&lt;/span&gt;
      &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&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;api-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Secrets: configuração sensível
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Secrets&lt;/strong&gt; têm a mesma forma de uso que ConfigMaps, mas são destinados a dados sensíveis — senhas, tokens, chaves de API. A diferença mais importante não é técnica (por padrão, o conteúdo de um Secret só é codificado em base64, não criptografado) e sim de &lt;strong&gt;intenção&lt;/strong&gt;: o Kubernetes trata Secrets de forma diferente em alguns pontos (não fica visível por padrão em &lt;code&gt;kubectl describe&lt;/code&gt;, por exemplo), e é o objeto certo para integrar com soluções de criptografia em repouso ou cofres externos (Vault, AWS Secrets Manager) em clusters de produção.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Criar um Secret diretamente pela CLI, sem escrever a senha em um YAML versionado&lt;/span&gt;
kubectl create secret generic api-db-credentials &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'troque-por-um-valor-real'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; minha-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consumindo o Secret, também como variáveis de ambiente:&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;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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;api&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minha-app/api:1.0&lt;/span&gt;
      &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&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;api-config&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&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;api-db-credentials&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um ponto importante — e que volta com força no Artigo 3 desta série, sobre GitOps: &lt;strong&gt;nunca commitar um Secret com valores reais em texto plano em um repositório Git&lt;/strong&gt;, nem mesmo privado. Ferramentas como Sealed Secrets ou integrações com cofres externos existem exatamente para permitir versionar segredos com segurança dentro de um fluxo GitOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Um deploy real: API com configuração e banco
&lt;/h2&gt;

&lt;p&gt;Juntando os conceitos, um exemplo mais próximo de uma aplicação real: uma API que lê configuração de um ConfigMap, credenciais de um Secret, e é exposta dentro do cluster por um Service:&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;apps/v1&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;Deployment&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;api&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;minha-app&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&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;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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;api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minha-app/api:1.0&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&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;api-config&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&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;api-db-credentials&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&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;Service&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;api&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;minha-app&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;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; api-deployment.yaml
kubectl rollout status deployment/api &lt;span class="nt"&gt;-n&lt;/span&gt; minha-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl rollout status&lt;/code&gt; acompanha o deploy até todas as réplicas ficarem prontas (ou aponta o erro, se alguma falhar) — útil tanto em terminal manual quanto como passo de verificação em um pipeline de CI/CD.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Com &lt;code&gt;kubectl describe&lt;/code&gt;/&lt;code&gt;logs&lt;/code&gt;/&lt;code&gt;exec&lt;/code&gt; para investigar, namespaces para organizar, e ConfigMaps/Secrets para separar configuração do código da aplicação, já é possível fazer deploys reais de forma organizada em um cluster Kubernetes. Até aqui, porém, todo &lt;code&gt;kubectl apply&lt;/code&gt; foi rodado à mão, a partir da máquina de quem está operando o cluster — o que não escala bem para times e não deixa rastro confiável do que foi aplicado e quando. No próximo artigo, a série entra em GitOps: o que é, por que faz sentido tratar infraestrutura como código versionado, e uma introdução ao ArgoCD para aplicar automaticamente o que está em um repositório Git ao cluster.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Kubernetes — repositório &lt;a href="https://github.com/kubernetes/kubernetes/tree/master/logo" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/kubectl/" rel="noopener noreferrer"&gt;Kubernetes Documentation — kubectl Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" rel="noopener noreferrer"&gt;Kubernetes Documentation — Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/configuration/configmap/" rel="noopener noreferrer"&gt;Kubernetes Documentation — ConfigMaps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/" rel="noopener noreferrer"&gt;Kubernetes Documentation — Secrets&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>kubectl</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Kubernetes - o que é, para que serve e conceitos iniciais</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Mon, 21 Sep 2026 03:16:02 +0000</pubDate>
      <link>https://dev.to/apsis-cc/kubernetes-o-que-e-para-que-serve-e-conceitos-iniciais-mm9</link>
      <guid>https://dev.to/apsis-cc/kubernetes-o-que-e-para-que-serve-e-conceitos-iniciais-mm9</guid>
      <description>&lt;h2&gt;
  
  
  1. O problema que o Kubernetes resolve
&lt;/h2&gt;

&lt;p&gt;Rodar um container isolado, à mão, com &lt;code&gt;docker run&lt;/code&gt;, resolve bem o problema de empacotar e distribuir uma aplicação. Mas assim que essa aplicação precisa rodar em produção — com múltiplas réplicas, tolerância a falhas, atualizações sem downtime e escalando conforme a demanda — o "container solto" vira um problema de gestão manual: quem reinicia um container que caiu? Quem distribui a carga entre várias instâncias? Quem decide em qual máquina cada container roda quando há dezenas de servidores disponíveis? Scripts caseiros de &lt;code&gt;docker run&lt;/code&gt; e &lt;code&gt;cron&lt;/code&gt; para verificar se o processo ainda está vivo resolvem por um tempo, mas não escalam além de poucos serviços.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Kubernetes&lt;/strong&gt; (às vezes abreviado como &lt;code&gt;k8s&lt;/code&gt;) é um orquestrador de containers: um sistema que recebe uma descrição de "o que" deve estar rodando (quantas réplicas, quanto de CPU/memória, quais portas expor) e se encarrega do "como" — decide em qual máquina cada container roda, reinicia o que falhar, distribui tráfego entre réplicas saudáveis e reagenda containers automaticamente se um servidor inteiro cair. Esta é a primeira parte de uma série que vai do zero ao avançado em Kubernetes e GitOps: hoje o foco é entender o problema que o Kubernetes resolve e os conceitos iniciais que sustentam tudo o que vem depois.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Containers soltos e VMs vs orquestração
&lt;/h2&gt;

&lt;p&gt;Comparado a rodar containers "soltos" (um &lt;code&gt;docker run&lt;/code&gt; por servidor, gerenciado manualmente ou por scripts), o Kubernetes automatiza justamente o que se torna inviável de fazer à mão em escala:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-recuperação (self-healing):&lt;/strong&gt; se um container trava ou o processo morre, o Kubernetes detecta e sobe um novo automaticamente — sem intervenção humana e, idealmente, sem impacto perceptível para quem usa a aplicação.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalonamento:&lt;/strong&gt; aumentar de 2 para 10 réplicas de uma aplicação é uma mudança de configuração (ou automática, via métricas de uso), não um processo manual de logar em servidores e rodar comandos um por um.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribuição de carga:&lt;/strong&gt; o Kubernetes decide em qual máquina (&lt;code&gt;node&lt;/code&gt;) cada container roda, considerando recursos disponíveis, e distribui tráfego entre as réplicas saudáveis de um serviço automaticamente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atualizações sem downtime:&lt;/strong&gt; trocar a versão de uma aplicação pode ser feito substituindo réplicas gradualmente (rolling update), mantendo o serviço no ar durante todo o processo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparado a &lt;strong&gt;máquinas virtuais&lt;/strong&gt; geridas manualmente (uma aplicação por VM, escalada criando mais VMs), o ganho é semelhante ao que containers já trazem sobre VMs isoladamente: densidade muito maior (várias aplicações compartilhando os mesmos servidores físicos com isolamento adequado) e inicialização em segundos em vez de minutos — mas agora com um sistema que decide automaticamente onde cada carga roda, em vez de alguém escolher manualmente em qual VM cada aplicação vai.&lt;/p&gt;

&lt;p&gt;Isso não significa que toda aplicação precisa de Kubernetes — um sistema pequeno, com um ou dois serviços e baixa necessidade de escala, pode viver tranquilamente com &lt;code&gt;docker run&lt;/code&gt; bem configurado ou Docker Compose (veja a série sobre Docker deste blog). O Kubernetes compensa o esforço quando a quantidade de serviços, a necessidade de alta disponibilidade ou a variação de carga tornam a gestão manual inviável.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Conceitos fundamentais: Pods, Deployments e Services
&lt;/h2&gt;

&lt;p&gt;O Kubernetes organiza tudo em torno de &lt;strong&gt;objetos&lt;/strong&gt; descritos declarativamente (normalmente em YAML), que dizem ao cluster o estado desejado. Três deles aparecem em praticamente toda aplicação:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pod:&lt;/strong&gt; a menor unidade que o Kubernetes gerencia diretamente. Um Pod contém um ou mais containers que compartilham rede e armazenamento — na prática, a grande maioria dos Pods tem exatamente um container, com containers adicionais reservados para casos específicos (como um "sidecar" que coleta logs). Pods são efêmeros: quando um Pod morre, ele não é "recriado" no sentido de reviver o mesmo Pod — um novo Pod é criado do zero em seu lugar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; descreve &lt;strong&gt;quantas réplicas&lt;/strong&gt; de um Pod devem existir e como atualizá-las. É o Deployment que garante que, se um Pod morrer, outro toma seu lugar automaticamente, e que gerencia rolling updates ao trocar a versão de uma imagem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service:&lt;/strong&gt; como Pods são efêmeros e ganham um IP novo cada vez que são recriados, não é possível apontar diretamente para o IP de um Pod de forma confiável. Um Service resolve isso expondo um endereço estável (nome DNS interno e IP fixo dentro do cluster) que distribui tráfego entre todos os Pods saudáveis que casam com um determinado rótulo (&lt;code&gt;label&lt;/code&gt;) — independentemente de quantos Pods existem ou de terem sido recriados.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deployment "api"
  ├── garante 3 réplicas do Pod
  ├── Pod (réplica 1) ── container: api:1.2
  ├── Pod (réplica 2) ── container: api:1.2
  └── Pod (réplica 3) ── container: api:1.2

Service "api" (IP estável, DNS interno "api")
  └── distribui tráfego entre as 3 réplicas do Pod acima
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa relação — Deployment garantindo réplicas de Pods, Service expondo um endereço estável para eles — é o alicerce sobre o qual praticamente todo o resto do Kubernetes (ConfigMaps, Secrets, Ingress, volumes, e os conceitos de GitOps discutidos mais adiante nesta série) se apoia.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Experimentando localmente com Minikube ou Kind
&lt;/h2&gt;

&lt;p&gt;Não é preciso um cluster de produção para começar a aprender Kubernetes. Duas ferramentas populares criam um cluster completo rodando localmente, dentro do Docker ou de uma VM leve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Minikube — cria um cluster local em uma VM/container&lt;/span&gt;
minikube start

&lt;span class="c"&gt;# Kind (Kubernetes in Docker) — cria o cluster inteiro dentro de containers Docker&lt;/span&gt;
kind create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ambas resultam em um cluster funcional acessível pelo &lt;code&gt;kubectl&lt;/code&gt; (a CLI oficial do Kubernetes, assunto completo do próximo artigo desta série). Para confirmar que o cluster está de pé:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl cluster-info
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O segundo comando lista os &lt;code&gt;nodes&lt;/code&gt; (máquinas, reais ou virtuais, que compõem o cluster) — em um cluster local, normalmente apenas um node fazendo o papel de servidor e worker ao mesmo tempo.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Um primeiro deploy na prática
&lt;/h2&gt;

&lt;p&gt;Com o cluster local de pé, um Deployment mínimo já ilustra os três conceitos vistos acima. Em um arquivo &lt;code&gt;nginx-deployment.yaml&lt;/code&gt;:&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;apps/v1&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;Deployment&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;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="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&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;template&lt;/span&gt;&lt;span class="pi"&gt;:&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.27&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&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;Service&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&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;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aplicando ao cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml
kubectl get pods
kubectl get deployments
kubectl get services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O primeiro comando (&lt;code&gt;kubectl apply&lt;/code&gt;) é a forma declarativa de trabalhar com Kubernetes: em vez de dizer passo a passo o que fazer, descreve-se o estado desejado (três réplicas do Nginx, expostas por um Service) e o Kubernetes se encarrega de fazer a realidade convergir para essa descrição — criando os três Pods, e mantendo-os assim mesmo que algum caia. Esse modelo declarativo, aplicado a partir de arquivos versionados em um repositório Git, é exatamente a ideia que a série vai aprofundar no Artigo 3, ao introduzir GitOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Conclusão e próximos passos
&lt;/h2&gt;

&lt;p&gt;Nesta primeira parte, vimos o problema real que o Kubernetes resolve — orquestrar containers em escala, algo inviável de gerenciar manualmente —, como ele se compara a containers soltos e VMs geridas manualmente, os três conceitos que sustentam qualquer aplicação no cluster (Pods, Deployments e Services) e subimos um primeiro Deployment de ponta a ponta em um cluster local. No próximo artigo, o foco vai para o &lt;code&gt;kubectl&lt;/code&gt; no dia a dia: comandos essenciais, organização de recursos com namespaces, ConfigMaps e Secrets, e os primeiros deploys de aplicações reais.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Kubernetes — repositório &lt;a href="https://github.com/kubernetes/kubernetes/tree/master/logo" rel="noopener noreferrer"&gt;kubernetes/kubernetes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/" rel="noopener noreferrer"&gt;Kubernetes Documentation — Concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/" rel="noopener noreferrer"&gt;Kubernetes Documentation — Pods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Kubernetes Documentation — Deployments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/" rel="noopener noreferrer"&gt;Kubernetes Documentation — Service&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>containers</category>
      <category>devops</category>
      <category>orchestration</category>
    </item>
    <item>
      <title>IaC além do Terraform - testando infraestrutura como código</title>
      <dc:creator>Rafael Dutra</dc:creator>
      <pubDate>Sat, 05 Sep 2026 09:30:58 +0000</pubDate>
      <link>https://dev.to/apsis-cc/iac-alem-do-terraform-testando-infraestrutura-como-codigo-3o5l</link>
      <guid>https://dev.to/apsis-cc/iac-alem-do-terraform-testando-infraestrutura-como-codigo-3o5l</guid>
      <description>&lt;h2&gt;
  
  
  1. Código de infraestrutura também quebra
&lt;/h2&gt;

&lt;p&gt;Nos dois artigos anteriores desta série, vimos o OpenTofu como alternativa para provisionar infraestrutura e o Ansible para configurá-la depois de criada. Mas há uma pergunta que fica no ar em qualquer um desses fluxos: como saber, &lt;strong&gt;antes&lt;/strong&gt; de rodar &lt;code&gt;apply&lt;/code&gt; em produção, que um módulo Terraform não vai abrir uma porta que não deveria, destruir um recurso por engano, ou simplesmente ter um erro de sintaxe? Testar infraestrutura como código é tão importante quanto testar qualquer outro software — só que, diferente de uma função pura, os "efeitos colaterais" de um teste malfeito aqui podem ser uma conta de nuvem inesperada ou um serviço em produção fora do ar.&lt;/p&gt;

&lt;p&gt;Este artigo fecha a série cobrindo três camadas complementares de teste: análise estática com &lt;strong&gt;tflint&lt;/strong&gt;, verificação de segurança e compliance com &lt;strong&gt;checkov&lt;/strong&gt;, e testes de integração de verdade com &lt;strong&gt;Terratest&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. As camadas de teste em IaC
&lt;/h2&gt;

&lt;p&gt;Vale pensar nessas ferramentas como camadas que rodam em momentos diferentes do ciclo de vida do código, da mais rápida/barata para a mais lenta/cara:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lint e análise estática&lt;/strong&gt; (tflint): roda em segundos, sem precisar de credenciais de nuvem nem de rodar &lt;code&gt;terraform plan&lt;/code&gt;. Pega erros de sintaxe, más práticas e problemas específicos de cada provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Análise de segurança e compliance&lt;/strong&gt; (checkov): também estática, mas focada em identificar configurações inseguras (bucket público, criptografia desabilitada, security group aberto para &lt;code&gt;0.0.0.0/0&lt;/code&gt;) comparando o código contra um catálogo de políticas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testes de integração&lt;/strong&gt; (Terratest): a camada mais próxima da realidade — de fato roda &lt;code&gt;terraform apply&lt;/code&gt; num ambiente isolado, valida o resultado, e depois roda &lt;code&gt;terraform destroy&lt;/code&gt;. Mais lento e mais caro (usa recursos reais de nuvem), mas é o único jeito de garantir que o módulo realmente funciona de ponta a ponta.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Um pipeline de CI/CD maduro roda as três, nessa ordem, falhando rápido nas camadas mais baratas antes de chegar nas mais caras.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. tflint na prática
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;tflint&lt;/code&gt; foca em problemas que o &lt;code&gt;terraform validate&lt;/code&gt; não pega, porque &lt;code&gt;validate&lt;/code&gt; só garante que a sintaxe HCL e os tipos estão corretos — não que o uso de um recurso específico faz sentido para aquele provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Instalação (Linux/macOS via script oficial)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash

&lt;span class="c"&gt;# Rodar no diretório do módulo&lt;/span&gt;
tflint &lt;span class="nt"&gt;--init&lt;/span&gt;   &lt;span class="c"&gt;# baixa o plugin do provider (ex.: AWS)&lt;/span&gt;
tflint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exemplo de configuração habilitando o plugin da AWS, que traz regras específicas (tipos de instância inválidos, uso de AMIs deprecated, etc.):&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="c1"&gt;# .tflint.hcl&lt;/span&gt;
&lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&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;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.31.0"&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;"github.com/terraform-linters/tflint-ruleset-aws"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="s2"&gt;"terraform_deprecated_interpolation"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="s2"&gt;"terraform_unused_declarations"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, &lt;code&gt;tflint&lt;/code&gt; sinaliza coisas como variáveis declaradas e nunca usadas, ou um &lt;code&gt;instance_type&lt;/code&gt; que não existe na AWS — erros que só apareceriam em produção, na hora do &lt;code&gt;apply&lt;/code&gt;, sem essa checagem prévia.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. checkov na prática
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;checkov&lt;/code&gt; (da Bridgecrew/Prisma Cloud) analisa o código Terraform contra centenas de políticas de segurança e compliance prontas (CIS Benchmarks, PCI-DSS, HIPAA, entre outras), sem precisar aplicar nada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;checkov

checkov &lt;span class="nt"&gt;-d&lt;/span&gt; ./terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exemplo de um recurso com problema de segurança óbvio:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"minha-empresa-dados"&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;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_acls&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_policy&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_public_acls&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rodar &lt;code&gt;checkov&lt;/code&gt; contra esse arquivo reporta uma falha correspondente à política &lt;code&gt;CKV_AWS_53&lt;/code&gt; ("Ensure S3 bucket has block public ACLs enabled"), com o número da linha e uma explicação do risco. É possível suprimir uma regra pontualmente quando a exceção é intencional e documentada:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# checkov:skip=CKV_AWS_53:bucket de assets públicos do site, intencional&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_acls&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_policy&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_public_acls&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso documenta a exceção diretamente no código, em vez de silenciar o alerta de forma invisível em algum lugar do pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Terratest na prática
&lt;/h2&gt;

&lt;p&gt;O Terratest é uma biblioteca Go (mantida pela Gruntwork) para escrever testes de integração reais contra código Terraform. O padrão típico de um teste é: &lt;code&gt;apply&lt;/code&gt; → validar → &lt;code&gt;destroy&lt;/code&gt;, sempre em um ambiente descartável.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// test/webserver_test.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gruntwork-io/terratest/modules/http-helper"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gruntwork-io/terratest/modules/terraform"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/stretchr/testify/assert"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestWebServerModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../examples/webserver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"test"&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="c"&gt;// Garante que 'terraform destroy' roda no final,&lt;/span&gt;
    &lt;span class="c"&gt;// mesmo se o teste falhar no meio do caminho.&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;publicIP&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"public_ip"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;publicIP&lt;/span&gt;

    &lt;span class="n"&gt;http_helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpGetWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Welcome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c"&gt;// tentativas&lt;/span&gt;
        &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// intervalo entre tentativas&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;publicIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"o output public_ip não deveria estar vazio"&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;Rodando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd test
&lt;/span&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-timeout&lt;/span&gt; 30m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;defer terraform.Destroy(...)&lt;/code&gt; logo após criar as opções é o detalhe mais importante desse padrão: garante que o &lt;code&gt;destroy&lt;/code&gt; roda mesmo se uma das validações (&lt;code&gt;HttpGetWithRetry&lt;/code&gt;, &lt;code&gt;assert&lt;/code&gt;) falhar no meio do teste, evitando deixar recursos reais de nuvem órfãos e gerando custo desnecessário.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Juntando tudo num pipeline de CI/CD
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/terraform-test.yml&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;Terraform Test Pipeline&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform/**"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;static-checks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;tflint&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-linters/setup-tflint@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tflint --init &amp;amp;&amp;amp; tflint --chdir=terraform&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;checkov&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridgecrewio/checkov-action@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&lt;/span&gt;

  &lt;span class="na"&gt;integration-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static-checks&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.22"&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;Terratest&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go test -v -timeout 30m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O job &lt;code&gt;integration-tests&lt;/code&gt; só roda depois que &lt;code&gt;static-checks&lt;/code&gt; passa — falhar rápido nas checagens estáticas (segundos) evita gastar tempo e dinheiro rodando &lt;code&gt;apply&lt;/code&gt;/&lt;code&gt;destroy&lt;/code&gt; reais (minutos) para um código que já tinha um problema óbvio de lint ou segurança.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Evitando drift em produção
&lt;/h2&gt;

&lt;p&gt;Testes automatizados evitam que código com problema chegue à produção, mas não evitam &lt;strong&gt;drift&lt;/strong&gt; — divergência entre o que o state do Terraform diz que existe e o que de fato existe na nuvem (alguém mudou algo manual pelo console, por exemplo). Para isso, a prática recomendada é rodar &lt;code&gt;terraform plan&lt;/code&gt; (ou &lt;code&gt;tofu plan&lt;/code&gt;) periodicamente contra o ambiente de produção, fora do fluxo de deploy, e alertar se houver diferença:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rodado por um cron/job agendado, não por um deploy&lt;/span&gt;
terraform plan &lt;span class="nt"&gt;-detailed-exitcode&lt;/span&gt;
&lt;span class="c"&gt;# exit code 0 = sem mudanças, 1 = erro, 2 = há diferenças (drift)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;-detailed-exitcode&lt;/code&gt; é o que torna isso automatizável: um pipeline pode checar o código de saída e abrir um alerta apenas quando o valor for &lt;code&gt;2&lt;/code&gt;, sem precisar fazer parsing do output textual do plano.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusão da série
&lt;/h2&gt;

&lt;p&gt;Fechamos esta série de três artigos indo além do Terraform puro: o OpenTofu como alternativa de provisionamento nascida de uma crise de licenciamento, o Ansible cobrindo a configuração do que já foi provisionado, e agora as três camadas de teste — tflint, checkov e Terratest — que dão confiança de que o código de infraestrutura faz exatamente o que deveria antes de chegar em produção. Nenhuma dessas ferramentas substitui o Terraform; todas ampliam o que é possível fazer em volta dele, e é essa combinação — provisionar, configurar e testar — que sustenta um pipeline de IaC realmente confiável.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Imagem de capa: Logo oficial do Terraform — &lt;a href="https://commons.wikimedia.org/wiki/File:Terraform-logo.png" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referências:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/terraform-linters/tflint" rel="noopener noreferrer"&gt;tflint — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.checkov.io/1.Welcome/What%20is%20Checkov.html" rel="noopener noreferrer"&gt;checkov Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://terratest.gruntwork.io/" rel="noopener noreferrer"&gt;Terratest Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>terraform</category>
      <category>testing</category>
      <category>checkov</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
