Docker & Kubernetes

Boas Práticas de ConfigMaps e Secrets em Kubernetes: Injeção por Env e Volume para Times Ágeis

14 min de leitura

Boas Práticas de ConfigMaps e Secrets em Kubernetes: Injeção por Env e Volume para Times Ágeis

ConfigMaps e Secrets em Kubernetes: Uma Abordagem Prática Quando desenvolvemos aplicações containerizadas, precisamos separar a configuração do código. Kubernetes oferece dois recursos nativos para isso: ConfigMaps para dados não-sensíveis e Secrets para dados sensíveis como senhas e tokens. Neste artigo, exploraremos como injetar essas configurações em seus pods através de variáveis de ambiente e volumes — duas estratégias com propósitos e comportamentos distintos que você precisa dominar para gerenciar aplicações profissionalmente. A principal diferença entre essas duas abordagens reside no momento e na forma como os dados chegam ao container. Enquanto a injeção por variáveis de ambiente carrega os valores no momento do deploy, a injeção por volume cria arquivos que podem ser atualizados dinamicamente sem reiniciar o pod. Compreender quando usar cada uma é fundamental para construir infraestrutura robusta e flexível. ConfigMaps: Armazenando Configurações Não-Sensíveis O que é um ConfigMap? Um ConfigMap é um objeto Kubernetes que armazena dados em formato chave-valor. Ele foi projetado para separar dados de

<h2>ConfigMaps e Secrets em Kubernetes: Uma Abordagem Prática</h2>

<p>Quando desenvolvemos aplicações containerizadas, precisamos separar a configuração do código. Kubernetes oferece dois recursos nativos para isso: <strong>ConfigMaps</strong> para dados não-sensíveis e <strong>Secrets</strong> para dados sensíveis como senhas e tokens. Neste artigo, exploraremos como injetar essas configurações em seus pods através de variáveis de ambiente e volumes — duas estratégias com propósitos e comportamentos distintos que você precisa dominar para gerenciar aplicações profissionalmente.</p>

<p>A principal diferença entre essas duas abordagens reside no momento e na forma como os dados chegam ao container. Enquanto a injeção por variáveis de ambiente carrega os valores no momento do deploy, a injeção por volume cria arquivos que podem ser atualizados dinamicamente sem reiniciar o pod. Compreender quando usar cada uma é fundamental para construir infraestrutura robusta e flexível.</p>

<h2>ConfigMaps: Armazenando Configurações Não-Sensíveis</h2>

<h3>O que é um ConfigMap?</h3>

<p>Um ConfigMap é um objeto Kubernetes que armazena dados em formato chave-valor. Ele foi projetado para separar dados de configuração do código da aplicação, permitindo reutilizar a mesma imagem de container em diferentes ambientes. Os dados em ConfigMaps <strong>não são criptografados</strong> — apenas codificados em base64 — então nunca devem ser usados para informações sensíveis.</p>

<p>Você pode armazenar desde simples pares chave-valor até arquivos inteiros de configuração. Um ConfigMap típico tem limite de 1MB, o que é suficiente para a maioria dos casos reais de uso.</p>

<h3>Criando um ConfigMap</h3>

<p>Existem múltiplas formas de criar um ConfigMap. A forma mais direta é via yaml:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: ConfigMap

metadata:

name: app-config

namespace: default

data:

database_url: &quot;postgresql://db.example.com:5432/myapp&quot;

log_level: &quot;INFO&quot;

cache_ttl: &quot;3600&quot;

api_timeout: &quot;30&quot;</code></pre>

<p>Se você tiver um arquivo de configuração específico (como <code>.env</code> ou JSON), pode carregá-lo diretamente:</p>

<pre><code class="language-bash">kubectl create configmap app-config \

--from-file=./config/application.properties \

--from-literal=environment=production</code></pre>

<p>Ou ainda, criando a partir de um diretório inteiro:</p>

<pre><code class="language-bash">kubectl create configmap config-dir --from-file=./config/</code></pre>

<h3>Injeção via Variáveis de Ambiente</h3>

<p>A forma mais comum de consumir um ConfigMap é através de variáveis de ambiente. Nesta abordagem, cada chave do ConfigMap se torna uma variável no container:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: app-pod

spec:

containers:

  • name: myapp

image: myapp:1.0

env:

  • name: DATABASE_URL

valueFrom:

configMapKeyRef:

name: app-config

key: database_url

  • name: LOG_LEVEL

valueFrom:

configMapKeyRef:

name: app-config

key: log_level</code></pre>

<p>Neste exemplo, a variável <code>DATABASE_URL</code> dentro do container receberá o valor <code>postgresql://db.example.com:5432/myapp</code>. Este padrão é simples e direto — ideal quando você tem um pequeno número de configurações e não precisa atualizar valores em tempo real.</p>

