<?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>Forem: Marcos Vilela</title>
    <description>The latest articles on Forem by Marcos Vilela (@marcos_vile).</description>
    <link>https://forem.com/marcos_vile</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png</url>
      <title>Forem: Marcos Vilela</title>
      <link>https://forem.com/marcos_vile</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/marcos_vile"/>
    <language>en</language>
    <item>
      <title>Quando o Banco Não Caiu: Um Incidente de Conexão Que Expôs um Problema de Resiliência na Aplicação</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Thu, 16 Apr 2026 11:26:28 +0000</pubDate>
      <link>https://forem.com/marcos_vile/quando-o-banco-nao-caiu-um-incidente-de-conexao-que-expos-um-problema-de-resiliencia-na-aplicacao-28ob</link>
      <guid>https://forem.com/marcos_vile/quando-o-banco-nao-caiu-um-incidente-de-conexao-que-expos-um-problema-de-resiliencia-na-aplicacao-28ob</guid>
      <description>&lt;p&gt;Incidentes reais são excelentes testes de maturidade técnica. Eles revelam não apenas falhas de código, mas principalmente falhas de premissas arquiteturais.&lt;/p&gt;

&lt;p&gt;Este artigo descreve a investigação de um incidente envolvendo perda de conexão com PostgreSQL em ambiente produtivo, durante um pico de eventos. O objetivo aqui não é apontar culpados, mas demonstrar capacidade analítica, organização de evidências e raciocínio estruturado de troubleshooting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto do Incidente
&lt;/h2&gt;

&lt;p&gt;Durante um pico elevado de processamento de eventos, o backend perdeu conexão com o banco de dados e não conseguiu se recuperar automaticamente. O banco permaneceu saudável durante todo o período. A aplicação, porém, entrou em modo degradado e passou a retornar dados mockados ao invés de consultar o banco real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primeira Hipótese: Banco Instável?
&lt;/h2&gt;

&lt;p&gt;Sempre que há perda de conexão, a suspeita inicial costuma ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failover do RDS&lt;/li&gt;
&lt;li&gt;Reinício automático&lt;/li&gt;
&lt;li&gt;Exaustão de conexões&lt;/li&gt;
&lt;li&gt;Saturação de CPU ou I/O&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A investigação começou pelo lado do banco.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidências coletadas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Status do RDS permaneceu &lt;strong&gt;Available&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Nenhum evento de failover&lt;/li&gt;
&lt;li&gt;Nenhum reboot automático&lt;/li&gt;
&lt;li&gt;Métricas de CPU e memória estáveis&lt;/li&gt;
&lt;li&gt;Log do banco registrou:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;could not receive data from client: Connection reset by peer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse log é crucial. Ele indica que &lt;strong&gt;quem encerrou a conexão foi o cliente&lt;/strong&gt;, não o banco. A hipótese de indisponibilidade do RDS foi descartada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mudança de Foco: Aplicação
&lt;/h2&gt;

&lt;p&gt;Com o banco saudável, a análise passou para a aplicação.&lt;/p&gt;

&lt;p&gt;Observações importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O container continuou rodando (não houve restart)&lt;/li&gt;
&lt;li&gt;Não houve crash&lt;/li&gt;
&lt;li&gt;A API permaneceu respondendo&lt;/li&gt;
&lt;li&gt;Porém, estava desconectada do banco&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso é um estado perigoso: sistema aparentemente saudável, mas funcionalmente degradado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Análise do Código
&lt;/h2&gt;

&lt;p&gt;Ao revisar a implementação do serviço de banco de dados, foi identificado o seguinte padrão:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existe uma variável interna &lt;code&gt;isConnected&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Após a primeira conexão bem-sucedida, essa variável é marcada como &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;O sistema passa a assumir que o banco continuará disponível indefinidamente&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Problemas estruturais identificados:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;isConnected&lt;/code&gt; é tratada como fonte de verdade permanente&lt;/li&gt;
&lt;li&gt;Não há mecanismo de recriação do pool&lt;/li&gt;
&lt;li&gt;Não existe estratégia de reconexão automática&lt;/li&gt;
&lt;li&gt;Falhas de socket não invalidam o estado interno&lt;/li&gt;
&lt;li&gt;O erro não força reinicialização do container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Durante o pico de eventos, o backend encerrou abruptamente o socket TCP.&lt;/p&gt;

&lt;p&gt;Após isso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O pool ficou inválido&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isConnected&lt;/code&gt; permaneceu &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A aplicação acreditava estar conectada&lt;/li&gt;
&lt;li&gt;Nenhuma nova tentativa de conexão foi realizada&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resultado: sistema ativo, porém inutilizável do ponto de vista de dados reais.&lt;/p&gt;

&lt;h2&gt;
  
  
  Causa Raiz
&lt;/h2&gt;

&lt;p&gt;A causa raiz não foi indisponibilidade do banco.&lt;/p&gt;

&lt;p&gt;Foi uma falha de resiliência na camada de aplicação.&lt;/p&gt;

&lt;p&gt;O sistema foi projetado com a premissa implícita de que:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Se conectou uma vez, permanecerá conectado."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa premissa não é válida em ambientes distribuídos.&lt;/p&gt;

&lt;p&gt;Conexões TCP podem ser encerradas por:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Picos de carga&lt;/li&gt;
&lt;li&gt;Timeouts&lt;/li&gt;
&lt;li&gt;Saturação temporária&lt;/li&gt;
&lt;li&gt;Interrupções de rede&lt;/li&gt;
&lt;li&gt;Problemas internos do runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sem mecanismo de reconexão, qualquer falha transitória se torna permanente até intervenção manual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ação Corretiva Imediata
&lt;/h2&gt;

&lt;p&gt;A solução operacional foi simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reset da task do backend&lt;/li&gt;
&lt;li&gt;Pool recriado&lt;/li&gt;
&lt;li&gt;Conexão restabelecida&lt;/li&gt;
&lt;li&gt;Serviço normalizado&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso confirmou o diagnóstico: o problema estava no estado interno da aplicação.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lições Arquiteturais
&lt;/h2&gt;

&lt;p&gt;Este incidente reforça princípios fundamentais de sistemas distribuídos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conexões não são permanentes.&lt;/li&gt;
&lt;li&gt;Pools não são imutáveis.&lt;/li&gt;
&lt;li&gt;Estado interno pode mentir.&lt;/li&gt;
&lt;li&gt;Healthcheck deve validar dependências reais.&lt;/li&gt;
&lt;li&gt;Resiliência não é opcional em cenários de alta carga.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O banco estava saudável. A infraestrutura estava saudável. O problema estava na suposição arquitetural.&lt;/p&gt;

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

&lt;p&gt;O incidente demonstrou que o maior risco não estava na infraestrutura, mas na camada de aplicação. A reinicialização resolveu o estado atual, mas não corrige o problema estrutural. Sistemas robustos não apenas se conectam — eles sabem se reconectar.&lt;/p&gt;

&lt;p&gt;E isso faz toda a diferença quando a carga aumenta e o comportamento real do sistema começa a divergir das premissas iniciais de projeto.&lt;/p&gt;

