<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pedro Oliveira</title>
    <description>The latest articles on DEV Community by Pedro Oliveira (@pramos).</description>
    <link>https://dev.to/pramos</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3778530%2F8adc1f8f-3bad-45e1-897a-e5e9073b2ae8.jpeg</url>
      <title>DEV Community: Pedro Oliveira</title>
      <link>https://dev.to/pramos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pramos"/>
    <language>en</language>
    <item>
      <title>Reconciliation Loop: Como o Kubernetes implementa infraestrutura declarativa</title>
      <dc:creator>Pedro Oliveira</dc:creator>
      <pubDate>Wed, 19 Aug 2026 23:16:31 +0000</pubDate>
      <link>https://dev.to/pramos/reconciliation-loop-como-o-kubernetes-implementa-infraestrutura-declarativa-2l6o</link>
      <guid>https://dev.to/pramos/reconciliation-loop-como-o-kubernetes-implementa-infraestrutura-declarativa-2l6o</guid>
      <description>&lt;p&gt;Ao meu ver, uma das maiores qualidades é o que faz o Kubernetes ser&lt;br&gt;
a ferramenta padrão quando se fala de operar infraestrutura em larga escala,&lt;br&gt;
seja ela em um &lt;em&gt;cloud provider&lt;/em&gt; ou rodando &lt;em&gt;on premisse&lt;/em&gt; é a sua abordagem&lt;br&gt;
na implementação de infraestrutura declarativa. O que faz com que seja usada por organizações&lt;br&gt;
de inúmeros tamanhos e áreas de atuação que desejam entregar uma plataforma de tecnologia&lt;br&gt;
que seja confiável e atenda inúmeros casos de uso&lt;/p&gt;

&lt;p&gt;Hoje, no mundo das ferramentas de infraestrutura como código (&lt;strong&gt;IaC&lt;/strong&gt;) existem&lt;br&gt;
duas abordagens principais para a implementação do conceito:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Infraestrutura Imperativa&lt;/em&gt;: Nessa abordagem, é necessário especificar as
etapas e comandos exatos para provisionar e orquestrar recursos&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Infraestrutura Declarativa&lt;/em&gt;: Ao contrário da forma imperativa, aqui o foco
não está em &lt;strong&gt;como&lt;/strong&gt; provisionar e orquestrar mas sim &lt;strong&gt;onde&lt;/strong&gt; o ponto final
da infraestrutura&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Além disso, para garantir que o estado declarado no arquivo YAML,&lt;br&gt;
ou outra linguagem de configuração, seja mantido ao longo do tempo,&lt;br&gt;
os sistemas de orquestração historicamente adotavam abordagens baseadas em&lt;br&gt;
&lt;strong&gt;Polling&lt;/strong&gt; (varreduras periódicas) ou &lt;strong&gt;Edge-Triggered&lt;/strong&gt; (reação a eventos pontuais):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modelo Baseado em Eventos (Edge-Triggered / Polling):&lt;/strong&gt; O sistema depende da
entrega perfeita de notificações de eventos ou de consultas periódicas (&lt;em&gt;polling&lt;/em&gt;).
Se uma notificação de "falha de Pod" for perdida devido a uma oscilação na rede,
a plataforma perde o contexto e a infraestrutura se mantém em um estado inconsistente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modelo Baseado no Estado Atual (Level-Triggered):&lt;/strong&gt; Em vez de se focar no
&lt;em&gt;evento que passou&lt;/em&gt;, o Kubernetes se baseia no &lt;em&gt;estado presente&lt;/em&gt;. Ele utiliza
uma conexão contínua (Watch) para capturar alterações, mas a decisão do que
fazer é tomada comparando a foto atual da infraestrutura com o estado desejado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É exatamente a implementação desse modelo &lt;strong&gt;Level-Triggered&lt;/strong&gt; que chamamos de&lt;br&gt;
&lt;strong&gt;Reconciliation Loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Durante esse artigo, tentarei explicar o funcionamento dessa abordagem através&lt;br&gt;
de um controller feito com kubebuilder (um framework Go para operator Kubernetes)&lt;br&gt;
que apresenta uma mensagem sempre que um &lt;code&gt;Pod&lt;/code&gt; é criado ou deletado&lt;/p&gt;
&lt;h2&gt;
  
  
  O motor por trás do Loop: Informers, Cache e WorkQue
