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.