&lt;p&gt;Foi uma lição aprendida, não atuei na camada da aplicação, mas no throubleshooting e resolução pontual do incidente, o que estava na minha alçada.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>rca</category>
      <category>devops</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Criando uma Composite Actions para utilizar em do CI/CD do GitHub</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Mon, 06 Apr 2026 11:11:51 +0000</pubDate>
      <link>https://forem.com/marcos_vile/criando-uma-composite-actions-para-utilizar-em-do-cicd-do-github-46mj</link>
      <guid>https://forem.com/marcos_vile/criando-uma-composite-actions-para-utilizar-em-do-cicd-do-github-46mj</guid>
      <description>&lt;p&gt;O GitHub Actions é uma ferramenta poderosa para orquestrar pipelines de CI/CD, mas às vezes você deseja um pouco mais de controle sobre o que acontece dentro deles. Uma &lt;strong&gt;composite action&lt;/strong&gt; oferece a flexibilidade de uma etapa de fluxo de trabalho normal com a conveniência de uma ação... e, quando combinada com um coletor baseado em TypeScript, torna-se um componente reutilizável e de nível de plataforma.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que uma composite action?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Executa &lt;strong&gt;no mesmo runner&lt;/strong&gt; do job: ideal para tarefas de orquestração e arquivo de contexto (nenhum contêiner separado).&lt;/li&gt;
&lt;li&gt;Permite &lt;strong&gt;mixar shell e outras actions&lt;/strong&gt; sem escrever JavaScript puro.&lt;/li&gt;
&lt;li&gt;Fácil de versionar e publicar no marketplace; repositórios mono ou multi-repo podem reutilizá-la.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nós escolhemos esse modelo porque queríamos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capturar contexto do workflow (branch, run_id, status, duração).&lt;/li&gt;
&lt;li&gt;Ler artefatos JSON produzidos pelo pipeline.&lt;/li&gt;
&lt;li&gt;Agregar logs/outputs e enviar para um serviço de telemetria.&lt;/li&gt;
&lt;li&gt;Não quebrar o job se algo falhar — execução sempre com &lt;code&gt;if: always()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Estrutura do repositório
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── action.yml               # definição da composite action
├── package.json             # dependências e scripts
├── src/
│   ├── collector.ts         # lógica em TypeScript
│   └── lib/                 # módulos auxiliares testáveis
├── dist/                    # bundle JS (commitado)
├── tests/                   # Jest unit tests
└── .github/workflows/       # workflows de demo, feature, main...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;collector.ts&lt;/code&gt; é o coração: lê variáveis de ambiente, analisa diretórios de artefatos, soma tamanhos de logs, faz retries no envio HTTP e gera um payload estruturado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exemplo de &lt;code&gt;action.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;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;[ORG]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Metrics-Collect&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Composite&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Action"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Collect&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;send&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;structured&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pipeline&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;external&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;endpoint."&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Platform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Engineering&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Team"&lt;/span&gt;

&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoint_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;endpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;receive&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;empty,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dry-run)"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;auth_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;used&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;auth&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(pass&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;as&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secret)"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;artifact_patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Comma-separated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;patterns&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;download"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;include_logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Whether&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;attempt&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;log&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;collection"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&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;span class="na"&gt;working_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Working&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;monorepos)"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;dry_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;true,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;send&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HTTP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;requests"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;

&lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;metrics_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Overall&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;partial&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;failed"&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.run-collector.outputs.metrics_status }}&lt;/span&gt;
  &lt;span class="na"&gt;payload_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;generated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;payload"&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.run-collector.outputs.payload_path }}&lt;/span&gt;

&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download artifacts (pattern)&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/download-artifact@v4&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.artifact_patterns }}&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;./artifacts&lt;/span&gt;
      &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Run Metrics Collector&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&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;node $GITHUB_ACTION_PATH/dist/index.js \&lt;/span&gt;
          &lt;span class="s"&gt;--endpoint "${{ inputs.endpoint_url }}" \&lt;/span&gt;
          &lt;span class="s"&gt;--auth-token "${{ inputs.auth_token }}" \&lt;/span&gt;
          &lt;span class="s"&gt;--artifact-dir "./artifacts" \&lt;/span&gt;
          &lt;span class="s"&gt;--include-logs "${{ inputs.include_logs }}" \&lt;/span&gt;
          &lt;span class="s"&gt;--working-directory "${{ inputs.working_directory }}" \&lt;/span&gt;
          &lt;span class="s"&gt;--dry-run "${{ inputs.dry_run }}" || true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Collector em TypeScript
&lt;/h2&gt;

&lt;p&gt;A justificativa por usar TypeScript:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tipagem, autocompletar e refatorações fáceis durante o desenvolvimento.&lt;/li&gt;
&lt;li&gt;Testes com Jest (busca por arquivos JSON, análise de logs, tratamento de erros).&lt;/li&gt;
&lt;li&gt;Construção um bundle único com &lt;code&gt;@vercel/ncc&lt;/code&gt; garante que o runner não precisa instalar nada.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trecho de &lt;code&gt;src/collector.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseArgs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;collectWorkflowContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artifactFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;discoverJsonFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artifactDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...agrega dados...&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...insere status/event_id no payload...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to send payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O código trata todos os erros localmente, grava &lt;code&gt;metrics-output/payload.json&lt;/code&gt; e produz outputs para a composite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integração com CI/CD
&lt;/h2&gt;

