ConfigMap e Secret no Kubernetes: Separando configuração da imagem

1. Visão geral

Ao empacotar uma aplicação como imagem de container, você não deve “queimar” valores como URL de banco de dados e chaves de API dentro da imagem. Este artigo ajuda você a entender ConfigMap e Secret no Kubernetes — dois objetos usados para separar configuração da imagem, como criá-los, como carregá-los em Pods através de variáveis de ambiente ou volumes, e como integrar com aplicações Spring Boot. Depois de ler, você terá confiança para configurar um Deployment que leia configurações dinamicamente sem precisar reconstruir a imagem sempre que mudar um valor.

2. O que é ConfigMap

ConfigMap é um objeto que armazena dados de configuração em formato chave-valor, sem criptografia, para dados não sensíveis: nomes de serviços, feature flags, arquivos de propriedades…

Os dados dentro de um ConfigMap existem independentemente do Pod — você pode editar o ConfigMap sem precisar reconstruir a imagem, apenas reiniciar o Pod (ou usar um mecanismo de recarga se a aplicação suportar).

3. Criando ConfigMap

3.1. A partir de arquivo YAML

apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "INFO"
  application.properties: |
    server.port=8080
    spring.jpa.hibernate.ddl-auto=validate

Aqui data contém dois tipos: chaves simples (APP_ENV, LOG_LEVEL) e uma chave contendo o conteúdo completo de um arquivo (application.properties). Aplique com:

kubectl apply -f order-service-config.yaml

3.2. A partir de literal ou arquivo existente

kubectl create configmap order-service-config \
  --from-literal=APP_ENV=production \
  --from-file=application.properties=./config/application.properties

Este comando cria um ConfigMap diretamente pela linha de comando sem precisar escrever YAML, útil para scripts de CI/CD.

4. Usando ConfigMap no Pod

4.1. Carregando como variáveis de ambiente

apiVersion: v1
kind: Pod
metadata:
  name: order-service
spec:
  containers:
    - name: order-service
      image: nonladev/order-service:1.0
      envFrom:
        - configMapRef:
            name: order-service-config

envFrom carrega todas as chaves no ConfigMap como variáveis de ambiente com os mesmos nomes. O container lê APP_ENV, LOG_LEVEL como variáveis de ambiente normais.

Se você quiser selecionar apenas uma chave específica:

      env:
        - name: APP_ENVIRONMENT
          valueFrom:
            configMapKeyRef:
              name: order-service-config
              key: APP_ENV

Esta abordagem permite renomear a variável de ambiente no container (APP_ENVIRONMENT) diferente da chave original no ConfigMap (APP_ENV).

4.2. Carregando como volume

      volumeMounts:
        - name: config-volume
          mountPath: /app/config
  volumes:
    - name: config-volume
      configMap:
        name: order-service-config

Cada chave no ConfigMap se torna um arquivo em /app/config (por exemplo /app/config/application.properties). Esta abordagem é adequada quando a aplicação precisa ler um arquivo de configuração inteiro em vez de variáveis de ambiente individuais.

Nota: Ao montar como volume, o Kubernetes atualiza automaticamente os arquivos quando o ConfigMap muda (com latência de alguns segundos a alguns minutos dependendo do período de sincronização do kubelet). Com variáveis de ambiente (env/envFrom), o Pod não recarrega automaticamente — você precisa reiniciá-lo.

5. O que é Secret e suas diferenças com ConfigMap

Secret tem uma estrutura similar a ConfigMap mas é destinado a dados sensíveis: senhas, tokens, chaves privadas. As principais diferenças:

Critério ConfigMap Secret
Criptografia Não Base64-codificado (não é criptografia real)
Armazenado em etcd Texto simples Pode ativar criptografia em repouso
Exibição com kubectl get Mostra valores Oculta valores (apenas base64)
Propósito Configuração normal Dados sensíveis

Nota: Secret apenas codifica base64, não criptografa por padrão. Qualquer pessoa com permissão get secret no namespace pode decodificar. Para segurança real, você precisa ativar criptografia em repouso no etcd ou usar um gerenciador de secrets externo (Vault, AWS Secrets Manager) integrado através do External Secrets Operator.

6. Criando Secret

6.1. A partir de YAML (você deve codificar base64)

echo -n "P@ssw0rd123" | base64
# UEBzc3cwcmQxMjM=
apiVersion: v1
kind: Secret
metadata:
  name: order-service-secret
type: Opaque
data:
  DB_PASSWORD: UEBzc3cwcmQxMjM=

O campo data requer valores já codificados em base64. Se você não quiser codificar manualmente, use stringData:

stringData:
  DB_PASSWORD: "P@ssw0rd123"

Kubernetes codificará automaticamente ao salvar. Use stringData para facilitar a leitura ao escrever manifests manualmente.

6.2. A partir da linha de comando

kubectl create secret generic order-service-secret \
  --from-literal=DB_PASSWORD=P@ssw0rd123 \
  --from-literal=DB_USERNAME=order_app

7. Usando Secret no Pod

A forma de carregamento é totalmente similar a ConfigMap:

      envFrom:
        - secretRef:
            name: order-service-secret

ou montar como volume:

      volumeMounts:
        - name: secret-volume
          mountPath: /app/secret
          readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: order-service-secret