<h3>Injeção via Volume</h3>

<p>Quando você precisa de múltiplas configurações ou quer que mudanças no ConfigMap sejam refletidas automaticamente no pod, use volumes:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: app-pod

spec:

containers:

  • name: myapp

image: myapp:1.0

volumeMounts:

  • name: config-volume

mountPath: /etc/config

volumes:

  • name: config-volume

configMap:

name: app-config

defaultMode: 0644</code></pre>

<p>Depois que o pod iniciar, todos os dados do ConfigMap aparecerão como arquivos em <code>/etc/config</code>. Se o ConfigMap for atualizado, o arquivo no volume será atualizado em até 60 segundos, sem necessidade de reiniciar o pod.</p>

<h2>Secrets: Protegendo Dados Sensíveis</h2>

<h3>O que é um Secret?</h3>

<p>Um Secret é semelhante ao ConfigMap, mas <strong>todos os seus dados são codificados em base64</strong> no etcd (o banco de dados do Kubernetes). Embora base64 não seja criptografia real, ele adiciona uma camada de proteção contra exposição acidental. Para segurança real, você deve usar encryption at rest no Kubernetes ou ferramentas como HashiCorp Vault.</p>

<p>Existem diferentes tipos de Secrets: <code>Opaque</code> (padrão, chave-valor), <code>kubernetes.io/dockercfg</code> (autenticação Docker), <code>kubernetes.io/service-account-token</code> (tokens), entre outros.</p>

<h3>Criando um Secret</h3>

<p>Para criar um Secret com dados sensíveis, use:</p>

<pre><code class="language-bash">kubectl create secret generic db-credentials \

--from-literal=username=admin \

--from-literal=password=SuperSecurePass123!</code></pre>

<p>Ou via arquivo yaml:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Secret

metadata:

name: db-credentials

type: Opaque

data:

username: YWRtaW4= # admin (base64)

password: U3VwZXJTZWN1cmVQYXNzMTIzIQ== # SuperSecurePass123! (base64)</code></pre>

<p>Para converter uma string para base64:</p>

<pre><code class="language-bash">echo -n &quot;SuperSecurePass123!&quot; | base64

Output: U3VwZXJTZWN1cmVQYXNzMTIzIQ==</code></pre>

<h3>Injeção de Secrets via Variáveis de Ambiente</h3>

<p>Similar ao ConfigMap, você pode injetar Secrets como variáveis de ambiente:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Deployment

metadata:

name: app-deployment

spec:

replicas: 2

selector:

matchLabels:

app: myapp

template:

metadata:

labels:

app: myapp

spec:

containers:

  • name: myapp

image: myapp:1.0

env:

  • name: DB_USERNAME

valueFrom:

secretKeyRef:

name: db-credentials

key: username

  • name: DB_PASSWORD

valueFrom:

secretKeyRef:

name: db-credentials

key: password</code></pre>

<p>Note o uso de <code>secretKeyRef</code> em vez de <code>configMapKeyRef</code>. Uma vantagem dessa abordagem é que a variável de ambiente fica imutável durante a vida do pod — mesmo que o Secret seja atualizado no cluster, a variável mantém seu valor original até o pod ser reiniciado.</p>

<h3>Injeção de Secrets via Volume</h3>

<p>Para casos onde você precisa que o Secret seja atualizado dinamicamente, use volumes:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: app-pod-secret

spec:

containers:

  • name: myapp

image: myapp:1.0

volumeMounts:

  • name: secret-volume

mountPath: /etc/secrets

readOnly: true

volumes:

  • name: secret-volume

secret:

secretName: db-credentials

defaultMode: 0400 # Leitura apenas pelo owner</code></pre>

<p>Os arquivos serão montados em <code>/etc/secrets/username</code> e <code>/etc/secrets/password</code>. Note o <code>readOnly: true</code> — prática recomendada para evitar modificação acidental de segredos dentro do container.</p>

<h2>Comparação Prática: Env vs Volume</h2>

<h3>Quando Usar Variáveis de Ambiente</h3>

<p>Use injeção por variáveis de ambiente quando você tiver um número pequeno de configurações e precisar que elas sejam imutáveis durante a execução do pod. Exemplos: <code>NODE_ENV=production</code>, <code>API_KEY</code>, <code>LOG_LEVEL</code>. Essa abordagem é simples, legível e tem overhead mínimo.</p>

<pre><code class="language-yaml">env:

  • name: NODE_ENV

value: &quot;production&quot;

  • name: CACHE_TTL

valueFrom:

configMapKeyRef:

name: app-config

key: cache_ttl</code></pre>

