<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joaochiroli]]></title><description><![CDATA[Everything about Devops and SRE]]></description><link>https://joaochiroli.com.br</link><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 18:07:18 GMT</lastBuildDate><atom:link href="https://joaochiroli.com.br/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Do Desvio Padrão ao Kolmogorov-Smirnov: Técnicas para Análise de Telemetria]]></title><description><![CDATA[Recentemente, ao ler o livro "Manual de DevOps", me deparei com um capítulo dedicado à telemetria em DevOps que expandiu significativamente minha compreensão sobre o tema. O autor apresenta a telemetria como um pilar fundamental da computação moderna...]]></description><link>https://joaochiroli.com.br/do-desvio-padrao-ao-kolmogorov-smirnov-tecnicas-para-analise-de-telemetria</link><guid isPermaLink="true">https://joaochiroli.com.br/do-desvio-padrao-ao-kolmogorov-smirnov-tecnicas-para-analise-de-telemetria</guid><category><![CDATA[technology]]></category><category><![CDATA[telemetry]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[OTP]]></category><category><![CDATA[Devops]]></category><category><![CDATA[SRE]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[observability]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Tue, 07 Oct 2025 18:02:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Vk0fxDodnnc/upload/61fdc2b89c12b9a22e5e5f5bb5d792ec.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recentemente, ao ler o livro "Manual de DevOps", me deparei com um capítulo dedicado à telemetria em DevOps que expandiu significativamente minha compreensão sobre o tema. O autor apresenta a telemetria como um pilar fundamental da computação moderna, e seus argumentos são convincentes.</p>
<p>A telemetria tornou-se essencial para garantir a confiabilidade e a performance dos sistemas atuais. É por meio dela que conseguimos verificar se nossas pipelines de CI/CD estão executando conforme esperado, se nossas aplicações mantêm os níveis de performance adequados e, crucialmente, quando algo dá errado, é através dos dados telemétricos que identificamos rapidamente os componentes responsáveis pelo problema.</p>
<p>Além do aspecto operacional, a telemetria possui um valor estratégico muitas vezes subestimado: ela permite quantificar métricas em nível empresarial, transformando dados técnicos em insights de negócio. Mais que isso, com as técnicas adequadas de análise estatística, podemos detectar anomalias de forma proativa, antecipando problemas antes que impactem usuários ou processos críticos.</p>
<h2 id="heading-niveis-de-registro"><strong>Níveis de Registro</strong></h2>
<p>Para suportar vários modelos de uso, com dados de diferentes lugares, temos diferentes níveis de registro, dos quais alguns podem disparar alertas e outros não há necessidade:</p>
<ul>
<li><p><strong>Nível DEPURAÇÃO:</strong> Neste nível, as informações são tudo o que acontece no programa, mas frequentemente usadas durante a depuração. Frequentemente os registros da depuração são desabilitados na produção ou habilitados temporariamente durante a solução de problemas.</p>
<p>  <strong>Nível INFORMAÇÃO:</strong> As informações deste nível consistem em ações tomadas pelo usuário ou específicas do sistema (por exemplo, "iniciar transação de cartão de crédito").</p>
<p>  <strong>Nível AVISO:</strong> As informações neste nível relatam condições que podem se tornar um erro (por exemplo, uma chamada de banco de dados demorando mais do que um tempo predefinido). Elas provavelmente iniciarão um alerta e uma solução de problemas, enquanto outras mensagens de registro podem nos ajudar a entender melhor o que levou a essa condição.</p>
<p>  <strong>Nível ERRO:</strong> As informações neste nível focam em condições de erro (por exemplo, falhas de chamada de API, condições de erro interno).</p>
<p>  <strong>Nível FATAL:</strong> As informações neste nível relatam quando devemos encerrar (por exemplo, um daemon de rede não consegue vincular um soquete de rede).</p>
</li>
</ul>
<h2 id="heading-lacunas-que-a-telemetria-pode-detectar-e-ajudar-a-resolver"><strong>Lacunas que a Telemetria pode detectar e ajudar a resolver</strong></h2>
<ul>
<li><p><strong>Nível empresarial:</strong> Exemplos incluem o número de transações de vendas, lucro de transações de vendas, inscrições de usuário, taxa de rotatividade, resultados de testes A/B etc.</p>
</li>
<li><p><strong>Nível de aplicativo:</strong> Exemplos incluem tempos de transação, tempos de resposta de usuário, falhas de aplicativo etc.</p>
</li>
<li><p><strong>Nível de infraestrutura (por exemplo, banco de dados, sistema operacional, rede, armazenamento):</strong> Exemplos incluem tráfego de servidor web, carga de CPU, utilização de disco etc.</p>
</li>
<li><p><strong>Nível de software cliente (por exemplo, JavaScript no navegador cliente, aplicativo móvel):</strong> Exemplos incluem erros e falhas de aplicativo, tempos de transação medidos pelo usuário etc.</p>
<p>  <strong>Nível de pipeline de implementação:</strong> Exemplos incluem status do pipeline de build (por exemplo, vermelho ou verde para nossos vários conjuntos de testes automatizados), tempos de execução de implantação, mudança, frequências de implementação, promoções de ambiente de teste e status de ambiente.</p>
</li>
</ul>
<p>O livro destaca problemas comuns que surgem quando os dados não seguem uma distribuição gaussiana (normal). Embora o uso de médias e desvio padrão seja extremamente útil em muitos casos, essas técnicas nem sempre geram os resultados desejados para diversos conjuntos de dados. Por exemplo, você pode enfrentar um cenário em que alertas são constantemente disparados ao usar a regra dos 3 desvios padrão, gerando muitos falsos positivos.</p>
<p><strong>Entendendo o Desvio Padrão:</strong> O desvio padrão é uma medida estatística que indica o quanto os valores de um conjunto de dados se dispersam em relação à média. Em uma distribuição normal, aproximadamente 68% dos dados estão dentro de 1 desvio padrão da média, 95% dentro de 2 desvios padrão, e 99,7% dentro de 3 desvios padrão. No contexto de monitoramento, a regra dos 3 desvios padrão é frequentemente usada para identificar anomalias: valores que ultrapassam 3 desvios padrão da média são considerados outliers. No entanto, quando os dados não seguem uma distribuição normal, essa abordagem pode gerar alertas excessivos e imprecisos. <strong>Ferramentas que possuem essa técnica:</strong> Praticamente todas as ferramentas de monitoramento modernas suportam cálculo de desvio padrão.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759859147154/b2060f14-294b-4b5f-ae9f-8801bcb2b8fe.jpeg" alt class="image--center mx-auto" /></p>
<p>Agora que sabemos quais dados são relevantes para o nosso ambiente e como classificá-los em caso de falhas, o livro apresenta uma visão crucial sobre identificação de problemas que realmente expandiu minha mente. O monitoramento eficaz não se resume apenas a gerar alertas, um dos maiores desafios que muitas empresas enfrentam hoje é o volume excessivo de incidentes abertos que, na realidade, não representam problemas reais. Essa "fadiga de alertas" acaba fazendo com que a equipe de operações descarte ou ignore notificações, incluindo aquelas que sinalizam problemas genuínos.</p>
<p>O monitoramento inteligente vai além de simplesmente disparar um alerta quando a CPU ultrapassa 90%. Trata-se de identificar anomalias ou padrões de uso de recursos que historicamente não ocorriam. Por exemplo, um banco de dados que normalmente recebe 100 acessos por minuto e subitamente cai para 80 acessos pode indicar um problema, mesmo que esse valor não ultrapasse nenhum limite pré-estabelecido. O livro explora como a estatística pode nos aproximar dessa identificação de problemas reais.</p>
<p><strong>Técnica de Suavização (Smoothing):</strong> Uma das técnicas apresentadas é a "suavização", que envolve o uso de médias móveis para transformar dados ruidosos em tendências mais claras. A suavização calcula a média de cada ponto em relação a todos os outros dados dentro de uma janela de tempo específica. Isso reduz flutuações de curto prazo e destaca padrões de longo prazo. Por exemplo, em vez de analisar valores individuais de CPU a cada segundo (que podem variar drasticamente), você calcula a média dos últimos 5 ou 10 minutos, obtendo uma visão mais estável do comportamento do sistema. Essa técnica é especialmente útil para eliminar "ruído" nos dados e facilitar a identificação de tendências reais. <strong>Ferramentas que possuem essa técnica:</strong> Grafana, Prometheus, Datadog, Graphite, NewRelic, Dynatrace, InfluxDB, Elasticsearch/Kibana.</p>
<p><strong>Teste de Kolmogorov-Smirnov:</strong> Outro método abordado, utilizado em ferramentas como Grafana e Graphite, é o teste de Kolmogorov-Smirnov (K-S). Esse é um teste estatístico não-paramétrico usado para comparar duas distribuições de dados e determinar se elas são significativamente diferentes. No contexto de monitoramento, o teste K-S é particularmente útil para analisar métricas periódicas ou sazonais. Por exemplo, você pode comparar o padrão de tráfego web de hoje com o padrão de tráfego de terças-feiras anteriores. Se o teste K-S indicar uma diferença estatisticamente significativa entre as distribuições, isso pode sinalizar uma anomalia que merece investigação. Essa abordagem é mais sofisticada do que simplesmente comparar médias, pois considera a forma completa da distribuição dos dados. <strong>Ferramentas que possuem essa técnica:</strong> Grafana, Graphite, NewRelic, Dynatrace, Datadog.</p>
<p>O livro também apresenta diversos casos de uso dessas ferramentas em diferentes empresas e cenários reais, demonstrando sua aplicabilidade prática.</p>
<p>Uma ideia interessante proposta é a implementação de uma arquitetura de monitoramento inteligente: você pode usar ferramentas tradicionais como Zabbix, Prometheus ou outras para coleta de métricas. Esses dados são então enviados para uma aplicação desenvolvida em Python ou R, que realiza processamento estatístico avançado. Através dessa análise estatística, a aplicação verifica se existe uma anomalia real antes de disparar um alerta. Essa abordagem híbrida combina a robustez das ferramentas de monitoramento estabelecidas com a flexibilidade e o poder da análise estatística, resultando em alertas mais precisos e significativos, reduzindo a fadiga de alertas e melhorando a eficiência operacional.</p>
<h2 id="heading-recomendacoes-por-cenario"><strong>Recomendações por Cenário</strong></h2>
<h3 id="heading-implementacao-basica">Implementação Básica:</h3>
<pre><code class="lang-yaml"><span class="hljs-string">Prometheus</span> <span class="hljs-string">+</span> <span class="hljs-string">Grafana</span>
</code></pre>
<ul>
<li><p>Desvio padrão: ✅ Nativo</p>
</li>
<li><p>Suavização: ✅ Nativo</p>
</li>
<li><p>K-S: ⚠️ Requer integração</p>
</li>
</ul>
<h3 id="heading-analise-estatistica-avancada">Análise Estatística Avançada:</h3>
<pre><code class="lang-yaml"><span class="hljs-string">Prometheus/Zabbix</span> <span class="hljs-string">(coleta)</span> <span class="hljs-string">→</span> <span class="hljs-string">Python/R</span> <span class="hljs-string">(análise)</span> <span class="hljs-string">→</span> <span class="hljs-string">Grafana</span> <span class="hljs-string">(visualização)</span>
</code></pre>
<ul>
<li><p>Todas as três técnicas: ✅ Totalmente suportadas</p>
</li>
<li><p>Controle completo sobre algoritmos</p>
</li>
<li><p>Flexibilidade máxima</p>
</li>
</ul>
<h3 id="heading-solucao-enterprise-com-iaml">Solução Enterprise com IA/ML:</h3>
<pre><code class="lang-yaml"><span class="hljs-string">Dynatrace</span> <span class="hljs-string">ou</span> <span class="hljs-string">Datadog</span>
</code></pre>
<ul>
<li><p>Desvio padrão: ✅ Automático</p>
</li>
<li><p>Suavização: ✅ Automática</p>
</li>
<li><p>K-S ou similar: ✅ Algoritmos proprietários de ML</p>
</li>
</ul>
<h3 id="heading-para-stack-elk">Para Stack ELK:</h3>
<pre><code class="lang-yaml"><span class="hljs-string">Elasticsearch</span> <span class="hljs-string">+</span> <span class="hljs-string">Kibana</span> <span class="hljs-string">+</span> <span class="hljs-string">Machine</span> <span class="hljs-string">Learning</span>
</code></pre>
<ul>
<li><p>Todas as técnicas através de agregações e ML</p>
</li>
<li><p>Ótimo para logs e métricas combinados</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Instrumentação manual de uma aplicação Python usando Open Telemetry]]></title><description><![CDATA[Introdução

Nos dois últimos artigos publicados, exploramos a auto-instrumentação e a instrumentação utilizando o OpenTelemetry Operator no Kubernetes. Agora, vamos abordar um caso de teste prático usando a instrumentação manual do OpenTelemetry.
A p...]]></description><link>https://joaochiroli.com.br/instrumentacao-manual-de-uma-aplicacao-python-usando-open-telemetry</link><guid isPermaLink="true">https://joaochiroli.com.br/instrumentacao-manual-de-uma-aplicacao-python-usando-open-telemetry</guid><category><![CDATA[telemetry]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[observability]]></category><category><![CDATA[SRE]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Tue, 07 Oct 2025 15:04:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759777115099/34077464-2769-4679-99d4-17e64454ba84.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introducao">Introdução</h2>
<hr />
<p>Nos dois últimos artigos publicados, exploramos a auto-instrumentação e a instrumentação utilizando o OpenTelemetry Operator no Kubernetes. Agora, vamos abordar um caso de teste prático usando a instrumentação manual do OpenTelemetry.</p>
<p>A principal diferença desta abordagem em relação às anteriores é que ela nos proporciona controle total sobre os dados de telemetria da nossa aplicação. Com a instrumentação manual, conseguimos capturar apenas as métricas e traces realmente relevantes para o contexto do negócio, como por exemplo: verificar se uma função crítica está sendo executada dentro do tempo esperado, monitorar etapas específicas de um fluxo de trabalho ou adicionar contexto personalizado aos spans.</p>
<p>Essa granularidade permite uma observabilidade mais precisa e alinhada com as necessidades específicas da sua aplicação.</p>
<p>Outro ponto importante é que, na sua empresa, o backend que recebe os dados de observabilidade pode mudar ao longo do tempo. Por exemplo, você pode estar usando o Dynatrace hoje e, meses depois, migrar para o New Relic. Essa mudança normalmente exigiria um retrabalho significativo na instrumentação da aplicação. No entanto, com o OpenTelemetry, é possível instrumentar sua aplicação uma única vez e, posteriormente, apenas reconfigurar o destino dos dados (backend). Isso reduz drasticamente o retrabalho e aumenta a flexibilidade na escolha de ferramentas de observabilidade.</p>
<h2 id="heading-estrutura-basica-da-implementacao">Estrutura básica da implementação</h2>
<p>Existem dois componentes fundamentais quando falamos de instrumentação manual: <strong>API</strong> e <strong>SDK</strong>.</p>
<p><strong>API</strong>: é responsável por instrumentar o código da aplicação. Através da API, você consegue criar e manipular estruturas de observabilidade como spans, subspans (spans filhos), definir atributos personalizados, registrar eventos de erro e incrementar contadores de métricas. Em resumo, a API fornece as ferramentas para capturar os dados de telemetria diretamente no código.</p>
<p><strong>SDK</strong>: fazendo uma analogia, o SDK funciona como o "motor" do OpenTelemetry. É através dele que os dados instrumentados pela API são recebidos, processados, enriquecidos e, finalmente, exportados para o OpenTelemetry Collector ou diretamente para um backend de observabilidade.</p>
<p>Essa separação entre API e SDK segue o princípio de responsabilidade única: a API se preocupa apenas com a coleta dos dados, enquanto o SDK cuida de todo o pipeline de processamento e exportação.</p>
<p><strong>Link do repositório do projeto:</strong> <a target="_blank" href="https://github.com/joaochiroli/otel-instrumentacao-python/tree/instrumentacao-manual">htttps://github.com/joaochiroli/otel-instrumentacao-python/pull/new/instrumentacao-manual</a></p>
<h2 id="heading-passo-1-instalar-as-dependencias-necessarias">Passo 1: Instalar as dependências necessárias</h2>
<p>Primeiro, você precisa adicionar os pacotes OpenTelemetry ao seu <code>requirements.txt</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-string">flask</span> 
<span class="hljs-string">Flask-SQLAlchemy</span>
<span class="hljs-string">psycopg2-binary</span>
<span class="hljs-string">flask_sqlalchemy</span>
<span class="hljs-string">opentelemetry-api</span>
<span class="hljs-string">opentelemetry-sdk</span>
<span class="hljs-comment"># Instrumentação automática para Flask e SQLAlchemy</span>
<span class="hljs-string">opentelemetry-instrumentation-flask</span>
<span class="hljs-string">opentelemetry-instrumentation-sqlalchemy</span>
<span class="hljs-comment"># Exporters (escolha baseado no seu backend)</span>
<span class="hljs-string">opentelemetry-exporter-otlp-proto-grpc</span>  <span class="hljs-comment"># Para gRPC</span>
<span class="hljs-string">opentelemetry-exporter-otlp-proto-http</span>  <span class="hljs-comment"># Para HTTP</span>
<span class="hljs-comment"># Opcional mas recomendado</span>
<span class="hljs-string">opentelemetry-instrumentation-logging</span>
</code></pre>
<h2 id="heading-passo-2-estrutura-basica-da-instrumentacao">Passo 2: Estrutura básica da instrumentação</h2>
<p>Vou criar um arquivo separado <code>otel_</code><a target="_blank" href="http://config.py"><code>config.py</code></a> para organizar melhor:</p>
<pre><code class="lang-yaml"><span class="hljs-string">from</span> <span class="hljs-string">opentelemetry</span> <span class="hljs-string">import</span> <span class="hljs-string">trace,</span> <span class="hljs-string">metrics</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.sdk.trace</span> <span class="hljs-string">import</span> <span class="hljs-string">TracerProvider</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.sdk.trace.export</span> <span class="hljs-string">import</span> <span class="hljs-string">BatchSpanProcessor</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.sdk.metrics</span> <span class="hljs-string">import</span> <span class="hljs-string">MeterProvider</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.sdk.metrics.export</span> <span class="hljs-string">import</span> <span class="hljs-string">PeriodicExportingMetricReader</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.sdk.resources</span> <span class="hljs-string">import</span> <span class="hljs-string">Resource,</span> <span class="hljs-string">SERVICE_NAME,</span> <span class="hljs-string">SERVICE_VERSION</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.exporter.otlp.proto.grpc.trace_exporter</span> <span class="hljs-string">import</span> <span class="hljs-string">OTLPSpanExporter</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.exporter.otlp.proto.grpc.metric_exporter</span> <span class="hljs-string">import</span> <span class="hljs-string">OTLPMetricExporter</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.instrumentation.flask</span> <span class="hljs-string">import</span> <span class="hljs-string">FlaskInstrumentor</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.instrumentation.sqlalchemy</span> <span class="hljs-string">import</span> <span class="hljs-string">SQLAlchemyInstrumentor</span>
<span class="hljs-string">import</span> <span class="hljs-string">os</span>
<span class="hljs-string">import</span> <span class="hljs-string">logging</span>

<span class="hljs-string">logger</span> <span class="hljs-string">=</span> <span class="hljs-string">logging.getLogger(__name__)</span>

<span class="hljs-string">def</span> <span class="hljs-string">init_telemetry(app,</span> <span class="hljs-string">db_engine):</span>
    <span class="hljs-string">""</span><span class="hljs-string">"
    Inicializa toda a instrumentação OpenTelemetry
    "</span><span class="hljs-string">""</span>

    <span class="hljs-comment"># Passo 2.1: Criar o Resource (identifica seu serviço)</span>
    <span class="hljs-string">resource</span> <span class="hljs-string">=</span> <span class="hljs-string">Resource.create({</span>
        <span class="hljs-attr">SERVICE_NAME:</span> <span class="hljs-string">os.getenv("OTEL_SERVICE_NAME",</span> <span class="hljs-string">"flask-app"</span><span class="hljs-string">),</span>
        <span class="hljs-attr">SERVICE_VERSION:</span> <span class="hljs-string">"1.0.0"</span><span class="hljs-string">,</span>
        <span class="hljs-attr">"environment":</span> <span class="hljs-string">os.getenv("ENVIRONMENT",</span> <span class="hljs-string">"development"</span><span class="hljs-string">),</span>
        <span class="hljs-attr">"team":</span> <span class="hljs-string">"platform-team"</span>
    <span class="hljs-string">})</span>

    <span class="hljs-comment"># Passo 2.2: Configurar Traces</span>
    <span class="hljs-string">trace_provider</span> <span class="hljs-string">=</span> <span class="hljs-string">TracerProvider(resource=resource)</span>

    <span class="hljs-comment"># Configurar o exporter para traces</span>
    <span class="hljs-string">otlp_trace_exporter</span> <span class="hljs-string">=</span> <span class="hljs-string">OTLPSpanExporter(</span>
        <span class="hljs-string">endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT",</span> <span class="hljs-string">"http://localhost:4317"</span><span class="hljs-string">),</span>
        <span class="hljs-string">insecure=True</span>  <span class="hljs-comment"># Use False em produção com TLS</span>
    <span class="hljs-string">)</span>

    <span class="hljs-comment"># Adicionar o processor</span>
    <span class="hljs-string">trace_provider.add_span_processor(</span>
        <span class="hljs-string">BatchSpanProcessor(otlp_trace_exporter)</span>
    <span class="hljs-string">)</span>

    <span class="hljs-comment"># Registrar o provider globalmente</span>
    <span class="hljs-string">trace.set_tracer_provider(trace_provider)</span>

    <span class="hljs-comment"># Passo 2.3: Configurar Metrics</span>
    <span class="hljs-string">metric_reader</span> <span class="hljs-string">=</span> <span class="hljs-string">PeriodicExportingMetricReader(</span>
        <span class="hljs-string">OTLPMetricExporter(</span>
            <span class="hljs-string">endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT",</span> <span class="hljs-string">"http://localhost:4317"</span><span class="hljs-string">),</span>
            <span class="hljs-string">insecure=True</span>
        <span class="hljs-string">),</span>
        <span class="hljs-string">export_interval_millis=60000</span>  <span class="hljs-comment"># Exporta a cada 60 segundos</span>
    <span class="hljs-string">)</span>

    <span class="hljs-string">metric_provider</span> <span class="hljs-string">=</span> <span class="hljs-string">MeterProvider(</span>
        <span class="hljs-string">resource=resource,</span>
        <span class="hljs-string">metric_readers=[metric_reader]</span>
    <span class="hljs-string">)</span>

    <span class="hljs-string">metrics.set_meter_provider(metric_provider)</span>

    <span class="hljs-comment"># Passo 2.4: Instrumentação automática</span>
    <span class="hljs-string">FlaskInstrumentor().instrument_app(app)</span>
    <span class="hljs-string">SQLAlchemyInstrumentor().instrument(engine=db_engine)</span>

    <span class="hljs-string">logger.info("OpenTelemetry</span> <span class="hljs-string">instrumentation</span> <span class="hljs-string">initialized</span> <span class="hljs-string">successfully")</span>

    <span class="hljs-string">return</span> <span class="hljs-string">trace.get_tracer(__name__),</span> <span class="hljs-string">metrics.get_meter(__name__)</span>
</code></pre>
<h2 id="heading-passo-3-modificar-o-apppyhttpapppy-para-usar-a-instrumentacao">Passo 3: Modificar o <a target="_blank" href="http://app.py">app.py</a> para usar a instrumentação</h2>
<p>Aqui está como integrar no seu código:</p>
<pre><code class="lang-yaml"><span class="hljs-string">import</span> <span class="hljs-string">logging</span>
<span class="hljs-string">import</span> <span class="hljs-string">os</span>
<span class="hljs-string">from</span> <span class="hljs-string">flask</span> <span class="hljs-string">import</span> <span class="hljs-string">Flask,</span> <span class="hljs-string">request,</span> <span class="hljs-string">jsonify</span>
<span class="hljs-string">from</span> <span class="hljs-string">flask_sqlalchemy</span> <span class="hljs-string">import</span> <span class="hljs-string">SQLAlchemy</span>
<span class="hljs-string">from</span> <span class="hljs-string">datetime</span> <span class="hljs-string">import</span> <span class="hljs-string">datetime</span>
<span class="hljs-string">import</span> <span class="hljs-string">sys</span>
<span class="hljs-string">from</span> <span class="hljs-string">sqlalchemy.sql</span> <span class="hljs-string">import</span> <span class="hljs-string">text</span>

<span class="hljs-comment"># ============================================</span>
<span class="hljs-comment"># IMPORTAR OpenTelemetry</span>
<span class="hljs-comment"># ============================================</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry</span> <span class="hljs-string">import</span> <span class="hljs-string">trace</span>
<span class="hljs-string">from</span> <span class="hljs-string">opentelemetry.trace</span> <span class="hljs-string">import</span> <span class="hljs-string">Status,</span> <span class="hljs-string">StatusCode</span>

<span class="hljs-string">app</span> <span class="hljs-string">=</span> <span class="hljs-string">Flask(__name__)</span>

<span class="hljs-comment"># Configuração do PostgreSQL via variáveis de ambiente</span>
<span class="hljs-string">DB_USER</span> <span class="hljs-string">=</span> <span class="hljs-string">os.getenv('POSTGRES_USER')</span>
<span class="hljs-string">DB_PASSWORD</span> <span class="hljs-string">=</span> <span class="hljs-string">os.getenv('POSTGRES_PASSWORD')</span>
<span class="hljs-string">DB_HOST</span> <span class="hljs-string">=</span> <span class="hljs-string">os.getenv('POSTGRES_HOST')</span>
<span class="hljs-string">DB_PORT</span> <span class="hljs-string">=</span> <span class="hljs-string">os.getenv('POSTGRES_PORT')</span>
<span class="hljs-string">DB_NAME</span> <span class="hljs-string">=</span> <span class="hljs-string">os.getenv('POSTGRES_DB')</span>

<span class="hljs-string">DATABASE_URL</span> <span class="hljs-string">=</span> <span class="hljs-string">f'postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}'</span>

<span class="hljs-string">app.config['SQLALCHEMY_DATABASE_URI']</span> <span class="hljs-string">=</span> <span class="hljs-string">DATABASE_URL</span>
<span class="hljs-string">app.config['SQLALCHEMY_TRACK_MODIFICATIONS']</span> <span class="hljs-string">=</span> <span class="hljs-literal">False</span>