&lt;p&gt;Adicionei um workflow de exemplo (&lt;code&gt;.github/workflows/feature-workflow.yml&lt;/code&gt;) que roda o pipeline principal e, em seguida, chama a composite para coletar métricas:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;call-shared-ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# job reutilizável de CI&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;org/shared-workflows/backend-ci@v1&lt;/span&gt;

  &lt;span class="na"&gt;collect-metrics&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="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;call-shared-ci&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&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@v6&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;org/metrics-collect@v1&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;endpoint_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.METRICS_ENDPOINT }}&lt;/span&gt;
          &lt;span class="na"&gt;auth_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.METRICS_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;artifact_patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step-result-*,coverage-report"&lt;/span&gt;
          &lt;span class="na"&gt;include_logs&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;A condição &lt;code&gt;if: always()&lt;/code&gt; garante que, mesmo que a verificação de lint/test falhe, ainda coletamos dados.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo local com &lt;code&gt;act&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O repositório contém um workflow de demo que gera artefatos JSON falsos e executa a action com &lt;code&gt;endpoint_url&lt;/code&gt; apontando para &lt;code&gt;webhook.site&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow_dispatch&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;demo&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@v6&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;mkdir artifacts; echo '{"foo":1}' &amp;gt; artifacts/metrics.json&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;./&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;endpoint_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.INPUT_ENDPOINT_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;include_logs&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;O &lt;code&gt;act&lt;/code&gt; pode executar esse workflow offline, contanto que você passe &lt;code&gt;-s INPUT_ENDPOINT_URL="https://webhook.site/..."&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Casos de uso
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry / Analytics&lt;/strong&gt;: enviar métricas de duração, falha e outputs para um motor centralizado.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditoria&lt;/strong&gt;: consolidar resultados de lint, testes e segurança em um único payload para relatórios de conformidade.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-repo dashboards&lt;/strong&gt;: equipes podem empacotar essa action e aplicar em todos os repositórios simples ou monorepos.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Arquitetura definida
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Composite Action (orquestração)&lt;/strong&gt;: inputs/outputs declarados, download de artefatos, execução do binário bundle.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collector TypeScript&lt;/strong&gt;: lógica pura, agnóstica de GitHub; módulos auxiliares testáveis.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle &lt;code&gt;dist/index.js&lt;/code&gt;&lt;/strong&gt;: resultante do &lt;code&gt;@vercel/ncc&lt;/code&gt; (commitado).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflows&lt;/strong&gt;: &lt;code&gt;demo&lt;/code&gt; (para desenvolvimento), &lt;code&gt;feature/staging/main&lt;/code&gt; (integração/prod).
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Tolerância a falhas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Falhas no download de artefatos ou envio HTTP não quebram o job (usar &lt;code&gt;continue-on-error&lt;/code&gt; e &lt;code&gt;|| true&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;Logs e payload são persistidos localmente (&lt;code&gt;metrics-output/&lt;/code&gt;) para inspeção.
&lt;/li&gt;
&lt;li&gt;Retries com backoff exponencial no módulo HTTP.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Publicando e versionando
&lt;/h2&gt;

&lt;p&gt;Usamos &lt;code&gt;semantic-release&lt;/code&gt; em uma workflow de &lt;code&gt;release&lt;/code&gt; que dispara na conclusão da &lt;code&gt;staging&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;dependencies&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;release-workflow&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;org/shared-workflows/shared-release@staging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada release produz uma tag (&lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v1.1.0&lt;/code&gt;, etc). Consumidores referenciam &lt;code&gt;org/metrics-collect@v1&lt;/code&gt; para estabilidade.&lt;/p&gt;

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

&lt;p&gt;Transformar lógica de coleta de métricas em uma composite action permite padronizar instrumentação em toda a organização. O uso de TypeScript e bundling com &lt;code&gt;ncc&lt;/code&gt; garante código limpo, testável e que roda rapidamente nos runners.  &lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Shared Workflows: minha experiência definindo pipelines reutilizáveis</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 03 Mar 2026 19:53:52 +0000</pubDate>
      <link>https://forem.com/marcos_vile/shared-workflows-minha-experiencia-definindo-pipelines-reutilizaveis-74b</link>
      <guid>https://forem.com/marcos_vile/shared-workflows-minha-experiencia-definindo-pipelines-reutilizaveis-74b</guid>
      <description>&lt;p&gt;Nos últimos meses trabalhei na definição de um modelo padrão de shared workflows para projetos de backend Node.js e recursos de infraestruturas implantados na AWS. Neste artigo descrevo a motivação, decisões de arquitetura, trechos de código e aprendizados que considero úteis para quem quer adotar um modelo similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problema e motivação
&lt;/h2&gt;

&lt;p&gt;Em organizações com múltiplos times e repositórios, cada projeto costuma inventar seu pipeline (lint, test, build, deploy). Isso gera retrabalho, inconsistências de segurança e custo de manutenção alto. Minha meta foi criar um conjunto de workflows reutilizáveis — fáceis de configurar por repositórios consumidores — que reduzissem duplicação e padronizassem boas práticas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abordagem
&lt;/h2&gt;

&lt;p&gt;Optei por usar reusable workflows do GitHub Actions (&lt;code&gt;on: workflow_call&lt;/code&gt;) e separar claramente responsabilidades, exemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI&lt;/strong&gt;: lint, install, cache, unit/integration tests, security scan (yarn audit).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infra CI&lt;/strong&gt;: Terraform fmt/init/plan (shared-terraform-ci).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CD&lt;/strong&gt;: build/push para ECR, deploy blue/green em ECS (shared-backend-deploy-ecs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release&lt;/strong&gt;: criação automática de branch de release e PR para &lt;code&gt;main&lt;/code&gt; (shared-release-workflow).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa separação facilita reaproveitamento: um repositório pode chamar apenas o CI shared, ou o deploy shared, ou ambos através de wrappers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trechos de consumo (exemplos)
&lt;/h2&gt;

&lt;p&gt;Consumo do shared CI num projeto:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ci&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;./.github/workflows/shared-backend-ci.yml&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;working_directory&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;node_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;
      &lt;span class="na"&gt;enable_security_scan&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;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrapper que executa deploy em &lt;code&gt;staging&lt;/code&gt; e, em seguida, chama o workflow de release se o deploy teve sucesso:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&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;./.github/workflows/shared-backend-deploy-ecs.yml&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
      &lt;span class="na"&gt;tf_backend_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-staging-state&lt;/span&gt;
      &lt;span class="na"&gt;tf_var_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;envs/staging/variables.tfvars&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

  &lt;span class="na"&gt;promote&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;deploy&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ needs.deploy.result == 'success' }}&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;./.github/workflows/shared-release.yml&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decisões arquiteturais chave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reusable workflows via &lt;code&gt;workflow_call&lt;/code&gt; favorecem versão e rollback centralizado. Consumidores apontam para uma tag (&lt;code&gt;@v1&lt;/code&gt;) no repositório de shared workflows.&lt;/li&gt;
&lt;li&gt;Segreguei permissões por job (princípio de menor privilégio): jobs de lint/test usam &lt;code&gt;contents: read&lt;/code&gt;; jobs de release/deploy recebem permissões mais elevadas apenas quando estritamente necessário.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Aprendizados e recomendações
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parametrização é essencial.&lt;/strong&gt; Trabalhei com &lt;code&gt;working_directory&lt;/code&gt;, &lt;code&gt;app_path&lt;/code&gt;, &lt;code&gt;image_tag&lt;/code&gt; e outros inputs para permitir que projetos com monorepos ou estruturas diferentes reutilizem os mesmos workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Teste local e validação estática.&lt;/strong&gt; Ferramentas como actionlint, shellcheck e checkov evitaram regressões e problemas de segurança antes do merge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documente uso e exemplos.&lt;/strong&gt; Consumidores adotam shared workflows muito mais rápido quando encontram exemplos práticos e um README conciso com nomes de secrets esperados.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tenha um piloto.&lt;/strong&gt; Validar o modelo em um projeto piloto permitiu ajustar inputs e detectar diferenças (por exemplo, onde o monorepo precisava de um &lt;code&gt;working_directory&lt;/code&gt; diferente).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Riscos &amp;amp; mitigação
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diferenças entre repositórios&lt;/strong&gt;: mitigação com inputs configuráveis e exemplos detalhados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissões excessivas&lt;/strong&gt;: mitigação com revisão de permissões por job e uso de roles para operações AWS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependência central&lt;/strong&gt;: documentar SLA de alterações e versionar via tags semânticas para evitar breaking changes.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Criar um modelo de shared workflows traz retorno rápido em escala: menos esforço duplicado, pipelines padronizados e melhorias de segurança centralizadas. A chave do sucesso foi manter o design simples, parametrizável e bem documentado, além de validar por meio de um piloto real.&lt;/p&gt;

&lt;p&gt;Se você estiver começando, recomendo iniciar pelo shared CI (lint/test) e, em seguida, evoluir para um shared deploy com validações de infraestrutura (Terraform) e deploys controlados.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>cicd</category>
      <category>devops</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 03 Mar 2026 19:53:00 +0000</pubDate>
      <link>https://forem.com/marcos_vile/-1pp8</link>
      <guid>https://forem.com/marcos_vile/-1pp8</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/marcos_vile" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png" alt="marcos_vile"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/marcos_vile/reestudando-sua-infraestrutura-324p" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Reestudando sua infraestrutura&lt;/h2&gt;
      &lt;h3&gt;Marcos Vilela ・ Jan 27&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#security&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#iac&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#vulnerabilityanalysis&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>security</category>
      <category>iac</category>
      <category>vulnerabilityanalysis</category>
      <category>devops</category>
    </item>
    <item>
      <title>Template de testes de API com K6</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 24 Feb 2026 12:18:31 +0000</pubDate>
      <link>https://forem.com/marcos_vile/template-de-testes-de-api-com-k6-2k3d</link>
      <guid>https://forem.com/marcos_vile/template-de-testes-de-api-com-k6-2k3d</guid>
      <description>&lt;p&gt;Escrevi um template para servir de modelo para escrita de testes de performance para testar APIs REST com k6, integrando Prometheus e Grafana localmente e adicionando utilitários reutilizáveis para autenticação, checks, thresholds e geração de relatórios. A seguir descrevo as decisões que tomei, exemplos de código e como rodar localmente e no CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que escolhi o k6
