第 7 章:K8s ConfigMap & Secret:从配置注入到安全加密

4 阅读4分钟

上一章讲了 Ingress。本章讲配置管理:ConfigMap 和 Secret。它们让镜像和配置解耦,让应用更灵活、更安全。

7.1 为什么要把配置从镜像里拆出来

早期的做法是:把配置文件打进 Docker 镜像里。这样做有几个问题:

  • 每改一次配置,就要重新构建镜像、重新发布
  • 同一个镜像在不同环境(开发、测试、生产)无法复用
  • 密码、密钥等敏感信息容易泄露在镜像层里
  • 配置变更无法动态下发,必须重启容器

K8s 用 ConfigMap 和 Secret 解决这些问题:

  • ConfigMap:保存非敏感配置,如应用端口、日志级别、Feature Flag
  • Secret:保存敏感信息,如密码、证书、镜像仓库凭证
镜像(不变) + 配置(动态) = 可运行的应用

7.2 ConfigMap 的三种创建方式

7.2.1 命令行创建

# 字面量方式
$ kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

# 从文件创建
$ kubectl create configmap app-config \
  --from-file=app.properties

# 从目录创建
$ kubectl create configmap app-config \
  --from-file=./config/

7.2.2 YAML 方式创建

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  app.properties: |
    server.port=8080
    db.pool.size=20

查看内容:

$ kubectl get configmap app-config -o yaml

7.3 把 ConfigMap 注入到 Pod

有三种注入方式:环境变量、envFrom 批量注入、volumeMount 挂载为文件。

ConfigMap/Secret 注入方式

7.3.1 环境变量注入

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: nginx:alpine
    env:
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: LOG_LEVEL

7.3.2 envFrom 批量注入

spec:
  containers:
  - name: app
    image: nginx:alpine
    envFrom:
    - configMapRef:
        name: app-config

所有 ConfigMap 里的键值对都会变成容器的环境变量。注意:如果键名里包含特殊字符,K8s 会自动替换为下划线。

7.3.3 volumeMount 挂载为文件

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: nginx:alpine
    volumeMounts:
    - name: config-vol
      mountPath: /etc/config
  volumes:
  - name: config-vol
    configMap:
      name: app-config

ConfigMap 中的每个 key 会变成 /etc/config 目录下的一个文件,value 是文件内容。

如果 mountPath 是已有目录,原来的文件会被覆盖。想保留原有文件,可以设置 subPathmountPropagation

7.4 Secret 的三种类型

Secret 用来保存敏感信息。创建方式和 ConfigMap 类似,但数据会用 Base64 编码。

7.4.1 generic 类型

# 命令行创建
$ kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password=admin123

# YAML 方式(推荐用 stringData,K8s 自动编码)
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  username: admin
  password: admin123

YAML 中也可以用 data 字段,但必须先手动 Base64 编码:

$ echo -n "admin123" | base64
YWRtaW4xMjM=

更推荐用 stringData,K8s 会自动编码。

7.4.2 tls 类型

保存 TLS 证书和私钥:

$ kubectl create secret tls app-tls \
  --cert=tls.crt --key=tls.key

7.4.3 docker-registry 类型

保存私有镜像仓库的登录凭证:

$ kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=admin \
  --docker-password=admin123

创建后,在 Pod 中引用:

spec:
  imagePullSecrets:
  - name: regcred
  containers:
  - name: app
    image: registry.example.com/myapp:v1

7.5 Secret 在 Pod 中的使用

Secret 也可以作为环境变量或文件挂载。

7.5.1 环境变量注入

spec:
  containers:
  - name: app
    image: nginx:alpine
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password

7.5.2 文件挂载

spec:
  containers:
  - name: app
    image: nginx:alpine
    volumeMounts:
    - name: secret-vol
      mountPath: /etc/secrets
  volumes:
  - name: secret-vol
    secret:
      secretName: db-secret

挂载后,目录下会有两个文件:/etc/secrets/username/etc/secrets/password,文件内容就是明文值。

7.6 Base64 不是加密

这是 K8s 初学者最容易误解的地方。

Secret 的 data 字段用 Base64 编码,但 Base64 只是编码方式,不是加密。任何拿到 Secret 的人都能轻松解码:

$ kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d
admin123

所以 Secret 只是比普通 ConfigMap 多一点访问控制,不是保险箱。真正的安全需要:

  1. RBAC 控制:限制谁能查看 Secret
  2. 加密 etcd 数据:启用 K8s 的 encryption at rest,让 etcd 中的 Secret 数据被加密
  3. 使用外部密钥管理:如 Vault、AWS KMS、阿里云 KMS

7.7 动态更新与不可动态更新

ConfigMap 和 Secret 挂载为卷时,K8s 会自动把更新同步到容器内文件。这个更新不是实时的,通常有几十秒延迟,但不需要重启 Pod。

$ kubectl edit configmap app-config
# 修改后,Pod 内 /etc/config 下的文件会自动更新

但如果是通过 envenvFrom 注入的环境变量,不会自动更新。环境变量在容器启动时就已经写死了,只有重启 Pod 才会生效。

ConfigMap/Secret 挂载卷 → 动态更新
ConfigMap/Secret 作为 env → 重启才生效

如果业务需要热加载配置,建议把配置放在文件里,并让应用监听文件变化。

7.8 实战:完整配置注入示例

下面是一个完整的 Pod 配置,同时用了 ConfigMap 和 Secret:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  LOG_LEVEL: "info"
  app.conf: |
    server=localhost
    port=3306
---
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  username: "admin"
  password: "admin123"
---
apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
  - name: webapp
    image: nginx:alpine
    env:
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: web-config
          key: LOG_LEVEL
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password
    volumeMounts:
    - name: config-vol
      mountPath: /etc/webapp
  volumes:
  - name: config-vol
    configMap:
      name: web-config

创建后验证:

$ kubectl apply -f config-example.yaml

$ kubectl exec -it webapp -- env | grep LOG_LEVEL
LOG_LEVEL=info

$ kubectl exec -it webapp -- cat /etc/webapp/app.conf
server=localhost
port=3306

$ kubectl exec -it webapp -- cat /etc/webapp/LOG_LEVEL
info

注意:因为 ConfigMap 也挂载到了 /etc/webapp,所以 LOG_LEVEL 既作为环境变量,也作为同名文件存在。

7.9 本章小结

  • ConfigMap 保存非敏感配置,Secret 保存敏感信息
  • 注入方式:环境变量、envFrom 批量注入、volumeMount 挂载文件
  • Base64 是编码不是加密,Secret 安全需要 RBAC 和 etcd 加密
  • 挂载卷可以动态更新,环境变量需要重启 Pod 才生效
  • 镜像应该保持无状态,配置和敏感信息通过 K8s 资源管理

7.10 课后练习

  1. 创建一个 ConfigMap,包含多个键值对,用 envFrom 批量注入到 Pod 环境变量中。
  2. 创建一个 Secret,把用户名密码挂载到 Pod 的 /etc/secrets 目录下,验证文件内容。
  3. 修改挂载为卷的 ConfigMap,观察 Pod 内文件是否自动更新。
  4. 尝试解码一个 Secret 的 Base64 字段,理解 Base64 不是加密。