<span class="hljs-comment"># Configure logging</span>
<span class="hljs-string">logging.basicConfig(</span>
    <span class="hljs-string">level=logging.INFO,</span>
    <span class="hljs-string">format='%(asctime)s</span> <span class="hljs-bullet">-</span> <span class="hljs-string">%(name)s</span> <span class="hljs-bullet">-</span> <span class="hljs-string">%(levelname)s</span> <span class="hljs-bullet">-</span> <span class="hljs-string">%(message)s',</span>
    <span class="hljs-string">handlers=[</span>
        <span class="hljs-string">logging.FileHandler('app.log'),</span>
        <span class="hljs-string">logging.StreamHandler(sys.stdout)</span>
    <span class="hljs-string">]</span>
<span class="hljs-string">)</span>

<span class="hljs-string">logger</span> <span class="hljs-string">=</span> <span class="hljs-string">logging.getLogger(__name__)</span>
<span class="hljs-string">logger.info(f"Database</span> <span class="hljs-attr">configuration:</span> <span class="hljs-string">Host={DB_HOST},</span> <span class="hljs-string">Port={DB_PORT},</span> <span class="hljs-string">DB={DB_NAME},</span> <span class="hljs-string">User={DB_USER}")</span>

<span class="hljs-comment"># Inicializar o banco de dados</span>
<span class="hljs-string">db</span> <span class="hljs-string">=</span> <span class="hljs-string">SQLAlchemy(app)</span>

<span class="hljs-comment"># ============================================</span>
<span class="hljs-comment"># MODELS (definir antes de init_telemetry)</span>
<span class="hljs-comment"># ============================================</span>
<span class="hljs-string">class</span> <span class="hljs-string">User(db.Model):</span>
    <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.Integer,</span> <span class="hljs-string">primary_key=True)</span>
    <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.String(100),</span> <span class="hljs-string">nullable=False)</span>
    <span class="hljs-string">created_at</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.DateTime,</span> <span class="hljs-string">default=datetime.utcnow)</span>

    <span class="hljs-string">def</span> <span class="hljs-string">to_dict(self):</span>
        <span class="hljs-string">return</span> {
            <span class="hljs-attr">'id':</span> <span class="hljs-string">self.id</span>,
            <span class="hljs-attr">'name':</span> <span class="hljs-string">self.name</span>,
            <span class="hljs-attr">'created_at':</span> <span class="hljs-string">self.created_at.isoformat()</span>
        }

<span class="hljs-string">class</span> <span class="hljs-string">Message(db.Model):</span>
    <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.Integer,</span> <span class="hljs-string">primary_key=True)</span>
    <span class="hljs-string">message</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.Text,</span> <span class="hljs-string">nullable=False)</span>
    <span class="hljs-string">ip_address</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.String(50))</span>
    <span class="hljs-string">created_at</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.DateTime,</span> <span class="hljs-string">default=datetime.utcnow)</span>

    <span class="hljs-string">def</span> <span class="hljs-string">to_dict(self):</span>
        <span class="hljs-string">return</span> {
            <span class="hljs-attr">'id':</span> <span class="hljs-string">self.id</span>,
            <span class="hljs-attr">'message':</span> <span class="hljs-string">self.message</span>,
            <span class="hljs-attr">'ip_address':</span> <span class="hljs-string">self.ip_address</span>,
            <span class="hljs-attr">'created_at':</span> <span class="hljs-string">self.created_at.isoformat()</span>
        }

<span class="hljs-string">class</span> <span class="hljs-string">RequestLog(db.Model):</span>
    <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.Integer,</span> <span class="hljs-string">primary_key=True)</span>
    <span class="hljs-string">method</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.String(10),</span> <span class="hljs-string">nullable=False)</span>
    <span class="hljs-string">endpoint</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.String(200),</span> <span class="hljs-string">nullable=False)</span>
    <span class="hljs-string">ip_address</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.String(50))</span>
    <span class="hljs-string">status_code</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.Integer)</span>
    <span class="hljs-string">created_at</span> <span class="hljs-string">=</span> <span class="hljs-string">db.Column(db.DateTime,</span> <span class="hljs-string">default=datetime.utcnow)</span>

    <span class="hljs-string">def</span> <span class="hljs-string">to_dict(self):</span>
        <span class="hljs-string">return</span> {
            <span class="hljs-attr">'id':</span> <span class="hljs-string">self.id</span>,
            <span class="hljs-attr">'method':</span> <span class="hljs-string">self.method</span>,
            <span class="hljs-attr">'endpoint':</span> <span class="hljs-string">self.endpoint</span>,
            <span class="hljs-attr">'ip_address':</span> <span class="hljs-string">self.ip_address</span>,
            <span class="hljs-attr">'status_code':</span> <span class="hljs-string">self.status_code</span>,
            <span class="hljs-attr">'created_at':</span> <span class="hljs-string">self.created_at.isoformat()</span>
        }

<span class="hljs-comment"># ============================================</span>
<span class="hljs-comment"># INICIALIZAR TELEMETRY DENTRO DO CONTEXTO</span>
<span class="hljs-comment"># ============================================</span>
<span class="hljs-string">from</span> <span class="hljs-string">otel_config</span> <span class="hljs-string">import</span> <span class="hljs-string">init_telemetry</span>

<span class="hljs-comment"># CRITICAL FIX: Inicializar dentro do app context</span>
<span class="hljs-string">with</span> <span class="hljs-string">app.app_context():</span>
    <span class="hljs-string">tracer,</span> <span class="hljs-string">meter</span> <span class="hljs-string">=</span> <span class="hljs-string">init_telemetry(app,</span> <span class="hljs-string">db.engine)</span>

    <span class="hljs-comment"># Criar tabelas</span>
    <span class="hljs-attr">try:</span>
        <span class="hljs-string">db.create_all()</span>
        <span class="hljs-string">logger.info("Database</span> <span class="hljs-string">tables</span> <span class="hljs-string">created</span> <span class="hljs-string">successfully")</span>
    <span class="hljs-attr">except Exception as e:</span>
        <span class="hljs-string">logger.error(f"Error</span> <span class="hljs-attr">creating database tables:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")

# Criar métricas customizadas (fora do context é ok)
request_counter = meter.create_counter(
    name="</span><span class="hljs-string">http_requests_total",</span>
    <span class="hljs-string">description="Total</span> <span class="hljs-string">de</span> <span class="hljs-string">requisições</span> <span class="hljs-string">HTTP",</span>
    <span class="hljs-string">unit="1"</span>
<span class="hljs-string">)</span>

<span class="hljs-string">message_counter</span> <span class="hljs-string">=</span> <span class="hljs-string">meter.create_counter(</span>
    <span class="hljs-string">name="messages_created_total",</span>
    <span class="hljs-string">description="Total</span> <span class="hljs-string">de</span> <span class="hljs-string">mensagens</span> <span class="hljs-string">criadas",</span>
    <span class="hljs-string">unit="1"</span>
<span class="hljs-string">)</span>

<span class="hljs-string">user_counter</span> <span class="hljs-string">=</span> <span class="hljs-string">meter.create_counter(</span>
    <span class="hljs-string">name="users_created_total",</span>
    <span class="hljs-string">description="Total</span> <span class="hljs-string">de</span> <span class="hljs-string">usuários</span> <span class="hljs-string">criados",</span>
    <span class="hljs-string">unit="1"</span>
<span class="hljs-string">)</span>

<span class="hljs-comment"># ============================================</span>
<span class="hljs-comment"># ROUTES</span>
<span class="hljs-comment"># ============================================</span>

<span class="hljs-string">@app.route('/')</span>
<span class="hljs-string">def</span> <span class="hljs-string">hello():</span>
    <span class="hljs-string">logger.info("Hello</span> <span class="hljs-string">endpoint</span> <span class="hljs-string">accessed")</span>
    <span class="hljs-string">return</span> <span class="hljs-string">"Hello, World!"</span>

<span class="hljs-string">@app.route('/user/&lt;name&gt;')</span>
<span class="hljs-string">def</span> <span class="hljs-string">get_user(name):</span>
    <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("get_user_operation")</span> <span class="hljs-attr">as span:</span>
        <span class="hljs-string">span.set_attribute("user.name",</span> <span class="hljs-string">name)</span>
        <span class="hljs-string">span.set_attribute("user.name.length",</span> <span class="hljs-string">len(name))</span>

        <span class="hljs-string">logger.info(f"User</span> <span class="hljs-attr">endpoint accessed for:</span> {<span class="hljs-string">name</span>}<span class="hljs-string">")

        try:
            if len(name) &lt; 2:
                span.set_status(Status(StatusCode.ERROR, "</span><span class="hljs-string">Name</span> <span class="hljs-string">too</span> <span class="hljs-string">short"))</span>
                <span class="hljs-string">raise</span> <span class="hljs-string">ValueError("Name</span> <span class="hljs-string">too</span> <span class="hljs-string">short")</span>

            <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("database.check_user"):</span>
                <span class="hljs-string">user</span> <span class="hljs-string">=</span> <span class="hljs-string">User.query.filter_by(name=name).first()</span>

            <span class="hljs-attr">if not user:</span>
                <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("database.create_user"):</span>
                    <span class="hljs-string">user</span> <span class="hljs-string">=</span> <span class="hljs-string">User(name=name)</span>
                    <span class="hljs-string">db.session.add(user)</span>
                    <span class="hljs-string">db.session.commit()</span>
                    <span class="hljs-string">logger.info(f"New</span> <span class="hljs-attr">user created:</span> {<span class="hljs-string">name</span>}<span class="hljs-string">")

                    user_counter.add(1, {"</span><span class="hljs-string">operation":</span> <span class="hljs-string">"create"</span><span class="hljs-string">})</span>
                    <span class="hljs-string">span.add_event("user_created",</span> {<span class="hljs-attr">"user_id":</span> <span class="hljs-string">user.id</span>}<span class="hljs-string">)</span>

            <span class="hljs-string">span.set_attribute("user.id",</span> <span class="hljs-string">user.id)</span>
            <span class="hljs-string">span.set_status(Status(StatusCode.OK))</span>

            <span class="hljs-string">return</span> <span class="hljs-string">jsonify({</span>
                <span class="hljs-attr">"message":</span> <span class="hljs-string">f"Hello,</span> {<span class="hljs-string">name</span>}<span class="hljs-string">!",</span>
                <span class="hljs-attr">"user":</span> <span class="hljs-string">user.to_dict()</span>
            <span class="hljs-string">})</span>

        <span class="hljs-attr">except ValueError as e:</span>
            <span class="hljs-string">logger.error(f"Validation</span> <span class="hljs-attr">error in get_user:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")
            return jsonify({"</span><span class="hljs-string">error":</span> <span class="hljs-string">"Name must be at least 2 characters"</span><span class="hljs-string">}),</span> <span class="hljs-number">400</span>
        <span class="hljs-attr">except Exception as e:</span>
            <span class="hljs-string">span.set_status(Status(StatusCode.ERROR,</span> <span class="hljs-string">str(e)))</span>
            <span class="hljs-string">span.record_exception(e)</span>
            <span class="hljs-string">logger.error(f"Database</span> <span class="hljs-attr">error in get_user:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")
            return jsonify({"</span><span class="hljs-string">error":</span> <span class="hljs-string">"Internal server error"</span><span class="hljs-string">}),</span> <span class="hljs-number">500</span>

<span class="hljs-string">@app.route('/submit',</span> <span class="hljs-string">methods=['POST'])</span>
<span class="hljs-string">def</span> <span class="hljs-string">submit_data():</span>
    <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("submit_data_operation")</span> <span class="hljs-attr">as span:</span>
        <span class="hljs-string">logger.info("Submit</span> <span class="hljs-string">endpoint</span> <span class="hljs-string">accessed")</span>

        <span class="hljs-attr">try:</span>
            <span class="hljs-string">data</span> <span class="hljs-string">=</span> <span class="hljs-string">request.get_json()</span>

            <span class="hljs-attr">if not data:</span>
                <span class="hljs-string">span.set_status(Status(StatusCode.ERROR,</span> <span class="hljs-string">"No JSON data"</span><span class="hljs-string">))</span>
                <span class="hljs-string">return</span> <span class="hljs-string">jsonify({"status":</span> <span class="hljs-string">"error"</span><span class="hljs-string">,</span> <span class="hljs-attr">"message":</span> <span class="hljs-string">"No JSON data received"</span><span class="hljs-string">}),</span> <span class="hljs-number">400</span>

            <span class="hljs-string">if</span> <span class="hljs-string">'message'</span> <span class="hljs-attr">not in data:</span>
                <span class="hljs-string">span.set_status(Status(StatusCode.ERROR,</span> <span class="hljs-string">"Missing message field"</span><span class="hljs-string">))</span>
                <span class="hljs-string">return</span> <span class="hljs-string">jsonify({"status":</span> <span class="hljs-string">"error"</span><span class="hljs-string">,</span> <span class="hljs-attr">"message":</span> <span class="hljs-string">"Missing 'message' field"</span><span class="hljs-string">}),</span> <span class="hljs-number">400</span>

            <span class="hljs-string">span.set_attribute("message.length",</span> <span class="hljs-string">len(data['message']))</span>
            <span class="hljs-string">span.set_attribute("request.ip",</span> <span class="hljs-string">request.remote_addr)</span>

            <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("database.insert_message"):</span>
                <span class="hljs-string">message_entry</span> <span class="hljs-string">=</span> <span class="hljs-string">Message(</span>
                    <span class="hljs-string">message=data['message'],</span>
                    <span class="hljs-string">ip_address=request.remote_addr</span>
                <span class="hljs-string">)</span>
                <span class="hljs-string">db.session.add(message_entry)</span>
                <span class="hljs-string">db.session.commit()</span>

                <span class="hljs-string">span.set_attribute("message.id",</span> <span class="hljs-string">message_entry.id)</span>

            <span class="hljs-string">message_counter.add(1,</span> {<span class="hljs-attr">"source":</span> <span class="hljs-string">"api"</span>}<span class="hljs-string">)</span>

            <span class="hljs-string">span.add_event("message_saved",</span> {
                <span class="hljs-attr">"message_id":</span> <span class="hljs-string">message_entry.id</span>,
                <span class="hljs-attr">"timestamp":</span> <span class="hljs-string">datetime.now().isoformat()</span>
            }<span class="hljs-string">)</span>

            <span class="hljs-string">span.set_status(Status(StatusCode.OK))</span>

            <span class="hljs-string">logger.info(f"Successfully</span> <span class="hljs-attr">processed and saved message:</span> {<span class="hljs-string">data</span>[<span class="hljs-string">'message'</span>]}<span class="hljs-string">")
            return jsonify({
                "</span><span class="hljs-string">status":</span> <span class="hljs-string">"success"</span><span class="hljs-string">,</span>
                <span class="hljs-attr">"received_message":</span> <span class="hljs-string">data['message'],</span>
                <span class="hljs-attr">"message_id":</span> <span class="hljs-string">message_entry.id,</span>
                <span class="hljs-attr">"response":</span> <span class="hljs-string">"Data received and saved successfully!"</span><span class="hljs-string">,</span>
                <span class="hljs-attr">"timestamp":</span> <span class="hljs-string">datetime.now().isoformat()</span>
            <span class="hljs-string">})</span>

        <span class="hljs-attr">except Exception as e:</span>
            <span class="hljs-string">span.set_status(Status(StatusCode.ERROR,</span> <span class="hljs-string">str(e)))</span>
            <span class="hljs-string">span.record_exception(e)</span>
            <span class="hljs-string">logger.error(f"Unexpected</span> <span class="hljs-attr">error in submit_data:</span> {<span class="hljs-string">e</span>}<span class="hljs-string">")
            db.session.rollback()
            return jsonify({"</span><span class="hljs-string">status":</span> <span class="hljs-string">"error"</span><span class="hljs-string">,</span> <span class="hljs-attr">"message":</span> <span class="hljs-string">"Internal server error"</span><span class="hljs-string">}),</span> <span class="hljs-number">500</span>

<span class="hljs-string">@app.before_request</span>
<span class="hljs-string">def</span> <span class="hljs-string">before_request_telemetry():</span>
    <span class="hljs-string">request_counter.add(1,</span> {
        <span class="hljs-attr">"method":</span> <span class="hljs-string">request.method</span>,
        <span class="hljs-attr">"endpoint":</span> <span class="hljs-string">request.endpoint</span> <span class="hljs-string">or</span> <span class="hljs-string">"unknown"</span>
    }<span class="hljs-string">)</span>

<span class="hljs-string">@app.route('/health')</span>
<span class="hljs-string">def</span> <span class="hljs-string">health_check():</span>
    <span class="hljs-string">logger.info("Health</span> <span class="hljs-string">check</span> <span class="hljs-string">accessed")</span>
    <span class="hljs-attr">try:</span>
        <span class="hljs-string">db.session.execute(text('SELECT</span> <span class="hljs-number">1</span><span class="hljs-string">'))
        db_status = "connected"
        user_count = User.query.count()
        message_count = Message.query.count()
        log_count = RequestLog.query.count()
    except Exception as e:
        db_status = f"error: {str(e)}"
        user_count = message_count = log_count = 0

    return jsonify({
        "status": "healthy",
        "database": db_status,
        "database_stats": {
            "users": user_count,
            "messages": message_count,
            "request_logs": log_count
        },
        "timestamp": datetime.now().isoformat(),
        "version": "1.0.0"
    })

if __name__ == '</span><span class="hljs-string">__main__':</span>
    <span class="hljs-string">host</span> <span class="hljs-string">=</span> <span class="hljs-string">os.getenv('FLASK_HOST',</span> <span class="hljs-string">'0.0.0.0'</span><span class="hljs-string">)</span>
    <span class="hljs-string">port</span> <span class="hljs-string">=</span> <span class="hljs-string">int(os.getenv('FLASK_PORT',</span> <span class="hljs-string">'5000'</span><span class="hljs-string">))</span>

    <span class="hljs-string">logger.info(f"Starting</span> <span class="hljs-string">Flask</span> <span class="hljs-string">application</span> <span class="hljs-string">on</span> {<span class="hljs-string">host</span>}<span class="hljs-string">:{port}...")</span>
    <span class="hljs-string">app.run(debug=False,</span> <span class="hljs-string">host=host,</span> <span class="hljs-string">port=port)</span>
</code></pre>
<p>Os dados podem ser vistos acessando o Jaeger</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759848663840/e1baa2cf-8b22-47f9-bb52-5a687daaa48c.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-outros-arquivos-usados-no-projeto">Outros arquivos usados no projeto</h2>
<p><code>Dockerfile</code></p>
<pre><code class="lang-yaml"><span class="hljs-string">FROM</span> <span class="hljs-string">python:3.9-slim</span>

<span class="hljs-comment"># Defina o diretório de trabalho</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">/app</span>

<span class="hljs-comment"># Copie os arquivos necessários para o diretório de trabalho</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">.</span> <span class="hljs-string">/app</span>

<span class="hljs-comment"># Instale as dependências</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">-r</span> <span class="hljs-string">requirements.txt</span>

<span class="hljs-comment"># Configura o OTLP</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">opentelemetry-bootstrap</span> <span class="hljs-string">-a</span> <span class="hljs-string">install</span>

<span class="hljs-comment"># Exponha a porta que a aplicação vai rodar</span>
<span class="hljs-string">EXPOSE</span> <span class="hljs-number">5000</span>

<span class="hljs-comment"># Comando para rodar a aplicação</span>
<span class="hljs-string">CMD</span> [<span class="hljs-string">"opentelemetry-instrument"</span>, <span class="hljs-string">"python"</span>, <span class="hljs-string">"app.py"</span>]
</code></pre>
<p><code>Docker-compose</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:14.15</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">postgres-db</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5432:5432"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">simple_app</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres_data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">healthcheck:</span> <span class="hljs-comment"># verifica se o db esta pronto para os outros servicos</span>
      <span class="hljs-attr">test:</span> [<span class="hljs-string">"CMD-SHELL"</span>, <span class="hljs-string">"pg_isready -U postgres"</span>]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">10</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">flask-app</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5000:5000"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_HOST:</span> <span class="hljs-string">db</span>
      <span class="hljs-attr">POSTGRES_PORT:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">simple_app</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">FLASK_ENV:</span> <span class="hljs-string">development</span>
      <span class="hljs-attr">FLASK_DEBUG:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_SERVICE_NAME:</span> <span class="hljs-string">flask-api</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_ENDPOINT:</span> <span class="hljs-string">otel-collector:4317</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_PROTOCOL:</span> <span class="hljs-string">grpc</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_INSECURE:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_TRACES_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_METRICS_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_LOGS_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_LOG_CORRELATION:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_FLASK_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_SQLALCHEMY_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_REQUESTS_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_TRACES_SAMPLER:</span> <span class="hljs-string">always_on</span>
      <span class="hljs-attr">OTEL_METRICS_SAMPLER:</span> <span class="hljs-string">always_on</span>
      <span class="hljs-comment"># OTEL_LOG_LEVEL: DEBUG</span>
      <span class="hljs-comment"># OTEL_TRACES_SAMPLER: "always_on"</span>
      <span class="hljs-comment"># OTEL_TRACES_SAMPLER_ARG: "1.0"</span>
      <span class="hljs-comment"># OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: ""</span>
      <span class="hljs-comment"># OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: ".*"</span>
      <span class="hljs-comment"># OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: ".*"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">db:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>

  <span class="hljs-attr">otel-collector:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">otel/opentelemetry-collector-contrib:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">otel-collector</span>
    <span class="hljs-attr">command:</span> [<span class="hljs-string">"--config=/etc/otel-collector-config.yaml"</span>]
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./otel-collector-config.yaml:/etc/otel-collector-config.yaml</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4317:4317"</span> <span class="hljs-comment"># OTLP gRPC receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4318:4318"</span> <span class="hljs-comment"># OTLP HTTP receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8888:8888"</span> <span class="hljs-comment"># Prometheus metrics</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8889:8889"</span> <span class="hljs-comment"># Prometheus exporter metrics</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>

  <span class="hljs-attr">jaeger:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jaegertracing/all-in-one:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">jaeger</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">COLLECTOR_OTLP_ENABLED=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LOG_LEVEL=debug</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"16686:16686"</span> <span class="hljs-comment"># Jaeger UI</span>
      <span class="hljs-comment"># - "4317:4317" # OTLP gRPC receiver</span>
      <span class="hljs-comment"># - "4318:4318" # OTLP HTTP receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"14250:14250"</span> <span class="hljs-comment"># Jaeger gRPC</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"14268:14268"</span> <span class="hljs-comment"># Jaeger HTTP</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>

  <span class="hljs-attr">prometheus:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">prom/prometheus:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">prometheus</span>
    <span class="hljs-attr">command:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--config.file=/etc/prometheus/prometheus.yml"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--storage.tsdb.path=/prometheus"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--web.console.libraries=/usr/share/prometheus/console_libraries"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--web.console.templates=/usr/share/prometheus/consoles"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./prometheus.yml:/etc/prometheus/prometheus.yml</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">prometheus-data:/prometheus</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9090:9090"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">otel-collector</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">postgres_data:</span>
  <span class="hljs-attr">prometheus-data:</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">app-network:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p><code>otel-collector-config.yaml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">receivers:</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">protocols:</span>
      <span class="hljs-attr">grpc:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4317</span>
      <span class="hljs-attr">http:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4318</span>

<span class="hljs-attr">processors:</span>
  <span class="hljs-attr">batch:</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
    <span class="hljs-attr">send_batch_size:</span> <span class="hljs-number">1024</span>

<span class="hljs-attr">exporters:</span>
  <span class="hljs-attr">debug:</span>
    <span class="hljs-attr">verbosity:</span> <span class="hljs-string">detailed</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">jaeger:4317</span>
    <span class="hljs-attr">tls:</span>
      <span class="hljs-attr">insecure:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">prometheus:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:8889</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">flask_app</span>

<span class="hljs-attr">service:</span>
  <span class="hljs-attr">pipelines:</span>
    <span class="hljs-attr">traces:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>, <span class="hljs-string">otlp</span>]
    <span class="hljs-attr">metrics:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>, <span class="hljs-string">prometheus</span>]
    <span class="hljs-attr">logs:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>]
</code></pre>
<p>Pra executar o projeto basta baixar o projeto do Github ou então criar os arquivos acima e executar um <code>docker compose up</code></p>
<h2 id="heading-ia"> </h2>
<p>Fluxo do projeto</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759847686922/8b5f7917-436b-4ccb-a406-dc91526973ea.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-a-ligacao-entre-os-arquivos">A Ligação Entre os Arquivos</h2>
<h3 id="heading-1-otelconfigpyhttpconfigpy-o-configurador">1. <code>otel_</code><a target="_blank" href="http://config.py"><code>config.py</code></a> - O Configurador</h3>
<p>Este arquivo contém a função <code>init_telemetry()</code> que:</p>
<ul>
<li><p>Configura o OpenTelemetry SDK</p>
</li>
<li><p>Define para onde enviar os traces (exporters)</p>
</li>
<li><p>Instrumenta automaticamente Flask e SQLAlchemy</p>
</li>
<li><p>Retorna o <code>tracer</code> e <code>meter</code> configurados</p>
</li>
</ul>
<h3 id="heading-2-apppyhttpapppy-o-consumidor">2. <a target="_blank" href="http://app.py"><code>app.py</code></a> - O Consumidor</h3>
<p>Importa e usa a configuração:</p>
<p>python</p>
<pre><code class="lang-python"><span class="hljs-comment"># IMPORTA a configuração</span>
<span class="hljs-keyword">from</span> otel_config <span class="hljs-keyword">import</span> init_telemetry

<span class="hljs-comment"># INICIALIZA a telemetria passando app Flask e DB engine</span>
<span class="hljs-keyword">with</span> app.app_context():
    tracer, meter = init_telemetry(app, db.engine)
</code></pre>
<h2 id="heading-onde-os-traces-sao-gerados">📍 Onde os Traces São Gerados</h2>
<h3 id="heading-traces-automaticos-configurados-no-otelconfigpyhttpconfigpy"><strong>Traces Automáticos</strong> (configurados no <code>otel_</code><a target="_blank" href="http://config.py"><code>config.py</code></a>):</h3>
<ol>
<li><p><strong>HTTP Requests</strong> - Todas as rotas Flask são automaticamente rastreadas</p>
</li>
<li><p><strong>SQL Queries</strong> - Todas as queries do SQLAlchemy são automaticamente rastreadas</p>
</li>
</ol>
<h3 id="heading-traces-manuais-criados-no-apppyhttpapppy"><strong>Traces Manuais</strong> (criados no <a target="_blank" href="http://app.py"><code>app.py</code></a>):</h3>
<p>python</p>
<pre><code class="lang-python"><span class="hljs-comment"># Exemplo na rota /user/&lt;name&gt;</span>
<span class="hljs-keyword">with</span> tracer.start_as_current_span(<span class="hljs-string">"get_user_operation"</span>) <span class="hljs-keyword">as</span> span:
    span.set_attribute(<span class="hljs-string">"user.name"</span>, name)  <span class="hljs-comment"># Adiciona metadados</span>

    <span class="hljs-comment"># Span filho para operação específica</span>
    <span class="hljs-keyword">with</span> tracer.start_as_current_span(<span class="hljs-string">"database.check_user"</span>):
        user = User.query.filter_by(name=name).first()
