2. Kubernetes Secret对象深入

在上一节中我们已经明确:Secret 是 Kubernetes 中用于承载敏感配置的 API 对象,它与 ConfigMap 在结构上高度相似,但定位不同——ConfigMap 面向普通配置,Secret 面向口令、令牌、证书、镜像仓库凭据等需要限制可见范围的数据。本节把 Secret 拆开来看:它有哪些创建方式、有哪些内置类型、数据在对象里以什么形式存在、Pod 又是如何把它变成容器里的文件或环境变量,以及最终它落在 etcd 中意味着什么风险。

一、创建方式:命令式与声明式

最直接的方式是 kubectl create secret 命令式创建。以 Opaque 类型为例,从字面量创建:

kubectl create secret generic db-cred \
  --from-literal=username=appuser \
  --from-literal=password='S3cr3t!2024'

也可以从文件创建,适合证书、密钥这类内容较长的数据:

kubectl create secret generic tls-pair \
  --from-file=./server.crt \
  --from-file=./server.key

命令式创建方便,但不利于版本管理和审计。生产环境更推荐声明式 YAML,把 Secret 清单纳入 Git 或配置管理流程。声明式清单中,data 字段要求值是 Base64 编码后的字符串,stringData 字段则允许直接写明文,API Server 会在写入时自动转成 Base64 存入 data:

apiVersion: v1
kind: Secret
metadata:
  name: db-cred
type: Opaque
stringData:
  username: appuser
  password: "S3cr3t!2024"

这里必须强调一个常见误解:Base64 不是加密。它只是把二进制或文本转成可安全放进 YAML/JSON 的 ASCII 字符集,任何人拿到编码串都能一条命令还原。因此把 Secret 清单直接提交到公开仓库,等同于泄露明文。

二、内置类型与适用场景

Kubernetes 为常见敏感数据场景预置了若干类型,类型本身不改变存储方式,但会影响字段校验规则和消费方的解析约定:

  • Opaque:默认类型,键值对形式,最通用,适合数据库口令、API Key 等自定义数据。
  • kubernetes.io/tls:要求包含 tls.crt 和 tls.key 两个键,供 Ingress 终止 TLS 时直接引用。
  • kubernetes.io/dockerconfigjson:存放 .dockerconfigjson,即 Docker 的 config.json 内容,用于拉取私有镜像仓库。
  • kubernetes.io/service-account-token:由控制器为 ServiceAccount 自动生成,挂载到 Pod 中供访问 API Server 使用。
  • kubernetes.io/basic-auth 与 kubernetes.io/ssh-auth:分别约定 username/password 与 ssh-privatekey 字段。

创建 Docker Registry 凭据时,常用命令式方式一步生成:

kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=ci \
  --docker-password='P@ssw0rd' \
  --docker-email=ci@example.com

生成后在 Pod 的 spec.imagePullSecrets 中引用,kubelet 拉镜像时就会带上该凭据。

三、数据编码与挂载机制

Secret 被 Pod 消费有两条路径:环境变量和卷挂载。环境变量方式通过 envFrom 或 valueFrom.secretKeyRef 注入,简单但有两个明显缺点:一是容器内进程可通过 /proc/self/environ 或崩溃转储泄露;二是 Secret 更新后环境变量不会刷新,必须重建 Pod。

卷挂载方式把每个键写成一个文件,文件名即键名,文件内容为解码后的原始值。kubelet 通过 tmpfs(内存文件系统)承载这些文件,不落磁盘。更重要的是,当 Secret 被更新时,挂载的文件会在约一分钟内自动同步(取决于 kubelet 同步周期与缓存),应用只需重新读取文件即可,无需重启。这也是推荐用卷挂载而非环境变量的核心原因。

下面这张图展示了 Secret 从创建到被容器消费的完整链路,以及 etcd 在其中的位置:

Secret 创建 → 存储 → 消费链路 创建方式 kubectl create secret 声明式 YAML data(Base64) stringData(明文) API Server 校验 type 字段 stringData → Base64 写入 etcd RBAC 鉴权 etcd 默认仅 Base64 未加密即明文可读 需启用 Encryption Configuration Pod 消费 Secret 的两条路径 路径 A:环境变量 envFrom / secretKeyRef 注入容器进程环境 ✗ 更新不刷新,需重建 Pod ✗ /proc/self/environ 可泄露 适用:启动时一次性读取 路径 B:卷挂载(推荐) volumes.secret → volumeMounts tmpfs 内存文件系统 ✓ 更新后约 1 分钟自动同步 ✓ 不落磁盘,权限可控 适用:证书、动态轮换凭据 kubelet 拉取并解码

四、etcd 存储风险与缓解

Secret 最终以 API 对象形式存放在 etcd 中。默认情况下,etcd 里保存的是 Base64 编码后的值,并非加密。这意味着:任何能读取 etcd 数据文件或能访问 etcd 端口的人,都能还原出明文。风险主要来自三个方向:

  1. etcd 备份泄露:快照文件被复制到不受控位置,等于所有 Secret 明文外泄。
  2. RBAC 过宽:授予了 get/list secrets 权限,攻击者可直接通过 API 读取。
  3. 节点侧泄露:有权限登录节点或进入容器的人,可读取挂载文件或环境变量。

对应的缓解措施是分层的。第一层是启用 etcd 静态加密,在 API Server 启动参数中配置 --encryption-provider-config,指定 aescbc 或 kms 提供者,使写入 etcd 的值先加密。第二层是收紧 RBAC,遵循最小权限,避免通配符授予 Secret 读权限。第三层是启用审计日志,记录谁在何时读取了哪个 Secret。第四层是结合外部密钥管理系统(如 Vault、云厂商 KMS),让 Secret 只在内存中短暂存在,减少持久化暴露面。

Base64 编码常被误认为加密。判断一个 Secret 是否真正安全,关键看 etcd 是否启用了静态加密、RBAC 是否最小化、以及是否有审计追踪,而不是看 YAML 里的值是否“看起来像乱码”。