2. Secrets对象模型与API机制

Secret 是 Kubernetes 中用于承载敏感配置的一等 API 资源,它和 ConfigMap 共享相似的“键值对”外形,但在数据编码、类型约束、消费方式与安全边界上都有独立设计。理解 Secret 的对象模型,关键不是记住 YAML 字段,而是弄清一条敏感数据从“写入 API”到“进入容器”的完整链路:它先以 base64 编码存入 etcd,再通过卷投影或环境变量两种路径被 Pod 消费,而这两条路径在更新可见性、文件权限和暴露面上差异明显。

资源定义与数据编码

Secret 的 data 字段要求值必须是 base64 编码的字符串,而 stringData 是只写字段,允许直接写明文,API Server 在写入时会自动将其合并进 data 并做 base64 编码。需要特别强调:base64 不是加密,它只是把二进制安全地放进文本字段,任何能读取 Secret 的人都能一行命令还原明文。因此 Secret 的安全性依赖 RBAC、etcd 加密和最小权限,而不是编码本身。

apiVersion: v1
kind: Secret
metadata:
  name: db-credential
  namespace: prod
type: Opaque
data:
  username: YXBwX3VzZXI=      # app_user
  password: UzNjcjN0LTIwMjQ=  # S3cr3t-2024
stringData:
  connection: "postgres://app_user@db.prod:5432/app"

上面示例中,username 与 password 是预先编码的,connection 则用 stringData 写明文。提交后 kubectl get secret db-credential -o jsonpath='{.data.password}' | base64 -d 可还原出 S3cr3t-2024,这正是验证编码是否正确的最直接方式。Secret 的 metadata.name 与 namespace 决定其作用域,跨命名空间引用必须显式创建同名 Secret,不能直接挂载别的命名空间的资源。

类型系统与约束

type 字段决定了 Secret 的用途语义和必填键。最常用的是 Opaque,表示任意键值对;kubernetes.io/basic-auth 要求至少包含 username 和 password;kubernetes.io/tls 要求 tls.crt 与 tls.key;kubernetes.io/dockerconfigjson 则要求 .dockerconfigjson 键,用于镜像拉取凭据。类型不是装饰,它会影响 kubelet 的校验和某些控制器的行为,例如 TLS 类型 Secret 被 Ingress 引用时,控制器会直接读取固定键名。

类型必填键典型用途
Opaque无通用应用配置、数据库口令
kubernetes.io/basic-authusername、password基础认证凭据
kubernetes.io/tlstls.crt、tls.keyIngress TLS 终止
kubernetes.io/dockerconfigjson.dockerconfigjson私有镜像仓库拉取

挂载与卷投影

把 Secret 作为卷挂载,是生产环境最推荐的方式。kubelet 会在容器文件系统中生成一个 tmpfs 卷,每个键对应一个文件,文件内容为解码后的明文。默认权限是 0644,可通过 defaultMode 或 items[].mode 收紧到 0400。卷挂载的另一个关键特性是:当 Secret 被更新后,挂载文件会在 kubelet 的同步周期内(通常几十秒到一分钟)自动刷新,应用无需重启即可读到新值——前提是应用每次读取文件而不是只在启动时缓存。

spec:
  containers:
  - name: api
    image: registry.example.com/api:1.4
    volumeMounts:
    - name: db-secret
      mountPath: /etc/db
      readOnly: true
  volumes:
  - name: db-secret
    secret:
      secretName: db-credential
      defaultMode: 0400
      items:
      - key: password
        path: db_password

上面的 items 只投影了 password 一个键,并重命名为 db_password,容器内最终看到的是 /etc/db/db_password。这种“按需投影”能减少敏感键在文件系统中的暴露面。若使用 subPath 挂载单个文件,则更新不会自动同步,这是常见的排障陷阱。

环境变量注入

环境变量注入通过 env[].valueFrom.secretKeyRef 实现,写法简洁,但有一个硬性限制:环境变量在容器启动时一次性注入,Secret 后续更新不会反映到已运行容器的环境变量中,必须重建 Pod 才能生效。此外,环境变量可能被进程转储、被 /proc 读取或被日志意外打印,暴露风险高于文件挂载。

env:
- name: DB_USER
  valueFrom:
    secretKeyRef:
      name: db-credential
      key: username
- name: DB_PASS
  valueFrom:
    secretKeyRef:
      name: db-credential
      key: password

如果 Secret 或键不存在,且未设置 optional: true,Pod 会卡在 CreateContainerConfigError。排查时可用 kubectl describe pod 查看事件,再用 kubectl get secret db-credential -o jsonpath='{.data}' 确认键名是否拼写正确。

Secret 从 API 到容器的两条消费路径 API Server data: base64 编码 stringData → data 合并 etcd 持久化 静态加密(可选) kubelet 同步 解码 + 投影 路径 A:卷投影 tmpfs 文件,权限 0400 /etc/db/db_password 更新后自动刷新 ✓ subPath 例外 ✗ 路径 B:环境变量 secretKeyRef 注入 DB_USER / DB_PASS 更新后不刷新 ✗ 需重建 Pod 容器进程 读取文件 / 读取环境变量 选型判断 • 需热更新 → 卷投影 • 需最小暴露 → items 投影 • 兼容旧应用 → 环境变量 • 避免日志泄露 → 卷 + 0400 • 键缺失 → optional 或校验

两条路径的工程取舍

把上面的机制落到工程决策上,可以归纳为几条判断依据。第一,需要在不重启 Pod 的前提下轮换凭据,应选择卷投影,并确保应用每次请求都重新读取文件;若应用只在启动时读取一次,卷的自动刷新优势就消失了。第二,对暴露面敏感的场景,优先用 items 只投影必要键,并把 defaultMode 设为 0400,避免整个 Secret 的所有键都出现在容器里。第三,环境变量适合无法改造读取逻辑的遗留应用,但要接受“更新需重建 Pod”和“可能被进程环境转储”两个代价。第四,无论哪条路径,都应通过 RBAC 限制 get/list/watch secret 权限,并开启 etcd 静态加密,因为 base64 编码本身不提供任何机密性。

验证挂载结果时,可用 kubectl exec -it <pod> -- ls -l /etc/db 查看文件权限,再用 cat /etc/db/db_password 确认内容。若权限不是预期的 0400,检查 defaultMode 是否被 items[].mode 覆盖。
不要把 Secret 的 base64 值直接提交到 Git 仓库或写入镜像层。base64 可被任意解码,等同于明文。生产环境应结合 SealedSecrets、External Secrets Operator 或 SOPS 等方案,把密文与密钥管理分离。

综上,Secret 的对象模型可以概括为“编码存储 + 类型约束 + 双路径消费”。data 与 stringData 解决写入便利性,type 约束键的语义,卷投影与环境变量注入则分别对应“可热更新、文件级权限控制”和“启动时一次性注入、兼容性强”两类需求。理解这三层关系,才能在凭据轮换、权限收敛和故障排查中做出正确选择。