&lt;/h2&gt;

&lt;p&gt;Escolhi o k6 por ser leve, confiável e por permitir escrever testes em JavaScript com boa legibilidade. Ele integra bem com observability (Prometheus/Grafana) e funciona tanto via CLI quanto em container — ideal para rodar localmente ou em pipelines de CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estrutura do projeto
&lt;/h2&gt;

&lt;p&gt;Organizei o repositório com foco em reutilização e clareza:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tests/&lt;/code&gt; — scripts organizados por tipo (smoke, load, stress, soak).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/&lt;/code&gt; — utilitários e helpers:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lib/common/&lt;/code&gt; — &lt;code&gt;auth.js&lt;/code&gt;, &lt;code&gt;checks.js&lt;/code&gt;, &lt;code&gt;generators.js&lt;/code&gt;, &lt;code&gt;thresholds.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/config/loader.js&lt;/code&gt; — carregador de configurações (.env, env vars e arquivos JSON)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/utils/&lt;/code&gt; — &lt;code&gt;logger.js&lt;/code&gt;, &lt;code&gt;reporter.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; — stack local com &lt;code&gt;k6&lt;/code&gt;, &lt;code&gt;prometheus&lt;/code&gt; e &lt;code&gt;grafana&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;.env.example&lt;/code&gt; — exemplo mínimo de variáveis locais&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Essa divisão mantém os testes pequenos e delega lógica compartilhada para &lt;code&gt;lib/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuração e precedência de variáveis
&lt;/h2&gt;

&lt;p&gt;Implementei uma lógica simples de carregamento de configuração em &lt;code&gt;lib/config/loader.js&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Variáveis passadas via CLI/CI (&lt;code&gt;__ENV&lt;/code&gt;) têm prioridade;&lt;/li&gt;
&lt;li&gt;Em seguida, eu leio o &lt;code&gt;.env&lt;/code&gt; local;&lt;/li&gt;
&lt;li&gt;Por fim, valores padrões vindos de &lt;code&gt;config/staging.json&lt;/code&gt; ou &lt;code&gt;config/production.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Trecho simplificado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/config/loader.js (trecho)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dotEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../../config/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentEnv&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dotEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;USERNAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USERNAME&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dotEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USERNAME&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USERNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dotEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dotEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_TOKEN&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;blockquote&gt;
&lt;p&gt;Nota: &lt;code&gt;__ENV&lt;/code&gt; é a forma padrão do k6 para acessar variáveis de ambiente passadas na execução.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Autenticação reutilizável
&lt;/h2&gt;

&lt;p&gt;Para endpoints protegidos criei &lt;code&gt;lib/common/auth.js&lt;/code&gt;. A função &lt;code&gt;authenticate()&lt;/code&gt; tenta:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retornar &lt;code&gt;AUTH_TOKEN&lt;/code&gt; se presente (útil para debug);&lt;/li&gt;
&lt;li&gt;Caso contrário, fazer login com &lt;code&gt;USERNAME&lt;/code&gt;/&lt;code&gt;PASSWORD&lt;/code&gt; e retornar o token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Uso típico em um teste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/common/auth.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;authenticate&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="nx"&gt;token&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;A função valida a resposta (checks) e chama &lt;code&gt;fail()&lt;/code&gt; em caso de erro para interromper o teste quando a autenticação falha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checks e thresholds
&lt;/h2&gt;

&lt;p&gt;Criei helpers para checks (&lt;code&gt;lib/common/checks.js&lt;/code&gt;) e thresholds (&lt;code&gt;lib/common/thresholds.js&lt;/code&gt;) para padronizar asserts e SLOs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Exemplo de test (smoke)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/config/loader.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;standardChecks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/common/checks.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;http_req_failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate&amp;lt;0.01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;http_req_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;500&lt;/span&gt;&lt;span class="dl"&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/health`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;standardChecks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is200&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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;Os thresholds possibilitam que o job do CI falhe automaticamente quando os SLOs não são atendidos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relatórios
&lt;/h2&gt;

&lt;p&gt;Integrei o &lt;code&gt;k6-reporter&lt;/code&gt; para gerar &lt;code&gt;summary.html&lt;/code&gt; e &lt;code&gt;summary.json&lt;/code&gt;. No &lt;code&gt;lib/utils/reporter.js&lt;/code&gt; exporto &lt;code&gt;handleSummary&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;htmlReport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSummary&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;htmlReport&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="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;E nos testes eu apenas faço:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handleSummary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/utils/reporter.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handleSummary&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Observability: Prometheus + Grafana
&lt;/h2&gt;

&lt;p&gt;Para observability local usei &lt;code&gt;docker-compose.yml&lt;/code&gt; com k6, Prometheus e Grafana. Configurei o k6 para enviar métricas via remote write para o Prometheus, e ativei &lt;code&gt;remote-write-receiver&lt;/code&gt; no contêiner do Prometheus.&lt;/p&gt;

&lt;p&gt;Execução local recomendada:&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;span class="c"&gt;# rodar um teste (container k6 já monta o repositório):&lt;/span&gt;
docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; k6 run tests/smoke/01-health-check.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Após subir a stack você pode abrir Grafana (&lt;code&gt;http://localhost:3000&lt;/code&gt;) e Prometheus (&lt;code&gt;http://localhost:9090&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Integração com CI (GitHub Actions)
&lt;/h2&gt;

&lt;p&gt;Usei a &lt;code&gt;grafana/k6-action&lt;/code&gt; para executar scripts no CI. Exemplo de step:&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="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;Run K6 Smoke Test&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;grafana/k6-action@v0.2.0&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;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tests/smoke/01-health-check.js&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BASE_URL }}&lt;/span&gt;
    &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recomendo rodar &lt;code&gt;smoke&lt;/code&gt; em PRs e &lt;code&gt;load/stress&lt;/code&gt; em jobs separados ou agendados para evitar impacto em ambientes de produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boas práticas que segui
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Não commitar segredos (&lt;code&gt;.env&lt;/code&gt; está no &lt;code&gt;.gitignore&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Reutilizar helpers em &lt;code&gt;lib/&lt;/code&gt; para diminuir duplicação.&lt;/li&gt;
&lt;li&gt;Definir thresholds para transformar métricas em critérios de sucesso/falha.&lt;/li&gt;
&lt;li&gt;Manter testes idempotentes para que possam ser executados várias vezes sem efeitos colaterais.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problemas que enfrentei e como resolvi
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Leitura de &lt;code&gt;.env&lt;/code&gt;: implementei parsing que aceita aspas e ignora comentários.&lt;/li&gt;
&lt;li&gt;Autenticação: incluí suporte a &lt;code&gt;AUTH_TOKEN&lt;/code&gt; estático para facilitar debug sem chamadas de login.&lt;/li&gt;
&lt;li&gt;Observability: configurei Prometheus para aceitar remote write do k6 no ambiente local.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exemplo mínimo pronto para copiar
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/smoke/01-health-check.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/config/loader.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;standardChecks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/common/checks.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10s&lt;/span&gt;&lt;span class="dl"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/health`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;standardChecks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is200&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Criar este template me ajudou a padronizar a forma como escrevemos e executamos testes de carga: fica mais fácil rodar localmente com observability, sendo assim para cada projeto, inicio um novo repositório a partir desse template, posso utilizar os testes padrões para validação e escrever novos testes logicamente, tendo assim mais flexibilidade, robustez, escala e padronização, além de poder, integrar/utilizar o K6 nas pipelines de CI/CD.&lt;/p&gt;

