ConfigMap 与 Secret 管理
核心问题:应用的数据库地址、功能开关、API 密钥——怎样从镜像中解耦出来,在不重建镜像的情况下修改配置?
配置与密钥解耦原则
将配置从代码中分离是 12-Factor App 的核心原则:
graph LR
IMG["镜像(不变)
myapp:v1.2.3"] CM["ConfigMap
数据库地址
功能开关"] SEC["Secret
数据库密码
API 密钥"] POD["Pod
(运行时组合)"] IMG --> POD CM --> POD SEC --> POD
myapp:v1.2.3"] CM["ConfigMap
数据库地址
功能开关"] SEC["Secret
数据库密码
API 密钥"] POD["Pod
(运行时组合)"] IMG --> POD CM --> POD SEC --> POD
| 对象 | 用于 | 存储方式 | 加密 |
|---|---|---|---|
| ConfigMap | 非敏感配置(URL、端口、功能开关) | etcd 明文 | ❌ |
| Secret | 敏感数据(密码、证书、Token) | etcd Base64 | 可选(需开启加密) |
ConfigMap
创建 ConfigMap
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
# 键值对
DB_HOST: "postgres-headless.production.svc.cluster.local"
DB_PORT: "5432"
REDIS_HOST: "redis.production.svc.cluster.local"
LOG_LEVEL: "warn"
FEATURE_NEW_UI: "true"
# 多行文件内容
nginx.conf: |
worker_processes auto;
events { worker_connections 1024; }
http {
server {
listen 80;
location / { proxy_pass http://localhost:3000; }
}
}
# 命令行创建
kubectl create configmap app-config \
--from-literal=DB_HOST=postgres.internal \
--from-file=nginx.conf=./nginx.conf \
-n production
# 从目录批量加载
kubectl create configmap app-config --from-file=configs/
使用 ConfigMap
# 方式 1:注入为环境变量(逐个引用)
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
# 方式 2:批量注入所有键为环境变量
envFrom:
- configMapRef:
name: app-config
# 方式 3:挂载为文件(适合配置文件)
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf # 只挂载 ConfigMap 中的 nginx.conf 键,不替换整个目录
volumes:
- name: nginx-config
configMap:
name: app-config
items:
- key: nginx.conf
path: nginx.conf
Secret
创建 Secret
# secret.yaml — 值需要 Base64 编码(不推荐手写,用命令行)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
data:
DB_PASSWORD: UzNjdXIzUEBzc3cwcmQ= # base64 encode "S3cur3P@ssw0rd"
API_KEY: eEtKbTkyTGFQeHZRa1RuWQ==
# 或使用 stringData(自动 Base64 编码,不会出现在 State 中)
stringData:
DB_PASSWORD: "S3cur3P@ssw0rd" # 明文,Kubernetes 自动编码
API_KEY: "xKJm92LaPxvQkTnY"
# 命令行创建(推荐,避免明文 YAML 提交到 Git)
kubectl create secret generic app-secrets \
--from-literal=DB_PASSWORD=S3cur3P@ssw0rd \
--from-literal=API_KEY=xKJm92LaPxvQkTnY \
-n production
# TLS Secret(cert + key)
kubectl create secret tls api-tls \
--cert=server.crt \
--key=server.key \
-n production
使用 Secret
# 方式 1:注入为环境变量
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_PASSWORD
# 方式 2:批量注入
envFrom:
- secretRef:
name: app-secrets
# 方式 3:挂载为文件(证书等)
volumeMounts:
- name: tls-certs
mountPath: /etc/ssl/certs
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: api-tls
External Secrets Operator:从外部密钥管理服务同步
原生 Secret 只是 Base64 编码,并非加密。生产推荐使用 External Secrets Operator 从 AWS Secrets Manager / HashiCorp Vault 同步:
# 安装 External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm upgrade --install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace
# SecretStore(连接到 AWS Secrets Manager)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: ap-southeast-1
auth:
serviceAccount:
name: external-secrets-sa
---
# ExternalSecret(声明要同步哪些密钥)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h # 每小时同步一次
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: app-secrets # 生成的 K8s Secret 名称
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD # K8s Secret 中的 key
remoteRef:
key: prod/myapp # AWS Secrets Manager 路径
property: db_password # JSON 字段名
- secretKey: API_KEY
remoteRef:
key: prod/myapp
property: api_key
Sealed Secrets:加密后安全提交 Git
Sealed Secrets 允许将加密的 Secret 提交到 Git:
# 安装 controller
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
# 安装客户端工具
curl -L https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/kubeseal-0.24.0-linux-amd64.tar.gz | tar xvz
sudo install -m 755 kubeseal /usr/local/bin/
# 将普通 Secret 加密为 SealedSecret
kubectl create secret generic app-secrets \
--from-literal=DB_PASSWORD=S3cur3P@ssw0rd \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-secret.yaml
# sealed-secret.yaml 可以安全提交到 Git
# Controller 会自动解密并创建 K8s Secret
最佳实践对比
| 方案 | 安全性 | 复杂度 | 适用场景 |
|---|---|---|---|
| 原生 Secret(Base64) | 低(etcd 不加密) | 低 | 学习、开发环境 |
| Sealed Secrets | 中 | 中 | 中小团队、GitOps |
| External Secrets + AWS SM | 高 | 中 | AWS 环境、需要密钥轮换 |
| External Secrets + Vault | 最高 | 高 | 大型企业、多云 |