<h3>Quando Usar Volumes</h3>

<p>Use volumes quando você precisar atualizar configurações sem reiniciar o pod, quando tiver muitas configurações (melhor usar um arquivo <code>config.json</code> do que 50 variáveis), ou quando precisar manter a estrutura de um arquivo de configuração existente.</p>

<pre><code class="language-yaml">volumeMounts:

  • name: config-volume

mountPath: /etc/config

volumes:

  • name: config-volume

configMap:

name: app-config</code></pre>

<h3>Exemplo Real: Aplicação Node.js</h3>

<p>Vamos ver um cenário completo com uma aplicação Node.js que usa ambas as estratégias:</p>

<p><strong>ConfigMap com múltiplos dados:</strong></p>

<pre><code class="language-yaml">apiVersion: v1

kind: ConfigMap

metadata:

name: nodejs-config

data:

database.json: |

{

&quot;host&quot;: &quot;postgres.default.svc.cluster.local&quot;,

&quot;port&quot;: 5432,

&quot;pool&quot;: {

&quot;min&quot;: 2,

&quot;max&quot;: 10

}

}

log_level: &quot;debug&quot;

api_timeout: &quot;30000&quot;</code></pre>

<p><strong>Secret com credenciais:</strong></p>

<pre><code class="language-yaml">apiVersion: v1

kind: Secret

metadata:

name: nodejs-secrets

type: Opaque

data:

db_user: dXNlcm5hbWU= # username

db_pass: cGFzc3dvcmQxMjM= # password123

jwt_secret: andGc2VjcmV0Zm9ydG9rZW4= # jwtsecretfortoken</code></pre>

<p><strong>Deployment injetando ambos:</strong></p>

<pre><code class="language-yaml">apiVersion: apps/v1

kind: Deployment

metadata:

name: nodejs-app

spec:

replicas: 2

selector:

matchLabels:

app: nodejs-app

template:

metadata:

labels:

app: nodejs-app

spec:

containers:

  • name: nodejs

image: nodejs-app:1.2

ports:

  • containerPort: 3000

env:

Variáveis diretas

  • name: NODE_ENV

value: &quot;production&quot;

Do ConfigMap

  • name: LOG_LEVEL

valueFrom:

configMapKeyRef:

name: nodejs-config

key: log_level

  • name: API_TIMEOUT

valueFrom:

configMapKeyRef:

name: nodejs-config

key: api_timeout

Do Secret

  • name: DB_USER

valueFrom:

secretKeyRef:

name: nodejs-secrets

key: db_user

  • name: DB_PASSWORD

valueFrom:

secretKeyRef:

name: nodejs-secrets

key: db_pass

  • name: JWT_SECRET

valueFrom:

secretKeyRef:

name: nodejs-secrets

key: jwt_secret

volumeMounts:

  • name: config-files

mountPath: /app/config

readOnly: true

volumes:

  • name: config-files

configMap:

name: nodejs-config

items:

  • key: database.json

path: database.json</code></pre>

<p>Neste exemplo, a aplicação consome variáveis de ambiente simples (<code>LOG_LEVEL</code>, <code>API_TIMEOUT</code>) e sensíveis (<code>DB_PASSWORD</code>, <code>JWT_SECRET</code>), enquanto o arquivo <code>database.json</code> é disponibilizado via volume para acesso estruturado.</p>

<p><strong>Código Node.js que consome essas configurações:</strong></p>