&lt;/h2&gt;

&lt;p&gt;Para implementar a abordagem &lt;em&gt;Level-Triggered&lt;/em&gt; na prática sem sobrecarregar&lt;br&gt;
o &lt;code&gt;kube-apiserver&lt;/code&gt;, a biblioteca &lt;code&gt;controller-runtime&lt;/code&gt; (base do Kubebuilder)&lt;br&gt;
utiliza uma arquitetura baseada em três pilares fundamentais:&lt;br&gt;
&lt;strong&gt;Informer&lt;/strong&gt;, &lt;strong&gt;Cache Local&lt;/strong&gt; e &lt;strong&gt;WorkQueue&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Informer: Escutando a API em Tempo Real
&lt;/h3&gt;

&lt;p&gt;Em vez de fazer requisições periódicas (&lt;em&gt;polling&lt;/em&gt;) para saber se algo mudou,&lt;br&gt;
o controller utiliza o &lt;strong&gt;Informer&lt;/strong&gt;. Ele estabelece uma conexão HTTP de&lt;br&gt;
streaming de longa duração (&lt;em&gt;Watch&lt;/em&gt;) com o &lt;code&gt;apiserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No código Go de exemplo, esse registro é feito de forma declarativa dentro&lt;br&gt;
do &lt;code&gt;SetupWithManager&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Watches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pod&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnqueueRequestForObject&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sempre que um evento ocorre em um Pod no cluster, o &lt;code&gt;apiserver&lt;/code&gt; notifica o&lt;br&gt;
informer em tempo real.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Cache Local: Performance e Escalabilidade
&lt;/h3&gt;

&lt;p&gt;Se em cada reconciliação, fosse preciso buscar os dados completos direto no&lt;br&gt;
&lt;code&gt;etcd&lt;/code&gt;, o cluster colapsaria em larga escala. Por isso, ao receber uma&lt;br&gt;
notificação, o Informer atualiza um &lt;strong&gt;Cache In-Memory&lt;/strong&gt; mantendo localmente&lt;br&gt;
dentro do processo do controller.&lt;/p&gt;

&lt;p&gt;Quando é executada a leitura do recurso dentro da função de reconciliação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;pod&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pod&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa consulta &lt;strong&gt;não faz uma request ao Kubernetes&lt;/strong&gt;; ela lê instantaneamente&lt;br&gt;
do cache local mantido pelo Informer.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. WorkQueue: Garantindo Resiliência e Desduplicação
&lt;/h3&gt;