</code></pre>
<h2 id="heading-o-fluxo-completo">🔄 O Fluxo Completo</h2>
<pre><code class="lang-yaml"><span class="hljs-number">1</span><span class="hljs-string">.</span> <span class="hljs-string">Request</span> <span class="hljs-string">chega</span> <span class="hljs-string">→</span> <span class="hljs-string">Flask</span> <span class="hljs-string">(instrumentado</span> <span class="hljs-string">automaticamente)</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">2</span><span class="hljs-string">.</span> <span class="hljs-string">Cria</span> <span class="hljs-string">span</span> <span class="hljs-string">root</span> <span class="hljs-string">automático</span> <span class="hljs-string">"HTTP GET /user/john"</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">3</span><span class="hljs-string">.</span> <span class="hljs-string">Seu</span> <span class="hljs-string">código</span> <span class="hljs-string">cria</span> <span class="hljs-string">span</span> <span class="hljs-string">filho</span> <span class="hljs-string">"get_user_operation"</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">4</span><span class="hljs-string">.</span> <span class="hljs-string">Dentro</span> <span class="hljs-string">dele,</span> <span class="hljs-string">cria</span> <span class="hljs-string">span</span> <span class="hljs-string">neto</span> <span class="hljs-string">"database.check_user"</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">5</span><span class="hljs-string">.</span> <span class="hljs-string">SQLAlchemy</span> <span class="hljs-string">(instrumentado)</span> <span class="hljs-string">cria</span> <span class="hljs-string">span</span> <span class="hljs-string">"SELECT FROM users..."</span>
   <span class="hljs-string">↓</span>
<span class="hljs-number">6</span><span class="hljs-string">.</span> <span class="hljs-string">Todos</span> <span class="hljs-string">os</span> <span class="hljs-string">spans</span> <span class="hljs-string">são</span> <span class="hljs-string">enviados</span> <span class="hljs-string">para</span> <span class="hljs-string">o</span> <span class="hljs-string">coletor</span> <span class="hljs-string">configurado</span>
</code></pre>
<h2 id="heading-para-onde-vao-os-traces">🎯 Para Onde Vão os Traces</h2>
<p>O <code>otel_</code><a target="_blank" href="http://config.py"><code>config.py</code></a> provavelmente configura um exporter que envia para:</p>
<ul>
<li><p><strong>Jaeger</strong> (porta 4317 ou 14250)</p>
</li>
<li><p><strong>OpenTelemetry Collector</strong> (porta 4317/4318)</p>
</li>
<li><p><strong>Zipkin</strong> (porta 9411)</p>
</li>
<li><p>Ou outro backend de observabilidade</p>
</li>
</ul>
<h2 id="heading-estrutura-da-telemetria">📊 Estrutura da Telemetria</h2>
<pre><code class="lang-yaml"><span class="hljs-string">Trace</span> <span class="hljs-string">(representa</span> <span class="hljs-string">toda</span> <span class="hljs-string">a</span> <span class="hljs-string">requisição)</span>
<span class="hljs-string">├──</span> <span class="hljs-attr">Span:</span> <span class="hljs-string">HTTP</span> <span class="hljs-string">GET</span> <span class="hljs-string">/user/john</span> [<span class="hljs-string">automático</span>]
<span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-attr">Span:</span> <span class="hljs-string">get_user_operation</span> [<span class="hljs-string">manual</span>]
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">├──</span> <span class="hljs-attr">Span:</span> <span class="hljs-string">database.check_user</span> [<span class="hljs-string">manual</span>]
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-attr">Span:</span> <span class="hljs-string">SELECT</span> <span class="hljs-string">*</span> <span class="hljs-string">FROM</span> <span class="hljs-string">users</span> [<span class="hljs-string">automático</span>]
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>   <span class="hljs-string">└──</span> <span class="hljs-attr">Span:</span> <span class="hljs-string">database.create_user</span> [<span class="hljs-string">manual</span>]
<span class="hljs-string">│</span>   <span class="hljs-string">│</span>       <span class="hljs-string">└──</span> <span class="hljs-attr">Span:</span> <span class="hljs-string">INSERT</span> <span class="hljs-string">INTO</span> <span class="hljs-string">users</span> [<span class="hljs-string">automático</span>]
</code></pre>
<h2 id="heading-momentos-chave-da-complementacao">🔍 Momentos-Chave da Complementação</h2>
<ol>
<li><p><strong>Inicialização</strong> (linha ~95-96):</p>
<ul>
<li><p><code>otel_</code><a target="_blank" href="http://config.py"><code>config.py</code></a> configura COMO coletar</p>
</li>
<li><p><a target="_blank" href="http://app.py"><code>app.py</code></a> define O QUE coletar</p>
</li>
</ul>
</li>
<li><p><strong>Durante Requisições</strong>:</p>
<ul>
<li><p>Instrumentação automática captura tudo</p>
</li>
<li><p>Spans manuais adicionam contexto de negócio</p>
</li>
</ul>
</li>
<li><p><strong>Métricas Customizadas</strong>:</p>
</li>
</ol>
<p>python</p>
<pre><code class="lang-python">   message_counter.add(<span class="hljs-number">1</span>, {<span class="hljs-string">"source"</span>: <span class="hljs-string">"api"</span>})  <span class="hljs-comment"># Conta mensagens</span>
   user_counter.add(<span class="hljs-number">1</span>, {<span class="hljs-string">"operation"</span>: <span class="hljs-string">"create"</span>})  <span class="hljs-comment"># Conta usuários</span>
</code></pre>
<p>O <code>otel_</code><a target="_blank" href="http://config.py"><code>config.py</code></a> é a <strong>infraestrutura</strong> (como um sistema de câmeras), enquanto o <a target="_blank" href="http://app.py"><code>app.py</code></a> é o <strong>uso</strong> dessa infraestrutura (onde você coloca as câmeras e o que filma com elas)</p>
<h2 id="heading-exemplo-de-melhoria-no-contexto-de-metricas-e-traces-de-negocio"><strong>Exemplo de Melhoria no Contexto de Métricas e Traces de Negócio</strong></h2>
<p>Você pode enriquecer a observabilidade do seu código configurando pontos estratégicos onde deseja obter mais informações sobre o comportamento da aplicação. Isso inclui:</p>
<ul>
<li><p>Adicionar contadores customizados para rastrear eventos específicos do negócio</p>
</li>
<li><p>Implementar validadores para garantir a qualidade dos dados coletados</p>
</li>
<li><p>Otimizar o número de spans, mantendo apenas aqueles que realmente agregam valor ao troubleshooting e à análise de performance</p>
</li>
<li><p>Criar métricas de negócio que reflitam KPIs importantes para a operação</p>
</li>
</ul>
<p>A instrumentação personalizada oferece possibilidades praticamente ilimitadas para adaptar sua estratégia de observabilidade às necessidades específicas do seu contexto.</p>
<pre><code class="lang-yaml"><span class="hljs-string">@app.route('/user/&lt;name&gt;')</span>
<span class="hljs-string">def</span> <span class="hljs-string">get_user(name):</span>
    <span class="hljs-comment"># Span customizado com informações do SEU negócio</span>
    <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("get_user_operation")</span> <span class="hljs-attr">as span:</span>

        <span class="hljs-comment"># 🎯 ADICIONAR MAIS ATRIBUTOS AO SPAN</span>
        <span class="hljs-string">span.set_attribute("user.name",</span> <span class="hljs-string">name)</span>
        <span class="hljs-string">span.set_attribute("user.type",</span> <span class="hljs-string">"premium"</span><span class="hljs-string">)</span>  <span class="hljs-comment"># ✨ Nova métrica</span>
        <span class="hljs-string">span.set_attribute("request.source",</span> <span class="hljs-string">request.headers.get('User-Agent'))</span>  <span class="hljs-comment"># ✨</span>
        <span class="hljs-string">span.set_attribute("feature.flag",</span> <span class="hljs-string">"new_ui_enabled"</span><span class="hljs-string">)</span>  <span class="hljs-comment"># ✨</span>

        <span class="hljs-comment"># 🎯 EVENTOS IMPORTANTES DO NEGÓCIO</span>
        <span class="hljs-string">span.add_event("validation_started",</span> {
            <span class="hljs-attr">"validator":</span> <span class="hljs-string">"name_length"</span>,
            <span class="hljs-attr">"min_length":</span> <span class="hljs-number">2</span>
        }<span class="hljs-string">)</span>

        <span class="hljs-comment"># 🎯 SPANS FILHOS PARA OPERAÇÕES ESPECÍFICAS</span>
        <span class="hljs-string">with</span> <span class="hljs-string">tracer.start_as_current_span("cache_check")</span> <span class="hljs-attr">as cache_span:</span>
            <span class="hljs-string">cache_span.set_attribute("cache.key",</span> <span class="hljs-string">f"user:{name}")</span>
            <span class="hljs-comment"># Simular check de cache</span>
            <span class="hljs-string">cache_hit</span> <span class="hljs-string">=</span> <span class="hljs-literal">False</span>
            <span class="hljs-string">cache_span.set_attribute("cache.hit",</span> <span class="hljs-string">cache_hit)</span>

            <span class="hljs-attr">if not cache_hit:</span>
                <span class="hljs-string">span.add_event("cache_miss",</span> {<span class="hljs-attr">"key":</span> <span class="hljs-string">f"user:</span>{<span class="hljs-string">name</span>}<span class="hljs-string">"})

        # 🎯 MÉTRICAS DE PERFORMANCE
        import time
        db_start = time.time()

        with tracer.start_as_current_span("</span><span class="hljs-string">database.check_user")</span> <span class="hljs-attr">as db_span:</span>
            <span class="hljs-string">user</span> <span class="hljs-string">=</span> <span class="hljs-string">User.query.filter_by(name=name).first()</span>

            <span class="hljs-string">db_latency</span> <span class="hljs-string">=</span> <span class="hljs-string">(time.time()</span> <span class="hljs-bullet">-</span> <span class="hljs-string">db_start)</span> <span class="hljs-string">*</span> <span class="hljs-number">1000</span>
            <span class="hljs-string">db_span.set_attribute("db.latency_ms"</span>, <span class="hljs-string">db_latency)</span>
            <span class="hljs-string">db_span.set_attribute("db.rows_examined"</span>, <span class="hljs-number">1</span><span class="hljs-string">)</span>

            <span class="hljs-comment"># 🎯 MÉTRICAS CONDICIONAIS</span>
            <span class="hljs-string">if</span> <span class="hljs-string">db_latency</span> <span class="hljs-string">&gt;</span> <span class="hljs-attr">100:</span>
                <span class="hljs-string">db_span.set_attribute("performance.slow_query"</span>, <span class="hljs-literal">True</span><span class="hljs-string">)</span>
                <span class="hljs-string">span.add_event("slow_query_detected"</span>, {
                    <span class="hljs-attr">"latency_ms":</span> <span class="hljs-string">db_latency</span>,
                    <span class="hljs-attr">"query_type":</span> <span class="hljs-string">"user_lookup"</span>
                }<span class="hljs-string">)</span>
</code></pre>
<h2 id="heading-conclusao"><strong>Conclusão</strong></h2>
<p>Chegamos ao final desta série de artigos sobre observabilidade e OpenTelemetry. Ao longo de quatro artigos, um introdutório e três práticos, abordamos temas fundamentais para implementar observabilidade efetiva em suas aplicações:</p>
<ul>
<li><p><strong>Fundamentos</strong>: O que é auto-instrumentação e como ela funciona</p>
</li>
<li><p><strong>Prática com instrumentação automática:</strong> utilizando OTLP para auto-instrumentação em ambientes em qualquer tipo de ambiente</p>
</li>
<li><p><strong>Prática com Kubernetes</strong>: Como utilizar o OpenTelemetry Operator para auto-instrumentação em ambientes Kubernetes</p>
</li>
<li><p><strong>Instrumentação Manual</strong>: Técnicas para instrumentar manualmente suas aplicações quando você precisa de maior controle e customização</p>
</li>
</ul>
<p>Espero que este conteúdo tenha proporcionado uma base sólida para você começar ou aprimorar sua jornada com OpenTelemetry e observabilidade.</p>
]]></content:encoded></item><item><title><![CDATA[Auto-Instrumentação de uma aplicação Python usando OTLP Operator]]></title><description><![CDATA[Introdução:
Anteriormente, realizamos a instrumentação manual da nossa aplicação. Neste artigo, vamos explorar a auto-instrumentação utilizando o OpenTelemetry Operator, uma funcionalidade que simplifica significativamente o processo de instrumentaçã...]]></description><link>https://joaochiroli.com.br/auto-instrumentacao-de-uma-aplicacao-python-usando-otlp-operator</link><guid isPermaLink="true">https://joaochiroli.com.br/auto-instrumentacao-de-uma-aplicacao-python-usando-otlp-operator</guid><category><![CDATA[Devops]]></category><category><![CDATA[SRE]]></category><category><![CDATA[OTP]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[telemetry]]></category><category><![CDATA[observability]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Sat, 04 Oct 2025 14:59:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759586087830/589fea07-cd79-4972-8fa9-2d9f63c69961.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introducao">Introdução:</h2>
<p>Anteriormente, realizamos a instrumentação manual da nossa aplicação. Neste artigo, vamos explorar a <strong>auto-instrumentação utilizando o OpenTelemetry Operator</strong>, uma funcionalidade que simplifica significativamente o processo de instrumentação em ambientes Kubernetes.</p>
<p>Todo o código desta etapa está disponível em:</p>
<ul>
<li><p><strong>Repositório:</strong> <a target="_blank" href="https://github.com/joaochiroli/otel-instrumentacao-python">https://github.com/joaochiroli/otel-instrumentacao-python</a></p>
</li>
<li><p><strong>Branch:</strong> <code>kubernetes-operator</code></p>
</li>
<li><p><strong>Diretório:</strong> <code>/kubernetes</code></p>
</li>
</ul>
<h2 id="heading-etapas-de-implementacao">Etapas de Implementação:</h2>
<p>Neste artigo, você aprenderá a configurar a auto-instrumentação seguindo estas etapas:</p>
<ol>
<li><p><strong>Preparação da aplicação</strong></p>
<ul>
<li><p>Alteração do Dockerfile</p>
</li>
<li><p>Alteração do requirements.txt</p>
</li>
</ul>
</li>
<li><p><strong>Instalação de dependências</strong></p>
<ul>
<li><p>Instalação do Cert Manager</p>
</li>
<li><p>Instalação do OpenTelemetry Operator</p>
</li>
</ul>
</li>
<li><p><strong>Configuração do ambiente Kubernetes</strong></p>
<ul>
<li><p>Criação do namespace</p>
</li>
<li><p>Criação e instalação do banco de dados (PostgreSQL)</p>
</li>
</ul>
</li>
<li><p><strong>Configuração da observabilidade</strong></p>
<ul>
<li><p>Criação e instalação do Jaeger</p>
</li>
<li><p>Criação e instalação do OpenTelemetry Collector</p>
</li>
<li><p>Criação e instalação do Instrumentation</p>
</li>
</ul>
</li>
<li><p><strong>Deploy da aplicação</strong></p>
<ul>
<li>Criação e instalação dos manifestos da aplicação Flask</li>
</ul>
</li>
</ol>
<h2 id="heading-preparacao-da-aplicacao"><strong>Preparação da aplicação</strong></h2>
<p>Alteramos o arquivo requirements.txt para instalarmos apenas as dependências usadas pela aplicação, retiramos as dependências do OTLP.</p>
<pre><code class="lang-yaml"><span class="hljs-string">flask</span> 
<span class="hljs-string">Flask-SQLAlchemy</span>
<span class="hljs-string">psycopg2-binary</span>
<span class="hljs-string">flask_sqlalchemy</span>
</code></pre>
<p>Fizemos o mesmo para o Dockerfile:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim

<span class="hljs-comment"># Defina o diretório de trabalho</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Copie os arquivos necessários para o diretório de trabalho</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . /app</span>

<span class="hljs-comment"># Instale as dependências</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install -r requirements.txt</span>

<span class="hljs-comment"># Exponha a porta que a aplicação vai rodar</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">5000</span>

<span class="hljs-comment"># Comando para rodar a aplicação</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"python"</span>, <span class="hljs-string">"app.py"</span>]</span>
</code></pre>
<h2 id="heading-instalacao-de-dependencias"><strong>Instalação de dependências</strong></h2>
<p>Para o o OTLP Operator funcionar precisa ter instalado o Cert Manager:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml</span>
</code></pre>
<p>Use o comando <strong>kubectl get pods -A</strong> para verificar se seus pods estão executando corretamente</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759587753089/444ebfce-be97-4133-8c44-2b7cda4faafb.png" alt class="image--center mx-auto" /></p>
<p>Vamos instalar o operator com o seguinte comando:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml</span>
</code></pre>
<p>Use o comando <strong>kubectl get pods -A</strong> para verificar se seus pods estão executando corretamente</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759587816070/f9a27a3c-9ee8-41f0-a636-25dba19a5f0e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-configuracao-do-ambiente-kubernetes">Configuração do ambiente Kubernetes</h2>
<p>Criamos um namespace pro nosso projeto.</p>
<p>Para fazer o deploy do namespace execute <strong>kubectl apply -f namespace.yaml:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Namespace</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">simple-app</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">simple-app</span>
</code></pre>
<p>Para fazer o deploy do configmap execute <strong>kubectl apply -f configmap-app.yaml:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># configmap-app.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ConfigMap</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">flask-app-config</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">DATABASE_URL:</span> <span class="hljs-string">"postgresql://postgres:postgres@postgres-db:5432/simple_app"</span>
  <span class="hljs-attr">FLASK_ENV:</span> <span class="hljs-string">"development"</span>
  <span class="hljs-attr">FLASK_DEBUG:</span> <span class="hljs-string">"false"</span>
</code></pre>
<p>Para fazer o deploy do secret execute <strong>kubectl apply -f secret-postgres.yaml:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Secret</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">Opaque</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">postgres-password:</span> <span class="hljs-string">cG9zdGdyZXM=</span>  <span class="hljs-comment"># postgres em base64</span>
  <span class="hljs-attr">postgres-user:</span> <span class="hljs-string">cG9zdGdyZXM=</span>     <span class="hljs-comment"># postgres em base64</span>
  <span class="hljs-attr">postgres-db:</span> <span class="hljs-string">c2ltcGxlX2FwcA==</span>   <span class="hljs-comment"># simple_app em base64</span>
</code></pre>
<p>Para fazer o deploy do Banco de Dados PostgreSQL execute <strong>kubectl apply -f postgres.yaml:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:latest</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5432</span>
        <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">postgres-db</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_USER</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">postgres-user</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">postgres-password</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_HOST_AUTH_METHOD</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"md5"</span>
        <span class="hljs-comment"># - name: PGDATA</span>
        <span class="hljs-comment">#   value: /var/lib/postgresql/data/pgdata</span>
        <span class="hljs-comment"># volumeMounts:</span>
        <span class="hljs-comment"># - name: postgres-storage</span>
        <span class="hljs-comment">#   mountPath: /var/lib/postgresql/data</span>
        <span class="hljs-comment">#   subPath: pgdata</span>
        <span class="hljs-attr">livenessProbe:</span>
          <span class="hljs-attr">exec:</span>
            <span class="hljs-attr">command:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">pg_isready</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">-U</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">30</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>
        <span class="hljs-attr">readinessProbe:</span>
          <span class="hljs-attr">exec:</span>
            <span class="hljs-attr">command:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">pg_isready</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">-U</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">5</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">5</span>
      <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
        <span class="hljs-attr">persistentVolumeClaim:</span>
          <span class="hljs-attr">claimName:</span> <span class="hljs-string">postgres-pvc</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">5432</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolumeClaim</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-pvc</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">accessModes:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>
  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">1Gi</span>
</code></pre>
<p>Você pode verificar se os pods estão executando usando o comando <strong>kubectl get pods -n simple-app</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759588315549/d9eb72c1-7e03-4b20-ab43-9c3e13acc440.png" alt class="image--center mx-auto" /></p>
<p>E vendo os logs do pod pra saber se está sendo executado como deveria. <strong>kubectl logs &lt;nome do pod&gt; -n simple-app</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759588397762/1a8c6039-8c75-45e2-af5d-1cc6405ea111.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-configuracao-da-observabilidade"><strong>Configuração da observabilidade</strong></h2>
<p>Podemos seguir com os próximos passos. O primeiro deles é subir nosso backend que no caso é o Jaeger. Para fazer o deploy execute <strong>kubectl apply -f jaeger.yaml:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">jaeger</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">jaeger</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">jaeger</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">jaeger</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">jaegertracing/all-in-one:latest</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5775</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">UDP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">6831</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">UDP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">6832</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">UDP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5778</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">16686</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">4317</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">4318</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">14250</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">14268</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">14269</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">9411</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">jaeger-query</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">16686</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">16686</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">query</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">jaeger</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">jaeger-collector</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">4317</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">4317</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">grpc</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">4318</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">4318</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">14268</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">14268</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">binary</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">jaeger</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
</code></pre>
<p>Verifique se os pods estão executando. <strong>kubectl get pods -n simple-app.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759588540293/ee346d0a-35eb-4dd3-a94c-de99e4e1647c.png" alt class="image--center mx-auto" /></p>
<p>Agora pra acessar a página web da sua aplicação faça <strong>kubectl port-forward -n simple-app svc/jaeger-query 16686:16686</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759588672508/82977f5c-1b09-4e64-8621-93da9935e43f.png" alt class="image--center mx-auto" /></p>
<p>Vamos agora fazer o deploy do OTLP Collector ele quem vai coletar os dados e enviar para o Backend.</p>
<p><strong>kubectl apply -f jaeger.yaml</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># otel-collector.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">opentelemetry.io/v1alpha1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">OpenTelemetryCollector</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">otel-collector</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">mode:</span> <span class="hljs-string">deployment</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">otel/opentelemetry-collector-contrib:latest</span>
  <span class="hljs-attr">config:</span> <span class="hljs-string">|
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
</span>
    <span class="hljs-attr">processors:</span>
      <span class="hljs-attr">batch:</span>
        <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
        <span class="hljs-attr">send_batch_size:</span> <span class="hljs-number">1024</span>
      <span class="hljs-attr">memory_limiter:</span>
        <span class="hljs-attr">check_interval:</span> <span class="hljs-string">1s</span>
        <span class="hljs-attr">limit_mib:</span> <span class="hljs-number">512</span>

    <span class="hljs-attr">exporters:</span>
      <span class="hljs-attr">debug:</span>
        <span class="hljs-attr">verbosity:</span> <span class="hljs-string">detailed</span>
        <span class="hljs-attr">sampling_initial:</span> <span class="hljs-number">5</span>
        <span class="hljs-attr">sampling_thereafter:</span> <span class="hljs-number">200</span>
      <span class="hljs-attr">otlp:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-string">jaeger-collector:4317</span>
        <span class="hljs-attr">tls:</span>
          <span class="hljs-attr">insecure:</span> <span class="hljs-literal">true</span>

    <span class="hljs-attr">service:</span>
      <span class="hljs-attr">pipelines:</span>
        <span class="hljs-attr">traces:</span>
          <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
          <span class="hljs-attr">processors:</span> [<span class="hljs-string">memory_limiter</span>, <span class="hljs-string">batch</span>]
          <span class="hljs-attr">exporters:</span> [<span class="hljs-string">otlp</span>]
        <span class="hljs-attr">metrics:</span>
          <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
          <span class="hljs-attr">processors:</span> [<span class="hljs-string">memory_limiter</span>, <span class="hljs-string">batch</span>]
          <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>]
        <span class="hljs-attr">logs:</span>
          <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
          <span class="hljs-attr">processors:</span> [<span class="hljs-string">memory_limiter</span>, <span class="hljs-string">batch</span>]
          <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>]
</code></pre>
<p>Agora vamos criar a configuração do Intrumentation. O Instrumentation é o <strong>"template de configuração"</strong> que diz ao Operator <strong>como</strong> instrumentar automaticamente suas aplicações sem modificar o código-fonte, definindo todos os parâmetros de configuração do OpenTelemetry de forma declarativa via Kubernetes.</p>
<p>Na documentação você pode verificar que é possível usar o Operator para instrumentar vários tipos de linguagem não só Python. Link: <a target="_blank" href="https://github.com/open-telemetry/opentelemetry-operator">https://github.com/open-telemetry/opentelemetry-operator</a>.</p>
<p>Vamos fazer o deploy <strong>kubectl apply -f instrumentation.yaml</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># instrumentation.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">opentelemetry.io/v1alpha1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Instrumentation</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">flask-instrumentation</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>  
  <span class="hljs-comment"># propagators:</span>
  <span class="hljs-comment">#   - tracecontext</span>
  <span class="hljs-comment">#   - baggage</span>
  <span class="hljs-comment"># sampler:</span>
  <span class="hljs-comment">#   type: parentbased_traceidratio</span>
  <span class="hljs-comment">#   argument: "1.0"  # ou "0.25" para 25% sampling  </span>
  <span class="hljs-attr">python:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest</span>
    <span class="hljs-attr">env:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">OTEL_TRACES_EXPORTER</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">otlp</span>
      <span class="hljs-comment"># - name: OTEL_METRICS_EXPORTER</span>
      <span class="hljs-comment">#   value: otlp</span>
      <span class="hljs-comment"># - name: OTEL_LOGS_EXPORTER</span>
      <span class="hljs-comment">#   value: otlp</span>
      <span class="hljs-comment"># - name: OTEL_EXPORTER_OTLP_PROTOCOL</span>
      <span class="hljs-comment">#   value: http/protobuf</span>
      <span class="hljs-comment"># - name: OTEL_EXPORTER_OTLP_PROTOCOL</span>
      <span class="hljs-comment">#   value: grpc</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">OTEL_SERVICE_NAME</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">flask-simple-app</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">OTEL_RESOURCE_ATTRIBUTES</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">deployment.environment=development</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">OTEL_EXPORTER_OTLP_ENDPOINT</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">http://otel-collector-collector-headless.simple-app.svc.cluster.local:4318</span>
  <span class="hljs-attr">exporter:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">http://otel-collector-collector-headless.simple-app.svc.cluster.local:4318</span>
