ConfigMap 与 Secret:应用配置管理
深入理解 ConfigMap 和 Secret 的使用方式、安全最佳实践,以及配置热更新的实现方法。
概述
配置管理是应用运维的核心能力。Kubernetes 提供了 ConfigMap 和 Secret 两种资源来管理配置数据。本文将深入探讨:
学习目标:
- 理解 ConfigMap 和 Secret 的使用场景
- 掌握多种配置注入方式
- 学会 Secret 的安全配置与加密存储
- 实现配置热更新与回滚
- 了解配置管理最佳实践
ConfigMap:非敏感配置
ConfigMap 概述
┌─────────────────────────────────────────────────────────────────┐
│ ConfigMap 定位 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ConfigMap 用于存储: │
│ ✓ 配置文件(如 nginx.conf, application.yaml) │
│ ✓ 环境变量(DB_HOST, LOG_LEVEL 等) │
│ ✓ 命令行参数(启动脚本参数) │
│ ✓ DNS 映射(hosts 文件等) │
│ │
│ ConfigMap 不能存储: │
│ ✗ 敏感信息(密码、Token、证书)→ 使用 Secret │
│ │
│ 特点: │
│ - 明文存储,任何能访问 API Server 的人可见 │
│ - 适合非敏感配置 │
│ - 支持多种格式(文件、键值对、JSON) │
│ │
└─────────────────────────────────────────────────────────────────┘
ConfigMap 创建方式
# 方式1:键值对
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "localhost"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"
---
# 方式2:多行文本(config-file)
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend:8080;
}
}
---
# 方式3:从文件创建
# kubectl create configmap app-config --from-file=config.yaml
ConfigMap 使用方式
# 方式1:环境变量
apiVersion: v1
kind: Pod
metadata:
name: config-env-pod
spec:
containers:
- name: app
image: myapp:1.0
env:
# 方式1.1:单个值
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
# 方式1.2:引用整个 ConfigMap
envFrom:
- configMapRef:
name: app-config
# 方式1.3:可选引用(不会因为 key 不存在而失败)
- name: OPTIONAL_VAR
valueFrom:
configMapKeyRef:
name: optional-config
key: SOME_KEY
optional: true
# 方式2:命令行参数
spec:
containers:
- name: app
image: myapp:1.0
command:
- /app/start.sh
- --config
- /etc/config/app.conf
env:
- name: CONFIG_PATH
value: /etc/config
volumeMounts:
- name: config-volume
mountPath: /etc/config
---
# 方式3:挂载为文件
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
# 可选:只挂载特定 keys
items:
- key: database.conf
path: db.conf
- key: app.yaml
path: application.yaml
# 可选:文件权限
defaultMode: 0644
ConfigMap 挂载对比
┌─────────────────────────────────────────────────────────────────┐
│ 环境变量 vs 文件挂载 对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 环境变量 │ │ 文件挂载 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ 启动时注入 │ │ 运行时可更新 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ 不可动态更新 │ │ 支持热更新(需配置)│ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ 适合固定配置 │ │ 适合配置文件 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ 容器启动后不可变 │ │ 可作为配置中心 │ │
│ └─────────────────┤ └─────────────────┘ │
│ │
│ 选择建议: │
│ - 不频繁变化的配置 → 环境变量 │
│ - 需要动态更新的配置 → 文件挂载 │
│ - 敏感配置 → Secret(环境变量或文件) │
│ │
└─────────────────────────────────────────────────────────────────┘
Secret:敏感数据存储
Secret 类型
┌─────────────────────────────────────────────────────────────────┐
│ Secret 类型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ Opaque │ │ kubernetes.io/ │ │ tls │ │
│ │ (通用) │ │ dockerconfigjson │ │ (证书) │ │
│ │ │ │ (镜像仓库) │ │ │ │
│ │ 键值对 │ │ │ │ TLS 证书 │ │
│ │ 任意数据 │ │ Harbor/Docker │ │ │ │
│ │ │ │ Hub │ │ 私有仓库 │ │
│ └─────────────────┘ │ │ │ 认证 │ │
│ └─────────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ basic-auth │ │ ssh-auth │ │
│ │ (用户密码) │ │ (SSH 密钥) │ │
│ │ │ │ │ │
│ │ 用户名/密码 │ │ SSH 公钥/私钥 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Secret 创建方式
# 方式1:手动定义
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
# 值必须是 base64 编码
username: YWRtaW4= # admin
password: cGFzc3dvcmQ= # password
# 注意:base64 不是加密,只是编码
# 任何能访问 API 的人都能解码
---
# 方式2:从文件创建
# kubectl create secret generic db-credentials \
# --from-literal=username=admin \
# --from-literal=password=password
# 从文件
# kubectl create secret generic tls-cert \
# --from-file=tls.crt=server.crt \
# --from-file=tls.key=server.key
TLS Secret
# TLS Secret
apiVersion: v1
kind: Secret
metadata:
name: myapp-tls
type: kubernetes.io/tls
data:
# base64 编码的证书和私钥
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
# 或使用 kubectl 创建
# kubectl create secret tls myapp-tls \
# --cert=server.crt \
# --key=server.key
Docker Registry Secret
# 镜像仓库认证
apiVersion: v1
kind: Secret
metadata:
name: docker-registry-secret
type: kubernetes.io/dockerconfigjson
data:
# 格式:{"auths":{"registry.example.com":{"username":"user","password":"pwd","email":"","auth":"..."}}}
.dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6InBhc3MifX19
# 或使用 kubectl
# kubectl create secret docker-registry my-registry \
# --docker-server=registry.example.com \
# --docker-username=user \
# --docker-password=password
Secret 使用方式
# 方式1:环境变量
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
envFrom:
- secretRef:
name: db-credentials
---
# 方式2:文件挂载
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
# 每个 key 生成一个文件
items:
- key: username
path: DB_USER
- key: password
path: DB_PASS
Secret 安全最佳实践
静态加密
# K8s 1.29+ 默认启用 ETCD 加密
# 或者手动配置
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-key>
- identity: {} # 不加密的后备方案
# 创建加密配置文件
cat > /etc/kubernetes/encryption-config.yaml << EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: encryption-key
secret: $(head -c 32 /dev/urandom | base64)
- identity: {}
EOF
# 修改 kube-apiserver 启动参数
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
RBAC 访问控制
# 只允许特定 ServiceAccount 读取 Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
# 限制命名空间
resourceNames: ["db-credentials"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-secret-reader
subjects:
- kind: ServiceAccount
name: app-sa
namespace: production
roleRef:
kind: Role
name: secret-reader
外部密钥管理
┌─────────────────────────────────────────────────────────────────┐
│ 集成外部密钥管理(最佳实践) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 推荐方案: │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ HashiCorp│ │ AWS │ │ GCP │ │
│ │ Vault │ │ Secrets │ │ Secret │ │
│ │ │ │ Manager │ │ Manager │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ External Secrets Operator │ │
│ └───────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ Kubernetes │ │
│ │ Secret │ │
│ └───────────────────────────────┘ │
│ │
│ 优点: │
│ ✓ Secret 不存储在 K8s 中 │
│ ✓ 集中审计和轮换 │
│ ✓ 自动同步更新 │
│ │
└─────────────────────────────────────────────────────────────────┘
External Secrets Operator 示例
# 安装 ESO
# helm install external-secrets external-secrets
---
# 创建 SecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
token:
secretRef:
name: vault-token
key: token
---
# ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-creds
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: database/credentials
property: username
- secretKey: password
remoteRef:
key: database/credentials
property: password
配置热更新
挂载卷的热更新
# 热更新实验
# 1. 挂载 ConfigMap 为文件
apiVersion: v1
kind: Pod
metadata:
name: config-watch
spec:
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: app-config
# 2. 更新 ConfigMap
kubectl apply -f updated-configmap.yaml
# 3. 观察挂载内容变化
# K8s 会在 ConfigMap 更新时自动更新挂载的文件
# 但应用程序需要能够检测/监听文件变化
# 策略:
# - 应用内监听文件变化
# - 使用 inotify / fsnotify 等机制
# - 或使用配置中心 SDK
应用内热更新实现
// 示例:Go 应用监听配置文件变化
package main
import (
"log"
"os"
"path/filepath"
"sync"
"time"
)
type Config struct {
mu sync.RWMutex
data map[string]string
}
func (c *Config) Reload(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
// 重新读取配置
// ...
log.Println("Config reloaded")
return nil
}
func watchConfig(path string, reloadFunc func() error) {
lastMod := time.Now()
for {
filepath.Walk(path, func(fpath string, info os.FileInfo, err error) error {
if info.ModTime().After(lastMod) {
lastMod = info.ModTime()
reloadFunc()
}
return nil
})
time.Sleep(5 * time.Second)
}
}
ConfigMap 滚动更新策略
# 同步更新策略
# 当 ConfigMap 更新时,Pod 内的挂载内容会更新
# 但有以下注意事项:
# 1. subPath 挂载的文件不会自动更新
volumeMounts:
- name: config
mountPath: /etc/config/database.yaml
subPath: database.yaml # 使用 subPath 不会热更新
# 2. 环境变量不会自动更新
# 环境变量在容器启动时注入,ConfigMap 更新后需要重启 Pod
# 解决方案:
# - 不使用 subPath
# - 使用 sidecar 重新加载配置
# - 或者完全避免热更新,滚动更新 Pod
应用层配置刷新
# Spring Boot Actuator 配置
# application.yaml
spring:
application:
name: myapp
config:
import: optional:file:./config/
management:
endpoints:
web:
exposure:
include: refresh,health,info
endpoint:
refresh:
enabled: true
# 当 ConfigMap 更新后,调用
# POST /actuator/refresh
# 会重新加载 @RefreshScope 注解的 Bean
配置管理最佳实践
配置分离策略
┌─────────────────────────────────────────────────────────────────┐
│ 配置管理分层 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 代码层(不变) │
│ └── 默认配置、打包在镜像中 │
│ │ │
│ ▼ │
│ ConfigMap(环境相关) │
│ └── 环境变量、非敏感配置 │
│ │ │
│ ▼ │
│ Secret(敏感数据) │
│ └── 密码、Token、证书 │
│ │ │
│ ▼ │
│ 外部配置中心(如 Consul/Zookeeper) │
│ └── 动态配置、集中管理 │
│ │
└─────────────────────────────────────────────────────────────────┘
多环境配置
# base-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: "myapp"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
---
# staging-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-staging
data:
LOG_LEVEL: "debug"
MAX_CONNECTIONS: "50"
---
# production-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-prod
data:
LOG_LEVEL: "warn"
MAX_CONNECTIONS: "200"
---
# Deployment 引用
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config # 基础配置
- configMapRef:
name: app-config-${ENV} # 环境配置覆盖
# 需要通过 helm 或 kustomize 注入 ENV
Kustomize 配置管理
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- configmap.yaml
---
# overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- path: staging-patch.yaml
configMapGenerator:
- name: app-config
behavior: overlay
literals:
- LOG_LEVEL=debug
---
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
configMapGenerator:
- name: app-config
behavior: overlay
literals:
- LOG_LEVEL=warn
Helm 配置管理
# values.yaml
app:
name: myapp
replicaCount: 3
image:
repository: myregistry/myapp
tag: "1.0.0"
config:
logLevel: info
maxConnections: 100
---
# values-prod.yaml
app:
replicaCount: 5
image:
tag: "1.2.0"
config:
logLevel: warn
maxConnections: 200
---
# 部署
# helm install myapp ./chart -f values-prod.yaml
常见问题与避坑指南
Q1:Secret 能否加密存储?
# 方法1:ETCD 加密(K8s 原生)
# 需要 kube-apiserver 开启 encryption-provider-config
# 方法2:第三方插件
# - Sealed Secrets:加密后存储,公钥加密
# - Vault + ESO:外部存储
# 方法3:Kubernetes Secrets 加密-at-rest
# K8s 1.29+ 默认启用
Q2:ConfigMap 更新后 Pod 不生效?
# 原因1:使用了 subPath
# subPath 挂载的文件不会自动更新
# 原因2:使用了环境变量
# 环境变量在 Pod 创建时注入,不会自动更新
# 解决方案:
# 1. 不使用 subPath
# 2. 重启 Pod
kubectl rollout restart deployment/myapp
# 3. 应用内监听文件变化
Q3:如何验证配置正确性?
# 方式1:使用 pre-sync hook(ArgoCD/GitOps)
# 在部署前验证配置
# 方式2:配置校验工具
# - kubeval
# - conftest
# 方式3:应用层验证
# 启动时检查必要配置,缺失则 panic
Q4:Secret 如何轮换?
# 轮换流程:
# 1. 创建新 Secret
kubectl create secret generic db-creds-v2 --from-literal=password=newpassword
# 2. 更新 Deployment 引用
kubectl patch deployment myapp -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","env":[{"name":"DB_PASSWORD","valueFrom":{"secretKeyRef":{"name":"db-creds-v2","key":"password"}}}]}]}}}}'
# 3. 滚动更新 Pod
kubectl rollout restart deployment myapp
# 4. 验证新密码生效
# 5. 删除旧 Secret
kubectl delete secret db-creds-v1
总结
┌─────────────────────────────────────────────────────────────────┐
│ 核心要点回顾 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ConfigMap │
│ ├── 非敏感配置存储 │
│ ├── 支持键值对和文件 │
│ ├── 环境变量/文件挂载两种方式 │
│ └── 挂载支持热更新,环境变量需要重启 │
│ │
│ Secret │
│ ├── 敏感数据存储 │
│ ├── 类型:Opaque/TLS/DockerConfigJSON │
│ ├── base64 编码(非加密) │
│ └── 推荐集成外部密钥管理 │
│ │
│ 安全最佳实践 │
│ ├── ETCD 加密存储 │
│ ├── RBAC 最小权限 │
│ └── 外部密钥管理(Vault/AWS SM/GCP SM) │
│ │
│ 配置管理 │
│ ├── 多环境分离(Kustomize/Helm) │
│ ├── 配置热更新(文件挂载+应用监听) │
│ └── 配置验证与回滚 │
│ │
└─────────────────────────────────────────────────────────────────┘
思考题
- 为什么 Secret 的 data 是 base64 编码而不是加密?这有什么安全风险?
- 如果应用不支持热更新,如何实现零停机配置切换?
- 如何设计一个配置管理系统,支持配置变更审计和回滚?
引用与参考
下篇预告
下一篇文章我们将探讨 存储卷与持久化存储,包括:
- EmptyDir/HostPath 临时存储
- PV/PVC 持久化存储
- StorageClass 动态供给
- CSI 与存储扩展
敬请期待!