&lt;p&gt;O Informer não chama a função &lt;code&gt;Reconcile()&lt;/code&gt; diretamente. Em vez disso, o&lt;br&gt;
utilitário &lt;code&gt;handler.EnqueueRequestForObject{}&lt;/code&gt; extrai apenas os metadados&lt;br&gt;
do recurso - a chave &lt;code&gt;Namespace/Name&lt;/code&gt; - e a insere em uma fila de trabalho&lt;br&gt;
(&lt;em&gt;WorkQueue&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;A WorkQueue resolve três grandes problemas de engenharia:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicação&lt;/strong&gt;: Se múltiplos eventos do mesmo Pod chegam em um curto
espaço de tempo, a chave é inserida uma vez na fila, evitando execuções
redundantes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting e Backoff&lt;/strong&gt;: Se a reconciliação falhar, a fila reagenda a
tentativa com tempo de espera exponencial (&lt;em&gt;exponential backoff&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desacoplamento&lt;/strong&gt;: O recebimento de eventos e processamento deles rodam
de forma assíncrona.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Desmistificando o Reconcile(): O Código na Prática
&lt;/h2&gt;

&lt;p&gt;Com &lt;strong&gt;Informer&lt;/strong&gt;, &lt;strong&gt;Cache Local&lt;/strong&gt; e &lt;strong&gt;WorkQueue&lt;/strong&gt; implementadas no&lt;br&gt;
&lt;code&gt;SetupWithManager&lt;/code&gt;, a execução chega no coração de tudo: A função &lt;code&gt;Reconcile()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Diferente de arquiteturas orientadas a eventos tradicionais, o parâmetro&lt;br&gt;
recebido por essa função não contém o Pod inteiro e nem o tipo de evento&lt;br&gt;
que ocorreu (como &lt;code&gt;Create&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt; ou &lt;code&gt;Delete&lt;/code&gt;). O controller recebe apenas&lt;br&gt;
a chave do recurso através do parâmetro &lt;code&gt;ctrl.Request&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ExampleReconciler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A seguir, vamos analisar como cada bloco da função interage com os componentes&lt;br&gt;
internos e onde inspecionar os logs para validar o comportamento em tempo&lt;br&gt;
real&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Leitura via Cache In-Memory e Tratamento de Exclusão
&lt;/h3&gt;

&lt;p&gt;O ciclo começa obtendo o estado atual do recurso&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;pod&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pod&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IgnoreNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The pod was deleted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interação com o Cache&lt;/strong&gt;: A chamada &lt;code&gt;r.Get()&lt;/code&gt; consulta diretamente o
Cache In-Memory mantido pelo Informer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caso de Pod Deletado&lt;/strong&gt;: Se o Pod foi removido do cluster, o Informer
atualiza o cache local. Quando o &lt;code&gt;r.Get()&lt;/code&gt; tenta buscar a chave
&lt;code&gt;req.NamespacedName&lt;/code&gt;, ele retorna um erro do tipo &lt;code&gt;NotFound&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resposta ao WorkQueue&lt;/strong&gt;: Ao usar &lt;code&gt;client.IgnoreNotFound(err)&lt;/code&gt;, o código
reconhece que a ausência do recurso é um estado válido final. Após isso,
imprime o log e retorna &lt;code&gt;ctrl.Result{}, nil&lt;/code&gt;, sinalizando para a WorkQueue
que o item foi processado com sucesso e pode ser removido da fila.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2026-08-19T17:09:45-03:00   INFO    The pod was deleted &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"controller"&lt;/span&gt;: &lt;span class="s2"&gt;"example"&lt;/span&gt;, &lt;span class="s2"&gt;"namespace"&lt;/span&gt;: &lt;span class="s2"&gt;"default"&lt;/span&gt;, &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"nginx"&lt;/span&gt;, &lt;span class="s2"&gt;"reconcileID"&lt;/span&gt;: &lt;span class="s2"&gt;"69af3331-bdd4-45c6-98dd-88eaf32f2439"&lt;/span&gt;, &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"nginx"&lt;/span&gt;, &lt;span class="s2"&gt;"namespace"&lt;/span&gt;: &lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Avaliação de Estado (Level-Triggered in Action)
&lt;/h3&gt;

&lt;p&gt;Se o recurso foi encontrado no cache, o código prossegue para avaliar o seu&lt;br&gt;
estado atual e tomar decisões&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;controllerutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainsFinalizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my.domain/finalizer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pod was deleted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pod created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lógica de Inspeção&lt;/strong&gt;: Como o controller não recebe flags imperativas
(como &lt;code&gt;isCreate&lt;/code&gt;), ele deve deduzir o cenário inspecionando os metadados
e o &lt;code&gt;Spec&lt;/code&gt;/&lt;code&gt;Status&lt;/code&gt; do Pod carregado da RAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uso de Finalizers:&lt;/strong&gt; Inspecionar o array de Finalizers permite ao controller
identificar se o objeto está em processo de &lt;em&gt;graceful deletion&lt;/em&gt; ou se é
uma reconciliação comum de criação/autorização&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Podemos verificar esse comportamento, rodando o comando abaixo para criação&lt;br&gt;
de um Pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run &lt;span class="nt"&gt;--image&lt;/span&gt; nginx nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Que vai gerar a seguinte ocorrência no log do controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-08-19T19:33:07-03:00   INFO    Pod created {"controller": "example", "namespace": "default", "name": "nginx", "reconcileID": "88f9cba6-502e-481e-b5a5-c9a01b72d880", "name": "nginx", "namespace": "default"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se colocarmos uma label nesse Pod recém criado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl label pod nginx &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A WorkQueue dispara o &lt;code&gt;Reconcile()&lt;/code&gt; novamente&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-08-19T19:35:53-03:00   INFO    Pod created {"controller": "example", "namespace": "default", "name": "nginx", "reconcileID": "55f7aa09-fd82-4905-b169-37b35bd1f908", "name": "nginx", "namespace": "default"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Sinalizando o Fim do Loop para a WorkQueue
&lt;/h3&gt;

&lt;p&gt;A instrução de encerramento do método define como a WorkQueue deve gerenciar aquela chave:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O retorno da função &lt;code&gt;Reconcile&lt;/code&gt; dita o fluxo da fila de trabalho:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctrl.Result{}, nil&lt;/code&gt;: O estado desejado é atingido
(ou o recurso não existe mais). A chave é removida da WorkQueue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ctrl.Result{}, err&lt;/code&gt;: Ocorreu uma falha. A WorkQueue retém a chave
e reagenda a execução aplicando &lt;strong&gt;Exponential Backoff&lt;/strong&gt; para evitar
&lt;em&gt;thundering herd&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ctrl.Result{RequeueAfter: 10 * time.Second}, nil&lt;/code&gt;: Força um re-agendamento
periódico (Polling/Health-check), útil quando o controller depende de externos
ao Kubernetes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão: A "Mágica" do Kubernetes Desmistificada
&lt;/h2&gt;

&lt;p&gt;A capacidade do Kubernetes de manter clusters massivos operando de forma&lt;br&gt;
resiliente não se deve a um passe de mágica, mas sim a elegância do&lt;br&gt;
&lt;strong&gt;Reconciliation Loop&lt;/strong&gt; e do modelo &lt;strong&gt;Level-Triggered&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ao longo desse artigo, vimos que o modelo declarativo é sustentado por&lt;br&gt;
componentes cirurgicamente projetados:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O &lt;strong&gt;Informer&lt;/strong&gt; reage a eventos sem sobrecarregar a rede com
&lt;em&gt;polling&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;Cache Local In-Memory&lt;/strong&gt; garante leituras de altíssima performance.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;WorkQueue&lt;/strong&gt; desacopla o recebimento de eventos da execução, garantindo
resiliência, deduplicação e &lt;em&gt;rate-limiting&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A função &lt;strong&gt;&lt;code&gt;Reconcile()&lt;/code&gt;&lt;/strong&gt; assume a responsabilidade de comparar o estado
atual (&lt;code&gt;Status&lt;/code&gt;) com o estado desejado (&lt;code&gt;Spec&lt;/code&gt;), agindo até que ambos convirjam.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Entender essa engrenagem é o diferencial entre encarar o Kubernetes como&lt;br&gt;
um "buraco negro de arquivos YAML" e utilizá-lo como a plataforma extensível que&lt;br&gt;
ele realmente é. Quando você domina o funcionamento interno do&lt;br&gt;
&lt;code&gt;controller-runtime&lt;/code&gt;, criar seus próprios CRDs e Operators para automatizar&lt;br&gt;
infraestrutura torna-se uma evolução natural na sua jornada de engenharia.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>infrastructure</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Kubernetes sem Cloud Provider (Parte 2): Criando Operators em Go para automação e self-service de plataforma</title>
      <dc:creator>Pedro Oliveira</dc:creator>
      <pubDate>Mon, 25 May 2026 23:37:29 +0000</pubDate>
      <link>https://dev.to/pramos/kubernetes-sem-cloud-provider-parte-2-criando-operators-em-go-para-automacao-e-self-service-de-1pp9</link>
      <guid>https://dev.to/pramos/kubernetes-sem-cloud-provider-parte-2-criando-operators-em-go-para-automacao-e-self-service-de-1pp9</guid>
      <description>&lt;p&gt;Uma das maiores cargas cognitivas quando falamos em Kubernetes é a configuração dos vários componentes necessários para entregar valor real ao cluster. Em outras palavras, deparamo-nos com uma quantidade massiva de YAMLs para configurar Custom Resources (CRs), Custom Resource Definitions (CRDs), Roles e tudo o que é necessário para que um serviço tenha um Ingress com TLS, por exemplo.&lt;/p&gt;

&lt;p&gt;Isso ficou ainda mais evidente para mim durante os meus estudos de Kubernetes sem as abstrações fornecidas pelos Cloud Providers (assunto que cobrimos na Parte 1). Para resolver esse problema, decidi aprofundar o conhecimento nas maneiras pelas quais o Kubernetes nos permite automatizar essas operações e criar abstrações &lt;em&gt;self-service&lt;/em&gt; através de &lt;strong&gt;Custom Operators&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Neste artigo, vou documentar a criação de dois operators desenvolvidos em Go que fazem a ponte entre o &lt;code&gt;HashiCorp Vault&lt;/code&gt; e os operadores de mercado &lt;code&gt;External Secrets Operator&lt;/code&gt; e &lt;code&gt;Cert-Manager&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vaultreaver&lt;/code&gt;:&lt;/strong&gt; Configura a integração e os limites de segurança entre o Kubernetes e a API externa do Vault.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;platform-operator&lt;/code&gt;:&lt;/strong&gt; Atua como o centralizador e orquestrador de configurações dentro do cluster.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Motivação
&lt;/h2&gt;

&lt;p&gt;Durante a configuração manual dos componentes responsáveis pela comunicação com o Vault, percebi um padrão repetitivo de tarefas que gerava muita fricção:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Criar uma &lt;code&gt;ServiceAccount&lt;/code&gt; no Kubernetes.&lt;/li&gt;
&lt;li&gt;Criar uma &lt;code&gt;Vault Role&lt;/code&gt; para permitir a generation de tokens atrelados a essa &lt;code&gt;ServiceAccount&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Criar uma política de acesso no Vault (&lt;code&gt;Vault Policy&lt;/code&gt;) e vinculá-la à Role.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Essa operação é mandatória para que os componentes do cluster se comuniquem de forma segura com o Vault (utilizando o token JWT da &lt;code&gt;ServiceAccount&lt;/code&gt; para se autenticarem). &lt;/p&gt;

&lt;p&gt;Para entender a fundo a mecânica desse ecossistema, resolvi construir um operador que utilizasse esse mesmo mecanismo de autenticação nativa, mas de forma totalmente automatizada. O objetivo era esconder essa complexidade do usuário final da plataforma, automatizando todo o &lt;em&gt;boilerplate&lt;/em&gt; de segurança e gerando os tokens necessários para as operações de cada componente.&lt;/p&gt;




&lt;h2&gt;
  
  
  Como tudo funciona por baixo dos panos
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Nota: Para esse fluxo funcionar, o Vault já deve estar configurado previamente com o método de autenticação de Kubernetes ativo (&lt;code&gt;auth/kubernetes&lt;/code&gt;). O nosso operador utiliza a sua própria identidade no cluster para interagir com a API do Vault e criar as novas permissões.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A configuração começa pelo &lt;strong&gt;&lt;code&gt;vaultreaver&lt;/code&gt;&lt;/strong&gt;. Ele é responsável por gerenciar o ciclo de vida dos recursos fora do cluster (na API do Vault) e recebe dois Custom Resources principais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;VaultPolicy&lt;/code&gt;:&lt;/strong&gt; Declara a política de segurança com as permissões que a aplicação/componente terá dentro do Vault.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;VaultKubernetesRoleBinding&lt;/code&gt;:&lt;/strong&gt; Faz o vínculo (&lt;em&gt;binding&lt;/em&gt;) da &lt;code&gt;VaultPolicy&lt;/code&gt; com a &lt;code&gt;ServiceAccount&lt;/code&gt; do Kubernetes e a respectiva &lt;code&gt;VaultRole&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O exemplo abaixo demonstra a criação de uma permissão declarativa de leitura no path &lt;code&gt;kv&lt;/code&gt; do Vault para um namespace de aplicação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security.platform.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VaultPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-external-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-apps&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;path "kv/data/app-teste/secret-secreto" {&lt;/span&gt;
      &lt;span class="s"&gt;capabilities = ["read"]&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;vaultPolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-external-secret-policy&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security.platform.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VaultKubernetesRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-external-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-apps&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault&lt;/span&gt;
  &lt;span class="na"&gt;authMount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes&lt;/span&gt;
  &lt;span class="na"&gt;boundNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nginx-apps&lt;/span&gt;
  &lt;span class="na"&gt;boundServiceAccounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-external-secret&lt;/span&gt;
  &lt;span class="na"&gt;roleName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-external-secret&lt;/span&gt;
  &lt;span class="na"&gt;tokenPolicies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-external-secret-policy&lt;/span&gt;
  &lt;span class="na"&gt;tokenTTL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois que o &lt;code&gt;vaultreaver&lt;/code&gt; estabelece com sucesso a ponte de segurança com o Vault, o &lt;code&gt;platform-operator&lt;/code&gt; assume a responsabilidade pelas automações dentro do cluster.&lt;/p&gt;

&lt;p&gt;A API do &lt;code&gt;platform-operator&lt;/code&gt; é desenhada para ser mais ampla, aceitando contratos simplificados focados na experiência do desenvolvedor (Self-Service). O exemplo a seguir demonstra o manifesto &lt;code&gt;VaultCertificate&lt;/code&gt;, que abstrai todo o setup necessário para provisionar TLS via Cert-Manager com o backend de PKI do Vault.&lt;br&gt;
Ao receber este único CR, o operador gera dinamicamente os recursos internos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A ServiceAccount configurada com o RBAC necessário para o fluxo de certificados.&lt;/li&gt;
&lt;li&gt;As regras do Cert-Manager (como o ClusterIssuer ou Issuer apontando para o Vault).&lt;/li&gt;
&lt;li&gt;O recurso final de Certificate que dispara a emissão real do certificado TLS.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security.platform.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VaultCertificate&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-tls&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-apps&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vaultUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;//172.18.0.12&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;8200&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;&lt;span class="s"&gt;(http://172.18.0.12:8200)&lt;/span&gt;
  &lt;span class="na"&gt;authPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/v1/auth/kubernetes&lt;/span&gt;
  &lt;span class="na"&gt;vaultRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-tls-role&lt;/span&gt;
  &lt;span class="na"&gt;pkiPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pki/sign/internal-dot-infra&lt;/span&gt;
  &lt;span class="na"&gt;commonName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app.internal.infra&lt;/span&gt;
  &lt;span class="na"&gt;dnsNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;my-app.internal.infra&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;my-app-teste.internal.infra&lt;/span&gt;
  &lt;span class="na"&gt;SecretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment-tls&lt;/span&gt;
  &lt;span class="na"&gt;certManagerServiceAccount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cert-manager"&lt;/span&gt;
  &lt;span class="na"&gt;certManagerNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cert-manager"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Da mesma forma, o operador possui controllers dedicados a simplificar o uso do &lt;code&gt;ExternalSecrets&lt;/code&gt;, encapsulando e criando de forma automatizada o &lt;code&gt;SecretStore&lt;/code&gt; e o &lt;code&gt;ExternalSecret&lt;/code&gt; correspondente a partir de uma interface limpa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testes e Validação
&lt;/h2&gt;

&lt;p&gt;Para validar o comportamento do ecossistema e garantir a idempotência dos controllers em Go, montei um laboratório completo que pode ser conferido neste repositório de demonstração:&lt;br&gt;
&lt;a href="https://github.com/pedrohro1992/homelab-app-demo" rel="noopener noreferrer"&gt;https://github.com/pedrohro1992/homelab-app-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O objetivo do laboratório foi:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Garantir a automação e o provisionamento de um certificado TLS válido via Cert-Manager usando o nosso platform-operator.&lt;/li&gt;
&lt;li&gt;Injetar credenciais sensíveis via ExternalSecrets vindas diretamente do Vault de forma totalmente declarativa.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O cenário de teste consiste no deployment de uma aplicação Nginx. Através do Ingress configurado com o TLS gerado e com o segredo resolvido pelo operador, conseguimos expor com sucesso a aplicação. Ao acessar a página de diagnóstico, é possível validar visualmente que todas as variáveis de ambiente baseadas no segredo do Vault (como o nosso VALOR_SECRET) foram injetadas perfeitamente no container em tempo de execução.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ru9pv8o8nw4buyzkqry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ru9pv8o8nw4buyzkqry.png" alt=" " width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8zc37mhuhgi6ek1h0ia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8zc37mhuhgi6ek1h0ia.png" alt=" " width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Considerações finais
&lt;/h1&gt;

&lt;p&gt;Apesar de existirem diversas soluções prontas para integração entre Kubernetes e Vault, implementar esses Operators foi um excelente exercício para compreender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reconciliação de recursos no Kubernetes&lt;/li&gt;
&lt;li&gt;Desenvolvimento de Operators com Kubebuilder&lt;/li&gt;
&lt;li&gt;Fluxos de autenticação Kubernetes ↔ Vault&lt;/li&gt;
&lt;li&gt;Automação de plataforma&lt;/li&gt;
&lt;li&gt;Criação de abstrações de self-service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Além do aprendizado técnico, o projeto também ajudou a reduzir significativamente a complexidade operacional envolvida na configuração manual desses componentes.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Kubernetes sem Cloud Provider (Parte 1): CNI, Storage Dinâmico, Ingress e Arquitetura Terraform</title>
      <dc:creator>Pedro Oliveira</dc:creator>
      <pubDate>Wed, 18 Feb 2026 02:40:16 +0000</pubDate>
      <link>https://dev.to/pramos/kubernetes-sem-cloud-provider-parte-1-cni-storage-dinamico-ingress-e-arquitetura-terraform-eof</link>
      <guid>https://dev.to/pramos/kubernetes-sem-cloud-provider-parte-1-cni-storage-dinamico-ingress-e-arquitetura-terraform-eof</guid>
      <description>&lt;p&gt;Durante muito tempo — e ainda hoje em muitos cenários on-premises — o Kubernetes foi (e é) executado sem integrações nativas de cloud provider. Antes de EKS, GKE e AKS se tornarem padrão, era responsabilidade do operador configurar explicitamente rede, armazenamento, ingress, identidade e registry.&lt;/p&gt;

&lt;p&gt;Este artigo é a primeira parte de uma série onde documento a construção de um cluster executando sobre &lt;code&gt;Kubernetes in Docker (KinD)&lt;/code&gt;, focando em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuração de CNI (rede de pods)&lt;/li&gt;
&lt;li&gt;Provisionamento dinâmico de storage com CSI&lt;/li&gt;
&lt;li&gt;Configuração de Ingress&lt;/li&gt;
&lt;li&gt;Estruturação e automação completa via Terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;A parte de TLS com Vault e cert-manager ficará para o próximo artigo, pois ainda estou finalizando essa integração.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🎯 Motivação
&lt;/h1&gt;

&lt;p&gt;Em ambientes gerenciados, temos automaticamente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CNI integrada à VPC&lt;/li&gt;
&lt;li&gt;Provisionamento dinâmico de discos&lt;/li&gt;
&lt;li&gt;Load Balancers&lt;/li&gt;
&lt;li&gt;Integrações de identidade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mas nada disso é inerente ao Kubernetes. São componentes adicionais configurados pelo provedor.&lt;/p&gt;

&lt;p&gt;A proposta deste projeto foi remover essas abstrações e implementar manualmente as capacidades essenciais de um cluster funcional.&lt;/p&gt;

&lt;p&gt;Você não precisa saber mecânica para dirigir.&lt;br&gt;
Mas quando o carro para no meio da estrada, esse conhecimento faz diferença.&lt;/p&gt;




&lt;h1&gt;
  
  
  🧱 Arquitetura do Projeto
&lt;/h1&gt;

&lt;p&gt;O cluster foi criado com KinD e inicialmente não possuía:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CNI funcional&lt;/li&gt;
&lt;li&gt;Provisionador de storage&lt;/li&gt;
&lt;li&gt;Ingress Controller&lt;/li&gt;
&lt;li&gt;Integrações externas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A partir disso, foram adicionados os seguintes componentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌐 Rede — CNI com Calico
&lt;/h2&gt;

&lt;p&gt;Para permitir comunicação entre pods e nós, foi instalado o:&lt;br&gt;
&lt;code&gt;Calico&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Funções habilitadas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Atribuição de IPs aos pods&lt;/li&gt;
&lt;li&gt;Encapsulamento VXLAN&lt;/li&gt;
&lt;li&gt;Network Policies&lt;/li&gt;
&lt;li&gt;Comunicação inter-node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sem CNI, pods não se comunicam — esse é o primeiro requisito real para um cluster utilizável.&lt;/p&gt;

&lt;h2&gt;
  
  
  💾 Storage Dinâmico — OpenEBS
&lt;/h2&gt;

&lt;p&gt;Para substituir o papel que discos gerenciados fariam em cloud, implementei:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OpenEBS&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Com isso foi possível:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Criar StorageClasses&lt;/li&gt;
&lt;li&gt;Provisionar PVCs dinamicamente&lt;/li&gt;
&lt;li&gt;Utilizar backend baseado em armazenamento local&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso reproduz o comportamento esperado por aplicações stateful em qualquer ambiente Kubernetes.&lt;/p&gt;




&lt;h1&gt;
  
  
  🧠 Estrutura Terraform — Organização Modular
&lt;/h1&gt;

&lt;p&gt;O repositório do módulo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pedrohro1992/tf-module-kind-cluster.git" rel="noopener noreferrer"&gt;https://github.com/pedrohro1992/tf-module-kind-cluster.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O módulo  &lt;code&gt;tf-module-kind-blueprint&lt;/code&gt; atua como um módulo agregador.&lt;/p&gt;

&lt;p&gt;Essa técnica é conhecida como:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root Module Composition Pattern (ou Aggregator Module Pattern)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nesse modelo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O módulo raiz não implementa recursos diretamente&lt;/li&gt;
&lt;li&gt;Ele compõe módulos especializados&lt;/li&gt;
&lt;li&gt;Centraliza variáveis e dependências&lt;/li&gt;
&lt;li&gt;Expõe uma interface limpa para consumo
Cada componente (CNI, OpenEBS, NGINX Ingress) está encapsulado em seu próprio módulo Terraform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vantagens arquiteturais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separação clara de responsabilidades&lt;/li&gt;
&lt;li&gt;Modularidade&lt;/li&gt;
&lt;li&gt;Reutilização&lt;/li&gt;
&lt;li&gt;Organização escalável&lt;/li&gt;
&lt;li&gt;Manutenção facilitada&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🧩 Desafios Técnicos
&lt;/h1&gt;

&lt;p&gt;Alguns pontos relevantes durante a implementação:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coordenação entre providers (Kubernetes e Helm)&lt;/li&gt;
&lt;li&gt;Garantia de ordem correta de dependências&lt;/li&gt;
&lt;li&gt;Idempotência dos módulos&lt;/li&gt;
&lt;li&gt;Encadeamento de outputs&lt;/li&gt;
&lt;li&gt;Inicialização do provider Kubernetes apenas após cluster estar disponível&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automatizar componentes de infraestrutura dentro de um cluster recém-criado exige atenção especial ao ciclo de vida dos recursos.&lt;/p&gt;




&lt;h1&gt;
  
  
  ⚠️ Disclaimer
&lt;/h1&gt;

&lt;p&gt;Tenho um senso de humor peculiar.&lt;/p&gt;

&lt;p&gt;O nome fictício da “empresa” usada como estudo de caso no código é:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cacetinho-SA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sim, há referências a isso no código.&lt;br&gt;
É intencional. 😄&lt;/p&gt;

&lt;p&gt;💽 &lt;strong&gt;PVC&lt;/strong&gt; provisionado dinamicamente pelo OpenEBS&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9oasy9zgmagfdk8mz5h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9oasy9zgmagfdk8mz5h.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔮 Próximos Passos
&lt;/h3&gt;

&lt;p&gt;Nos próximos artigos da série:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuração de Identity Provider&lt;/li&gt;
&lt;li&gt;OIDC e Workload Identity&lt;/li&gt;
&lt;li&gt;DNS automático do Ingress com ExternalDNS&lt;/li&gt;
&lt;li&gt;TLS automático no Ingress com:&lt;/li&gt;
&lt;li&gt;cert-manager&lt;/li&gt;
&lt;li&gt;HashiCorp Vault como CA&lt;/li&gt;
&lt;li&gt;Evolução para Gateway API&lt;/li&gt;
&lt;li&gt;Service Mesh multi-cluster com Istio&lt;/li&gt;
&lt;li&gt;Empacotamento como Composition Function no Crossplane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(E provavelmente mais algumas ideias que surgirem no caminho.)&lt;/p&gt;




&lt;h1&gt;
  
  
  📌 Conclusão
&lt;/h1&gt;

&lt;p&gt;Rodar Kubernetes sem cloud provider não é algo exótico — foi assim que muitos clusters rodaram por anos, e ainda rodam em ambientes bare-metal, edge e data centers privados.&lt;/p&gt;

&lt;p&gt;Cloud providers apenas empacotam e integram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CNI&lt;/li&gt;
&lt;li&gt;CSI&lt;/li&gt;
&lt;li&gt;Ingress&lt;/li&gt;
&lt;li&gt;Identidade&lt;/li&gt;
&lt;li&gt;PKI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementar manualmente essas camadas muda completamente o nível de entendimento sobre a plataforma.&lt;/p&gt;

&lt;p&gt;Este projeto não foi apenas sobre “fazer funcionar”, mas sobre compreender o que está acontecendo por baixo das abstrações.&lt;/p&gt;

&lt;p&gt;No próximo artigo, entro na camada de identidade e TLS — que está se mostrando um desafio interessante.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>platformengineering</category>
      <category>terraform</category>
    </item>
  </channel>
</rss>