Ao montar Secret como volume, você sempre deve definir readOnly: true e restringir permissões de arquivo com defaultMode se necessário:

      secret:
        secretName: order-service-secret
        defaultMode: 0400

8. Integração com Spring Boot

Spring Boot lê variáveis de ambiente seguindo o padrão relaxed binding, então você não precisa codificar nada especial — apenas precisa nomear as variáveis corretamente.

      envFrom:
        - configMapRef:
            name: order-service-config
        - secretRef:
            name: order-service-secret
      env:
        - name: SPRING_DATASOURCE_USERNAME
          valueFrom:
            secretKeyRef:
              name: order-service-secret
              key: DB_USERNAME
        - name: SPRING_DATASOURCE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: order-service-secret
              key: DB_PASSWORD

Spring Boot mapeia automaticamente SPRING_DATASOURCE_USERNAME → spring.datasource.username. No código, você apenas precisa de @ConfigurationProperties normal:

@ConfigurationProperties(prefix = "spring.datasource")
public record DataSourceProperties(String url, String username, String password) {
}
@Service
public class PaymentService {

    private final DataSourceProperties dataSourceProperties;

    public PaymentService(DataSourceProperties dataSourceProperties) {
        this.dataSourceProperties = dataSourceProperties;
    }

    public String describeConnection() {
        return "Connecting as %s".formatted(dataSourceProperties.username());
    }
}

PaymentService não sabe nada sobre Kubernetes — ele apenas recebe valores que já foram vinculados pelo Spring Boot a partir de variáveis de ambiente fornecidas pelo ConfigMap/Secret.

8.1. Testando independentemente do cluster

Você testa a lógica definindo variáveis de ambiente simuladas através de @TestPropertySource, sem precisar de Kubernetes real:

@SpringBootTest
@TestPropertySource(properties = {
    "spring.datasource.username=order_app",
    "spring.datasource.password=test-pass"
})
class PaymentServiceTest {

    @Autowired
    private PaymentService paymentService;

    @Test
    void shouldDescribeConnectionUsingBoundProperties() {
        assertThat(paymentService.describeConnection())
            .isEqualTo("Connecting as order_app");
    }
}

Este teste verifica se DataSourceProperties é vinculado corretamente e se PaymentService usa os valores corretos — completamente separado do fato dos valores virem de um ConfigMap ou Secret real no cluster.

PaymentServiceTest > shouldDescribeConnectionUsingBoundProperties() PASSED

9. Atualizando dinamicamente sem reiniciar o Pod

Se montar ConfigMap/Secret como volume, os arquivos se atualizam automaticamente, mas Spring Boot não relê automaticamente a menos que você:

  • Use Spring Cloud Kubernetes (spring-cloud-starter-kubernetes-client-config) para fazer polling do ConfigMap e atualizar beans com @RefreshScope.
  • Ou implemente seu próprio file-watcher para ler os arquivos montados novamente.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring.cloud.kubernetes.config.enabled=true
spring.cloud.kubernetes.reload.enabled=true
spring.cloud.kubernetes.reload.mode=polling

Esta configuração ativa o recurso do Spring Cloud Kubernetes para fazer polling automático do ConfigMap em ciclos regulares e atualizar beans marcados com @RefreshScope, sem precisar reiniciar o Pod manualmente.

Nota: Polling contínuo consome recursos do API server. Com clusters grandes, considere usar spring.cloud.kubernetes.reload.mode=event (watch API) em vez de polling.

10. Erros comuns e quando não usar

  • Deixar senhas em ConfigMap: ConfigMap não é criptografado, kubectl get configmap -o yaml mostra os valores claramente. Sempre use Secret para dados sensíveis.
  • Esquecer de codificar base64 ao escrever data em Secret YAML: resulta em erro illegal base64 data. Use stringData se não quiser codificar manualmente.
  • Alterar ConfigMap mas o Pod não receber o novo valor: porque carregou via env/envFrom que não recarrega automaticamente — você precisa fazer kubectl rollout restart deployment.
  • Montar volume Secret mas esquecer readOnly: true: o container pode sobrescrever os arquivos, causando confusão ao fazer debug.
  • Usar Secret como um repositório de segredos de longo prazo: Secret no Kubernetes é adequado para configuração em tempo de execução, não substitui um gerenciador de secrets dedicado (Vault, AWS Secrets Manager) quando você precisa de logs de auditoria, rotação automática ou versionamento rigoroso.
  • ConfigMap/Secret muito grande: Kubernetes limita o tamanho dos objetos a cerca de 1 MiB (devido aos limites do etcd). Se você precisar de um arquivo de configuração grande, considere montar um volume a partir de um PersistentVolume ou use um init container para carregar de um object storage.

11. Conclusão

ConfigMap e Secret ajudam você a separar configuração da imagem, permitindo implantar a mesma imagem em vários ambientes (dev, staging, production) apenas alterando os objetos de configuração. ConfigMap é para dados públicos, Secret é para dados sensíveis — mas lembre-se que Secret por padrão apenas codifica, não criptografa de verdade. Com Spring Boot, você aproveita o relaxed binding para carregar valores através de variáveis de ambiente sem precisar alterar o código de negócio, e pode ativar Spring Cloud Kubernetes se precisar recarregar configuração dinamicamente sem reiniciar o Pod.

kubernetesconfigmapsecretspring-bootdevops