</code></pre>
<p>Agora execute o comando <strong>kubectl get instrumentation -n simple-app</strong> para saber se seu instrumentation está devidamente configurado.</p>
<h2 id="heading-deploy-da-aplicacao"><strong>Deploy da aplicação</strong></h2>
<p>Por último e mais importante, vamos fazer o deploy da aplicação. A parte mais importante e que faz toda essa mágica ser possível é essa configuração abaixo, através dela a aplicação e o instrumentation conseguem se comunicar e enviar dados para o collector.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">annotations:</span>
        <span class="hljs-attr">instrumentation.opentelemetry.io/inject-python:</span> <span class="hljs-string">"simple-app/flask-instrumentation"</span>
</code></pre>
<p>Para fazer o deploy iremos usar o comando <strong>kubectl apply -f flask-app-deployment.yaml.</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># flask-app-deployment.yaml - VERSÃO CORRIGIDA</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">flask-app</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">flask-app</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">flask-app</span>
      <span class="hljs-attr">annotations:</span>
        <span class="hljs-attr">instrumentation.opentelemetry.io/inject-python:</span> <span class="hljs-string">"simple-app/flask-instrumentation"</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">flask-app</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">joaochiroli123/app-flask:latest</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5000</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
        <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_USER</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">postgres-user</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">postgres-password</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
          <span class="hljs-attr">valueFrom:</span>
            <span class="hljs-attr">secretKeyRef:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
              <span class="hljs-attr">key:</span> <span class="hljs-string">postgres-db</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_HOST</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"postgres"</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PORT</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"5432"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">FLASK_ENV</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"development"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">FLASK_DEBUG</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"false"</span>
        <span class="hljs-attr">livenessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/health</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">5000</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">30</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>
        <span class="hljs-attr">readinessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/health</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">5000</span>
          <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">10</span>
          <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">5</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"128Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"100m"</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"512Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"500m"</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">flask-app</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">simple-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">flask-app</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">5000</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
</code></pre>
<p>Você pode verificar se os pods estão executando através do comando <strong>kubectl get pods -n simple-app</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759589426372/ed347244-951f-42e6-8ca1-2446452046b3.png" alt class="image--center mx-auto" /></p>
<p>Verificar os logs da aplicação se estão corretos. <strong>kubectl logs &lt;nome do pod&gt; -n simple-app</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759589505004/ea2d8ba6-4f94-4f20-8386-e0768ce8f7e4.png" alt class="image--center mx-auto" /></p>
<p>E o mais importante através do próximo comando é possivel verificar se a instrumentação foi feita na sua aplicação sem quebrar internamente alguma coisa <strong>kubectl exec -n simple-app &lt;nome do pod&gt; -- python -c "import sys; print('\n'.join(sys.path))"</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759589630795/9a2eca7d-f156-4008-aa73-924ba0f5da54.png" alt class="image--center mx-auto" /></p>
<p>Para executar os comandos abaixo para testar nossa aplicação:</p>
<pre><code class="lang-yaml"><span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/</span>
<span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/user/John</span>
<span class="hljs-string">curl</span> <span class="hljs-string">-X</span> <span class="hljs-string">POST</span> <span class="hljs-string">http://localhost:5000/submit</span> <span class="hljs-string">\</span>
  <span class="hljs-string">-H</span> <span class="hljs-string">"Content-Type: application/json"</span> <span class="hljs-string">\</span>
  <span class="hljs-string">-d</span> <span class="hljs-string">'{"message": "Hello from curl!"}'</span>
<span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/health</span>
<span class="hljs-string">curl</span> <span class="hljs-string">-X</span> <span class="hljs-string">POST</span> <span class="hljs-string">http://localhost:5000/submit</span> <span class="hljs-string">\</span>
  <span class="hljs-string">-H</span> <span class="hljs-string">"Content-Type: application/json"</span> <span class="hljs-string">\</span>
  <span class="hljs-string">-d</span> <span class="hljs-string">'{
    "message": "Complex request",
    "user": "admin",
    "priority": "high"
  }'</span>
<span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/09</span>
<span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/user/João</span>
</code></pre>
<p>E pra ver ela na página web vamos executar. <strong>kubectl port-forward -n simple-app svc/flask-app 5000:80</strong></p>
<p>Agora já é possível fazer algumas execuções na nossa página Web e verificar os traces chegando no Jaeger.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759589917770/bca76da0-2ea8-4144-83c1-de3414bcc546.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759589959528/4e8305a1-2d79-42e6-8edd-e90ddeb3aa2b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusao">Conclusão:</h2>
<p>Neste artigo, exploramos como a auto-instrumentação utilizando o <strong>OpenTelemetry Operator</strong> simplifica significativamente a implementação de observabilidade em aplicações Python rodando em Kubernetes. Diferentemente da instrumentação manual, essa abordagem elimina a necessidade de modificar o código-fonte da aplicação ou gerenciar manualmente as dependências do OpenTelemetry.</p>
<p>Através do Operator, conseguimos:</p>
<ul>
<li><p><strong>Automatizar</strong> a injeção de bibliotecas de instrumentação via init containers</p>
</li>
<li><p><strong>Centralizar</strong> as configurações de telemetria usando o recurso Instrumentation</p>
</li>
<li><p><strong>Padronizar</strong> a coleta de traces, métricas e logs em todo o cluster</p>
</li>
<li><p><strong>Simplificar</strong> o gerenciamento de observabilidade em ambientes Kubernetes</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Auto-Instrumentação de uma aplicação Python usando Open Telemetry]]></title><description><![CDATA[Introdução:
Vamos começar fazendo a instrumentação de uma aplicação em Python que salva os dados em um banco de dados Postgresql. O que esse tutorial vai abordar:

Vou compartilhar a aplicação em Python utilizada

Criação do Dockerfile

Criação do Do...]]></description><link>https://joaochiroli.com.br/auto-instrumentacao-de-uma-aplicacao-python-part-1</link><guid isPermaLink="true">https://joaochiroli.com.br/auto-instrumentacao-de-uma-aplicacao-python-part-1</guid><category><![CDATA[OTP]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[Devops]]></category><category><![CDATA[SRE]]></category><category><![CDATA[cloud native]]></category><category><![CDATA[telemetry]]></category><category><![CDATA[observability]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Tue, 30 Sep 2025 20:02:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758915697788/2fdd508d-4cd0-4253-a457-6c9c218b05f3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introducao">Introdução:</h2>
<p>Vamos começar fazendo a instrumentação de uma aplicação em Python que salva os dados em um banco de dados Postgresql. O que esse tutorial vai abordar:</p>
<ul>
<li><p>Vou compartilhar a aplicação em Python utilizada</p>
</li>
<li><p>Criação do Dockerfile</p>
</li>
<li><p>Criação do Docker Compose</p>
</li>
<li><p>Criação do Arquivo de configuração do Otlp Collector</p>
</li>
<li><p>Além de explicar o que cada componente faz pretendo mostrar algumas outras formas de como fazer essa instrumentação.</p>
</li>
<li><p>Uma série de videos que eu recomnedo são destes dois canais no youtube: <a target="_blank" href="https://www.youtube.com/watch?v=NEYVJSp4rKo">https://www.youtube.com/watch?v=NEYVJSp4rKo</a> e <a target="_blank" href="https://www.youtube.com/watch?v=9mifCIFhtIQ">https://www.youtube.com/watch?v=9mifCIFhtIQ</a></p>
</li>
</ul>
<h2 id="heading-entendendo-o-opentelemetry-python-auto-instrumentation">Entendendo o Opentelemetry Python <strong>Auto-Instrumentation:</strong></h2>
<p>É um projeto de código aberto que oferece uma abordagem unificada à observabilidade, oferecendo ferramentas e bibliotecas para coletar dados de telemetria, como rastros, métricas e logs. Um de seus principais recursos é a autoinstrumentação, que automatiza o processo de instrumentação de aplicativos para rastreamento distribuído sem a necessidade de alterações manuais no código.</p>
<p>Na documentação oficial do OTLP você consegue acessar outras linguagens que suportam este tipo de instrumentação: <a target="_blank" href="https://opentelemetry.io/docs/zero-code/">https://opentelemetry.io/docs/zero-code/</a></p>
<h2 id="heading-passo-1">Passo 1:</h2>
<p>Nessa etapa, antes de transformar nossa aplicação em um container e salvar os dados em um banco de dados Postgresql, resolvi dar um passo atrás pra mostrar como podemos fazer a instrumentação e visualização dos dados usando apenas comandos manuais e vamos progredindo a partir disso.</p>
<p>Então nós temos uma aplicação super simples em Python usando Flask:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, jsonify
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">import</span> sys

app = Flask(__name__) 

<span class="hljs-comment"># Configure logging</span>
logging.basicConfig(
    level=logging.INFO,
    format=<span class="hljs-string">'%(asctime)s - %(name)s - %(levelname)s - %(message)s'</span>,
    handlers=[
        logging.FileHandler(<span class="hljs-string">'app.log'</span>),
        logging.StreamHandler(sys.stdout)
    ]
)

<span class="hljs-comment"># Create logger</span>
logger = logging.getLogger(__name__)

<span class="hljs-comment"># Request logging middleware</span>
<span class="hljs-meta">@app.before_request</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">log_request_info</span>():</span>
    logger.info(<span class="hljs-string">f"Request: <span class="hljs-subst">{request.method}</span> <span class="hljs-subst">{request.url}</span> from <span class="hljs-subst">{request.remote_addr}</span>"</span>)
    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">'POST'</span>:
        logger.info(<span class="hljs-string">f"Request data: <span class="hljs-subst">{request.get_data()}</span>"</span>)

<span class="hljs-meta">@app.after_request</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">log_response_info</span>(<span class="hljs-params">response</span>):</span>
    logger.info(<span class="hljs-string">f"Response: <span class="hljs-subst">{response.status_code}</span> for <span class="hljs-subst">{request.method}</span> <span class="hljs-subst">{request.path}</span>"</span>)
    <span class="hljs-keyword">return</span> response

<span class="hljs-comment"># Error handler</span>
<span class="hljs-meta">@app.errorhandler(404)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">not_found</span>(<span class="hljs-params">error</span>):</span>
    logger.warning(<span class="hljs-string">f"404 Error: <span class="hljs-subst">{request.method}</span> <span class="hljs-subst">{request.url}</span> - <span class="hljs-subst">{error}</span>"</span>)
    <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"error"</span>: <span class="hljs-string">"Endpoint not found"</span>}), <span class="hljs-number">404</span>

<span class="hljs-meta">@app.errorhandler(500)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">internal_error</span>(<span class="hljs-params">error</span>):</span>
    logger.error(<span class="hljs-string">f"500 Error: <span class="hljs-subst">{request.method}</span> <span class="hljs-subst">{request.url}</span> - <span class="hljs-subst">{error}</span>"</span>)
    <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"error"</span>: <span class="hljs-string">"Internal server error"</span>}), <span class="hljs-number">500</span>

<span class="hljs-comment"># Define a simple route</span>
<span class="hljs-meta">@app.route('/')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello</span>():</span>
    logger.info(<span class="hljs-string">"Hello endpoint accessed"</span>)
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello, World!"</span>

<span class="hljs-comment"># GET request - retrieve user info</span>
<span class="hljs-meta">@app.route('/user/&lt;name&gt;')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user</span>(<span class="hljs-params">name</span>):</span>
    logger.info(<span class="hljs-string">f"User endpoint accessed for: <span class="hljs-subst">{name}</span>"</span>)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">if</span> len(name) &lt; <span class="hljs-number">2</span>:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Name too short"</span>)
        <span class="hljs-keyword">return</span> <span class="hljs-string">f"Hello, <span class="hljs-subst">{name}</span>! This is a GET request."</span>
    <span class="hljs-keyword">except</span> ValueError <span class="hljs-keyword">as</span> e:
        logger.error(<span class="hljs-string">f"Validation error in get_user: <span class="hljs-subst">{e}</span>"</span>)
        <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"error"</span>: <span class="hljs-string">"Name must be at least 2 characters"</span>}), <span class="hljs-number">400</span>

<span class="hljs-comment"># POST request - create/submit data</span>
<span class="hljs-meta">@app.route('/submit', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">submit_data</span>():</span>
    logger.info(<span class="hljs-string">"Submit endpoint accessed"</span>)
    <span class="hljs-keyword">try</span>:
        data = request.get_json()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> data:
            logger.warning(<span class="hljs-string">"No JSON data received in submit"</span>)
            <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"status"</span>: <span class="hljs-string">"error"</span>, <span class="hljs-string">"message"</span>: <span class="hljs-string">"No JSON data received"</span>}), <span class="hljs-number">400</span>

        <span class="hljs-keyword">if</span> <span class="hljs-string">'message'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> data:
            logger.warning(<span class="hljs-string">"Missing 'message' field in submit data"</span>)
            <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"status"</span>: <span class="hljs-string">"error"</span>, <span class="hljs-string">"message"</span>: <span class="hljs-string">"Missing 'message' field"</span>}), <span class="hljs-number">400</span>

        logger.info(<span class="hljs-string">f"Successfully processed message: <span class="hljs-subst">{data[<span class="hljs-string">'message'</span>]}</span>"</span>)
        <span class="hljs-keyword">return</span> jsonify({
            <span class="hljs-string">"status"</span>: <span class="hljs-string">"success"</span>,
            <span class="hljs-string">"received_message"</span>: data[<span class="hljs-string">'message'</span>],
            <span class="hljs-string">"response"</span>: <span class="hljs-string">"Data received successfully!"</span>,
            <span class="hljs-string">"timestamp"</span>: datetime.now().isoformat()
        })
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        logger.error(<span class="hljs-string">f"Unexpected error in submit_data: <span class="hljs-subst">{e}</span>"</span>)
        <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"status"</span>: <span class="hljs-string">"error"</span>, <span class="hljs-string">"message"</span>: <span class="hljs-string">"Internal server error"</span>}), <span class="hljs-number">500</span>

<span class="hljs-comment"># Health check endpoint</span>
<span class="hljs-meta">@app.route('/health')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">health_check</span>():</span>
    logger.info(<span class="hljs-string">"Health check accessed"</span>)
    <span class="hljs-keyword">return</span> jsonify({
        <span class="hljs-string">"status"</span>: <span class="hljs-string">"healthy"</span>,
        <span class="hljs-string">"timestamp"</span>: datetime.now().isoformat(),
        <span class="hljs-string">"version"</span>: <span class="hljs-string">"1.0.0"</span>
    })

<span class="hljs-comment"># Run the application</span>
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    logger.info(<span class="hljs-string">"Starting Flask application..."</span>)
    app.run(debug=<span class="hljs-literal">True</span>, host=<span class="hljs-string">'0.0.0.0'</span>, port=<span class="hljs-number">5000</span>)
</code></pre>
<p>A primeira coisa que devemos fazer é criar nosso ambiente virtual:</p>
<pre><code class="lang-python">python -m venv venv
source venv/bin/activate
</code></pre>
<p>Agora vamos usar uma ferramente que se chama <strong>otel-tui</strong> para instalar essa ferramenta é necessário que você tenha go instalado na sua vm.</p>
<p>O <strong>otel-tui</strong> é uma ferramenta que vai receber os seus dados instrumtnados da sua aplicação,ou seja, ele vai se comportar como otlp collector e backend ao mesmo tempo. Essa ferramenta é muito boa para casos de troubleshooting ou visualização incial dos dados, se eles estão se comportando como você gostaria, etc.</p>
<p>Se quiser saber mais sobre aqui está o link do repositório: <a target="_blank" href="https://github.com/ymtdzzz/otel-tui">https://github.com/ymtdzzz/otel-tui</a></p>
<p>Para instalar a ferramenta basta:</p>
<pre><code class="lang-go"><span class="hljs-keyword">go</span> install github.com/ymtdzzz/otel-tui@latest
</code></pre>
<p>E para ver a ferramenta em execução faça:</p>
<pre><code class="lang-go">otel-tui
</code></pre>
<p>Vai mostrar algo assim, veja que se você tiver uma aplicação instrumentada, já é possivel ver os Traces, Metricas, Logs, Topologia, etc.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759148072278/24a651f5-9902-4f55-ae4a-4cba214fb0f5.png" alt class="image--center mx-auto" /></p>
<p>Voltando ao que nos interessa, vamos definir as variáveis:<br />OTEL_SERVICE_NAME: nome do serviço</p>
<p>OTEL_EXPORTER_OTLP_ENDPOINT: pra onde seus dados devem ir, no nosso caso os dados vão ser enviados para o otel-tui.</p>
<p>OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER e OTEL_LOGS_EXPORTER: vamos definir como console e otlp, porque ativando o console conseguimos ver o que nossa aplicação instrumentada está fazendo, se realmente os dados estão sendo enviados para o destino esperado e se metricas, traces, logs estão sendo coletados.</p>
<pre><code class="lang-python">export OTEL_SERVICE_NAME=<span class="hljs-string">'flask-python'</span>
export OTEL_EXPORTER_OTLP_ENDPOINT=<span class="hljs-string">'localhost:4317'</span>
export OTEL_EXPORTER_OTLP_INSECURE=<span class="hljs-string">'true'</span>
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=<span class="hljs-string">'true'</span>
export OTEL_TRACES_EXPORTER=<span class="hljs-string">'console,otlp'</span>
export OTEL_METRICS_EXPORTER=<span class="hljs-string">'console,otlp'</span>
export OTEL_LOGS_EXPORTER=<span class="hljs-string">'console,otlp'</span>
</code></pre>
<p>vamos instalar os binários necessários para nossa aplicação executar:</p>
<pre><code class="lang-python">pip install flask fastapi opentelemetry-distro opentelemetry-exporter-otlp
</code></pre>
<p>Configure o OTLP:</p>
<pre><code class="lang-python">opentelemetry-bootstrap -a install
</code></pre>
<p>Agora vamos executar nossa aplicação instrumentada, primeiro em um outro cmd deixe o otel-tui executando porque ele quem vai receber os dados:</p>
<pre><code class="lang-python">otel-tui
</code></pre>
<p>Execute a aplicação:</p>
<pre><code class="lang-python">OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true opentelemetry-instrument --traces_exporter otlp,console --metrics_exporter otlp,console --logs_exporter otlp,console --service_name flask-python flask run -p <span class="hljs-number">5000</span>
</code></pre>
<p>Você vai ver algo como:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759149626274/743595aa-7734-4b12-b9d4-8efce8f4522a.png" alt class="image--center mx-auto" /></p>
<p>Alguns testes que você pode fazer é:</p>
<pre><code class="lang-bash">curl http://localhost:5000/
curl http://localhost:5000/user/John
curl -X POST http://localhost:5000/submit \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{"message": "Hello from curl!"}'</span>
curl http://localhost:5000/health
curl -X POST http://localhost:5000/submit \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "message": "Complex request",
    "user": "admin",
    "priority": "high"
  }'</span>
curl http://localhost:5000/09
curl http://localhost:5000/user/João
</code></pre>
<p>E no seu otel-tui você já vai começar a receber dados de traces e métricas:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759149750679/7de1cb9b-11f3-4dfd-b70e-975e2e890b06.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759149762826/b3104d28-fc97-4ef5-8266-111c9782d815.png" alt class="image--center mx-auto" /></p>
<p>Agora vamos dar um próximo passo no nosso ambiente, ao invés de usarmos o <strong>otel-tui</strong> para receber os dados, vamos fazer uma configuração usando docker-compose, nós ainda vamos fazer a instrumentação do código na mão. Porém, o destino agora será um container que vai usar uma imagem <strong>grafana/otel-lgtm:latest</strong> essa imagem já tem o otlp collector embutido, além disso, a imagem possui alguns serviços web como o prometheus, grafana, tempo, etc, que nos possibilita visualizar os dados que estão chegando. Docker-compose:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
    <span class="hljs-attr">otel-lgtm:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/otel-lgtm</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">otel-lgtm</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4317:4317"</span> <span class="hljs-comment"># OTLP gRPC receiver</span>
      <span class="hljs-comment"># - "4318:4318" # OTLP HTTP receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span> <span class="hljs-comment"># Grafana UI</span>
    <span class="hljs-comment"># networks:</span>
    <span class="hljs-comment">#   - app-network</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
</code></pre>
<p>Pode parar a execução do otel-tui e basta executar:</p>
<pre><code class="lang-yaml"><span class="hljs-string">docker</span> <span class="hljs-string">compose</span> <span class="hljs-string">up</span> <span class="hljs-string">-d</span>
</code></pre>
<p>Para executar a aplicação vamos fazer o mesmo comando:</p>
<pre><code class="lang-bash">OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=<span class="hljs-literal">true</span> opentelemetry-instrument --traces_exporter otlp,console --metrics_exporter otlp,console --logs_exporter otlp,console --service_name flask-python flask run -p 5000
</code></pre>
<p>Agora você pode ir Explorer no Grafana escolher o que você quer visualizar, usando Tempo, Prometheus ou até mesmo Loki:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759154143732/69791a43-c4a8-409d-ab68-6dc1c46d1775.png" alt /></p>
<h2 id="heading-passo-2">Passo 2:</h2>
<p>Já definimos nossa aplicação, fizemos a istrumentação e já estamos usando uma ferramenta que está recebendo nossos dados. Agora vamos dar mais uma incrementada na nossa stack, vamos melhorar nossa aplicação, agora vamos ajustar a aplicação para ela começar a salvar dados em um DB e depois veremos se a instrumentação está coletando este dado.</p>
<p>Você pode baixar a aplicação e os arquivos do projeto: <a target="_blank" href="https://github.com/joaochiroli/otel-instrumentacao-python">https://github.com/joaochiroli/otel-instrumentacao-python</a>. Criei uma nova branch pra essa etapa do projeto <strong>application-with-db</strong>.</p>
<p>OBS: quando estiver instrumentando sua aplicação não deixe o Debug enable, isso pode atrapalhar o otlp na hora de coletar os dados.</p>
<p>Vamos alterar e colocar as dependências no arquivo <strong>requirements.txt</strong> para:</p>
<pre><code class="lang-plaintext">flask 
fastapi 
opentelemetry-distro 
opentelemetry-exporter-otlp
Flask-SQLAlchemy
psycopg2-binary
python-dotenv
opentelemetry-instrumentation-flask
opentelemetry-instrumentation-sqlalchemy
opentelemetry-instrumentation-requests
opentelemetry-instrumentation-wsgi
flask_sqlalchemy
</code></pre>
<p>OBS: talvez eu tenha colocado dependencias demais, acredito que voces possam testar com menos.</p>
<p>Vamos fazer um ajuste no <strong>Dockerfile</strong>:</p>
<pre><code class="lang-yaml"><span class="hljs-string">FROM</span> <span class="hljs-string">python:3.9-slim</span>

<span class="hljs-comment"># Defina o diretório de trabalho</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">/app</span>

<span class="hljs-comment"># Copie os arquivos necessários para o diretório de trabalho</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">.</span> <span class="hljs-string">/app</span>

<span class="hljs-comment"># Instale as dependências</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">-r</span> <span class="hljs-string">requirements.txt</span>

<span class="hljs-comment"># Configura o OTLP</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">opentelemetry-bootstrap</span> <span class="hljs-string">-a</span> <span class="hljs-string">install</span>

<span class="hljs-comment"># Exponha a porta que a aplicação vai rodar</span>
<span class="hljs-string">EXPOSE</span> <span class="hljs-number">5000</span>

<span class="hljs-comment"># Comando para rodar a aplicação</span>
<span class="hljs-string">CMD</span> [<span class="hljs-string">"opentelemetry-instrument"</span>, <span class="hljs-string">"python"</span>, <span class="hljs-string">"app.py"</span>]
</code></pre>
<p>Vamos fazer um update no nosso docker-compose, iremos colocar configurações do Postgresql e colocar as variáveis necessárias para nossa aplicação enviar os traces, métricas e logs pro no backend(grafana-lgtm):</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:14.15</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">postgres-db</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5432:5432"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">simple_app</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgres_data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">healthcheck:</span> 
      <span class="hljs-attr">test:</span> [<span class="hljs-string">"CMD-SHELL"</span>, <span class="hljs-string">"pg_isready -U postgres"</span>]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">10</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>

  <span class="hljs-attr">otel-lgtm:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">grafana/otel-lgtm</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">otel-lgtm</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4317:4317"</span> <span class="hljs-comment"># OTLP gRPC receiver</span>
      <span class="hljs-comment"># - "4318:4318" # OTLP HTTP receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span> <span class="hljs-comment"># Grafana UI</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">flask-app</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5000:5000"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_HOST:</span> <span class="hljs-string">db</span>
      <span class="hljs-attr">POSTGRES_PORT:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">simple_app</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">FLASK_ENV:</span> <span class="hljs-string">development</span>
      <span class="hljs-attr">FLASK_DEBUG:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_SERVICE_NAME:</span> <span class="hljs-string">flask-api</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_ENDPOINT:</span> <span class="hljs-string">otel-lgtm:4317</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_PROTOCOL:</span> <span class="hljs-string">grpc</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_INSECURE:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_TRACES_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_METRICS_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_LOGS_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-comment"># OTEL_LOG_LEVEL: DEBUG</span>
      <span class="hljs-attr">OTEL_PYTHON_LOG_CORRELATION:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_FLASK_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_SQLALCHEMY_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_REQUESTS_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_TRACES_SAMPLER:</span> <span class="hljs-string">always_on</span>
      <span class="hljs-attr">OTEL_METRICS_SAMPLER:</span> <span class="hljs-string">always_on</span>
      <span class="hljs-comment"># OTEL_TRACES_SAMPLER: "always_on"</span>
      <span class="hljs-comment"># OTEL_TRACES_SAMPLER_ARG: "1.0"</span>
      <span class="hljs-comment"># OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: ""</span>
      <span class="hljs-comment"># OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: ".*"</span>
      <span class="hljs-comment"># OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: ".*"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">db:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">postgres_data:</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">app-network:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p>Depois basta executar: <strong>docker compose up -d —build</strong></p>
