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 secretno 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 yamlmostra os valores claramente. Sempre use Secret para dados sensíveis. - Esquecer de codificar base64 ao escrever
dataem Secret YAML: resulta em erroillegal base64 data. UsestringDatase não quiser codificar manualmente. - Alterar ConfigMap mas o Pod não receber o novo valor: porque carregou via
env/envFromque não recarrega automaticamente — você precisa fazerkubectl 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.