</description>
      <category>k6</category>
      <category>performance</category>
      <category>platformengineer</category>
      <category>devops</category>
    </item>
    <item>
      <title>Como integrei testes de carga com K6, GitHub Actions e OpenTelemetry</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 10 Feb 2026 11:24:58 +0000</pubDate>
      <link>https://forem.com/marcos_vile/como-integrei-testes-de-carga-com-k6-github-actions-e-opentelemetry-23eh</link>
      <guid>https://forem.com/marcos_vile/como-integrei-testes-de-carga-com-k6-github-actions-e-opentelemetry-23eh</guid>
      <description>&lt;p&gt;Neste artigo, compartilho minha experiência construindo uma infraestrutura de testes de carga gerida de forma eficiente com GitHub Actions e observável via integração com OpenTelemetry (OTel). O objetivo era garantir que o desempenho da nossa API pudesse ser monitorado facilmente dentro dos pipelines de CI/CD.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Problema
&lt;/h2&gt;

&lt;p&gt;Precisávamos executar testes de desempenho regularmente contra nossos ambientes de staging e produção sem intervenção manual. Além disso, as métricas geradas por esses testes precisavam ficar visíveis nos nossos dashboards centralizados do Grafana, que usam o Amazon Managed Prometheus (AMP) alimentado por um OpenTelemetry Collector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que o K6?
&lt;/h2&gt;

&lt;p&gt;Escolhi o K6 porque permite escrever testes em JavaScript, tornando-os acessíveis para toda a equipe. É leve, compatível com containers e tem suporte nativo para exportar métricas para OpenTelemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estrutura do caso de uso
&lt;/h2&gt;

&lt;p&gt;Estruturei o repositório para lidar com diferentes tipos de testes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smoke Tests&lt;/strong&gt;: Verificações rápidas de saúde executadas em pull requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load Tests&lt;/strong&gt;: Baselines de desempenho padrão executadas em agendamentos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stress &amp;amp; Soak Tests&lt;/strong&gt;: Testes mais agressivos para identificar pontos de ruptura.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aqui está um exemplo simplificado de como estruturei um teste básico usando checks e thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;http_req_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;2000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// 95% das requisições devem estar abaixo de 2s&lt;/span&gt;
        &lt;span class="na"&gt;http_req_failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate&amp;lt;0.01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;     &lt;span class="c1"&gt;// Menos de 1% de falha&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.staging.example.com/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status é 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Passo 1: Implementação no GitHub Actions
&lt;/h2&gt;

&lt;p&gt;O núcleo da automação fica no GitHub Actions. Criei um workflow que permite execução agendada (cron) e acionamento manual (&lt;code&gt;workflow_dispatch&lt;/code&gt;) para ambientes específicos.&lt;/p&gt;

&lt;p&gt;Um desafio específico foi garantir que o binário do K6 estivesse corretamente instalado e disponível no PATH. Usar a action oficial &lt;code&gt;grafana/setup-k6-action&lt;/code&gt; foi a solução mais confiável.&lt;/p&gt;

&lt;p&gt;Aqui está a configuração essencial do arquivo de workflow &lt;code&gt;.github/workflows/load-tests.yml&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K6 Load Tests&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;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5'&lt;/span&gt; &lt;span class="c1"&gt;# Toda sexta-feira à meia-noite&lt;/span&gt;
    &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&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;choice&lt;/span&gt;
                &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
            &lt;span class="na"&gt;test_type&lt;/span&gt;&lt;span class="pi"&gt;:&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;choice&lt;/span&gt;
                &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;smoke&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;load&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;run-tests&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;Setup k6&lt;/span&gt;
                &lt;span class="s"&gt;uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/setup-k6-action@v1&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;Run K6 Test&lt;/span&gt;
                &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k6 run --out opentelemetry tests/${{ inputs.test_type }}/test.js&lt;/span&gt;
                &lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;K6_OTEL_EXPORTER_TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
                    &lt;span class="na"&gt;K6_OTEL_HTTP_EXPORTER_ENDPOINT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.OTEL_COLLECTOR_ENDPOINT }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Passo 2: Integração com OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;Esta foi a parte mais crítica da configuração. Integrar o K6 com nosso OpenTelemetry Collector exigiu configurações específicas para garantir que as métricas viajassem do runner do GitHub Action para nosso collector e, finalmente, para o Prometheus.&lt;/p&gt;

&lt;p&gt;O suporte nativo do K6 ao OTel é excelente, mas nuances de configuração importam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuração do protocolo
&lt;/h3&gt;

&lt;p&gt;Precisei definir explicitamente o tipo do exporter para &lt;code&gt;http&lt;/code&gt; porque o collector escuta na porta 4318 para payloads HTTP/OTLP. Por padrão, algumas ferramentas assumem gRPC (porta 4317).&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;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;K6_OTEL_EXPORTER_TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;K6_OTEL_HTTP_EXPORTER_ENDPOINT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.OTEL_COLLECTOR_ENDPOINT }}&lt;/span&gt;
    &lt;span class="na"&gt;K6_OTEL_EXPORTER_INSECURE&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;p&gt;A variável de endpoint geralmente é algo como &lt;code&gt;seu-domínio.com:4318&lt;/code&gt;. O K6 adiciona o protocolo/caminho correto se configurado apropriadamente, mas aprendi que &lt;code&gt;K6_OTEL_HTTP_EXPORTER_ENDPOINT&lt;/code&gt; é a variável específica quando o tipo é &lt;code&gt;http&lt;/code&gt;, enquanto &lt;code&gt;K6_OTEL_EXPORTER_ENDPOINT&lt;/code&gt; costuma ser usada para gRPC ou defaults genéricos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atributos de recurso e filtragem
&lt;/h3&gt;

&lt;p&gt;Em um ambiente de observabilidade compartilhado, taguear suas métricas é obrigatório. Nosso collector descarta qualquer métrica que não contenha um &lt;code&gt;client_id&lt;/code&gt; específico. Resolvi isso injetando &lt;code&gt;K6_OTEL_RESOURCE_ATTRIBUTES&lt;/code&gt; diretamente no pipeline:&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;K6_OTEL_RESOURCE_ATTRIBUTES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client_id=test3,environment=${{ matrix.environment }},service_name=k6-load-tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso garante que, ao consultar &lt;code&gt;k6_http_req_duration&lt;/code&gt; no Grafana, eu possa filtrar exatamente pela execução do teste e ambiente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passo 3: Simulação local com Act
&lt;/h2&gt;

&lt;p&gt;Testar workflows do GitHub Actions cometendo e dando push com mensagens "fix ci" é tedioso. Integrei o &lt;code&gt;act&lt;/code&gt; para simular o ambiente do GitHub Actions localmente.&lt;/p&gt;

&lt;p&gt;Criando um arquivo simples de payload de evento &lt;code&gt;dispatch-event.json&lt;/code&gt;, pude verificar o comportamento exato da injeção de variáveis de ambiente e da execução do comando K6 sem sair do terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;act workflow_dispatch &lt;span class="nt"&gt;-e&lt;/span&gt; .github/dispatch-event.json &lt;span class="nt"&gt;-W&lt;/span&gt; .github/workflows/load-tests.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso economizou horas de depuração, especialmente ao ajustar as variáveis de ambiente do OTel.&lt;/p&gt;

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

&lt;p&gt;Ao combinar K6, GitHub Actions e OpenTelemetry, implementei um sistema onde o desempenho é monitorado como a saúde da aplicação.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automação:&lt;/strong&gt; Não há mais execução manual dos testes de carga.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibilidade:&lt;/strong&gt; Métricas estão no mesmo dashboard que as métricas da aplicação.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depuração:&lt;/strong&gt; Testes locais com &lt;code&gt;act&lt;/code&gt; garantem que o pipeline permaneça estável.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se for montar testes de carga, recomendo priorizar o aspecto de observabilidade desde cedo. Ver gráficos em tempo real de Virtual Users (VUs) e Duração de Requisições ao lado do uso de CPU do servidor oferece insights que um relatório local isolado nunca proporciona.&lt;/p&gt;