<p>Comandos que você pode usar pra testar a aplicação:</p>
<pre><code class="lang-yaml"><span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/</span>
<span class="hljs-string">curl</span> <span class="hljs-string">http://localhost:5000/user/joao</span>
<span class="hljs-string">curl</span> <span class="hljs-string">-X</span> <span class="hljs-string">POST</span> <span class="hljs-string">http://localhost:5000/submit</span> <span class="hljs-string">-H</span> <span class="hljs-string">"Content-Type: application/json"</span> <span class="hljs-string">-d</span> <span class="hljs-string">'{"message":"test"}'</span>
</code></pre>
<p>Depois disso, abrindo a página web do Grafana → Explore → Tempo → Query type: Search</p>
<p>E você vai ver os Traces e spans dos eventos que foram gerados através do curl, vai conseguir verificar os dados que foram coletados, o que foi feito, quanto tempo demorou pra cada evento acontecer, entre outras coisas.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759240395567/69472e46-ecde-4d98-836e-69a0da744086.png" alt class="image--center mx-auto" /></p>
<p>Se você for até o Prometheus vai conseguer verificar os dados das requisições HTTP:</p>
<ul>
<li><p>GET</p>
</li>
<li><p>POST</p>
</li>
<li><p>PUT</p>
</li>
<li><p>DELETE</p>
</li>
<li><p>SELECT - do banco de dados</p>
</li>
</ul>
<p>E você pode criar filtros de status code: 200, 404, 500, etc. Depende de quais status sua aplicação retorna.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759240911316/fa0f8c72-7cbc-4163-a4af-24949f0e3d43.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-passo-3">Passo 3:</h2>
<p>Agora já temos um projeto com uma stack a mais que seria a parte de salvar os dados em um banco. Para incremetar um pouco mais vamos retirar o Grafana LGTM e iremos substituir por um OTLP Collector, além disso, vamos subir dois backends um Jaeger e um Prometheus que serão responsáveis por recebr estes dados.</p>
<p>Nesse caso vamos fazer alterações apenas no <strong>docker-compose.</strong> Você pode ter acesso ao código através do repositório do projeto <a target="_blank" href="https://github.com/joaochiroli/otel-instrumentacao-python">https://github.com/joaochiroli/otel-instrumentacao-python</a> e foi criado a branch <strong>otlp-collector</strong> para essa parte do artigo.</p>
<p>Primeira coisa que devemos fazer é alterar a variável de ambiente da nossa aplicação instrumentada, antes os dados estavam indo para o Grafana LGTM e agora vamos enviar para o Otlp Collector:</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">flask-app</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5000:5000"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_HOST:</span> <span class="hljs-string">db</span>
      <span class="hljs-attr">POSTGRES_PORT:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">simple_app</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgres</span>
      <span class="hljs-attr">FLASK_ENV:</span> <span class="hljs-string">development</span>
      <span class="hljs-attr">FLASK_DEBUG:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_SERVICE_NAME:</span> <span class="hljs-string">flask-api</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_ENDPOINT:</span> <span class="hljs-string">otel-collector:4317</span> <span class="hljs-comment">### ALTERADO</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_PROTOCOL:</span> <span class="hljs-string">grpc</span>
      <span class="hljs-attr">OTEL_EXPORTER_OTLP_INSECURE:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_TRACES_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_METRICS_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_LOGS_EXPORTER:</span> <span class="hljs-string">console,otlp</span>
      <span class="hljs-attr">OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_LOG_CORRELATION:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_FLASK_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_SQLALCHEMY_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_PYTHON_REQUESTS_ENABLED:</span> <span class="hljs-string">"true"</span>
      <span class="hljs-attr">OTEL_TRACES_SAMPLER:</span> <span class="hljs-string">always_on</span>
      <span class="hljs-attr">OTEL_METRICS_SAMPLER:</span> <span class="hljs-string">always_on</span>
      <span class="hljs-comment"># OTEL_LOG_LEVEL: DEBUG</span>
      <span class="hljs-comment"># OTEL_TRACES_SAMPLER: "always_on"</span>
      <span class="hljs-comment"># OTEL_TRACES_SAMPLER_ARG: "1.0"</span>
      <span class="hljs-comment"># OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: ""</span>
      <span class="hljs-comment"># OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: ".*"</span>
      <span class="hljs-comment"># OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: ".*"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">db:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
</code></pre>
<p>Agora vamos criar o nosso serviço do OTLP Collector:</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">otel-collector:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">otel/opentelemetry-collector-contrib:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">otel-collector</span>
    <span class="hljs-attr">command:</span> [<span class="hljs-string">"--config=/etc/otel-collector-config.yaml"</span>]
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./otel-collector-config.yaml:/etc/otel-collector-config.yaml</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4317:4317"</span> <span class="hljs-comment"># OTLP gRPC receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"4318:4318"</span> <span class="hljs-comment"># OTLP HTTP receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8888:8888"</span> <span class="hljs-comment"># Prometheus metrics</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8889:8889"</span> <span class="hljs-comment"># Prometheus exporter metrics</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
</code></pre>
<p>Nós iremos usar a imagem <strong>otel/opentelemetry-collector-contrib:latest</strong>. Estamos usando o collector-contrib porque é uma imagem completa que já possui todas as dependências e extensões, como vamos usar diferentes destinos de backend essa imagem vai nos ajudar. Mas esta é uma imagem para ambientes de desenvolvimento, por conta do seu tamanho e quantidade muito grande de extensões que na maioria das vezes não serão usados. <a target="_blank" href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension">https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension</a></p>
<p>para ambientes de produção é recomendado que você faça a criação da sua própria imagem do collector usando uma imagem do collector oficial, sem dependências e bibliotecas. O otel-collector-contrib é baseado no otel-collector oficial.</p>
<p>Arquivo de configuração do otlp collector:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">receivers:</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">protocols:</span>
      <span class="hljs-attr">grpc:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4317</span>
      <span class="hljs-attr">http:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4318</span>

<span class="hljs-attr">processors:</span>
  <span class="hljs-attr">batch:</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
    <span class="hljs-attr">send_batch_size:</span> <span class="hljs-number">1024</span>

<span class="hljs-attr">exporters:</span>
  <span class="hljs-attr">debug:</span>
    <span class="hljs-attr">verbosity:</span> <span class="hljs-string">detailed</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">jaeger:4317</span>
    <span class="hljs-attr">tls:</span>
      <span class="hljs-attr">insecure:</span> <span class="hljs-literal">true</span>
  <span class="hljs-comment"># prometheus:</span>
  <span class="hljs-comment">#   endpoint: 0.0.0.0:8889</span>
  <span class="hljs-comment">#   namespace: flask_app</span>

<span class="hljs-attr">service:</span>
  <span class="hljs-attr">pipelines:</span>
    <span class="hljs-attr">traces:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>, <span class="hljs-string">otlp</span>]
    <span class="hljs-comment"># metrics:</span>
    <span class="hljs-comment">#   receivers: [otlp]</span>
    <span class="hljs-comment">#   processors: [batch]</span>
    <span class="hljs-comment">#   exporters: [debug, prometheus]</span>
    <span class="hljs-attr">logs:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>]
</code></pre>
<p><strong>Receivers</strong> (Receptores) - Recebem dados de telemetria das aplicações. São o "ponto de entrada" do coletor.</p>
<ul>
<li><p><code>otlp</code>: Protocolo padrão do OpenTelemetry</p>
<ul>
<li><p><strong>gRPC</strong> (porta 4317): Protocolo binário, mais performático</p>
</li>
<li><p><strong>HTTP</strong> (porta 4318): Protocolo baseado em REST, mais compatível</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>Processors</strong> (Processadores) - Transformam, filtram ou enriquecem os dados antes de exportá-los.</p>
<ul>
<li><p><code>batch</code>: Agrupa dados em lotes para melhorar performance</p>
<ul>
<li><p><code>timeout: 10s</code>: Envia a cada 10 segundos</p>
</li>
<li><p><code>send_batch_size: 1024</code>: Envia quando acumular 1024 itens</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>Exporters</strong> (Exportadores) - Enviam os dados processados para destinos finais (backends).</p>
<ul>
<li><p><code>debug</code>: Exibe logs detalhados no console (útil para troubleshooting)</p>
</li>
<li><p><code>otlp</code>: Envia para o Jaeger (sistema de tracing) via gRPC sem TLS</p>
</li>
<li><p><code>prometheus</code> (comentado): Exportaria métricas no formato Prometheus</p>
</li>
</ul>
<hr />
<p><strong>Service</strong> (Serviço) - Define os <strong>pipelines</strong> - fluxo de dados do receiver → processor → exporter.</p>
<ul>
<li><p><code>traces</code>: Pipeline para rastreamento distribuído (spans)</p>
</li>
<li><p><code>metrics</code> (comentado): Pipeline para métricas</p>
</li>
<li><p><code>logs</code>: Pipeline para logs estruturados</p>
</li>
</ul>
<hr />
<p>Configuração do Jaeger:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jaeger:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">jaegertracing/all-in-one:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">jaeger</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">COLLECTOR_OTLP_ENABLED=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">LOG_LEVEL=debug</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"16686:16686"</span> <span class="hljs-comment"># Jaeger UI</span>
      <span class="hljs-comment"># - "4317:4317" # OTLP gRPC receiver</span>
      <span class="hljs-comment"># - "4318:4318" # OTLP HTTP receiver</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"14250:14250"</span> <span class="hljs-comment"># Jaeger gRPC</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"14268:14268"</span> <span class="hljs-comment"># Jaeger HTTP</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
</code></pre>
<p>Agora você pode visualizar os traces:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759260219308/42621eee-ca3b-44fe-abdd-423faa80f4bd.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759260253081/69e8ef15-9b88-4842-a014-02d32b607ed3.png" alt class="image--center mx-auto" /></p>
<p>E pra finalizar vamos adicionar a configuração do Prometheus</p>
<p>Vamos começar descomentando as linhas do arquivo otel-collector-config.yaml:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">receivers:</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">protocols:</span>
      <span class="hljs-attr">grpc:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4317</span>
      <span class="hljs-attr">http:</span>
        <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:4318</span>

<span class="hljs-attr">processors:</span>
  <span class="hljs-attr">batch:</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
    <span class="hljs-attr">send_batch_size:</span> <span class="hljs-number">1024</span>

<span class="hljs-attr">exporters:</span>
  <span class="hljs-attr">debug:</span>
    <span class="hljs-attr">verbosity:</span> <span class="hljs-string">detailed</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">jaeger:4317</span>
    <span class="hljs-attr">tls:</span>
      <span class="hljs-attr">insecure:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">prometheus:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:8889</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">flask_app</span>

<span class="hljs-attr">service:</span>
  <span class="hljs-attr">pipelines:</span>
    <span class="hljs-attr">traces:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>, <span class="hljs-string">otlp</span>]
    <span class="hljs-attr">metrics:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>, <span class="hljs-string">prometheus</span>]
    <span class="hljs-attr">logs:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">batch</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">debug</span>]
</code></pre>
<p>Vamos ajustar o arquivo de configuração do docker-compose:</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">prometheus:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">prom/prometheus:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">prometheus</span>
    <span class="hljs-attr">command:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--config.file=/etc/prometheus/prometheus.yml"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--storage.tsdb.path=/prometheus"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--web.console.libraries=/usr/share/prometheus/console_libraries"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"--web.console.templates=/usr/share/prometheus/consoles"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./prometheus.yml:/etc/prometheus/prometheus.yml</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">prometheus-data:/prometheus</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9090:9090"</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">app-network</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">otel-collector</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">postgres_data:</span>
  <span class="hljs-attr">prometheus-data:</span>
</code></pre>
<p>Precisamos criar um arquivo prometheus.yml, ele quem vai até o collector na porta 8889 para coletar as métricas:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">global:</span>
  <span class="hljs-attr">scrape_interval:</span> <span class="hljs-string">15s</span>
  <span class="hljs-attr">evaluation_interval:</span> <span class="hljs-string">15s</span>

<span class="hljs-attr">scrape_configs:</span>
  <span class="hljs-comment"># Scrape metrics do OpenTelemetry Collector</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">'otel-collector'</span>
    <span class="hljs-attr">static_configs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'otel-collector:8889'</span>]
        <span class="hljs-attr">labels:</span>
          <span class="hljs-attr">service:</span> <span class="hljs-string">'flask-api'</span>

  <span class="hljs-comment"># Scrape metrics do próprio Prometheus</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">'prometheus'</span>
    <span class="hljs-attr">static_configs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">targets:</span> [<span class="hljs-string">'localhost:9090'</span>]
