Gerenciamento de configurações em produção: ConfigMaps e Secrets

1. Fundamentos da separação entre código e configuração

Em ambientes de produção, um dos princípios mais importantes da engenharia de software é a separação entre código e configuração. Quando configurações como URLs de banco de dados, chaves de API ou parâmetros de ambiente ficam hardcoded no código-fonte, cada alteração exige uma nova compilação, novo build de imagem e novo deploy. Isso torna o processo lento, propenso a erros e incompatível com práticas modernas de CI/CD.

O Kubernetes resolve esse problema com um modelo declarativo, onde ConfigMaps e Secrets são recursos nativos que permitem injetar configurações em Pods sem modificar as imagens dos containers. Dessa forma, uma mesma imagem pode ser promovida entre ambientes (dev, staging, prod) simplesmente alterando os objetos de configuração.

2. ConfigMaps: armazenando dados de configuração não sensíveis

ConfigMaps são objetos do Kubernetes projetados para armazenar dados de configuração não confidenciais em pares chave-valor. Eles podem ser criados a partir de literais, arquivos ou diretórios inteiros.

Criação a partir de literais:

kubectl create configmap app-config --from-literal=LOG_LEVEL=debug --from-literal=MAX_CONNECTIONS=100

Criação a partir de arquivos:

# arquivo: application.properties
kubectl create configmap app-properties --from-file=./application.properties

Injeção como variáveis de ambiente no Pod:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:1.0
    envFrom:
    - configMapRef:
        name: app-config

Injeção como volume montado (atualização automática):

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:1.0
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
  volumes:
  - name: config-volume
    configMap:
      name: app-config

Boas práticas: nomeie ConfigMaps com escopo claro (frontend-config, api-config), use namespaces para isolar ambientes e evite armazenar dados sensíveis neles.

3. Secrets: protegendo dados sensíveis em produção

Secrets são similares aos ConfigMaps, mas projetados para dados sensíveis como senhas, tokens e chaves SSH. É fundamental entender que Secrets não são criptografados por padrão — eles apenas usam codificação base64. A segurança real vem da criptografia em repouso no etcd e do controle de acesso via RBAC.

Criação manual:

kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=S3nh@F0rt3

Criação a partir de arquivos:

kubectl create secret generic tls-secret \
  --from-file=tls.crt=./cert.pem \
  --from-file=tls.key=./key.pem

Consumo como variáveis de ambiente:

apiVersion: v1
kind: Pod
metadata:
  name: db-pod
spec:
  containers:
  - name: app
    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

Consumo como volume montado (recomendado para atualizações dinâmicas):

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:1.0
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: db-credentials

4. Estratégias de atualização sem downtime

Em produção, atualizar configurações sem interromper o serviço é essencial. Quando ConfigMaps e Secrets são montados como volumes, o kubelet sincroniza as mudanças automaticamente. Porém, a aplicação precisa ser capaz de detectar alterações nos arquivos montados.

Exemplo de aplicação que recarrega configuração automaticamente:

# Aplicação Node.js que monitora alterações no arquivo de configuração
const fs = require('fs');
const configPath = '/etc/config/app.properties';

function loadConfig() {
  const data = fs.readFileSync(configPath, 'utf8');
  console.log('Configuração recarregada:', data);
}

fs.watchFile(configPath, (curr, prev) => {
  console.log('Arquivo modificado, recarregando...');
  loadConfig();
});

loadConfig();

Limitações: O tempo de propagação pode levar alguns minutos (padrão: 60 segundos de sincronização). Para ambientes críticos, considere usar ConfigMaps imutáveis com rolling update:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
immutable: true
data:
  LOG_LEVEL: warn
  MAX_CONNECTIONS: 200

Com ConfigMaps imutáveis, você cria uma nova versão (app-config-v2) e atualiza a referência no Deployment, que fará um rolling update gradual.

5. Gerenciamento de ciclo de vida e versionamento

Para ambientes críticos, a imutabilidade de ConfigMaps e Secrets é uma prática recomendada. Ela impede alterações acidentais e permite rastrear qual versão da configuração estava ativa em cada deploy.

Estratégia de nomenclatura:

api-config-v1   # Versão inicial
api-config-v2   # Após alteração de parâmetros
api-config-v3   # Nova alteração

Política de limpeza: Após confirmar que uma versão antiga não está mais em uso, remova-a:

kubectl delete configmap api-config-v1

Garbage collection: Use labels para agrupar ConfigMaps por release e facilitar a limpeza:

kubectl delete configmap -l release=v1.0

6. Segurança avançada e integração com ferramentas externas

Para ambientes de produção reais, a segurança dos Secrets deve ir além do básico.

Criptografia em repouso no etcd:

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aesgcm:
        keys:
        - name: key1
          secret: c2VjcmV0LWtleS0xMjM0NTY3ODkw
    - identity: {}

Integração com HashiCorp Vault usando o CSI Driver:

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-db-secret
spec:
  provider: vault
  parameters:
    vaultAddress: "https://vault.example.com"
    roleName: "app-role"
    objects: |
      - objectName: "db-password"
        secretPath: "secret/data/db"
        secretKey: "password"

RBAC para controle de acesso:

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

7. Casos de uso práticos e padrões de produção

Configuração de múltiplos ambientes:

# Namespace: dev
kubectl create configmap app-config -n dev \
  --from-literal=DB_URL=localhost:5432 \
  --from-literal=LOG_LEVEL=debug

# Namespace: prod
kubectl create configmap app-config -n prod \
  --from-literal=DB_URL=prod-db.internal:5432 \
  --from-literal=LOG_LEVEL=warn

Credenciais de banco de dados como Secrets:

kubectl create secret generic db-credentials \
  --from-literal=username=app_user \
  --from-literal=password=$(openssl rand -base64 32)

Combinação com Helm charts:

# values.yaml
config:
  logLevel: info
  maxConnections: 100

secrets:
  dbPassword: "CHANGE_ME"

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  LOG_LEVEL: {{ .Values.config.logLevel }}
  MAX_CONNECTIONS: {{ .Values.config.maxConnections | quote }}

Esse padrão permite versionar templates e injetar valores específicos por ambiente, mantendo Secrets protegidos em sistemas externos como Vault ou AWS Secrets Manager.

Referências