</description>
      <category>k6</category>
      <category>githubactions</category>
      <category>prometheus</category>
      <category>opentelemetry</category>
    </item>
    <item>
      <title>Economia não é sorte, é automação. Veja como monitorei custos na AWS com Terraform.</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 03 Feb 2026 12:09:13 +0000</pubDate>
      <link>https://forem.com/marcos_vile/economia-nao-e-sorte-e-automacao-veja-como-monitorei-custos-na-aws-com-terraform-39fe</link>
      <guid>https://forem.com/marcos_vile/economia-nao-e-sorte-e-automacao-veja-como-monitorei-custos-na-aws-com-terraform-39fe</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/marcos_vile" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png" alt="marcos_vile"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/marcos_vile/automatizando-finops-na-aws-como-construimos-um-modulo-de-monitoramento-de-custos-com-terraform-e-3da3" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Automatizando FinOps na AWS: Como construímos um módulo de Monitoramento de Custos com Terraform e incident.io&lt;/h2&gt;
      &lt;h3&gt;Marcos Vilela ・ Feb 3&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#finops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#aws&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#terraform&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#iac&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>finops</category>
      <category>aws</category>
      <category>terraform</category>
      <category>iac</category>
    </item>
    <item>
      <title>A /home encheu? Como usei Block Storage para expandir o armazenamento sem dor.</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 03 Feb 2026 12:07:59 +0000</pubDate>
      <link>https://forem.com/marcos_vile/a-home-encheu-como-usei-block-storage-para-expandir-o-armazenamento-sem-dor-55c1</link>
      <guid>https://forem.com/marcos_vile/a-home-encheu-como-usei-block-storage-para-expandir-o-armazenamento-sem-dor-55c1</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" class="crayons-story__hidden-navigation-link"&gt;Como expandi o armazenamento da minha pasta /home com Block Storage&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/marcos_vile" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png" alt="marcos_vile profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/marcos_vile" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Marcos Vilela
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Marcos Vilela
                
              
              &lt;div id="story-author-preview-content-2070920" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/marcos_vile" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Marcos Vilela&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 6 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" id="article-link-2070920"&gt;
          Como expandi o armazenamento da minha pasta /home com Block Storage
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/linux"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;linux&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blockstorage"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blockstorage&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/braziliandevs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;braziliandevs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>beginners</category>
      <category>linux</category>
      <category>blockstorage</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>A /home encheu? Como usei Block Storage para expandir o armazenamento sem dor</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 03 Feb 2026 12:06:56 +0000</pubDate>
      <link>https://forem.com/marcos_vile/a-home-encheu-como-usei-block-storage-para-expandir-o-armazenamento-sem-dor-1ahk</link>
      <guid>https://forem.com/marcos_vile/a-home-encheu-como-usei-block-storage-para-expandir-o-armazenamento-sem-dor-1ahk</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" class="crayons-story__hidden-navigation-link"&gt;Como expandi o armazenamento da minha pasta /home com Block Storage&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/marcos_vile" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png" alt="marcos_vile profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/marcos_vile" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Marcos Vilela
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Marcos Vilela
                
              
              &lt;div id="story-author-preview-content-2070920" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/marcos_vile" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2315917%2F2b4dcd53-2153-4239-b273-5c867b7d1e25.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Marcos Vilela&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 6 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" id="article-link-2070920"&gt;
          Como expandi o armazenamento da minha pasta /home com Block Storage
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/linux"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;linux&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blockstorage"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blockstorage&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/braziliandevs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;braziliandevs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/marcos_vile/como-expandi-o-armazenamento-da-minha-pasta-home-com-block-storage-4cjc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>beginners</category>
      <category>linux</category>
      <category>blockstorage</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Automatizando FinOps na AWS: Como construímos um módulo de Monitoramento de Custos com Terraform e incident.io</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 03 Feb 2026 12:03:52 +0000</pubDate>
      <link>https://forem.com/marcos_vile/automatizando-finops-na-aws-como-construimos-um-modulo-de-monitoramento-de-custos-com-terraform-e-3da3</link>
      <guid>https://forem.com/marcos_vile/automatizando-finops-na-aws-como-construimos-um-modulo-de-monitoramento-de-custos-com-terraform-e-3da3</guid>
      <description>&lt;p&gt;Gerenciar custos em nuvem e um desafio constante para equipes de engenharia. A disciplina de FinOps busca trazer visibilidade e accountability para esses gastos, mas sem as ferramentas certas, e fácil ser surpreendido pela fatura no final do mês.&lt;/p&gt;

&lt;p&gt;Neste artigo, compartilho a experiência e os detalhes técnicos da criação de um módulo Terraform reutilizável para automatizar alertas de orçamento na AWS, integrando diretamente com ferramentas de resposta a incidentes (neste caso, o incident.io), eliminando intermediários desnecessários como funções Lambda para esse fim específico.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Problema
&lt;/h2&gt;

&lt;p&gt;Precisávamos de uma maneira padronizada de criar "guardrails" financeiros para novos projetos. Toda nova conta ou ambiente (staging, production) na AWS deveria nascer com um orçamento definido e um canal de alerta configurado.&lt;/p&gt;

&lt;p&gt;Os requisitos eram:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Definir um limite mensal em dólares.&lt;/li&gt;
&lt;li&gt;Alertar quando o gasto real atingir certas porcentagens (ex: 80%, 100%).&lt;/li&gt;
&lt;li&gt;Alertar quando a previsão (forecast) indicar que o orçamento será estourado.&lt;/li&gt;
&lt;li&gt;Enviar esses alertas para nossa plataforma de gerenciamento de incidentes.&lt;/li&gt;
&lt;li&gt;Ser seguro (dados criptografados em repouso).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Arquitetura Simplificada
&lt;/h2&gt;

&lt;p&gt;Inicialmente, considerou-se usar AWS Lambda para processar os alertas do AWS Budgets e formatar o JSON para o webhook. No entanto, percebemos que poderíamos simplificar a arquitetura utilizando a capacidade nativa do SNS de realizar chamadas HTTPS (Webhooks).&lt;/p&gt;

&lt;p&gt;O fluxo ficou assim:&lt;br&gt;
&lt;code&gt;AWS Budgets&lt;/code&gt; -&amp;gt; &lt;code&gt;SNS Topic (Criptografado)&lt;/code&gt; -&amp;gt; &lt;code&gt;HTTP POST&lt;/code&gt; -&amp;gt; &lt;code&gt;Incident.io&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Essa abordagem reduz a complexidade operacional (menos código para manter) e o custo.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Implementação com Terraform
&lt;/h2&gt;

&lt;p&gt;Abaixo, detalho as partes cruciais do módulo, focando em como resolvemos os desafios de segurança e flexibilidade.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Segurança Primeiro: Criptografia KMS
&lt;/h3&gt;