<pre><code class="language-javascript">const express = require(&#039;express&#039;);

const app = express();

const fs = require(&#039;fs&#039;);

// Variáveis de ambiente

const logLevel = process.env.LOG_LEVEL | | &#039;info&#039;; const apiTimeout = parseInt(process.env.API_TIMEOUT) || 30000;

const dbUser = process.env.DB_USER;

const dbPassword = process.env.DB_PASSWORD;

const jwtSecret = process.env.JWT_SECRET;

// Arquivo de configuração via volume

const dbConfig = JSON.parse(

fs.readFileSync(&#039;/app/config/database.json&#039;, &#039;utf8&#039;)

);

console.log([${logLevel}] Starting app with API timeout: ${apiTimeout}ms);

console.log(Database host: ${dbConfig.host}:${dbConfig.port});

// Conectar ao banco

const dbConnection = {

user: dbUser,

password: dbPassword,

host: dbConfig.host,

port: dbConfig.port,

pool: dbConfig.pool

};

app.get(&#039;/health&#039;, (req, res) =&gt; {

res.json({ status: &#039;ok&#039;, log_level: logLevel });

});

app.listen(3000, () =&gt; {

console.log(&#039;Server running on port 3000&#039;);

});</code></pre>

<h2>Boas Práticas e Considerações de Segurança</h2>

<h3>RBAC e Acesso a Secrets</h3>

<p>Sempre configure <strong>Role-Based Access Control (RBAC)</strong> para limitar quem pode ler Secrets. Um desenvolvedor não deve conseguir fazer <code>kubectl get secret</code> em produção:</p>

<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1

kind: Role

metadata:

name: secret-reader

rules:

  • apiGroups: [&quot;&quot;]

resources: [&quot;secrets&quot;]

verbs: [&quot;get&quot;, &quot;list&quot;]</code></pre>

<h3>Encryption at Rest</h3>

<p>Configure encryption at rest no Kubernetes para proteger dados no etcd. No kubeadm:</p>

<pre><code class="language-bash"># Edite /etc/kubernetes/manifests/kube-apiserver.yaml

--encryption-provider-config=/etc/kubernetes/enc/enc.yaml</code></pre>

<h3>Nunca Commite Secrets no Git</h3>

<p>Use ferramentas como <strong>Sealed Secrets</strong> ou <strong>External Secrets Operator</strong> para gerenciar secrets versionados com segurança. Exemplo com Sealed Secrets:</p>

<pre><code class="language-bash">echo -n &#039;mysecretpassword&#039; | kubectl create secret generic \ my-secret --dry-run=client --from-file=/dev/stdin -o yaml | \

kubeseal -f - &gt; sealed-secret.yaml</code></pre>

<h3>Volume readOnly em Secrets</h3>

<p>Sempre monte volumes de Secret como <code>readOnly: true</code> para prevenir modificações acidentais dentro do container.</p>

<h3>Rotação de Secrets</h3>

<p>Implemente rotação regular de Secrets. Muitos sistemas (AWS, Azure) integram-se com External Secrets Operator para sincronizar automaticamente:</p>

<pre><code class="language-yaml">apiVersion: external-secrets.io/v1beta1

kind: SecretStore

metadata:

name: aws-secrets

spec:

provider:

aws:

service: SecretsManager

region: us-east-1</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que ConfigMaps e Secrets são dois lados da mesma moeda — separar configuração do código. A injeção por variáveis de ambiente é simples e ideal para valores imutáveis e pequenas quantidades de dados. A injeção por volume é poderosa para atualizar configurações dinamicamente, manter estruturas de arquivo e gerenciar múltiplas definições sem sobrecarregar a lista de variáveis de ambiente.</p>

<p>O terceiro ponto crítico é que <strong>Secrets não são verdadeiramente criptografados</strong> apenas por estarem base64-encoded — você deve implementar encryption at rest, RBAC, e ferramentas como Sealed Secrets para segurança real em produção. A escolha entre env e volume deve ser guiada pelo seu caso de uso específico: simplicidade versus flexibilidade.</p>

<h2>Referências</h2>

<ol>

<li><a href="https://kubernetes.io/docs/concepts/configuration/configmap/" target="_blank" rel="noopener noreferrer">Kubernetes Official Documentation - ConfigMaps</a></li>

<li><a href="https://kubernetes.io/docs/concepts/configuration/secret/" target="_blank" rel="noopener noreferrer">Kubernetes Official Documentation - Secrets</a></li>

<li><a href="https://github.com/bitnami-labs/sealed-secrets" target="_blank" rel="noopener noreferrer">Sealed Secrets - Bitnami</a></li>

<li><a href="https://external-secrets.io/" target="_blank" rel="noopener noreferrer">External Secrets Operator Documentation</a></li>

<li><a href="https://kubernetes.io/docs/concepts/security/secrets-good-practices/" target="_blank" rel="noopener noreferrer">Kubernetes Security Best Practices - CNCF</a></li>

</ol>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

O que Todo Dev Deve Saber sobre Kustomize em Kubernetes: Overlays, Patches e Bases Reutilizáveis
O que Todo Dev Deve Saber sobre Kustomize em Kubernetes: Overlays, Patches e Bases Reutilizáveis

Introdução ao Kustomize: Por que Precisamos Dele Kubernetes é poderoso, mas g...

Como Usar Kubernetes: Arquitetura do Cluster, Control Plane e Worker Nodes em Produção
Como Usar Kubernetes: Arquitetura do Cluster, Control Plane e Worker Nodes em Produção

Introdução ao Kubernetes e Sua Arquitetura Kubernetes, frequentemente abrevia...

O que Todo Dev Deve Saber sobre kubectl em Profundidade: Comandos, Contexts e Kubeconfig
O que Todo Dev Deve Saber sobre kubectl em Profundidade: Comandos, Contexts e Kubeconfig

Introdução: O que é kubectl e Por Que Importa O kubectl é a ferramenta de lin...