</code></pre>
<p>Para testar se essa parte de métricas está funcionando existem algumas maneiras, uma delas é indo até a página web do Prometheus → Target health e verificando se o Prometheus está acessando o Collector na porta correta</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759261392926/1333eed7-5d65-4a26-9d66-799150deee9c.png" alt class="image--center mx-auto" /></p>
<p>Para ver as métricas você pode acessar:</p>
<p><a target="_blank" href="http://localhost:8889/metrics">http://localhost:8889/metrics</a></p>
<p>E também é possivel fazer uma busca no Prometheus pelo nome do nosso serviço, assim vai me trazer todas as métricas deste serviço, usei o comando a seguir: {__name__=~"flask_app.*"}. Você pode usar diferentes filtros, o que vai depender é quais métricas você está querendo acessar e quais são os componentes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759261498044/b9162ab1-e44b-4079-b04e-cfb1153bb59c.png" alt class="image--center mx-auto" /></p>
<p>O OTLP é tão completo que ele já me traz informações a respeito do meu outro componente o DB:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759261610330/ff603645-5233-4580-b244-3132a7f16c8a.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusao">Conclusão:</h2>
<p>Neste artigo foi feito:</p>
<ul>
<li><p>A criação de uma aplicação Flask e sua auto-instrumentação inicial, integrada com o otel-tui e depois com o Grafana LGTM.</p>
</li>
<li><p>Depois progredimos e melhoramos a aplicação, criamos um DB para salvar os dados e fizemos a auto-instrumentação de todos os componentes que foram integrados ao Grafana LGTM.</p>
</li>
<li><p>Por último e não menos importante fizemos a criação da stack completa com a aplicação e seus componentes auto instrumentados, enviando dados para o OTLP Collector e os dados foram enviados para o Jaeger e para o Prometheus.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Introdução ao Opentelemetry]]></title><description><![CDATA[OpenTelemetry é uma estrutura (biblioteca) de observabilidade open-source que fornece um conjunto unificado de APIs, SDKs e ferramentas para coletar, processar e exportar dados de telemetria (métricas, logs e traces) de aplicações e serviços.
Princip...]]></description><link>https://joaochiroli.com.br/introducao-ao-opentelemetry</link><guid isPermaLink="true">https://joaochiroli.com.br/introducao-ao-opentelemetry</guid><category><![CDATA[telemetry]]></category><category><![CDATA[observability]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Thu, 25 Sep 2025 15:14:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758806928038/21cd2e20-0047-44f0-813a-cdcfb085967c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>OpenTelemetry é uma estrutura (biblioteca) de observabilidade open-source que fornece um conjunto unificado de APIs, SDKs e ferramentas para coletar, processar e exportar dados de telemetria (métricas, logs e traces) de aplicações e serviços.</p>
<h2 id="heading-principais-caracteristicas">Principais características:</h2>
<p>Ele não está vinculado a nenhum fornecedor específico, permitindo que você colete dados uma vez e os exporte para diferentes backends de observabilidade como Prometheus, Jaeger, Elasticsearch, Dynatrace, etc.</p>
<p>Ou seja, após a instrumentação da sua aplicação você consegue fazer o envio dos dados para qualquer destino que você queira. Isso é muito útil já que as vezes existem mudanças nos provedores, por exemplo: você instrumentou sua aplicação para usar Datadog mas 2 ou 3 anos depois sua empresa decide mudar de contrato para um NewRelic, isso exigiria um retrabalho já que você vai precisar fazer alguns ajustes para a coleta e envio destes dados. Usando o OpenTelemetry (OTLP) você não teria este retrabalho já que sua aplicação já está instrumentada, basta indicar pra onde o dado precisa ir.</p>
<h2 id="heading-tres-pilares-da-observabilidade"><strong>Três pilares da observabilidade</strong>:</h2>
<ul>
<li><p><strong>Traces</strong>: Rastreiam o fluxo de requisições através de sistemas distribuídos.</p>
</li>
<li><p><strong>Metrics</strong>: Coletam medições numéricas sobre performance e comportamento.</p>
</li>
<li><p><strong>Logs</strong>: Capturam eventos discretos e mensagens de debug.</p>
</li>
</ul>
<h2 id="heading-instrumentacao">Instrumentação:</h2>
<ul>
<li><p><strong>Auto-instrumentação</strong>: Oferece instrumentação automática para linguagens populares (Java, Python, .NET, Node.js, Go) sem necessidade de modificar o código da aplicação. Não precisa de mudanças no código.</p>
</li>
<li><p><strong>Auto-instrumentação no Kubernetes</strong>: É possivel receber e enviar dados da sua aplicação através do OTLP Operator, você pode ter instrumentação completamente automática apenas com annotations. São 3 modos do Operator: Deployment, Sidecar, Daemonset. Não precisa de mudanças no código.</p>
</li>
<li><p><strong>Instrumentação manual</strong>: Permite adicionar telemetria customizada específica para sua aplicação.Utilizado OTLP API e OTLP SDK. Precisa de mudanças no código.</p>
</li>
</ul>
<p>OBS: também é possivel usar a instrumentação manual ou auto-instrumentação no Kubernetes, basicamente são 3 formas diferentes de conseguir coletar dados da sua aplicação. Fica a seu critério definir qual será a melhor maneira pra você.</p>
<h2 id="heading-arquitetura">Arquitetura:</h2>
<p>Existem vários tipos de arquiteturas possíveis, abaixo está uma delas. Eu digo que existem várias, porque o OTLP pode enviar os dados dependendo do Backend diretamente, sem ter um OTLP Collector fazendo essa intermediação para o recebimento, processamento e envio destes dados.</p>
<p>O Jaeger por exemplo aceita dados OTLP, alguns outros providers de backend não aceitam, então você precisaria utilizar o OTLP Collector pra fazer esse processamento.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758808812084/f7a0ad4e-42a0-4d0b-8998-705cf2b42648.png" alt class="image--center mx-auto" /></p>
<p>A arquitetura utilizando o OTLP Operator no Kubernetes é um pouco diferente também já que o Cluster Kubernetes possui um Server API para o recebimento de algumas chamadas HTTP.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758812893458/dad25a44-8f26-4481-b72f-7db3d8728dc2.png" alt class="image--center mx-auto" /></p>
<p>O operator consegue fazer essa “mágica” de descobrir as aplicações através dessa configuração do seu Deployment:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Sua aplicação</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">instrumentation.opentelemetry.io/inject-java:</span> <span class="hljs-string">"true"</span>  <span class="hljs-comment"># 👈 ESTA é a "chave"</span>
</code></pre>
<h2 id="heading-vantagens">Vantagens:</h2>
<ul>
<li><p>Padronização da coleta de telemetria</p>
</li>
<li><p>Evita vendor lock-in</p>
</li>
<li><p>Reduz complexidade de instrumentação</p>
</li>
<li><p>Comunidade ativa e suporte da CNCF</p>
</li>
<li><p>Integração nativa com Kubernetes</p>
</li>
</ul>
<h2 id="heading-proximos-passos">Próximos Passos:</h2>
<p>Vou criar mais 3 tutoriais mostrando como é feito os diferentes tipos de implementação e qual o resultado.</p>
]]></content:encoded></item><item><title><![CDATA[Arquitetura de uma pipeline]]></title><description><![CDATA[Youtube Video: https://www.youtube.com/watch?v=A_kuxQAgNv4&t=111s
Trabalhando nestes últimos meses, me questionei a respeito de alguns assuntos:

Quais seriam as melhores práticas de uma pipeline?

Quais estágios essa pipeline deve ter?

Devemos nos ...]]></description><link>https://joaochiroli.com.br/arquitetura-de-uma-pipeline</link><guid isPermaLink="true">https://joaochiroli.com.br/arquitetura-de-uma-pipeline</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[Pipeline]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Thu, 21 Aug 2025 19:41:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758748333085/127b43c8-0d5b-44ac-9efc-23684e9a8298.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Youtube Video</strong>: <a target="_blank" href="https://www.youtube.com/watch?v=A_kuxQAgNv4&amp;t=111s">https://www.youtube.com/watch?v=A_kuxQAgNv4&amp;t=111s</a></p>
<p>Trabalhando nestes últimos meses, me questionei a respeito de alguns assuntos:</p>
<ul>
<li><p>Quais seriam as melhores práticas de uma pipeline?</p>
</li>
<li><p>Quais estágios essa pipeline deve ter?</p>
</li>
<li><p>Devemos nos preocupar apenas com CI/CD ou existem estágios que antecedem e sucedem o CI/CD?</p>
</li>
<li><p>Quanto tempo deve durar uma pipeline?</p>
</li>
</ul>
<p>Fiquei refletindo sobre essas perguntas, já que existem diversas ferramentas que podem ser integradas a uma pipeline, diversos tipos de testes, diferentes formas de deploy e afins.</p>
<p>Gostaria de usar este artigo para esclarecer quais componentes mínimos são necessários em uma pipeline. A ideia não é apresentar isso como uma verdade absoluta, mas sim como um guia prático que pode ajudá-lo a garantir entregas de software com qualidade.</p>
<h2 id="heading-etapas-de-uma-pipeline">Etapas de uma pipeline</h2>
<p>Analisando as pipelines do dia a dia, discussões no Stack Overflow e Reddit, além de ler alguns artigos sobre o tema, identifiquei que uma pipeline pode ser dividida em 5 etapas principais.</p>
<ol>
<li><p><strong>Source Control &amp; Triggers</strong></p>
</li>
<li><p><strong>Continuous Integration (CI)</strong></p>
</li>
<li><p><strong>Infrastructure &amp; Orchestration</strong></p>
</li>
<li><p><strong>Continuous Deployment (CD)</strong></p>
</li>
<li><p><strong>Observability &amp; Monitoring</strong></p>
</li>
</ol>
<h3 id="heading-source-control-amp-triggers"><strong>Source Control &amp; Triggers</strong></h3>
<p>É uma etapa mais focada em governança, processo humano, políticas e regras organizacionais. Dentro deste contexto existem alguns pontos interessantes que merecem um pouco de atenção:</p>
<ul>
<li><p><strong>Git Repository</strong></p>
<ul>
<li><p>Etapa em que você vai definir qual plataforma será usada (AzureDevops, Gitlab, Github,etc).</p>
</li>
<li><p>Pode implementar regras de proteção em branches.</p>
</li>
</ul>
</li>
<li><p><strong>Webhook Triggers</strong></p>
<ul>
<li>Dispara um evento, sendo ele um alerta por exemplo na sua caixa de e-mail, caso tenha ocorrido um push, commit ou eventos merge.</li>
</ul>
</li>
<li><p><strong>Code Reviews</strong></p>
<ul>
<li><p><strong>Manuais:</strong> você estabelece políticas que exigem um número mínimo de aprovações de revisores qualificados antes que um Pull Request (PR) possa ser merged para a branch principal.</p>
</li>
<li><p><strong>Automáticos:</strong> utilizam pipelines de CI/CD para executar validações antes do merge, estabelecendo quality gates obrigatórios. Exemplos incluem: cobertura mínima de 80% em testes unitários e máximo de 4 vulnerabilidades de segurança. Esses thresholds são configuráveis conforme a maturidade do projeto. A implementação dessas automações será detalhada no próximo tópico.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-continuous-integration-ci"><strong>Continuous Integration (CI)</strong></h2>
<p>É o coração da automação de qualidade do código.</p>
<ul>
<li><p><strong>Pré build</strong></p>
<ul>
<li><p><strong>Conventional Commits</strong>: Verificação do formato (feat:, fix:, docs:, etc.)</p>
</li>
<li><p><strong>Prettier:</strong> verificação de formatação ou formatação automática.</p>
</li>
<li><p><strong>Semantic Release Validation:</strong> verificação da semâtica, releases automáticos baseado nos commits.</p>
</li>
<li><p><strong>Lint:</strong> o objetivo é detecta variável não utilizada, função sem retorno, imports não usados. A finalidade é encontrar código morto e elementos não utilizados no código.</p>
</li>
<li><p><strong>Unit test (testes unitários):</strong> verifica se as funções/métodos funcionam corretamente. Exemplo testar se uma função de soma retorna o resultado esperado.</p>
</li>
</ul>
</li>
<li><p><strong>Build &amp; Compilação</strong></p>
<ul>
<li>Nessa etapa fazemos a instalação das dependências e bibliotecas necessárias, transforma o código em um arquivo executável e ocorre a geração de arquivos ou artefatos para os próximos estágios.</li>
</ul>
</li>
<li><p><strong>Pós build</strong></p>
<ul>
<li><p><strong>Teste de cobertura:</strong> verifica quanto do seu código está coberto por testes, identificando áreas não testadas.</p>
</li>
<li><p><strong>Security Scanning:</strong> verifica se seu código tem alguma vulnerabilidade, hardcoded. Você pode usar o Sast para fazer essa verificação.</p>
</li>
<li><p><strong>Sonarqube:</strong> ferramenta que faz anaálises códigos duplicados, code smell, detecta bugs, vulnerabilidades e pode ser integrada a outras ferramentas.</p>
</li>
</ul>
</li>
<li><p><strong>Container Build &amp; Artefatos</strong></p>
<ul>
<li><p>Utilização de containeres para criar uma imagem da aplicação, sempre é recomendado fazer um multi-stage build.</p>
</li>
<li><p>Nessa etapa é possivel fazer um scanning de vulnerabilidade da imagem.</p>
</li>
<li><p>Depois é recomendado que você salve essa imagem gerada em algum registry. Ecr, Acr ou até mesmo no docker.io.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-infrastructure-amp-orchestration"><strong>Infrastructure &amp; Orchestration</strong></h2>
<p>Estágio de provisionamento da infraestrutura necessária.</p>
<ul>
<li><p><strong>Infraestrutura as Code:</strong> Terraform, Ansible, CloudFormation.</p>
</li>
<li><p><strong>Container Orchestration:</strong> Kubernetes, AKS, ECS.</p>
</li>
<li><p><strong>Multi-Cloud:</strong> AWS, Azure, GCP.</p>
</li>
<li><p><strong>Service Mesh:</strong> Istio.</p>
</li>
<li><p><strong>Secrets Management:</strong> HashiCorp Vault, AWS Secrets Manager.</p>
</li>
</ul>
<p><strong>Continuous Deployment (CD)</strong></p>
<p>Deploy nos ambientes provisionados.</p>
<ul>
<li><p><strong>Staging Deployment:</strong> é um ambiente de pré produção que replica um ambiente de produção. Nele você poderá fazer:</p>
<ul>
<li><p><strong>Smoke test:</strong> verifica se os recursos críticos de um aplicativo funcionam corretamente.</p>
</li>
<li><p><strong>E2E testing:</strong> simula cenários reais de uso da aplicação, garante compatibilidade entre diferentes navegadores. Valida API.</p>
</li>
<li><p><strong>Performance testing:</strong> você pode usar uma ferramenta chamada Lighthouse para fazer isso, com ela você pode fazer validações de fluxo completo da aplicação, testes de acessibilidade, verificação da performance do site.</p>
</li>
<li><p><strong>Security testing:</strong> você pode usar o OWASP para fazer verificações de vulnerabilidades na sua página web, testes de penetração, DAST, etc.</p>
</li>
</ul>
</li>
<li><p><strong>Production Deploy:</strong> utilização de estratégias de deploy em produção.</p>
<ul>
<li><p><strong>Blue-Green Deployment</strong>: Dois ambientes idênticos, switch instantâneo.</p>
</li>
<li><p><strong>Canary Deployment</strong>: Release gradual para subset de usuários.</p>
</li>
<li><p><strong>Rolling Deployment</strong>: Atualização incremental de instâncias.</p>
</li>
<li><p><strong>Feature flags</strong>: Controle de features sem redeploy.</p>
</li>
<li><p><strong>Health checks</strong>: Validação automática pós-deployment.</p>
</li>
</ul>
</li>
<li><p><strong>Rollback Strategy:</strong> mecanismo de reversão em caso de problemas:</p>
<ul>
<li><p><strong>Automated rollback</strong>: Triggers baseados em métricas (error rate, latency)</p>
</li>
<li><p><strong>Health monitoring</strong>: Monitoramento contínuo pós-deployment</p>
</li>
<li><p><strong>Database migrations</strong>: Estratégias de rollback para mudanças de schema</p>
</li>
<li><p><strong>Traffic routing</strong>: Redirecionamento rápido para versão anterior</p>
</li>
<li><p><strong>Incident response</strong>: Procedimentos documentados para rollback manual</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-observability-amp-monitoring"><strong>Observability &amp; Monitoring</strong></h2>
<p>Monitoramento da aplicação em produção ou até mesmo em outros ambientes. E aqui existem várias ferramentas que podem te ajudar com isso:</p>
<ul>
<li><p><strong>Metrics:</strong> Prometheus, Opentelemetry, Grafana.</p>
</li>
<li><p><strong>Trace:</strong> Jaeger, Zipikin, Opentelemetry, Hélios.</p>
</li>
<li><p><strong>Logs:</strong> ELK stack, Fluentd.</p>
</li>
<li><p><strong>APM:</strong> Site24×7, Dynatrace, Datadog.</p>
</li>
</ul>
<h2 id="heading-quanto-tempo-deve-durar-uma-pipeline">Quanto tempo deve durar uma pipeline?</h2>
<p>Isso também não é uma regra fixa - depende de vários fatores como tasks executando em paralelo, configuração de hardware do runner, tamanho do projeto, quantidade de testes, etc. Mas se precisássemos estabelecer um padrão aceitável, seria algo entre 5 a 15 minutos. Claro que existem casos que podem ultrapassar muito esse tempo.</p>
<h2 id="heading-observacoes-finais"><strong>Observações finais</strong></h2>
<p>Acredito que estes são os principais passos para ter uma pipeline moderna e performática. Várias etapas podem ser modificadas - testes podem rodar antes ou depois dos deploys, por exemplo.</p>
<p>É importante reforçar que nem todos os itens são necessários na sua pipeline, e nem precisam seguir exatamente essa ordem. Mas considero esta uma maneira simples e resumida de entender o que fazer do início ao fim de uma pipeline.</p>
]]></content:encoded></item><item><title><![CDATA[Pipeline com SonarQubeCloud + Slack]]></title><description><![CDATA[Laboratório 3 – SonarQubeCloud + ESLint + Prettier e Slack

Análise estática com SonarQube/SonarCloud

Integração de ESLint + Prettier na pipeline

Bloquear PRs com cobertura < 50%

Notificações no Teams/Slack

Tópicos cobertos: SAST, Code Smells, Co...]]></description><link>https://joaochiroli.com.br/pipeline-com-sonarqubecloud-slack</link><guid isPermaLink="true">https://joaochiroli.com.br/pipeline-com-sonarqubecloud-slack</guid><category><![CDATA[azure-devops]]></category><category><![CDATA[Azure]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Docker]]></category><category><![CDATA[slack]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Tue, 27 May 2025 02:30:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748309412004/fa4f6eeb-b009-4a0c-a328-0afb46f2a408.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Laboratório 3 – SonarQubeCloud + ESLint + Prettier e Slack</strong></p>
<ul>
<li><p>Análise estática com SonarQube/SonarCloud</p>
</li>
<li><p>Integração de ESLint + Prettier na pipeline</p>
</li>
<li><p>Bloquear PRs com cobertura &lt; 50%</p>
</li>
<li><p>Notificações no Teams/Slack</p>
</li>
<li><p><strong>Tópicos cobertos:</strong> <code>SAST</code>, <code>Code Smells</code>, <code>Cobertura de testes</code>, <code>Notificações</code></p>
</li>
</ul>
<h2 id="heading-pre-requisitos"><strong>Pré requisitos</strong></h2>
<ul>
<li><p>Ter feito o laboratório <strong>Integração Contínua + ACR</strong> <strong>(</strong><a target="_blank" href="https://joaochiroli.com.br/integracao-continua-acr">https://joaochiroli.com.br/integracao-continua-acr</a>) iremos usar a mesma estrutura</p>
</li>
<li><p>Criar uma conta no Slack (<a target="_blank" href="https://slack.com/">https://slack.com/</a>)</p>
</li>
<li><p>Criar uma conta SonarQubeCloud integrada com sua pipeline (<a target="_blank" href="https://sonarcloud.io/login">https://sonarcloud.io/login</a>)</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748310435658/c3727e84-e3fe-4c1f-9e15-e9ca0289b926.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Basta ir dando next até aparecer a sessão com o <strong>token</strong></p>
</li>
<li><p>Depois disso instale a extensão do SonarqubeCloud (<a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud">https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud</a>)</p>
</li>
<li><p>Depois copie o token que foi criado anteriormente → vá até o service connection no AzureDevops e efetue a configuração do SonarqubeCloud</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748310900240/7b038ba4-39c0-4084-be27-b9b6a14daf30.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-1-step-1-ponto-de-partida-azure-pipelinesyml"><strong>1️⃣ Step 1: Ponto de partida</strong> <code>azure-pipelines.yml</code></h2>
<ul>
<li><p>Iremos criar uma variável para <code>workingDirectory: '$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</code> deixando assim a pipeline mais clean</p>
</li>
<li><p>E vamos comentar todo o stage 2 porque não iremos utiliza-lo agora</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">trigger:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

<span class="hljs-attr">pr:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>


<span class="hljs-attr">stages:</span>

<span class="hljs-comment"># 1️⃣ Instala, Valida e Publica o Código</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">And</span> <span class="hljs-string">Validate</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies with ci'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>      

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          npm install --save-dev prettier
          npm run prettier
</span>        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Formatter (Prettier)'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-string">'true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span> 
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Run Tests with Coverage'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishPipelineArtifact@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(workingDirectory)'</span>
          <span class="hljs-attr">artifact:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">publishLocation:</span> <span class="hljs-string">'pipeline'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Validated Source'</span>


<span class="hljs-comment"># # 2️⃣ Builda e publica a imagem no Acr</span>

<span class="hljs-comment">#   - stage: BuildAndPackage</span>
<span class="hljs-comment">#     displayName: Build And Package</span>
<span class="hljs-comment">#     dependsOn: InstallAndValidate</span>
<span class="hljs-comment">#     condition: succeeded()</span>
<span class="hljs-comment">#     pool: </span>
<span class="hljs-comment">#       vmImage: 'ubuntu-latest'</span>
<span class="hljs-comment">#     jobs:</span>
<span class="hljs-comment">#     - job: BuildAndPackage</span>
<span class="hljs-comment">#       displayName: 'BuildAndPackage'</span>
<span class="hljs-comment">#       steps:</span>
<span class="hljs-comment">#       - task: DownloadPipelineArtifact@2</span>
<span class="hljs-comment">#         inputs:</span>
<span class="hljs-comment">#           artifactName: 'validated-source'</span>
<span class="hljs-comment">#           targetPath: '$(Pipeline.Workspace)/validated-source'</span>

<span class="hljs-comment">#       - task: AzureCLI@2</span>
<span class="hljs-comment">#         inputs:</span>
<span class="hljs-comment">#           azureSubscription: 'projeto-user-arm'</span>
<span class="hljs-comment">#           scriptType: 'bash'</span>
<span class="hljs-comment">#           scriptLocation: 'inlineScript'</span>
<span class="hljs-comment">#           inlineScript: | </span>
<span class="hljs-comment">#             az acr login --name acrplaygroundproject         </span>
<span class="hljs-comment">#             docker build -t acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId) $(Pipeline.Workspace)/validated-source</span>
<span class="hljs-comment">#             docker push acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId)</span>
<span class="hljs-comment">#         displayName: 'Build and Push Docker Image'</span>
</code></pre>
<h2 id="heading-2step-2-adicionando-o-sonarqubecloud-na-pipeline-e-alterando-dockerfile">2️⃣<strong>Step 2: Adicionando o SonarQubeCloud na pipeline e alterando Dockerfile</strong></h2>
<ul>
<li><p>Nessa step além de colocar o sonarqube na nossa pipeline iremos:</p>
<ul>
<li><p>Comentar o ruin build</p>
</li>
<li><p>O Sonar pra funcionar corretamente é preciso acrescentar 3 tasks novas:</p>
<ul>
<li><p><code>task:SonarCloudPrepare@3</code> precisa estar no começo da pipeline</p>
</li>
<li><p><code>task:SonarCloudAnalyze@3</code> e <code>task:SonarCloudPublish@3</code> devem estar na fase final da pipeline</p>
</li>
</ul>
</li>
<li><p>Vamos acresentar o parâmetro fetchDepth: 0 logo após do parâmetro steps:</p>
<ul>
<li><pre><code class="lang-yaml">  <span class="hljs-attr">jobs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
        <span class="hljs-attr">steps:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
          <span class="hljs-attr">fetchDepth:</span> <span class="hljs-number">0</span> 
          <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>
</code></pre>
</li>
</ul>
</li>
<li><p>Também vamos ajustar a task de testes para que, ao executá-lo, seja gerado um arquivo de cobertura (coverage). Esse arquivo será enviado ao SonarQube para análise dos dados. Além disso, definiremos um limiar mínimo (threshold) de cobertura para garantir a qualidade do código.</p>
</li>
</ul>
</li>
<li><p>Nessa etapa também iremos alterar o Dockerfile que está na raiz do projeto, iremos fazer o build usando o Dockerfile e não na pipeline como foi feito nos últimos laboratórios, agora ele terá a seguinte estrutura:</p>
<ul>
<li><pre><code class="lang-yaml">  <span class="hljs-string">FROM</span> <span class="hljs-string">node:15</span> <span class="hljs-string">AS</span> <span class="hljs-string">builder</span>
  <span class="hljs-string">WORKDIR</span> <span class="hljs-string">/app</span>
  <span class="hljs-string">COPY</span> <span class="hljs-string">.</span> <span class="hljs-string">.</span>
  <span class="hljs-string">RUN</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span> <span class="hljs-string">--ignore-scripts</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>

  <span class="hljs-string">FROM</span> <span class="hljs-string">node:15</span>
  <span class="hljs-string">WORKDIR</span> <span class="hljs-string">/app</span>
  <span class="hljs-string">COPY</span> <span class="hljs-string">--from=builder</span> <span class="hljs-string">/app</span> <span class="hljs-string">.</span>
  <span class="hljs-string">RUN</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span> <span class="hljs-string">--ignore-scripts</span> <span class="hljs-string">--omit=dev</span> 
  <span class="hljs-string">EXPOSE</span> <span class="hljs-number">3000</span>
  <span class="hljs-string">CMD</span> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]
</code></pre>
</li>
</ul>
</li>
<li><p>Como ficou o <code>azure-pipelines.yml</code>:</p>
<p>  ```yaml
  trigger:
    branches:
      include:</p>
<ul>
<li><p>main</p>
<p>pr:
branches:
include:</p>
</li>
<li><p>main</p>
<p>variables:
workingDirectory: '$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</p>
</li>
</ul>
</li>
</ul>
<p>    stages:</p>
<h1 id="heading-1-instala-valida-e-publica-o-codigo">1️⃣ Instala, Valida e Publica o Código</h1>
<ul>
<li><p>stage: InstallAndValidate
displayName: Install And Validate
pool: 
  vmImage: 'ubuntu-latest'
jobs:</p>
<ul>
<li><p>job: InstallAndValidate
displayName: 'InstallAndValidate'
steps:</p>
<ul>
<li><p>checkout: self
fetchDepth: 0 
displayName: 'Checkout code'</p>
</li>
<li><p>task: UseNode@1
inputs:
  version: '15.x'
displayName: 'Install Node.js'</p>
</li>
<li><p>task: SonarCloudPrepare@3
inputs:
  SonarQube: 'projeto-user-sonar'
  organization: 'joaochiroli123'
  scannerMode: 'cli'
  configMode: 'manual'
  cliProjectKey: 'joaochiroli123_Projeto-Pessoal'
  cliProjectName: 'Projeto Pessoal'
  cliSources: '.'
  extraProperties: |
    extraProperties: |
      sonar.qualitygate.wait=true<br />      sonar.javascript.lcov.reportPaths=$(workingDirectory)/coverage/lcov.info</p>
</li>
<li><p>script: npm ci
workingDirectory: '$(workingDirectory)'
displayName: 'Install dependencies with ci'</p>
</li>
<li><p>script: npm run lint
workingDirectory: '$(workingDirectory)'
displayName: 'Linter (ESLint)'      </p>
</li>
<li><p>script: |
  npm install --save-dev prettier
  npm run prettier
workingDirectory: '$(workingDirectory)'
displayName: 'Formatter (Prettier)'
continueOnError: 'true'</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>script: npm test -- --coverage --watchAll=false  --coverageThreshold='{"global":{"lines":50}}'
workingDirectory: '$(workingDirectory)'
displayName: 'Run Tests with Coverage'
env:
  CI: true</li>
</ul>
<ul>
<li>task: PublishCodeCoverageResults@2
inputs:
  codeCoverageTool: 'Clover'
  summaryFileLocation: '$(workingDirectory)/coverage/clover.xml'
  reportDirectory: '$(workingDirectory)/coverage'</li>
</ul>
<ul>
<li>task: SonarCloudAnalyze@3
inputs:
  jdkversion: 'JAVA_HOME_17_X64'</li>
</ul>
<ul>
<li><p>task: SonarCloudPublish@3
inputs:
  pollingTimeoutSec: '300'</p>
</li>
<li><p>task: PublishPipelineArtifact@1
inputs:
  targetPath: '$(workingDirectory)'
  artifact: 'validated-source'
  publishLocation: 'pipeline'
displayName: 'Validated Source'
```</p>
<ul>
<li>Após a execução da pipeline podemos verficar no SonarQube que algumas métricas já começam a aparecer</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748795450333/b613fc3a-2703-4b8a-ab82-9f55e0f5cb2e.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<h2 id="heading-3step-3-integracao-com-slack">3️⃣<strong>Step 3: Integração com Slack</strong></h2>
<ul>
<li><p>Após criação da sua conta → Vá em canais e crie um projeto</p>
</li>
<li><p>No canto superior direito vá até os 3 pontos → clique em editar configurações</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748796021333/68ac0a27-4f77-47d2-9f23-9aab3a5da8f5.png" alt class="image--center mx-auto" /></p>
<p>  Ao abrir Editar configurações → Clique em integrações → Adicionar Apps e procure por Azure Devops Pipelines</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748796067994/20850716-12f5-48de-9b91-ebfdfe792786.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Agora você precisa interagir com o bot usando <code>/azrepos signin</code> para fazer a conexão entre sua pipeline e o slack</p>
</li>
<li><p><img src="https://learn.microsoft.com/en-us/azure/devops/repos/integrations/media/integrations-slack/sign-in.png?view=azure-devops" alt="Screenshot of Sign in prompt for Slack." /></p>
</li>
<li><p>Selecione <code>sign in</code> → copie o código que vai aparecer pra você → use este código em <code>enter code</code></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>Agora é preciso fazer um ajuste nas politicas da organização do Azure Devops para que essa integração realmente funcione então siga os passos abaixo:</p>
<ul>
<li><p>Entre na sua organização (<a target="_blank" href="https://dev.azure.com/{yourorganization}"><code>https://dev.azure.com/{yourorganization}</code></a>).</p>
</li>
<li><p>Selecione <strong>Organization settings</strong>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748796878586/8f18b005-bdf7-4a60-9a42-5dde2e3b54f9.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Deixe as configurações seguindo esta estrutura:</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748796915102/d0535ac3-363b-4683-ae0b-919fbf4d3ab3.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p>Agora basta executar sua pipeline que seu slack vai começar a receber as notificações:</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748797024700/9d0dab5a-53dc-4be6-bec0-645fb0429f9c.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-4step-4-pipeline-final-azure-pipelinesyml">4️⃣<strong>Step 4: Pipeline final</strong> <code>azure-pipelines.yml</code></h2>
<pre><code class="lang-yaml">    <span class="hljs-attr">trigger:</span>
      <span class="hljs-attr">branches:</span>
        <span class="hljs-attr">include:</span>
        <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

    <span class="hljs-attr">pr:</span>
      <span class="hljs-attr">branches:</span>
        <span class="hljs-attr">include:</span>
        <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

    <span class="hljs-attr">variables:</span>
      <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>


    <span class="hljs-attr">stages:</span>

    <span class="hljs-comment"># 1️⃣ Instala, Valida e Publica o Código</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">InstallAndValidate</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">And</span> <span class="hljs-string">Validate</span>
        <span class="hljs-attr">pool:</span> 
          <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
        <span class="hljs-attr">jobs:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
          <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
          <span class="hljs-attr">steps:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
            <span class="hljs-attr">fetchDepth:</span> <span class="hljs-number">0</span> 
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>

          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">SonarCloudPrepare@3</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">SonarQube:</span> <span class="hljs-string">'projeto-user-sonar'</span>
              <span class="hljs-attr">organization:</span> <span class="hljs-string">'joaochiroli123'</span>
              <span class="hljs-attr">scannerMode:</span> <span class="hljs-string">'cli'</span>
              <span class="hljs-attr">configMode:</span> <span class="hljs-string">'manual'</span>
              <span class="hljs-attr">cliProjectKey:</span> <span class="hljs-string">'joaochiroli123_Projeto-Pessoal'</span>
              <span class="hljs-attr">cliProjectName:</span> <span class="hljs-string">'Projeto Pessoal'</span>
              <span class="hljs-attr">cliSources:</span> <span class="hljs-string">'.'</span>
              <span class="hljs-attr">extraProperties:</span> <span class="hljs-string">|
                extraProperties: |
                  sonar.qualitygate.wait=true            
                  sonar.javascript.lcov.reportPaths=$(workingDirectory)/coverage/lcov.info
</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
            <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies with ci'</span>

          <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
            <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>      

          <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
              npm install --save-dev prettier
              npm run prettier
</span>            <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Formatter (Prettier)'</span>
            <span class="hljs-attr">continueOnError:</span> <span class="hljs-string">'true'</span>


          <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span> <span class="hljs-string">--</span> <span class="hljs-string">--coverage</span> <span class="hljs-string">--watchAll=false</span>  <span class="hljs-string">--coverageThreshold='{"global":{"lines":50}}'</span>
            <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(workingDirectory)'</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Run Tests with Coverage'</span>
            <span class="hljs-attr">env:</span>
              <span class="hljs-attr">CI:</span> <span class="hljs-literal">true</span>


          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishCodeCoverageResults@2</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">codeCoverageTool:</span> <span class="hljs-string">'Clover'</span>
              <span class="hljs-attr">summaryFileLocation:</span> <span class="hljs-string">'$(workingDirectory)/coverage/clover.xml'</span>
              <span class="hljs-attr">reportDirectory:</span> <span class="hljs-string">'$(workingDirectory)/coverage'</span>


          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">SonarCloudAnalyze@3</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">jdkversion:</span> <span class="hljs-string">'JAVA_HOME_17_X64'</span>


          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">SonarCloudPublish@3</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">pollingTimeoutSec:</span> <span class="hljs-string">'300'</span>

          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishPipelineArtifact@1</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(workingDirectory)'</span>
              <span class="hljs-attr">artifact:</span> <span class="hljs-string">'validated-source'</span>
              <span class="hljs-attr">publishLocation:</span> <span class="hljs-string">'pipeline'</span>
            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Validated Source'</span>


    <span class="hljs-comment"># 2️⃣ Builda e publica a imagem no Acr</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">BuildAndPackage</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">Build</span> <span class="hljs-string">And</span> <span class="hljs-string">Package</span>
        <span class="hljs-attr">dependsOn:</span> <span class="hljs-string">InstallAndValidate</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">succeeded()</span>
        <span class="hljs-attr">pool:</span> 
          <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
        <span class="hljs-attr">jobs:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">BuildAndPackage</span>
          <span class="hljs-attr">displayName:</span> <span class="hljs-string">'BuildAndPackage'</span>
          <span class="hljs-attr">steps:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DownloadPipelineArtifact@2</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">artifactName:</span> <span class="hljs-string">'validated-source'</span>
              <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Pipeline.Workspace)/validated-source'</span>

          <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureCLI@2</span>
            <span class="hljs-attr">inputs:</span>
              <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">'projeto-user-arm'</span>
              <span class="hljs-attr">scriptType:</span> <span class="hljs-string">'bash'</span>
              <span class="hljs-attr">scriptLocation:</span> <span class="hljs-string">'inlineScript'</span>
              <span class="hljs-attr">inlineScript:</span> <span class="hljs-string">| 
                az acr login --name acrplaygroundproject         
                docker build -t acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId) $(Pipeline.Workspace)/validated-source
                docker push acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId)
</span>            <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Build and Push Docker Image'</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Integração Contínua + ACR]]></title><description><![CDATA[Laboratório 2 – Pipeline com Testes de Código + Publicação de Docker Image no ACR

Cenário: O time agora precisa versionar builds internamente.

Tarefas:

Gerar uma Docker image

Publicar no Azure Container Registry

Stages separados



Desafio real:...]]></description><link>https://joaochiroli.com.br/integracao-continua-acr</link><guid isPermaLink="true">https://joaochiroli.com.br/integracao-continua-acr</guid><category><![CDATA[Devops]]></category><category><![CDATA[Azure]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[azure-devops]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Wed, 21 May 2025 12:47:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747832060785/3296ff4e-f280-4230-8c44-aee312b9948a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Laboratório 2 – Pipeline com Testes de Código + Publicação de Docker Image no ACR</strong></p>
<ul>
<li><p>Cenário: O time agora precisa versionar builds internamente.</p>
</li>
<li><p>Tarefas:</p>
<ul>
<li><p>Gerar uma Docker image</p>
</li>
<li><p>Publicar no Azure Container Registry</p>
</li>
<li><p>Stages separados</p>
</li>
</ul>
</li>
<li><p>Desafio real: Garantir que somente builds que passam os testes são publicados.</p>
</li>
</ul>
<h2 id="heading-pre-requisitos"><strong>Pré requisitos</strong></h2>
<ul>
<li><p>Ter feito o laboratório <strong>Pipeline CI com Validação (</strong><a target="_blank" href="https://joaochiroli.com.br/pipeline-ci-com-validacao">https://joaochiroli.com.br/pipeline-ci-com-validacao</a><strong>)</strong> iremos usar a mesma estrutura</p>
</li>
<li><p>Nesta etapa iremos fazer as configurações direto na branch main, desabilite a politica que bloqueia edições e commits direto na branch main.</p>
</li>
</ul>
<h2 id="heading-1-step-1-ajustar-configuracoes-no-arquivo-azure-pipelinesyml"><strong>1️⃣ Step 1: Ajustar configurações no arquivo</strong> <code>azure-pipelines.yml</code></h2>
<pre><code class="lang-yaml"><span class="hljs-attr">trigger:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

<span class="hljs-attr">pr:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>


<span class="hljs-attr">stages:</span>

<span class="hljs-comment"># 1️⃣ Instala, Valida e Publica o Código</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">And</span> <span class="hljs-string">Validate</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies with ci'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>      

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          npm install --save-dev prettier
          npm run prettier 
</span>        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Formatter (Prettier)'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-string">'true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">CI=true</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Test (Jest) CI=true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishPipelineArtifact@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
          <span class="hljs-attr">artifact:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">publishLocation:</span> <span class="hljs-string">'pipeline'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Validated Source'</span>
</code></pre>
<h2 id="heading-2-step-2-configurar-uma-conexao-com-o-arm-service-connection">2️⃣ <strong>Step 2: Configurar uma conexão com o ARM service Connection</strong></h2>
<ol>
<li><p><strong>Criar o Service Connection ARM:</strong></p>
<ul>
<li><p><strong>Acesse o projeto</strong> no Azure DevOps.</p>
</li>
<li><p>Vá até <strong>Project Settings</strong> (canto inferior esquerdo).</p>
</li>
<li><p>Clique em <strong>Service connections</strong> (na seção “Pipelines”).</p>
</li>
<li><p>Clique em <strong>New service connection</strong>.</p>
</li>
<li><p>Escolha <strong>Azure Resource Manager</strong>.</p>
</li>
<li><p>Selecione <strong>Service principal (automatic)</strong> e clique em <strong>Next</strong>.</p>
</li>
<li><p>Escolha:</p>
<ul>
<li><p><strong>Subscription</strong>: selecione a desejada.</p>
</li>
<li><p><strong>Resource Group</strong> (opcional): para limitar o escopo.</p>
</li>
<li><p><strong>Service connection name</strong>: nome amigável para uso nas pipelines.</p>
</li>
</ul>
</li>
<li><p>Marque <strong>Grant access permission to all pipelines</strong> (se necessário).</p>
</li>
<li><p>Clique em <strong>Save</strong>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747778529249/b1dbae12-d7ed-46c7-b163-d58a0027b550.jpeg" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-3step-3-criar-o-docker-container-registry-no-azure">3️⃣<strong>Step 3: Criar o Docker Container Registry no Azure</strong></h2>
<p>1. <strong>Criar o ACR e Habilitar o Acesso Admin :</strong></p>
<ul>
<li>Acesse o portal do Azure.</li>
</ul>
<ul>
<li><p>No menu lateral, clique em <strong>"Container registries"</strong>.</p>
</li>
<li><p>Clique em <strong>"+ Create"</strong>.</p>
</li>
<li><p>Preencha os campos:</p>
<ul>
<li><p>Subscription: selecione sua assinatura.</p>
</li>
<li><p>Resource Group: escolha um existente ou clique em "Create new".</p>
</li>
<li><p>Registry name: defina um nome único (ex.: meuacrdevops).</p>
</li>
<li><p>Location: selecione a região desejada.</p>
</li>
<li><p>SKU: escolha Basic, Standard ou Premium (geralmente Standard para DevOps).</p>
</li>
</ul>
</li>
<li><p>Clique em "Review + Create".</p>
</li>
<li><p>Clique em "Create" para finalizar.</p>
</li>
</ul>
<ol>
<li><p><strong>Habilitar o Acesso Admin (Access Keys)</strong></p>
<ol>
<li><p>Após criado, acesse seu ACR (vá em <strong>Container registries → Seu Registry</strong>).</p>
</li>
<li><p>No menu lateral, clique em <strong>"Access keys"</strong>.</p>
</li>
<li><p>Ative a opção <strong>"Admin user"</strong> → marque como <strong>"Enabled"</strong>.</p>
</li>
<li><p>Serão exibidos:</p>
<ul>
<li><p><strong>Username</strong> (usuário do ACR)</p>
</li>
<li><p><strong>Password / Password2</strong> (senhas geradas)  </p>
</li>
</ul>
</li>
</ol>
</li>
</ol>
<h2 id="heading-4-step-4-criar-o-dockerfile-na-raiz-do-projeto">4️⃣ <strong>Step 4: Criar o Dockerfile na raiz do projeto</strong></h2>
<ul>
<li><p>Nesta etapa iremos realizar também a <strong>instalação dos pacotes</strong> e o <strong>build da aplicação</strong> tudo isso direto no Dockerfile.</p>
</li>
<li><p>Dockerfile:</p>
<pre><code class="lang-dockerfile">  <span class="hljs-keyword">FROM</span> node:<span class="hljs-number">15</span> AS builder
  <span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
  <span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
  <span class="hljs-keyword">RUN</span><span class="bash"> npm ci &amp;&amp; npm run build</span>

  <span class="hljs-keyword">FROM</span> node:<span class="hljs-number">15</span>
  <span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
  <span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app .</span>
  <span class="hljs-keyword">RUN</span><span class="bash"> npm ci --omit=dev</span>
  <span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
  <span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
</li>
</ul>
<h2 id="heading-5-step-5-build-e-push-para-o-acr">5️⃣ <strong>Step 5: Build e push para o ACR</strong></h2>
<ul>
<li><p><strong>Criação do stage que será responsável por fazer o build e o envio da imagem para o ACR:</strong></p>
<ul>
<li><h3 id="heading-azure-pipelinesyml"><code>azure-pipelines.yml</code></h3>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-comment"># 2️⃣ Builda e publica a imagem no Acr</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">BuildAndPackage</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Build</span> <span class="hljs-string">And</span> <span class="hljs-string">Package</span>
    <span class="hljs-attr">dependsOn:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">condition:</span> <span class="hljs-string">succeeded()</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">BuildAndPackage</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'BuildAndPackage'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DownloadPipelineArtifact@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">artifactName:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Pipeline.Workspace)/validated-source'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureCLI@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">'projeto-user-arm'</span>
          <span class="hljs-attr">scriptType:</span> <span class="hljs-string">'bash'</span>
          <span class="hljs-attr">scriptLocation:</span> <span class="hljs-string">'inlineScript'</span>
          <span class="hljs-attr">inlineScript:</span> <span class="hljs-string">| 
            az acr login --name acrplaygroundproject         
            docker build -t acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId) $(Pipeline.Workspace)/validated-source
            docker push acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId)