&lt;p&gt;O AWS SNS suporta criptografia de dados (Server-Side Encryption). Para ambientes corporativos, isso não é opcional. No entanto, usar uma chave KMS gerenciada pelo cliente (CMK) requer politicas de acesso especificas para permitir que o serviço de AWS Budgets (que é um serviço global) chame um recurso regional e use a chave para criptografar a mensagem antes de enviá-la ao tópico.&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_kms_key"&lt;/span&gt; &lt;span class="s2"&gt;"cost_alerts_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"KMS key for encrypting FinOps SNS topics"&lt;/span&gt;
  &lt;span class="nx"&gt;deletion_window_in_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;enable_key_rotation&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;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enable IAM User Permissions"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::${local.account_id}:root"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kms:*"&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&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="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow AWS Budgets to use the key"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"budgets.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;"kms:GenerateDataKey*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"kms:Decrypt"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&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="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;Observe a permissão explicita para &lt;code&gt;budgets.amazonaws.com&lt;/code&gt;. Sem isso, o orçamento falha silenciosamente ao tentar publicar no tópico.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. O Tópico SNS e a política de Publicação
&lt;/h3&gt;

&lt;p&gt;O tópico SNS atua como o roteador. Além de criar o tópico, precisamos de uma &lt;code&gt;aws_sns_topic_policy&lt;/code&gt; para autorizar o serviço de orçamento a publicar nele.&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_sns_topic"&lt;/span&gt; &lt;span class="s2"&gt;"cost_alerts"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name_prefix&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name_prefix}-cost-alerts-"&lt;/span&gt;
  &lt;span class="nx"&gt;kms_master_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost_alerts_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&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_sns_topic_policy"&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost_alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWSBudgets-Publish"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"budgets.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SNS:Publish"&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost_alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Flexibilidade com Dynamic Blocks
&lt;/h3&gt;

&lt;p&gt;Um dos maiores desafios ao criar módulos reutilizáveis e atender as diferentes necessidades de notificação de cada time. Alguns querem alertas aos 50%, outros apenas aos 100%.&lt;/p&gt;

&lt;p&gt;Utilizamos o recurso &lt;code&gt;dynamic&lt;/code&gt; do Terraform para gerar as configurações de notificação com base em uma lista de objetos fornecida via variável.&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_budgets_budget"&lt;/span&gt; &lt;span class="s2"&gt;"cost_budget"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name_prefix}-budget"&lt;/span&gt;
  &lt;span class="nx"&gt;budget_type&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"COST"&lt;/span&gt;
  &lt;span class="nx"&gt;limit_amount&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit_amount&lt;/span&gt;
  &lt;span class="nx"&gt;limit_unit&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit_unit&lt;/span&gt;
  &lt;span class="nx"&gt;time_period_start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2024-01-01_00:00"&lt;/span&gt;
  &lt;span class="nx"&gt;time_unit&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time_unit&lt;/span&gt;

  &lt;span class="c1"&gt;# Bloco dinamico para notificacoes&lt;/span&gt;
  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"notification"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;comparison_operator&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comparison_operator&lt;/span&gt;
      &lt;span class="nx"&gt;threshold&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;
      &lt;span class="nx"&gt;threshold_type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold_type&lt;/span&gt;
      &lt;span class="nx"&gt;notification_type&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notification_type&lt;/span&gt;
      &lt;span class="nx"&gt;subscriber_sns_topic_arns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost_alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso permite que o consumidor do module configure alertas de forma declarativa e simples no arquivo &lt;code&gt;.tfvars&lt;/code&gt;:&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;notifications&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;comparison_operator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GREATER_THAN"&lt;/span&gt;
    &lt;span class="nx"&gt;threshold&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;threshold_type&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PERCENTAGE"&lt;/span&gt;
    &lt;span class="nx"&gt;notification_type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ACTUAL"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;comparison_operator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GREATER_THAN"&lt;/span&gt;
    &lt;span class="nx"&gt;threshold&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="nx"&gt;threshold_type&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PERCENTAGE"&lt;/span&gt;
    &lt;span class="nx"&gt;notification_type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FORECASTED"&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;h3&gt;
  
  
  4. Integração via Webhook (A Parte "Chata")
&lt;/h3&gt;

&lt;p&gt;A integração final e feita via uma assinatura SNS com protocolo HTTPS:&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_sns_topic_subscription"&lt;/span&gt; &lt;span class="s2"&gt;"incident_io"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;topic_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_sns_topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost_alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https"&lt;/span&gt; &lt;span class="c1"&gt;# Detectado dinamicamente no codigo real&lt;/span&gt;
  &lt;span class="nx"&gt;endpoint&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incident_io_webhook_url&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;O Desafio da Confirmação da Assinatura:&lt;/strong&gt;&lt;br&gt;
Diferente de Lambda ou SQS, endpoints HTTP/HTTPS requerem uma confirmação de assinatura. O SNS envia um POST inicial com uma URL de confirmação.&lt;br&gt;
Como estamos usando uma ferramenta de terceiros (incident.io), não temos controle direto para clicar programaticamente nesse link.&lt;/p&gt;

&lt;p&gt;A solução adotada foi processual:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O Terraform aplica a infraestrutura.&lt;/li&gt;
&lt;li&gt;O estado da assinatura fica como "PendingConfirmation".&lt;/li&gt;
&lt;li&gt;O administrador acessa os logs do incident.io, localiza a mensagem de &lt;code&gt;SubscriptionConfirmation&lt;/code&gt; e clica no link manualmente.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Embora não seja 100% automatizado (zero-touch), e um compromisso aceitável para uma configuração que ocorre apenas uma vez por ambiente.&lt;/p&gt;

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

&lt;p&gt;Criar módulos de infraestrutura como código para FinOps e essencial para escalar o uso da nuvem de forma responsável. Ao centralizar a lógica de orçamentos, criptografia e notificações em um único modulo, garantimos que todos os ambientes seguem as melhores praticas de segurança e observabilidade financeira.&lt;/p&gt;

&lt;p&gt;A escolha de remover camadas intermediarias (como Lambdas) simplificou a stack, tornando-a mais robusta e fácil de manter a longo prazo.&lt;/p&gt;

</description>
      <category>finops</category>
      <category>aws</category>
      <category>terraform</category>
      <category>iac</category>
    </item>
    <item>
      <title>Reestudando sua infraestrutura</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 27 Jan 2026 17:30:47 +0000</pubDate>
      <link>https://forem.com/marcos_vile/reestudando-sua-infraestrutura-324p</link>
      <guid>https://forem.com/marcos_vile/reestudando-sua-infraestrutura-324p</guid>
      <description>&lt;p&gt;Recentemente, parei para rever/revisar o IaC de infra/redes e sua infraestrutura aplicada, de um ecossistema de uma fintech (que chamaremos aqui de &lt;em&gt;GlobalPay&lt;/em&gt;). O objetivo era avaliar a postura de segurança de seus apps diferentes que compõem o produto principal.&lt;/p&gt;

&lt;p&gt;O que parecia ser uma varredura de rotina revelou uma lição valiosa: &lt;strong&gt;a segurança não é apenas um problema técnico, mas um desafio de governança e automação&lt;/strong&gt; e também, revisão.&lt;/p&gt;

&lt;p&gt;Aqui estão os principais aprendizados para quem trabalha com DevOps, Cloud e Segurança de Aplicações que pude tirar reestudando meu próprio código.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. O perigo dos Templates de IaC Inadequados
&lt;/h2&gt;

&lt;p&gt;Um dos achados mais curiosos foi que &lt;strong&gt;nos apps analisados&lt;/strong&gt; apresentavam exatamente a mesma falha: a fuga de nomes internos de Load Balancers de &lt;em&gt;staging&lt;/em&gt; e regiões da no cabeçalho &lt;code&gt;Location&lt;/code&gt; de redirecionamentos HTTP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O aprendizado:&lt;/strong&gt; Quando usamos ferramentas como Terraform ou CloudFormation, um erro no template original é replicado em escala. Adversários usam essas informações para mapear ambientes de teste, que geralmente possuem controles de segurança menos rigorosos.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A Inconsistência é o seu maior inimigo
&lt;/h2&gt;

