# k8s 配置与密钥管理


## 导读

将配置和凭据从镜像中剥离，是云原生部署的基础能力。Kubernetes 提供 ConfigMap 和 Secret 两类对象，分别承载普通配置与敏感数据，通过环境变量或 Volume 两条途径注入容器。掌握它们的注入差异、热更新边界以及与镜像解耦的实践模式，才能写出可跨环境复用的部署清单。

## 核心概念

ConfigMap 用于存储非敏感的配置数据，以键值对形式存在，支持从字面量、文件或目录创建。最常见的创建方式有三种：用 `kubectl create configmap --from-literal` 直接传键值对、用 `--from-file` 把整个文件内容作为值、以及用声明式 YAML 定义 `data` 字段。ConfigMap 本身不做类型校验，存储的是字符串，容器消费时由应用自行解析。

Secret 与 ConfigMap 结构相似，但面向敏感数据，其 `data` 字段的值经过 Base64 编码存储。Kubernetes 内置多种 Secret 类型：**Opaque** 用于通用键值对，**kubernetes.io/dockerconfigjson** 存放私有镜像仓库认证，**kubernetes.io/tls** 存放证书与私钥。Base64 只是编码格式而非加密，任何能读取该 Secret 的用户都能解码出原文，因此 Secret 的安全性依赖 RBAC 权限控制与 etcd 静态加密。

两类对象进入容器有两条路径，env 注入与 volume 挂载各有适用场景。前者适合启动参数，后者适合配置文件。理解二者的生效机制差异，是正确使用配置对象的前提。

```mermaid
flowchart LR
    CM[ConfigMap] --> PATH{注入方式}
    SEC[Secret] --> PATH
    PATH -->|env 注入| ENV[容器环境变量]
    PATH -->|volume 挂载| VOL[容器文件系统]
    ENV --> R1[Pod 启动时固化<br/>变更需重启 Pod]
    VOL --> R2[kubelet 周期同步<br/>支持热更新]
```

图 1 展示了 ConfigMap 和 Secret 殊途同归的两条注入路径。env 注入在容器启动时把键值写进环境变量，一旦 Pod 运行便固定，ConfigMap 变更不会反映到已注入的环境变量，需要重启 Pod。volume 挂载则把对象内容以文件形式投射到容器文件系统，kubelet 会周期性同步，从而支持热更新。选择哪条路径，直接决定配置变更的生效方式。

## 图解原理

配置热更新的能力来源于 volume 投射机制。当 ConfigMap 以 Volume 挂载时，kubelet 在节点上将其内容写为临时文件，容器通过 projected volume 读取。kubelet 默认每分钟同步一次，ConfigMap 的改动会在下一次同步后反映到容器内的文件。应用若想实时感知，需要自行监听文件变更或定期重读，Kubernetes 本身不主动通知进程。

SubPath 挂载是热更新的一个重要限制。当 volumeMount 指定了 `subPath`，该文件以独立卷形式挂载，kubelet 不再同步其内容。这意味着通过 SubPath 引用的 ConfigMap 文件，在源对象更新后不会热更新，必须重启 Pod。需要热更新的场景应避免 SubPath，改为挂载整个目录。

将配置从镜像中抽离，是配置管理的核心实践。同一镜像配合不同 ConfigMap 即可适配多环境部署。镜像只承载应用代码，环境差异全部交给配置对象。

```mermaid
graph TB
    IMG[应用镜像 myapp:v1.2.0] --> DEP[Deployment 模板]
    DEP --> DEV[Dev 环境]
    DEP --> STG[Staging 环境]
    DEP --> PRD[Prod 环境]
    DEV -.读取.-> CM1[ConfigMap: dev-config]
    STG -.读取.-> CM2[ConfigMap: staging-config]
    PRD -.读取.-> CM3[ConfigMap: prod-config]
    DEV -.读取.-> SEC1[Secret: dev-credentials]
    PRD -.读取.-> SEC2[Secret: prod-credentials]
```