</span>        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Build and Push Docker Image'</span>
</code></pre>
<p>Imagem salva no meu registry:</p>
<ul>
<li>Ponto de atenção: ao usar o comando <code>az acr login --name acrplaygroundproject</code> o <code>acrplaygroundproject</code> tem que ser o nome do seu acr</li>
</ul>
<h2 id="heading-6-step-6-arquivo-yaml-finalizado">6️⃣ <strong>Step 6: Arquivo yaml finalizado</strong></h2>
<pre><code class="lang-yaml"><span class="hljs-attr">trigger:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

<span class="hljs-attr">pr:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>


<span class="hljs-attr">stages:</span>

<span class="hljs-comment"># 1️⃣ Instala, Valida e Publica o Código</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">And</span> <span class="hljs-string">Validate</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies with ci'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>      

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          npm install --save-dev prettier
          npm run prettier 
</span>        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Formatter (Prettier)'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-string">'true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">CI=true</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Test (Jest) CI=true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishPipelineArtifact@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
          <span class="hljs-attr">artifact:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">publishLocation:</span> <span class="hljs-string">'pipeline'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Validated Source'</span>


<span class="hljs-comment"># 2️⃣ Builda e publica a imagem no Acr</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">BuildAndPackage</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Build</span> <span class="hljs-string">And</span> <span class="hljs-string">Package</span>
    <span class="hljs-attr">dependsOn:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">condition:</span> <span class="hljs-string">succeeded()</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">BuildAndPackage</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'BuildAndPackage'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DownloadPipelineArtifact@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">artifactName:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Pipeline.Workspace)/validated-source'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureCLI@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">'projeto-user-arm'</span>
          <span class="hljs-attr">scriptType:</span> <span class="hljs-string">'bash'</span>
          <span class="hljs-attr">scriptLocation:</span> <span class="hljs-string">'inlineScript'</span>
          <span class="hljs-attr">inlineScript:</span> <span class="hljs-string">| 
            az acr login --name acrplaygroundproject         
            docker build -t acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId) $(Pipeline.Workspace)/validated-source
            docker push acrplaygroundproject.azurecr.io/playgroundgapp:$(Build.BuildId)
</span>        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Build and Push Docker Image'</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Pipeline CI com Validação]]></title><description><![CDATA[Laboratório 1 – Primeiro Pipeline CI com Validação

Cenário: Um time começa a usar Azure DevOps para um app Node.js.

Tarefas:

Criar pipeline YAML com npm install, lint, test, build.

Criar stage de Instalação e Build

Exibir badges de status no rep...]]></description><link>https://joaochiroli.com.br/pipeline-ci-com-validacao</link><guid isPermaLink="true">https://joaochiroli.com.br/pipeline-ci-com-validacao</guid><category><![CDATA[azure-devops]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[npm]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Mon, 19 May 2025 21:04:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747689420043/29df9002-5bbf-4235-8771-79ddc4a1fcf8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Laboratório 1 – Primeiro Pipeline CI com Validação</strong></p>
<ul>
<li><p>Cenário: Um time começa a usar Azure DevOps para um app Node.js.</p>
</li>
<li><p>Tarefas:</p>
<ul>
<li><p>Criar pipeline YAML com <code>npm install</code>, <code>lint</code>, <code>test</code>, <code>build</code>.</p>
</li>
<li><p>Criar stage de Instalação e Build</p>
</li>
<li><p>Exibir badges de status no repositório.</p>
</li>
<li><p>Validação de PRs com lint + testes.</p>
</li>
<li><p>Usar cache</p>
</li>
<li><p>Publicar artefato no Pipeline Artifact</p>
</li>
<li><p>Publicar o artefato no Azure Artifact</p>
</li>
</ul>
</li>
<li><p>Desafio real: Automatizar o mínimo necessário para evitar que código quebrado entre na main.</p>
</li>
</ul>
<h2 id="heading-pre-requisitos"><strong>Pré requisitos</strong></h2>
<ul>
<li>Azure Devops Account</li>
</ul>
<h3 id="heading-1-step-1-criar-e-clonar-o-repositorio">1️⃣ <strong>Step 1: Criar e clonar o Repositório</strong></h3>
<ol>
<li><p>Criar Novo Projeto:</p>
</li>
<li><p>Ao entrar no painel do Azure DevOps da sua organização, clique no botão "Novo Projeto", geralmente no canto superior direito ou no centro da tela.</p>
</li>
<li><p>Configurar Detalhes do Projeto:</p>
</li>
<li><p>Nome do Projeto: Insira "Projeto Pessoal" como nome do projeto.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747662231953/afa0ee43-5e02-49c3-a59c-ec6372af4875.png" alt class="image--center mx-auto" /></p>
<ol>
<li>Acesse Repositórios -&gt; Importar Repositório -&gt; Clonar URL: <a target="_blank" href="https://github.com/joaochiroli/Devops-Challenge">https://github.com/joaochiroli/Devops-Challenge</a></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747662305756/6821ea40-73af-4378-9494-905e86e0d853.png" alt class="image--center mx-auto" /></p>
<ol>
<li>Como a intenção é começar um projeto novo do zero e testar algumas habilidades iremos excluir o conteúdo que está no arquivo <strong>azure-pipelines.yml</strong></li>
</ol>
<h3 id="heading-2-step-2-criar-pipeline-yaml-azure-pipelinesyml-na-branch-feature">2️⃣ <strong>Step 2:</strong> Criar pipeline YAML (<code>azure-pipelines.yml</code>) na branch feature</h3>
<ol>
<li><p>Criar a Branch feature</p>
<ol>
<li>Clique na branch main → clique em seguida em new branch → crie a branch feature</li>
</ol>
</li>
<li><p>Criar Pipeline:</p>
<ol>
<li><p>Vá até a aba Pipelines → Criar Pipeline → Integre sua Pipeline com o Azure Repos</p>
</li>
<li><p>Crie o arquivo <code>azure-pipelines.yml</code>:</p>
</li>
</ol>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">trigger:</span> <span class="hljs-string">none</span>

<span class="hljs-attr">pr:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>


<span class="hljs-attr">stages:</span>

<span class="hljs-comment"># Instala, Valida e Publica o Código</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">And</span> <span class="hljs-string">Validate</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">'src'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">Cache@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">'npm | "$(Agent.OS)" | $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0/package-lock.json'</span>
          <span class="hljs-attr">restoreKeys:</span> <span class="hljs-string">|
            npm | "$(Agent.OS)"
</span>          <span class="hljs-attr">path:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Cache node_modules'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span> 
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies with ci'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          npm install --save-dev prettier
          npm run prettier
</span>        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Formatter (Prettier)'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">CI=true</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Test (Jest) CI=true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishPipelineArtifact@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
          <span class="hljs-attr">artifact:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">publishLocation:</span> <span class="hljs-string">'pipeline'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Validated Source'</span>
</code></pre>
<ul>
<li><p>Tarefas:</p>
<ul>
<li><p>Criar pipeline YAML com npm install, lint, test, build ✅</p>
</li>
<li><p>Criar stage de Instalação e Build ✅</p>
</li>
<li><p>Exibir badges de status no repositório ❗</p>
</li>
<li><p>Validação de PRs com lint + testes ❗</p>
</li>
<li><p>Usar cache ✅</p>
</li>
<li><p>Publicar artefato no Pipeline Artifact ✅</p>
</li>
<li><p>Publicar o artefato no Azure Artifact❗</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-3-configurar-validacao-de-pull-request">3️⃣ Configurar <strong>Validação de Pull Request</strong></h2>
<p>A configuração <code>pr:</code> no YAML já cuida disso, mas você pode reforçar no portal:</p>
<ol>
<li><p>Vá para o repositório → <strong>Branches</strong></p>
</li>
<li><p>Clique no botão <code>...</code> ao lado da branch <code>main</code> → <strong>Branch policies</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747667213524/68d2ec02-8324-4e95-8343-634096ebc5fc.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Ative:</p>
<ul>
<li><p>✔️ Require a minimum number of reviewers</p>
</li>
<li><p>✔️ Check for linked work items</p>
</li>
<li><p>✔️ Build validation → Selecione o pipeline YAML criado (deixe como obrigatório</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747667856622/1b97f46c-1af8-4cce-aa23-44e6ed984d37.png" alt class="image--center mx-auto" /></p>
<ol start="4">
<li><p>Tarefas:</p>
<ul>
<li><p>Criar pipeline YAML com <code>npm install</code>, <code>lint</code>, <code>test</code>, <code>build</code> ✅</p>
</li>
<li><p>Criar stage de Instalação e Build ✅</p>
</li>
<li><p>Exibir badges de status no repositório ❗</p>
</li>
<li><p>Validação de PRs com lint + testes ✅</p>
</li>
<li><p>Usar cache ✅</p>
</li>
<li><p>Publicar artefato no Pipeline Artifact ✅</p>
</li>
<li><p>Publicar o artefato no Azure Artifact❗</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-4-exibir-badges-de-status-no-repositorio">4️⃣ Exibir badges de status no repositório</h2>
<ol>
<li><p>Vá em <strong>Pipelines</strong> → clique no pipeline → ⚙️ <code>View YAML</code> → Copie o <strong>Status Badge Markdown</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747668211615/6da73e3a-939b-4620-93e5-40e7eeff553d.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747668229295/b4772f80-7ec1-4da3-b0f3-ba32f611c287.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747668293686/9ff05453-c850-4198-b14c-778ffe3d9c89.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Adicione ao seu <a target="_blank" href="http://README.md"><code>README.md</code></a>:</p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-string">mdCopiarEditar![Build</span> <span class="hljs-string">Status](https://dev.azure.com/&lt;organization&gt;/&lt;project&gt;/_apis/build/status/&lt;pipeline-name&gt;?branchName=main)</span>
</code></pre>
<ol start="3">
<li><p>Depois de adicionar no README.md basta fazer o commit:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747668307091/cf625fd6-b6ad-4eb2-adf8-56df7622f176.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Tarefas:</p>
<ul>
<li><p>Criar pipeline YAML com <code>npm install</code>, <code>lint</code>, <code>test</code>, <code>build</code> ✅</p>
</li>
<li><p>Criar stage de Instalação e Build ✅</p>
</li>
<li><p>Exibir badges de status no repositório ✅</p>
</li>
<li><p>Validação de PRs com lint + testes ✅</p>
</li>
<li><p>Usar cache ✅</p>
</li>
<li><p>Publicar artefato no Pipeline Artifact ✅</p>
</li>
<li><p>Publicar o artefato no Azure Artifact❗</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-5-publicar-o-artefato-no-azure-artifact">5️⃣ Publicar o artefato no Azure Artifact</h2>
<ol>
<li><p>Criar um <strong>Azure Artifacts feed</strong> no seu projeto DevOps com o nome Projeto Pessoal</p>
</li>
<li><p>Adicionar a seguinte task no seu arquivo <code>azure-pipelines.yml</code>:</p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UniversalPackages@0</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">'publish'</span>
          <span class="hljs-attr">publishDirectory:</span> <span class="hljs-string">'$(Build.ArtifactStagingDirectory)'</span>
          <span class="hljs-attr">feedsToUsePublish:</span> <span class="hljs-string">'internal'</span>
          <span class="hljs-attr">vstsFeedPublish:</span> <span class="hljs-string">'ee03fcac-046a-4290-b5f4-79508dd47b04/82dd1496-0532-4c69-b3e0-feb482096c20'</span>
          <span class="hljs-attr">vstsFeedPackagePublish:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">versionOption:</span> <span class="hljs-string">'patch'</span>
</code></pre>
<ol start="3">
<li><p>Detalhe o vstsFeedPublish é o ID do meu feed no Azure Artifact ou seja, no seu caso será uma ID diferente.</p>
</li>
<li><p>Agora salva e executa sua pipeline.</p>
</li>
<li><p>No meu caso, tive o seguinte problema: <strong>User 'b83dcc85-38ca-4958-b336-c89e9ffbca0d' lacks permission to complete this action. You need to have 'AddPackage'.","@i":"c2baee9b","@l":"Error","SourceContext":"ArtifactTool.Program","UtcTimestamp":"2025-05-19 17:05:49.785Z"})</strong></p>
<ol>
<li>Neste caso vá até seu feed → Clique na engrenagem no canto superior direito → Permissão → Add User/Group → Contributor → &lt;Nome_do_Projeto_Service_Build&gt;</li>
</ol>
</li>
<li><p>Execute sua pipeline e você irá encontrar um novo artefato gerado no seu feed, neste caso é um artefato de patch mas você pode alterar esta opção.</p>
</li>
<li><p>Esta publicação é incremental, ou seja, cada nova execução irá gerar um novo artefato.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747676886908/566fc680-4346-42a5-b78e-ec52d906244f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Tarefas:</p>
<ul>
<li><p>Criar pipeline YAML com <code>npm install</code>, <code>lint</code>, <code>test</code>, <code>build</code> ✅</p>
</li>
<li><p>Criar stage de Instalação e Build ✅</p>
</li>
<li><p>Exibir badges de status no repositório ✅</p>
</li>
<li><p>Validação de PRs com lint + testes ✅</p>
</li>
<li><p>Usar cache ✅</p>
</li>
<li><p>Publicar artefato no Pipeline Artifact ✅</p>
</li>
<li><p>Publicar o artefato no Azure Artifact ✅</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-6-merge-com-a-branch-main">6️⃣ Merge com a branch main</h2>
<ol>
<li><p>Agora só basta ir até Azure Repos → Pull Request → Aprovar a PR e fazer o Merge com a branch main</p>
<ol>
<li>Arquivo <code>azure-pipelines.yml</code> completo:</li>
</ol>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">trigger:</span> <span class="hljs-string">none</span>

<span class="hljs-attr">pr:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>


<span class="hljs-attr">stages:</span>

<span class="hljs-comment"># Instala, Valida e Publica o Código</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">InstallAndValidate</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Install</span> <span class="hljs-string">And</span> <span class="hljs-string">Validate</span>
    <span class="hljs-attr">pool:</span> 
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">InstallAndValidate</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'InstallAndValidate'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">'src'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">Cache@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">'npm | "$(Agent.OS)" | $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0/package-lock.json'</span>
          <span class="hljs-attr">restoreKeys:</span> <span class="hljs-string">|
            npm | "$(Agent.OS)"
</span>          <span class="hljs-attr">path:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Cache node_modules'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span> 
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies with ci'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          npm install --save-dev prettier
          npm run prettier -- --write
</span>        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Formatter (Prettier)'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">CI=true</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Test (Jest) CI=true'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishPipelineArtifact@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">targetPath:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
          <span class="hljs-attr">artifact:</span> <span class="hljs-string">'build-artifact'</span>
          <span class="hljs-attr">publishLocation:</span> <span class="hljs-string">'pipeline'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Validated Source'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UniversalPackages@0</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">'publish'</span>
          <span class="hljs-attr">publishDirectory:</span> <span class="hljs-string">'$(Build.ArtifactStagingDirectory)'</span>
          <span class="hljs-attr">feedsToUsePublish:</span> <span class="hljs-string">'internal'</span>
          <span class="hljs-attr">vstsFeedPublish:</span> <span class="hljs-string">'ee03fcac-046a-4290-b5f4-79508dd47b04/82dd1496-0532-4c69-b3e0-feb482096c20'</span>
          <span class="hljs-attr">vstsFeedPackagePublish:</span> <span class="hljs-string">'validated-source'</span>
          <span class="hljs-attr">versionOption:</span> <span class="hljs-string">patch</span>
</code></pre>
<h2 id="heading-7-bonus-publicar-o-artefato-no-azure-artifact-no-formato-npmrc">7️⃣ Bônus - Publicar o artefato no Azure Artifact no formato .npmrc</h2>
<ol>
<li>Vá até o arquivo <strong>.npmrc</strong> na raiz do projeto e coloque as seguintes informações de acordo com o seu feed:</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-string">registry=https://pkgs.dev.azure.com/joaochiroli123/ee03fcac-046a-4290-b5f4-79508dd47b04/_packaging/ProjetoPessoal/npm/registry/</span>

<span class="hljs-string">engine-strict=true</span>
</code></pre>
<ol start="2">
<li>Altere também <code>azure-pipelines.yml</code>:</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">Npm@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">'publish'</span>
          <span class="hljs-attr">workingDir:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
          <span class="hljs-attr">publishRegistry:</span> <span class="hljs-string">'useFeed'</span>
          <span class="hljs-attr">publishFeed:</span> <span class="hljs-string">'ee03fcac-046a-4290-b5f4-79508dd47b04/82dd1496-0532-4c69-b3e0-feb482096c20'</span>
</code></pre>
<ol start="3">
<li><p>Execute a Pipeline</p>
</li>
<li><p>Provavelmente vai dar um erro dizendo que o arquivo package.json está com uma flag private habilitada o que você pode fazer é adicionar o script abaixo que irá remover o private do seu arquivo de configuração:</p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
    echo "🔧 Removendo 'private' de package.json após o npm ci"
    sed -i '/"private": true,/d' package.json
    cat package.json
</span>  <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
  <span class="hljs-attr">displayName:</span> <span class="hljs-string">'🔧 Remoção do campo private após instalação'</span>
</code></pre>
<ol start="5">
<li>Execute a pipeline</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747684302870/11cb1986-360f-40aa-854e-928d9119f375.png" alt class="image--center mx-auto" /></p>
<ol start="6">
<li><p><strong>Observação:</strong> Quando você realiza a publicação de pacotes do tipo <strong>npm</strong> no Azure Artifacts, o processo é diferente da publicação via <strong>Universal Packages</strong>.</p>
<p> No caso do <strong>npm</strong>, o Azure utiliza a versão definida no arquivo <code>package.json</code>. Isso significa que, se a versão <code>0.1.3</code> já tiver sido publicada no feed, será necessário atualizar o <code>package.json</code> para <code>0.1.4</code> (ou superior) para que a nova publicação seja aceita. Caso contrário, a pipeline falhará com erro de versão duplicada.</p>
<p> Para evitar esse tipo de falha, você pode alterar o valor da versão manualmente ou automatizar a atualização da versão dentro da própria pipeline, utilizando comandos como <code>npm version patch</code>. Essa abordagem garante um controle incremental e contínuo das versões publicadas, evitando conflitos.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747755987988/1aa57c79-1f47-4f57-8bf8-a1a0271a3531.png" alt class="image--center mx-auto" /></p>
<p> Exemplo de script incremental:</p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
           echo "Versão atual:"
           node -p "require('./package.json').version"
</span>
           <span class="hljs-string">npm</span> <span class="hljs-string">version</span> <span class="hljs-string">patch</span> <span class="hljs-string">--no-git-tag-version</span>

           <span class="hljs-string">echo</span> <span class="hljs-string">"Nova versão:"</span>
           <span class="hljs-string">node</span> <span class="hljs-string">-p</span> <span class="hljs-string">"require('./package.json').version"</span>
         <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Pipeline.Workspace)/validated-source</span>
         <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Incrementar versão patch no package.json'</span>