&lt;p&gt;Durante a minha análise, encontramos um cenário de "dois mundos". Enquanto um app apresentava uma postura exemplar (o nosso &lt;em&gt;benchmark&lt;/em&gt;), com &lt;strong&gt;HSTS, CSP e X-Frame-Options&lt;/strong&gt; configurados corretamente, os apps adjacentes — teoricamente mais sensíveis — não possuíam poucos desses controles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A lição:&lt;/strong&gt; A segurança não pode ser seletiva. A falta de uma &lt;strong&gt;baseline mínima obrigatória&lt;/strong&gt; centralizada faz com que cada time de desenvolvimento implemente sua própria versão de "segurança", criando buracos previsíveis no perímetro.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Vulnerabilidades "Clássicas" ainda derrubam gigantes
&lt;/h2&gt;

&lt;p&gt;Identificamos uma vulnerabilidade ao ataque &lt;strong&gt;Slowloris DoS&lt;/strong&gt; (CVE-2007-6750) em um dos endpoints. É uma falha de 2009 que ainda assombra ambientes que não configuram corretamente os &lt;em&gt;timeouts&lt;/em&gt; de conexão em seus Load Balancers ou WAFs.&lt;/p&gt;

&lt;p&gt;Além disso, o &lt;strong&gt;Bypass de Rate Limit&lt;/strong&gt; no endpoint de autenticação foi classificado como crítico. Sem um limite rigoroso, a aplicação fica exposta a ataques de &lt;strong&gt;Account Takeover (ATO)&lt;/strong&gt; via &lt;em&gt;Credential Stuffing&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Hardening de Borda: Mais que apenas HTTPS
&lt;/h2&gt;

&lt;p&gt;Muitos desenvolvedores acreditam que subir um certificado SSL/TLS é o suficiente. No entanto, a análise revelou que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;CORS Permissivo:&lt;/strong&gt; Configurações de &lt;code&gt;access-control-allow-origin: *&lt;/code&gt; (wildcard) em APIs financeiras permitem que qualquer site malicioso faça requisições em nome de usuários autenticados.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Fingerprinting de Tecnologia:&lt;/strong&gt; Manter o cabeçalho &lt;code&gt;X-Powered-By: Express&lt;/code&gt; facilita a vida do atacante, que pode buscar exploits específicos para aquela versão do framework.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Segurança de Cabeçalhos:&lt;/strong&gt; A ausência de cabeçalhos críticos (como CSP para evitar XSS e HSTS para forçar HTTPS) foi uma falha abrangente.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Do Manual para o "Security as Code"
&lt;/h2&gt;

&lt;p&gt;A maior conclusão deste relato não foi sobre quais portas estavam abertas, mas sobre a ausência de &lt;strong&gt;validações automáticas em pipelines de CI/CD&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se tivesse implementado verificações de segurança automatizadas, seria impossível um app chegar à produção sem os cabeçalhos obrigatórios ou com políticas de CORS abertas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusão: O Projeto Arquitetônico Digital
&lt;/h3&gt;

&lt;p&gt;A infraestrutura de uma empresa é como um &lt;strong&gt;edifício residencial&lt;/strong&gt;. Não adianta o andar de investimentos ter portas blindadas se o andar de gestão de contas foi construído com janelas que não trancam.&lt;/p&gt;

&lt;p&gt;A solução definitiva para os problemas encontrados não é consertar cada domínio manualmente, mas atualizar o &lt;strong&gt;único projeto arquitetônico digital (os templates de IaC)&lt;/strong&gt; e garantir que cada novo andar construído siga o mesmo padrão de segurança máxima desde o alicerce.&lt;/p&gt;

</description>
      <category>security</category>
      <category>iac</category>
      <category>vulnerabilityanalysis</category>
      <category>devops</category>
    </item>
    <item>
      <title>Produtividade no Terminal: O Poder dos Aliases no Linux</title>
      <dc:creator>Marcos Vilela</dc:creator>
      <pubDate>Tue, 13 Jan 2026 11:46:53 +0000</pubDate>
      <link>https://forem.com/marcos_vile/produtividade-no-terminal-o-poder-dos-aliases-no-linux-13bm</link>
      <guid>https://forem.com/marcos_vile/produtividade-no-terminal-o-poder-dos-aliases-no-linux-13bm</guid>
      <description>&lt;p&gt;Se você utiliza a linha de comando diariamente, já deve ter percebido que passamos boa parte do tempo digitando os mesmos comandos repetidamente. Seja para limpar o cache do Docker, realizar um deploy ou formatar uma string, a repetição é a inimiga da eficiência. É aqui que entram os aliases do Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que são Aliases?
&lt;/h2&gt;

&lt;p&gt;De forma simplificada, um alias é um apelido ou um atalho para um comando mais longo e complexo. Eles funcionam como substituições de texto no seu shell (Bash ou Zsh). Em vez de digitar uma sequência de 50 caracteres, você pode definir uma palavra de quatro letras que executa exatamente a mesma função.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por que utilizar?
&lt;/h3&gt;

&lt;p&gt;Redução de erros de digitação: Comandos complexos com muitas flags são propensos a erros.&lt;/p&gt;

&lt;h3&gt;
  
  
  Padronização: Você pode criar um fluxo de trabalho padrão para sua equipe ou para seus diferentes projetos.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Velocidade: A economia de segundos em cada comando se traduz em horas de produtividade ao final de um mês.
&lt;/h3&gt;

&lt;h2&gt;
  
  
  Exemplos simples mas práticos de uso
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Gerenciamento de Infraestrutura
Quem trabalha com Docker sabe que resíduos de contêineres e redes podem consumir gigabytes de memória rapidamente. Um alias de limpeza pode ser um grande aliado, o que costumo utilizar:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alias docker-prune-all="docker system prune --all --volumes -f"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Normalização de Strings
Muitas vezes precisamos renomear arquivos removendo espaços, acentos e transformando tudo em minúsculas. Uma função no seu arquivo de perfil pode automatizar isso, esse me ajuda a normalizar frases que posso utilizar na criação de branchs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;normaliza() {
  echo "$*" | iconv -f utf8 -t ascii//TRANSLIT | tr "[:upper:]" "[:lower:]" | sed "s/ /-/g"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Lembretes de Workflow
Se o seu processo de merge e deploy envolve muitos passos manuais, você pode criar um alias que apenas imprime o passo a passo na tela, servindo como um guia rápido, como um &lt;code&gt;man&lt;/code&gt; de algum software/função:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alias deploy-help='echo -e "1. git pull\n2. npm run build\n3. dep deploy stage"'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Como configurar
&lt;/h2&gt;

&lt;p&gt;Para tornar seus aliases permanentes, você deve adicioná-los ao arquivo de configuração do seu shell, geralmente o &lt;code&gt;.bashrc&lt;/code&gt; ou &lt;code&gt;.zshrc&lt;/code&gt;, localizados na pasta raiz do seu usuário (~/).&lt;/p&gt;

&lt;p&gt;Uma boa prática é manter seus aliases em um arquivo separado chamado &lt;code&gt;.bash_aliases&lt;/code&gt; e apenas o chamar dentro do seu arquivo principal. Isso mantém suas configurações organizadas e fáceis de transportar para outras máquinas.&lt;/p&gt;

&lt;p&gt;Aliases não são apenas sobre digitar menos, são sobre criar um ambiente de trabalho que se adapta às suas necessidades. Ao identificar padrões no seu dia a dia e automatizá-los, você libera espaço mental para focar no que realmente importa: a solução dos problemas e o desenvolvimento de código de qualidade.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>debian</category>
      <category>devops</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