图 2 描绘了配置与镜像解耦的部署模式。同一份镜像推送到所有环境，每个环境通过各自的 ConfigMap 注入数据库地址、日志级别等参数，通过各自的 Secret 注入凭据。这种模式让镜像只需构建一次，环境间的差异收敛为配置对象的差异，大幅降低多环境维护成本。

## 动手实操

创建 ConfigMap 最直接的方式是用命令行传入字面量。

```bash
kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=debug \
  --from-literal=MAX_CONNECTIONS=100
```

声明式 YAML 更适合纳入版本管理，`data` 字段存放明文字符串，`binaryData` 存放 Base64 编码内容。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  app.properties: |
    server.port=8080
    cache.ttl=300
```

把 ConfigMap 注入为环境变量，用 `envFrom` 批量导入，或用 `env.valueFrom` 逐个引用。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
      - name: app
        image: busybox:1.36
        command: ["sh", "-c", "env | grep LOG_LEVEL && sleep 3600"]
        envFrom:
        - configMapRef:
            name: app-config
```

把 ConfigMap 挂载为 Volume，每个键变成一个文件，文件名即键名，内容即键值。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-volume
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "cat /etc/config/app.properties && sleep 3600"]
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
  volumes:
  - name: config-volume
    configMap:
      name: app-config
```

创建 Opaque 类型的 Secret，命令行会自动对值做 Base64 编码。

```bash
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=S3cr3t
```

声明式 YAML 需要手动编码，用 `echo -n` 避免尾随换行符混入编码结果。

```bash
echo -n "admin" | base64
echo -n "S3cr3t" | base64
```

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: YWRtaW4=
  password: UzNjcjN0
```

拉取私有镜像仓库的镜像，需要创建 dockerconfigjson 类型的 Secret。

```bash
kubectl create secret docker-registry reg-cred \
  --docker-server=registry.example.com \
  --docker-username=admin \
  --docker-password=S3cr3t \
  --docker-email=admin@example.com
```

在 ServiceAccount 或 Pod 的 `imagePullSecrets` 字段中引用该 Secret，即可完成拉取认证。TLS Secret 用于 Ingress 等场景，用证书和私钥文件直接创建。

```bash
kubectl create secret tls tls-cred \
  --cert=server.crt \
  --key=server.key
```

验证热更新行为，先挂载 Volume 形式的 ConfigMap，再修改其内容并观察容器内文件。

```bash
kubectl edit configmap app-config
# 修改 LOG_LEVEL 的值后保存退出
kubectl exec demo-volume -- cat /etc/config/LOG_LEVEL
# 等待约一分钟，再次执行上述命令观察文件内容变化
```

## 常见问题与避坑

**SubPath 挂载不享受热更新**。当 volumeMount 指定了 `subPath`，该文件以独立卷形式挂载，kubelet 不再同步其内容。依赖配置热更新的场景应挂载整个目录而非单文件。

**Secret 的 Base64 不是加密**。Base64 可被任意解码，Secret 在 etcd 中默认以 Base64 明文存储。生产环境应开启 etcd 静态加密并配合 RBAC 限制 Secret 的读取权限。

**env 注入的配置不会热更新**。环境变量在容器启动时固化，修改 ConfigMap 后已运行的 Pod 不会感知。需要动态生效的配置应改用 Volume 挂载，或在配置变更后触发滚动更新。

单个 ConfigMap 的 data 总量上限为 1 MiB，超过会被 API Server 拒绝。大配置文件应考虑挂载独立卷或引入外部配置中心，而非全部塞进 ConfigMap。

## 小结与进阶

ConfigMap 和 Secret 是配置与镜像解耦的基石，选择 env 还是 Volume 注入决定了配置能否热更新。下一篇将进入存储体系，讲解 PersistentVolume 与 PersistentVolumeClaim 如何为有状态应用提供持久化能力。

