🔐 第05课:ConfigMap与Secret

📌 课程阶段:K8s基础(5/5)|预计时间:60分钟|难度:⭐⭐☆☆☆

一、为什么需要配置分离?

容器镜像应该是不可变的——同一镜像能在dev/staging/prod运行,区别只在配置。ConfigMap存储非敏感配置,Secret存储敏感数据(密码、证书、Token)。

┌─────────────── 配置分离原则 ───────────────┐
│                                              │
│  ❌ 反模式:配置写死在镜像里                   │
│  Dockerfile: ENV DB_HOST=prod-db.internal    │
│  → 换环境就要重新构建镜像!                    │
│                                              │
│  ✅ 最佳实践:配置从外部注入                    │
│  镜像:myapp:v1(不含任何环境配置)            │
│  dev环境:ConfigMap(DB_HOST=dev-db)           │
│  prod环境:ConfigMap(DB_HOST=prod-db)         │
│  → 同一镜像,不同配置!                        │
│                                              │
│  敏感数据 → Secret(base64编码 + RBAC保护)    │
│  非敏感配置 → ConfigMap(明文存储)            │
└──────────────────────────────────────────────┘

二、ConfigMap详解

2.1 创建ConfigMap

# 方式1:命令行直接创建
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

# 方式2:从文件创建
cat > /tmp/app.properties < /tmp/config-dir/environment
echo "info" > /tmp/config-dir/log-level
kubectl create configmap dir-config --from-file=/tmp/config-dir

# 方式4:YAML定义(最常用)
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
  namespace: default
data:
  # 简单键值对
  APP_ENV: "production"
  LOG_LEVEL: "info"
  DB_HOST: "mysql-service"
  DB_PORT: "3306"
  
  # 多行配置文件
  nginx.conf: |
    server {
        listen 80;
        server_name localhost;
        location / {
            proxy_pass http://backend:8080;
            proxy_set_header Host $host;
        }
    }
  
  application.yml: |
    server:
      port: 8080
    spring:
      datasource:
        url: jdbc:mysql://mysql:3306/app
      redis:
        host: redis-master

# ✅ 验证通过
kubectl apply -f web-config.yaml
kubectl get configmap web-config
# NAME         DATA   AGE
# web-config   6      5s

2.2 ConfigMap消费方式

方式A:环境变量注入

# env-from-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
  name: env-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ['sh', '-c', 'env | grep APP_ && sleep 3600']
    
    # 方式1:引用单个键
    env:
    - name: APP_ENVIRONMENT      # 容器内变量名
      valueFrom:
        configMapKeyRef:
          name: web-config       # ConfigMap名
          key: APP_ENV           # ConfigMap中的key
    - name: APP_LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: web-config
          key: LOG_LEVEL
    
    # 方式2:注入全部键值对
    envFrom:
    - configMapRef:
        name: web-config
      prefix: CFG_              # 可选前缀,变为CFG_APP_ENV等

# ✅ 验证通过
kubectl apply -f env-from-configmap.yaml
kubectl logs env-demo
# APP_ENVIRONMENT=production
# CFG_APP_ENV=production
# CFG_LOG_LEVEL=info

方式B:Volume挂载

# volume-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
  name: volume-demo
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    volumeMounts:
    - name: config-volume
      mountPath: /etc/nginx/conf.d        # 挂载目录
      readOnly: true
    - name: app-config-volume
      mountPath: /app/config              # 另一个挂载
      readOnly: true
  
  volumes:
  - name: config-volume
    configMap:
      name: web-config
      items:                              # 可选:只挂载特定key
      - key: nginx.conf
        path: default.conf                # 重命名
  - name: app-config-volume
    configMap:
      name: web-config
      defaultMode: 0644                   # 文件权限

# ✅ 验证通过
kubectl apply -f volume-configmap.yaml
kubectl exec volume-demo -- cat /etc/nginx/conf.d/default.conf
# server {
#     listen 80;
#     server_name localhost;
#     ...

方式C:子路径挂载单个文件

# subpath-configmap.yaml - 不覆盖目录中其他文件
apiVersion: v1
kind: Pod
metadata:
  name: subpath-demo
spec:
  containers:
  - name: app
    image: nginx:1.25
    volumeMounts:
    - name: config-volume
      mountPath: /etc/nginx/conf.d/default.conf  # 精确到文件
      subPath: nginx.conf                         # ConfigMap中的key
  volumes:
  - name: config-volume
    configMap:
      name: web-config

2.3 ConfigMap热更新

⚠️ Volume挂载的ConfigMap更新后,Pod内文件会自动同步(kubelet定期刷新,约60秒延迟)。但环境变量不会更新——需要重启Pod!
# 更新ConfigMap
kubectl edit configmap web-config
# 修改 LOG_LEVEL: "debug"

# Volume挂载的文件会自动更新
kubectl exec volume-demo -- cat /app/config/LOG_LEVEL
# debug  ← 已更新

# 环境变量不会更新
kubectl exec env-demo -- env | grep LOG_LEVEL
# CFG_LOG_LEVEL=info  ← 还是旧值!

三、Secret详解

3.1 Secret类型

