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

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

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 configuração do código da aplicação, permitindo reutilizar a mesma imagem de container em diferentes ambientes. Os dados em ConfigMaps não são criptografados — apenas codificados em base64 — então nunca devem ser usados para informações sensíveis.

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.

Criando um ConfigMap

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

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  database_url: "postgresql://db.example.com:5432/myapp"
  log_level: "INFO"
  cache_ttl: "3600"
  api_timeout: "30"

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

kubectl create configmap app-config \
  --from-file=./config/application.properties \
  --from-literal=environment=production

Ou ainda, criando a partir de um diretório inteiro:

kubectl create configmap config-dir --from-file=./config/

Injeção via Variáveis de Ambiente

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:

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

Neste exemplo, a variável DATABASE_URL dentro do container receberá o valor postgresql://db.example.com:5432/myapp. 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.

Injeção via Volume

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

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

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

Secrets: Protegendo Dados Sensíveis

O que é um Secret?

Um Secret é semelhante ao ConfigMap, mas todos os seus dados são codificados em base64 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.

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

Criando um Secret

Para criar um Secret com dados sensíveis, use:

kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=SuperSecurePass123!

Ou via arquivo yaml:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: YWRtaW4=  # admin (base64)
  password: U3VwZXJTZWN1cmVQYXNzMTIzIQ==  # SuperSecurePass123! (base64)

Para converter uma string para base64:

echo -n "SuperSecurePass123!" | base64
# Output: U3VwZXJTZWN1cmVQYXNzMTIzIQ==

Injeção de Secrets via Variáveis de Ambiente

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

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

Note o uso de secretKeyRef em vez de configMapKeyRef. 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.

Injeção de Secrets via Volume

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

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

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

Comparação Prática: Env vs Volume

Quando Usar Variáveis de Ambiente

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: NODE_ENV=production, API_KEY, LOG_LEVEL. Essa abordagem é simples, legível e tem overhead mínimo.

env:
- name: NODE_ENV
  value: "production"
- name: CACHE_TTL
  valueFrom:
    configMapKeyRef:
      name: app-config
      key: cache_ttl

Quando Usar Volumes

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

volumeMounts:
- name: config-volume
  mountPath: /etc/config
volumes:
- name: config-volume
  configMap:
    name: app-config

Exemplo Real: Aplicação Node.js

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

ConfigMap com múltiplos dados:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nodejs-config
data:
  database.json: |
    {
      "host": "postgres.default.svc.cluster.local",
      "port": 5432,
      "pool": {
        "min": 2,
        "max": 10
      }
    }
  log_level: "debug"
  api_timeout: "30000"

Secret com credenciais:

apiVersion: v1
kind: Secret
metadata:
  name: nodejs-secrets
type: Opaque
data:
  db_user: dXNlcm5hbWU=  # username
  db_pass: cGFzc3dvcmQxMjM=  # password123
  jwt_secret: andGc2VjcmV0Zm9ydG9rZW4=  # jwtsecretfortoken

Deployment injetando ambos:

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: "production"
        # 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

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

Código Node.js que consome essas configurações:

const express = require('express');
const app = express();
const fs = require('fs');

// Variáveis de ambiente
const logLevel = process.env.LOG_LEVEL || 'info';
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('/app/config/database.json', 'utf8')
);

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('/health', (req, res) => {
  res.json({ status: 'ok', log_level: logLevel });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Boas Práticas e Considerações de Segurança

RBAC e Acesso a Secrets

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

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list"]

Encryption at Rest

Configure encryption at rest no Kubernetes para proteger dados no etcd. No kubeadm:

# Edite /etc/kubernetes/manifests/kube-apiserver.yaml
--encryption-provider-config=/etc/kubernetes/enc/enc.yaml

Nunca Commite Secrets no Git

Use ferramentas como Sealed Secrets ou External Secrets Operator para gerenciar secrets versionados com segurança. Exemplo com Sealed Secrets:

echo -n 'mysecretpassword' | kubectl create secret generic \
  my-secret --dry-run=client --from-file=/dev/stdin -o yaml | \
  kubeseal -f - > sealed-secret.yaml

Volume readOnly em Secrets

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

Rotação de Secrets

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

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1

Conclusão

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.

O terceiro ponto crítico é que Secrets não são verdadeiramente criptografados 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.

Referências

  1. Kubernetes Official Documentation - ConfigMaps
  2. Kubernetes Official Documentation - Secrets
  3. Sealed Secrets - Bitnami
  4. External Secrets Operator Documentation
  5. Kubernetes Security Best Practices - CNCF

Artigos relacionados