</code></pre>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Setting Up Prometheus with NGINX Proxy & Authentication + Configuring Agent Pools]]></title><description><![CDATA[Challenge: Monitoring System
Deploy a Prometheus monitoring system on an Ubuntu cloud instance. The system should be secured and accessible through a reverse proxy, with proper authentication, and connected to an external Node Exporter. You can follo...]]></description><link>https://joaochiroli.com.br/step-by-step-guide-setting-up-prometheus-with-nginx-proxy-and-authentication-configuring-agent-pools</link><guid isPermaLink="true">https://joaochiroli.com.br/step-by-step-guide-setting-up-prometheus-with-nginx-proxy-and-authentication-configuring-agent-pools</guid><category><![CDATA[#prometheus]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Azure]]></category><category><![CDATA[azure-devops]]></category><category><![CDATA[nginx]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Tue, 18 Feb 2025 01:47:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739893073402/c9172dc2-18f5-42b7-8341-a2781d36f65e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-challenge-monitoring-system">Challenge: Monitoring System</h2>
<p>Deploy a Prometheus monitoring system on an Ubuntu cloud instance. The system should be secured and accessible through a reverse proxy, with proper authentication, and connected to an external Node Exporter. You can follow these steps:</p>
<ul>
<li>Install Prometheus and Node Exporter:</li>
</ul>
<p>Update your package list and install Prometheus with the following commands:</p>
<pre><code class="lang-plaintext">sudo apt update
sudo apt install prometheus prometheus-node-exporter
</code></pre>
<p>Verify the installation by checking the status of Prometheus services:</p>
<pre><code class="lang-plaintext">sudo service prometheus status
sudo service prometheus-node-exporter status
</code></pre>
<p>Completion Criteria</p>
<ul>
<li>Use NGINX as a reverse proxy to forward requests from your server's IP address to Prometheus' default ports as follows:</li>
</ul>
<pre><code class="lang-plaintext">http://{YOUR-SERVER-IP}/ to Prometheus (&lt;http://localhost:9090/&gt;)
http://{YOUR-SERVER-IP}/metrics/ to the Node Exporter (&lt;http://localhost:9100/&gt;)
</code></pre>
<ul>
<li><p>Basic authentication is set up, requiring credentials to access both endpoints.</p>
</li>
<li><p>Direct access to ports 9090 (Prometheus) and 9100 (Node Exporter) is blocked, and access is only allowed via NGINX.</p>
</li>
<li><p>Prometheus is scraping metrics from an external Node Exporter running the same saver</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Azure Devops Account</p>
</li>
<li><p>Cloud Azure Account</p>
</li>
<li><p>Created a vm1 on Azure</p>
</li>
</ul>
<h3 id="heading-step-1-create-vm1-on-azure-and-configure-the-security-group"><strong>Step 1: Create Vm1 on Azure and configure the security group</strong></h3>
<ol>
<li><p>Go to <a target="_blank" href="https://portal.azure.com/">Azure Port</a><a target="_blank" href="https://portal.azure.com/">al.</a></p>
</li>
<li><p><a target="_blank" href="https://portal.azure.com/">Sign in</a> with your A<a target="_blank" href="https://portal.azure.com/">zure account</a>.</p>
</li>
<li><p>In the Azure Portal, search for <strong>Virtual Machines</strong> in the search bar</p>
</li>
<li><p>Click <strong>Create</strong> → <strong>Azure Virtual Machine</strong>.</p>
<ol>
<li><p><strong>Subscription</strong>: Select your Azure subscription.</p>
</li>
<li><p><strong>Resource Group</strong>: Choose an existing one or create a new one.</p>
</li>
<li><p><strong>Region</strong>: Select the closest or preferred Azure region.</p>
</li>
<li><p><strong>Image</strong>: Select the OS (e.g., <strong>Ubuntu 20.04 LTS</strong>).</p>
</li>
<li><p><strong>VM Size</strong>: Choose a size based on your workload (e.g., <code>Standard_B2s</code> for small workloads).</p>
</li>
</ol>
</li>
<li><p>Open the <strong>NSG</strong> you just created.</p>
</li>
<li><p>Go to <strong>Inbound security rules</strong> under <strong>Settings</strong>.</p>
</li>
<li><p>Click <strong>+ Add Rule</strong> to create a new rule.</p>
<ol>
<li><p>First, go to this site to know what your public IP is - <a target="_blank" href="https://nordvpn.com/pt-br/what-is-my-ip/">https://nordvpn.com/pt-br/what-is-my-ip/</a></p>
</li>
<li><p>Add an Inbound role with source your public IP and destination your VM, you will create two roles one for port 80 and the other for port 22.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739887820066/2c515a8f-ae89-4283-aa77-d0fdfb937dc0.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
</ol>
<h3 id="heading-step-2-connect-in-your-vm-with-putty"><strong>Step 2: Connect in your VM with putty</strong></h3>
<ol>
<li><p>Go to Putty Gen with your credentials, access Conversions → Import Key, and select .pem key</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739888199287/00a68232-9adf-4c8a-8413-7292e07c0f62.png" alt class="image--center mx-auto" /></p>
<p> Select on creating your private key</p>
</li>
<li><p>Now you will open the Putty</p>
</li>
<li><p>Select the public IP of your VM and Go to SSH → Auth → Browser and select your .ppk key</p>
</li>
</ol>
<h3 id="heading-step-3-configure-and-install-agent-pools-in-azure-devops"><strong>Step 3: Configure and Install Agent Pools in Azure DevOps</strong></h3>
<ol>
<li><p>Go to <a target="_blank" href="https://dev.azure.com/"><strong>Azure DevOps</strong></a>.</p>
</li>
<li><p>Sign in with your account.</p>
</li>
<li><p>Select your <strong>organization</strong></p>
</li>
<li><p>Click on <strong>Organization Settings</strong> (bottom left corner).</p>
</li>
<li><p>Under <strong>Pipelines</strong>, select <strong>Default</strong>.</p>
</li>
<li><p>Click <strong>+ New Agent</strong>.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739890175087/e801fffe-7b2d-4b78-84ed-2e24de1f76ec.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Select Linux and follow this installation</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739890252710/66680c63-6c9e-402f-8837-a64d4fe42673.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>When you are executing the Configure Agent with ./config in one step will ask you about the token, so you will need to create this token:</p>
<ol>
<li><p>Click on your <strong>Profile Icon</strong> (top-right corner).</p>
</li>
<li><p>Select <strong>Personal Access Tokens</strong>.</p>
</li>
<li><p>Click <strong>+ New Token</strong>.</p>
</li>
<li><p>Put the name.</p>
</li>
<li><p>Scope: <strong>Full Access</strong>.</p>
</li>
</ol>
</li>
<li><p>After you enter the token, just click enter with the default settings.</p>
</li>
<li><p>Start the Agent with ./run.sh</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739891523108/e0b00890-3bdf-41f6-88f5-5ace3b64f7e4.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-step-4-create-configure-and-test-your-pipeline"><strong>Step 4: Create, Configure, and test your Pipeline</strong></h3>
<ol>
<li><p>Create file <code>azure-pipelines.yml</code></p>
</li>
<li><p>Enter these configurations:</p>
<ol>
<li><pre><code class="lang-plaintext"> trigger:
 - main

 pr:
   branches:
     include:
     -  main

 stages:
     - stage: Prometheus
     displayName: Prometheus
     pool:
       name: Default
       vmImage: 'vm1'
     jobs:
     - job: Prometheus
       displayName: "Prometheus"
       steps:
       - script: |
           sudo apt install -y prometheus prometheus-node-exporter nginx apache2-utils

           sudo systemctl enable --now prometheus
           sudo systemctl enable --now prometheus-node-exporter
           sudo systemctl enable --now nginx

           systemctl status prometheus --no-pager
           systemctl status prometheus-node-exporter --no-pager
           systemctl status nginx --no-pager

         displayName: "Prometheus Install"

       - script: |
           echo 'global:
             scrape_interval: 15s

           scrape_configs:
             - job_name: "prometheus"
               static_configs:
                 - targets: ["localhost:9090"]

             - job_name: "node_exporter"
               static_configs:
                 - targets: ["localhost:9100"]
           ' | sudo tee /etc/prometheus/prometheus.yml

           sudo systemctl restart prometheus
         displayName: "Configurar Prometheus para coletar métricas"

       - script: |
           sudo ufw allow ssh
           sudo ufw allow http
           sudo ufw allow https
           sudo ufw deny 9090
           sudo ufw deny 9100
           sudo ufw enable
         displayName: "Configurar Acessos"

       - script: |
           echo 'server {
               listen 80;
               server_name _;

               location / {
                   proxy_pass http://127.0.0.1:9090/;
                   proxy_set_header Host $host;
                   proxy_set_header X-Real-IP $remote_addr;
                   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                   auth_basic "Restricted Access";
                   auth_basic_user_file /etc/nginx/.htpasswd;
               }

               location /metrics/ {
                   proxy_pass http://127.0.0.1:9100/;
                   proxy_set_header Host $host;
                   proxy_set_header X-Real-IP $remote_addr;
                   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                   auth_basic "Restricted Access";
                   auth_basic_user_file /etc/nginx/.htpasswd;
               }
           }' | sudo tee /etc/nginx/sites-available/default

           sudo systemctl restart nginx
         displayName: "Configurar Nginx"

       - script: |
           echo "Setting up Nginx authentication..."

           # Create .htpasswd file and add user
           echo "senha123" | sudo htpasswd -c -i /etc/nginx/.htpasswd joaochiroli

           sudo systemctl restart nginx
         displayName: "Configure Basic Auth for Nginx"
</code></pre>
</li>
</ol>
</li>
<li><p>Test the configuration</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739892143761/bffd3091-b6b9-4801-b174-46cb6f87b390.png" alt class="image--center mx-auto" /></p>
<ol start="4">
<li>See this Dashboard</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739892361850/f20a847a-47df-45e2-b1cd-ccf4980de892.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Continuous Deployment with Kubernetes and Load Testing with JMeter]]></title><description><![CDATA[Challenge: Part 2 Continuous Deployment and Load Testing
Deploy the provided application to your chosen cloud provider using any service that runs the application as a Docker container. Then, create a test plan for the application's home page and gen...]]></description><link>https://joaochiroli.com.br/continuos-deploeyment-load-test</link><guid isPermaLink="true">https://joaochiroli.com.br/continuos-deploeyment-load-test</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Mon, 17 Feb 2025 16:54:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739841532673/c4e14654-4c74-4def-a65f-cec0147adf16.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-challenge-part-2-continuous-deployment-and-load-testing">Challenge: Part 2 Continuous Deployment and Load Testing</h1>
<p>Deploy the provided application to your chosen cloud provider using any service that runs the application as a Docker container. Then, create a test plan for the application's home page and generate a testing report using JMeter.</p>
<p>The application needs to:</p>
<p>Available for anyone with Internet access only during the challenge recording</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Azure Devops Account</p>
</li>
<li><p>Cloud Azure Account</p>
</li>
<li><p>Completed Challenge 1. This is a continuation of Series 1. Challenge 1 link: <a target="_blank" href="https://hashnode.com/edit/cm795upao000609js5scu0j9u">https://hashnode.com/edit/cm795upao000609js5scu0j9u</a></p>
</li>
</ul>
<h3 id="heading-step-1-create-azure-container-registry-and-azure-devops-connections"><strong>Step 1: Create Azure Container Registry and Azure Devops Connections</strong></h3>
<p><strong>Create Azure Registry:</strong></p>
<ol>
<li><p>Go to the Azure portal.</p>
</li>
<li><p>Create a new resource group.</p>
</li>
<li><p>Click on "Container registries".</p>
</li>
<li><p>Click on "Create container registry".</p>
</li>
<li><p>On the "Create container registry" page, enter the following information:</p>
</li>
</ol>
<ul>
<li><p>Registry name - Enter <strong>mdcregistryID</strong>. # The name needs to be unique change the last two letters by the initials of your name</p>
</li>
<li><p>Region - Select the region where you want to create your registry.</p>
</li>
<li><p>Pricing tier - Select the pricing tier that you want to use for your registry.</p>
</li>
<li><p>Security - Select the security options that you want to use for your registry.</p>
</li>
<li><p>Choose the Resource Group you created earlier</p>
</li>
</ul>
<p><strong>Azure Devops Connections:</strong></p>
<ol>
<li><p>Navigate to Azure DevOps Service Connections</p>
</li>
<li><p>Sign in to Azure DevOps.</p>
</li>
<li><p>Select your organization and project.</p>
</li>
<li><p>In the left sidebar, go to Project Settings.</p>
</li>
<li><p>Under Pipelines, click Service Connections.</p>
</li>
<li><p>Click New Service Connection.</p>
</li>
<li><p>Choose Docker Registry</p>
</li>
<li><p>Authentication Type Select Service Principal</p>
</li>
<li><p>Enter your settings and click create</p>
</li>
</ol>
<h3 id="heading-step-2-create-kubernetes"><strong>Step 2: Create Kubernetes</strong></h3>
<ol>
<li><p>Log in to Azure</p>
</li>
<li><p>Log in to your <strong>Azure account</strong> via CLI: <code>az login</code></p>
</li>
<li><p>Create the <strong>Kubernetes cluster</strong>: <code>az aks create --resource-group projeto-flask --name k8s-cluster --node-count 2 --enable-addons monitoring --generate-ssh-keys</code></p>
</li>
<li><p><strong>Step 3: Get ACR credentials and create a secret on Kubernetes</strong></p>
</li>
<li><p>Get ACR name: <code>az acr list --query "[].{Name:name, ResourceGroup:resourceGroup}" --output table</code></p>
</li>
<li><p>Get Docker Server Credential: <code>az acr show --name Your_ACR_Name --query loginServer --output tsv</code></p>
</li>
<li><p>Get Docker Username: <code>az acr credential show --name Your_ACR_Name --query username --output tsv</code></p>
</li>
<li><p>Get Docker Password: <code>az acr credential show --name Your_ACR_Name --query passwords[0].value --output tsv</code></p>
</li>
<li><p>Access your Kubernetes: <code>az aks get-credentials --resource-group Your_Resource_Group_Name --name mdc-aks</code></p>
</li>
<li><p>Create Secret:</p>
<pre><code class="lang-plaintext">  kubectl create secret docker-registry acr-secret \
          --docker-server=Your_Docker_Server \ 
          --docker-username=Your_Docker_Username \ 
          --docker-password=Your_Docker_Password \
</code></pre>
</li>
<li><p>Configure Assign AcrPull Role:</p>
<pre><code class="lang-plaintext">ACR_ID=$(az acr show --name mdcrepositorychiroli --query "id" -o tsv)

AKS_ID=$(az aks show --resource-group projeto-flask --name k8scluster --query "identityProfile.kubeletidentity.objectId" -o tsv)

az role assignment create --assignee $AKS_ID --role AcrPull --scope $ACR_ID
</code></pre>
</li>
</ol>
<h3 id="heading-step-4-create-azure-resource-manager-on-connections"><strong>Step 4: Create Azure Resource Manager on Connections</strong></h3>
<ol>
<li><p>Navigate to Azure DevOps Service Connections</p>
</li>
<li><p>Sign in to Azure DevOps.</p>
</li>
<li><p>Select your organization and project.</p>
</li>
<li><p>In the left sidebar, go to Project Settings.</p>
</li>
<li><p>Under Pipelines, click Service Connections.</p>
</li>
<li><p>Click New Service Connection.</p>
</li>
<li><p>Choose Azure Resource Manager</p>
</li>
<li><p>Enter your settings, you can use the default setting and click create.</p>
</li>
</ol>
<h3 id="heading-step-5-create-dockerfile"><strong>Step 5: Create Dockerfile</strong></h3>
<ol>
<li><p>Access Azure Repo</p>
</li>
<li><p>Go to: <code>assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</code></p>
</li>
<li><p>Edit Dockerfile</p>
</li>
</ol>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Etapa de build</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">15</span> AS builder

<span class="hljs-comment"># Define o diretório de trabalho</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Copia os arquivos necessários</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package.json package-lock.json ./</span>

<span class="hljs-comment"># Instala dependências</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-comment"># Copia o restante dos arquivos da aplicação</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-comment"># Faz o build da aplicação (se necessário)</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>

<span class="hljs-comment"># Etapa de produção</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">15</span>

<span class="hljs-comment"># Define o diretório de trabalho</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Copia arquivos do estágio anterior</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app /app</span>

<span class="hljs-comment"># Instala as dependências de produção</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install --only=production</span>

<span class="hljs-comment"># Define o comando para iniciar a aplicação</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>

<span class="hljs-comment"># Exposição de porta (opcional)</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
</code></pre>
<h3 id="heading-step-6-create-deployment-and-load-balancer"><strong>Step 6: Create Deployment and Load Balancer</strong></h3>
<ol>
<li><p>Access Azure Repo</p>
</li>
<li><p>Access the repository on the same place where azure-pipelines.yml and create the file <code>deployment_service.yml</code></p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">nodejs-deployment</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
   <span class="hljs-attr">selector:</span>
     <span class="hljs-attr">matchLabels:</span>
       <span class="hljs-attr">app:</span> <span class="hljs-string">nodejs</span>
   <span class="hljs-attr">template:</span> 
     <span class="hljs-attr">metadata:</span>
       <span class="hljs-attr">name:</span> <span class="hljs-string">nodejs</span>
       <span class="hljs-attr">labels:</span>
         <span class="hljs-attr">app:</span> <span class="hljs-string">nodejs</span>  
     <span class="hljs-attr">spec:</span>
       <span class="hljs-attr">containers:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">nodejsapp</span>
           <span class="hljs-attr">image:</span> <span class="hljs-string">mdcrepositorychiroli.azurecr.io/node.js:latest</span>
           <span class="hljs-attr">ports:</span>
             <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
       <span class="hljs-attr">imagePullSecrets:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">acr-secret</span>    
 <span class="hljs-string">---</span>
 <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">nodejs-service</span>
   <span class="hljs-attr">labels:</span> 
     <span class="hljs-attr">app:</span> <span class="hljs-string">nodejs</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span> 
   <span class="hljs-attr">selector:</span>
     <span class="hljs-attr">app:</span> <span class="hljs-string">nodejs</span>
   <span class="hljs-attr">ports:</span> 
     <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">3000</span>
       <span class="hljs-attr">targetPort:</span> <span class="hljs-number">3000</span>
</code></pre>
</li>
</ol>
<h3 id="heading-step-7-create-a-file-for-the-jmeter-test"><strong>Step 7: Create a file for the Jmeter test</strong></h3>
<ol>
<li><p>Access Azure Repo</p>
</li>
<li><p>Access the repository on the same place where azure-pipelines.yml and create the file <code>test.jmx</code></p>
<pre><code class="lang-plaintext"> &lt;?xml version="1.0" encoding="UTF-8"?&gt;
 &lt;jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3"&gt;
   &lt;hashTree&gt;
     &lt;!-- Test Plan --&gt;
     &lt;TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Basic Test Plan" enabled="true"&gt;
       &lt;stringProp name="TestPlan.comments"&gt;&lt;/stringProp&gt;
     &lt;/TestPlan&gt;
     &lt;hashTree&gt;
       &lt;!-- Thread Group --&gt;
       &lt;ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Load Test Group" enabled="true"&gt;
         &lt;stringProp name="ThreadGroup.on_sample_error"&gt;continue&lt;/stringProp&gt;

         &lt;!-- Adicionando o Loop Controller --&gt;
         &lt;elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControllerGui" testclass="LoopController" testname="Loop Controller" enabled="true"&gt;
           &lt;boolProp name="LoopController.continue_forever"&gt;false&lt;/boolProp&gt;
           &lt;stringProp name="LoopController.loops"&gt;1&lt;/stringProp&gt;
         &lt;/elementProp&gt;

         &lt;stringProp name="ThreadGroup.num_threads"&gt;10&lt;/stringProp&gt;
         &lt;stringProp name="ThreadGroup.ramp_time"&gt;5&lt;/stringProp&gt;
         &lt;boolProp name="ThreadGroup.scheduler"&gt;false&lt;/boolProp&gt;
       &lt;/ThreadGroup&gt;
       &lt;hashTree&gt;
         &lt;!-- HTTP Sampler --&gt;
         &lt;HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"&gt;
           &lt;stringProp name="HTTPSampler.domain"&gt;${__P(host, localhost)}&lt;/stringProp&gt;
           &lt;stringProp name="HTTPSampler.port"&gt;3000&lt;/stringProp&gt;
           &lt;stringProp name="HTTPSampler.protocol"&gt;http&lt;/stringProp&gt;
           &lt;stringProp name="HTTPSampler.path"&gt;/&lt;/stringProp&gt;
           &lt;stringProp name="HTTPSampler.method"&gt;GET&lt;/stringProp&gt;
         &lt;/HTTPSamplerProxy&gt;
         &lt;hashTree/&gt;
       &lt;/hashTree&gt;
     &lt;/hashTree&gt;
   &lt;/hashTree&gt;
 &lt;/jmeterTestPlan&gt;
</code></pre>
</li>
</ol>
<h3 id="heading-step-7-configure-the-pipeline"><strong>Step 7: Configure the Pipeline</strong></h3>
<ol>
<li><p>Access Azure Repo</p>
</li>
<li><p>Edit file <code>azure-pipelines.yml</code></p>
</li>
<li><p>With the new configuration and the configuration of the last challenge the pipeline looks like this:</p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">trigger:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">main</span>

<span class="hljs-attr">pr:</span>
  <span class="hljs-attr">branches:</span>
    <span class="hljs-attr">include:</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">main</span>

<span class="hljs-attr">stages:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">stage:</span> <span class="hljs-string">BuildAndTest</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">Build</span> <span class="hljs-string">and</span> <span class="hljs-string">Test</span> <span class="hljs-string">Job</span>
    <span class="hljs-attr">pool:</span>
      <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>
    <span class="hljs-attr">jobs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">job:</span> <span class="hljs-string">Build</span>
      <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Build and Test Job'</span>
      <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">checkout:</span> <span class="hljs-string">self</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">'src'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Checkout code'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">UseNode@1</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">'15.x'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Linter (ESLint)'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span> <span class="hljs-string">--save-dev</span> <span class="hljs-string">prettier</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Formatter (Prettier)'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">prettier</span> <span class="hljs-string">--</span> <span class="hljs-string">--write</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Format Code (Prettier)'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">CI=true</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Test (Jest)'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>
        <span class="hljs-attr">workingDirectory:</span> <span class="hljs-string">$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Run Build'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">Docker@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">containerRegistry:</span> <span class="hljs-string">'MdcRegistry'</span>
          <span class="hljs-attr">repository:</span> <span class="hljs-string">'Node.js'</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">'buildAndPush'</span>
          <span class="hljs-attr">Dockerfile:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0/Dockerfile'</span>
          <span class="hljs-attr">buildContext:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0'</span>
          <span class="hljs-attr">tags:</span> <span class="hljs-string">'latest'</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Build and Push Docker Image'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-literal">true</span>  

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureCLI@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">'MdcAzureRm'</span>
          <span class="hljs-attr">scriptType:</span> <span class="hljs-string">'bash'</span>
          <span class="hljs-attr">scriptLocation:</span> <span class="hljs-string">'inlineScript'</span>
          <span class="hljs-attr">inlineScript:</span> <span class="hljs-string">|
            az aks get-credentials --resource-group projeto-flask --name k8scluster --overwrite-existing
            kubectl apply -f $(Build.SourcesDirectory)/deployment_service.yml
            kubectl get services
</span>        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Execute Kubernetes'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-literal">true</span>


      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          echo "Installing JMeter..."
          sudo apt-get update -y
          sudo apt-get install -y wget openjdk-11-jre  # Install Java and wget
</span>
          <span class="hljs-string">wget</span> <span class="hljs-string">https://downloads.apache.org//jmeter/binaries/apache-jmeter-5.6.3.tgz</span>
          <span class="hljs-string">tar</span> <span class="hljs-string">-xvzf</span> <span class="hljs-string">apache-jmeter-5.6.3.tgz</span>
          <span class="hljs-string">sudo</span> <span class="hljs-string">mv</span> <span class="hljs-string">apache-jmeter-5.6.3</span> <span class="hljs-string">/opt/jmeter</span>

          <span class="hljs-comment"># Adiciona JMeter ao PATH corretamente</span>
          <span class="hljs-string">echo</span> <span class="hljs-string">"export PATH=/opt/jmeter/bin:\$PATH"</span> <span class="hljs-string">|</span> <span class="hljs-string">sudo</span> <span class="hljs-string">tee</span> <span class="hljs-string">-a</span> <span class="hljs-string">/etc/profile</span>
          <span class="hljs-string">export</span> <span class="hljs-string">PATH=/opt/jmeter/bin:$PATH</span>  <span class="hljs-comment"># Atualiza PATH na sessão atual</span>

          <span class="hljs-string">echo</span> <span class="hljs-string">"JMeter installed successfully."</span>
        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install JMeter'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          echo "Creating JMeter test file..."
          cp $(Build.SourcesDirectory)/test.jmx /opt/jmeter/test.jmx
</span>        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Create JMeter Test File'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureCLI@2</span>
        <span class="hljs-attr">inputs:</span>
          <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">'MdcAzureRm'</span>
          <span class="hljs-attr">scriptType:</span> <span class="hljs-string">'bash'</span>
          <span class="hljs-attr">scriptLocation:</span> <span class="hljs-string">'inlineScript'</span>
          <span class="hljs-attr">inlineScript:</span> <span class="hljs-string">|
            NODEJS_IP=$(kubectl get svc nodejs-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
            echo "##vso[task.setvariable variable=NODEJS_IP]$NODEJS_IP"
            echo "Fetched IP: $NODEJS_IP"
</span>        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Retrieve NodeJS Service External IP'</span>
        <span class="hljs-attr">continueOnError:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">|
          export PATH=/opt/jmeter/bin:$PATH
          jmeter -v
          jmeter -n -t /opt/jmeter/test.jmx -l /opt/jmeter/results.jtl -j jmeter.log -Jhost=$NODEJS_IP
          cat /opt/jmeter/results.jtl
          cat jmeter.log
</span>        <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Run JMeter'</span>
</code></pre>
<h3 id="heading-step-8-access-the-application"><strong>Step 8: Access the application</strong></h3>
<ol>
<li><p>Log in to Azure</p>
</li>
<li><p>Log in to your <strong>Azure account</strong> via CLI: <code>az login</code></p>
</li>
<li><p>Access your Kubernetes: <code>az aks get-credentials --resource-group Your_Resource_Group_Name --name mdc-aks</code></p>
</li>
<li><p>Execute the command <code>az get services</code> and access the page with the external IP</p>
</li>
<li><p>Access <a target="_blank" href="http://4.246.235.225:3000/"><code>http://Your_external_ip:3000/</code></a></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739821392267/89cdac73-cffe-43b4-a1f5-abb7357c4614.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-step-8-see-the-jmeter-test-results"><strong>Step 8: See the Jmeter test results</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739828359065/9a9c69f9-6aa5-40ae-ba0e-d7a8fe7a02ed.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Automating Node.js Builds with Azure DevOps Pipelines]]></title><description><![CDATA[Challenge: Part 1 Continuous integration
Set up a new repository and CI pipeline using any code version provider The CI steps should be created. The pipeline should run the following steps:

Install dependencies npm install

Linter (ESLint) npm run l...]]></description><link>https://joaochiroli.com.br/automating-nodejs-builds-with-azure-devops-pipelines</link><guid isPermaLink="true">https://joaochiroli.com.br/automating-nodejs-builds-with-azure-devops-pipelines</guid><category><![CDATA[Azure]]></category><category><![CDATA[azure-devops]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[João Chiroli]]></dc:creator><pubDate>Mon, 17 Feb 2025 14:38:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739803026138/4ea464d5-2460-436b-809c-f161572cfe5f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-challenge-part-1-continuous-integration">Challenge: Part 1 Continuous integration</h1>
<p>Set up a new repository and CI pipeline using any code version provider The CI steps should be created. The pipeline should run the following steps:</p>
<ul>
<li><p>Install dependencies npm install</p>
</li>
<li><p>Linter (ESLint) npm run lint</p>
</li>
<li><p>Formatter (Prettier) npm run prettier</p>
</li>
<li><p>Test (Jest) CI=true npm run test</p>
</li>
<li><p>Build npm run build</p>
</li>
</ul>
<p>The pipeline should be successful. Please provide some pull requests to show pass or fail status in the CI pipeline.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Azure Devops Account</li>
</ul>
<h3 id="heading-step-1-create-and-clone-the-repository">Step 1: Create and clone the Repository</h3>
<ol>
<li><p>Create New Project:</p>
</li>
<li><p>Once inside your organization's Azure DevOps dashboard, click on the "New Project" button, usually at the top right or in the center of the screen.</p>
</li>
<li><p>Configure Project Details:</p>
</li>
<li><p>Project Name: Enter <strong>"DevOps Challenge"</strong> as the project name.</p>
</li>
<li><p>Go to Repos -&gt; Import Repository -&gt; Clone URL: <a target="_blank" href="https://github.com/joaochiroli/Devops-Challenge">https://github.com/joaochiroli/Devops-Challenge</a></p>
</li>
</ol>
<h3 id="heading-step-2-create-and-configure-azure-pipelinesyml">Step 2: Create and configure azure-pipelines.yml</h3>
<ol>
<li><p>Create file <code>azure-pipelines.yml</code></p>
<pre><code class="lang-plaintext"> trigger:
 - main

 pr:
   branches:
     include:
     -  main

 stages:
   - stage: BuildAndTest
     displayName: Build and Test Job
     pool:
       vmImage: 'ubuntu-latest'
     jobs:
     - job: Build
       displayName: 'Build and Test Job'
       steps:
       - checkout: self
         path: 'src'
         displayName: 'Checkout code'

       - task: UseNode@1
         inputs:
           version: '15.x'
         displayName: 'Install Node.js'

       - script: npm install
         workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
         displayName: 'Install dependencies'

       - script: npm run lint
         workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
         displayName: 'Linter (ESLint)'

       - script: npm install --save-dev prettier
         workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
         displayName: 'Install Formatter (Prettier)'

       - script: npm run prettier -- --write
         workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
         displayName: 'Format Code (Prettier)'

       - script: CI=true npm run test
         workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
         displayName: 'Test (Jest)'

       - script: npm run build
         workingDirectory: $(Build.SourcesDirectory)/assessment-cc-sre-kubernetes-sr-01/codebase/rdicidr-0.1.0
         displayName: 'Run Build'
</code></pre>
</li>
<li><p><strong>Commit and Save</strong></p>
</li>
<li><p>Explanations:</p>
<ol>
<li><p>I created trigger main and pr to start my pipeline. I use 1 stage to Build my application.</p>
</li>
<li><p>Task: UseNode@1 → Define the Node.js version</p>
</li>
<li><p>The other tasks do what the above steps ask, and the key to this build is to run it in the correct repository.</p>
</li>
</ol>
</li>
</ol>
<h3 id="heading-step-3-results">Step 3: Results</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739802380752/20826b54-e7c1-4107-b7f3-d4efd9803c66.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item></channel></rss>