类型用途示例
Opaque通用密钥数据库密码、API Key
kubernetes.io/tlsTLS证书HTTPS证书+私钥
kubernetes.io/dockerconfigjson镜像拉取凭证私有仓库认证
kubernetes.io/basic-auth基础认证用户名/密码
kubernetes.io/ssh-authSSH认证SSH私钥
kubernetes.io/service-account-tokenServiceAccount自动创建

3.2 创建Secret

# 方式1:命令行创建
kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password='S3cur3P@ss!'

# 方式2:从文件创建
echo -n 'admin' > /tmp/username
echo -n 'S3cur3P@ss!' > /tmp/password
kubectl create secret generic db-secret-file \
  --from-file=username=/tmp/username \
  --from-file=password=/tmp/password

# 方式3:TLS Secret
kubectl create secret tls my-tls \
  --cert=/path/to/cert.pem \
  --key=/path/to/key.pem

# 方式4:镜像拉取Secret
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=pass \
  --docker-email=user@example.com

# 方式5:YAML定义(注意base64编码)
apiVersion: v1
kind: Secret
metadata:
  name: db-creds
type: Opaque
data:
  username: YWRtaW4=                    # echo -n 'admin' | base64
  password: UzNjdXIzUEBzcyE=           # echo -n 'S3cur3P@ss!' | base64
stringData:                              # 明文写,自动编码
  database: myapp_production

# ✅ 验证通过
kubectl get secrets db-creds
# NAME        TYPE     DATA   AGE
# db-creds    Opaque   3      5s

# 解码查看
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
# S3cur3P@ss!

3.3 Secret消费方式

# secret-consumer.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secret-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ['sh', '-c', 'echo "DB: $DB_HOST:$DB_PORT" && sleep 3600']
    env:
    - name: DB_USER
      valueFrom:
        secretKeyRef:
          name: db-creds
          key: username
    - name: DB_PASS
      valueFrom:
        secretKeyRef:
          name: db-creds
          key: password
    envFrom:
    - secretRef:
        name: db-creds
      prefix: SEC_
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: db-creds
      defaultMode: 0400                 # 只读权限

# ✅ 验证通过
kubectl apply -f secret-consumer.yaml
kubectl exec secret-demo -- ls -la /etc/secrets/
# total 0
# dr--------  2 root root 100 ... .
# -r--------  1 root root ...  username
# -r--------  1 root root ...  password

3.4 Secret安全最佳实践

⚠️ Secret默认只做base64编码,不是加密!任何人有API权限就能解码。
# 1. 启用静态加密(EncryptionConfiguration)
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: <32-byte-base64-key>
    - identity: {}

# 2. 启用RBAC限制Secret访问
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: default
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["db-creds"]          # 限制特定Secret
  verbs: ["get"]

# 3. 使用外部密钥管理(推荐)
# - Vault (HashiCorp)
# - AWS Secrets Manager
# - Azure Key Vault
# - External Secrets Operator

# 4. 审计Secret访问
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
  resources:
  - group: ""
    resources: ["secrets"]

四、Immutable ConfigMap/Secret

# K8s 1.21+ 支持不可变配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: immutable-config
immutable: true              # 一旦创建不可修改!
data:
  APP_VERSION: "2.1.0"
  MAX_RETRIES: "3"

# 不可变的好处:
# 1. kubelet不需要watch变化,降低API Server负载
# 2. 防止意外修改导致应用行为异常
# 3. Kubelet可以缓存内容,提升性能

# 修改方法:删除重建
kubectl delete configmap immutable-config
kubectl apply -f immutable-config.yaml  # 修改data后重新apply

五、故障排查实战

5.1 ConfigMap不存在导致Pod启动失败

# 现象:Pod一直ContainerCreating
kubectl describe pod <pod-name>
# Warning  FailedMount  ...  configmap "web-config" not found

# 排查:
kubectl get configmap web-config
# Error from server: configmaps "web-config" not found

# 解决:先创建ConfigMap,再创建Pod
kubectl apply -f web-config.yaml
kubectl delete pod <pod-name>  # 重建Pod

5.2 Secret Volume权限问题

# 现象:应用无法读取Secret文件
kubectl exec <pod> -- cat /etc/secrets/password
# cat: can't open '/etc/secrets/password': Permission denied

# 排查:检查defaultMode
# volumes[].secret.defaultMode: 0400  ← root only
# 如果容器以非root用户运行,则无法读取

# 解决:设置合适的权限
# defaultMode: 0444  或 0644

5.3 ConfigMap更新后应用不生效

# 环境变量方式:必须重启Pod
kubectl rollout restart deployment/web-app

# Volume方式:自动更新但可能延迟
# 检查更新时间
kubectl exec <pod> -- ls -la /app/config/

# 强制立即刷新(删除Pod让RS重建)
kubectl delete pod <pod-name>

六、练习

  1. 创建包含Nginx配置的ConfigMap,通过Volume挂载到Pod,验证Nginx使用了自定义配置
  2. 创建包含数据库凭证的Secret,以环境变量方式注入Pod
  3. 测试ConfigMap热更新:修改ConfigMap值,对比环境变量和Volume的更新行为
  4. 配置镜像拉取Secret,从私有仓库拉取镜像
  5. 创建Immutable ConfigMap,尝试修改并观察结果

🏆 第05课成就解锁

下一课预告:第06课深入PV/PVC存储——K8s持久化存储体系。