1、Secret概述
Configmap一般是用来存放明文数据的,如配置文件,对于一些敏感数据,如密码、私钥等数据时,要用secret类型。
Secret解决了密码、token、秘钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者PodSpec中。Secret可以以Volume或者环境变量的方式使用。要使用secret,pod需要引用secret。Pod可以用两种方式使用secret:作为volume中的文件被挂载到pod中的一个或者多个容器里,或者当kubelet为pod拉取镜像时使用。
2、Secret核心字段与语义
Secret 对象的字段不多,但 data、stringData、immutable 三者的行为差异是日常使用中最容易混淆的地方。
| 字段 | 类型 | 关键语义 | 注意事项 |
|---|---|---|---|
apiVersion |
string | 固定为 v1 |
--- |
kind |
string | 固定为 Secret |
--- |
metadata |
ObjectMeta | 名称、命名空间、标签等 | Secret 是命名空间级别的资源 |
data |
mapstring\[\]byte | 存放 Base64 编码后的值;键仅允许字母数字、-、_、. |
总大小受 MaxSecretSize 限制;从 API 读取时可见 |
stringData |
mapstringstring | 只写 字段,写入时自动合并到 data,免除手动 Base64 编码 |
读取 API 时永远不会输出,适合在 YAML 中直接写明文 |
type |
string | 提示系统该 Secret 的用途,影响校验逻辑 | 空值等价于 Opaque |
immutable |
boolean | 设为 true 后 data 不可修改,仅 metadata 可改 |
一旦设为不可变,无法逆转;适合高频挂载、无需轮换的场景 |
关键区分 :data 中的"Base64 编码"仅是一种编码,不是加密 ;任何人拿到 Secret 对象后执行 base64 --decode 即可还原明文。stringData 的便利性也意味着,如果用 YAML 声明式管理,明文曾存在于文件中,需注意版本控制的安全边界。
3、Secret内置类型:校验约定不同,不是加密等级不同
type 字段的主要作用是触发服务端对必需键的校验,而不是提供不同的加密强度。
| 类型 | 用途 | 必需键 / 约定 | 典型场景 |
|---|---|---|---|
Opaque(默认) |
任意用户自定义键值对 | 无强制校验 | API Key、密码、连接串等通用敏感数据 |
kubernetes.io/dockerconfigjson |
私有镜像仓库认证 | .dockerconfigjson |
imagePullSecrets 引用,供 kubelet 拉取私有镜像 |
kubernetes.io/tls |
TLS 证书与私钥 | tls.crt、tls.key |
Ingress TLS 终结、服务端/客户端 TLS 认证 |
kubernetes.io/basic-auth |
用户名密码认证 | username、password |
需要标准 Basic Auth 键名的场景 |
kubernetes.io/ssh-auth |
SSH 私钥 | ssh-privatekey |
Git SSH 认证、SSH 跳板配置 |
4、Secret创建方式:命令式 vs 声明式
| 维度 | 命令式(kubectl create secret) |
声明式(YAML + kubectl apply) |
|---|---|---|
| 数据来源 | 字面量 --from-literal;文件 --from-file;环境文件 --from-env-file |
在 YAML 中直接写 data(需 Base64)或 stringData(写明文) |
| 典型命令 | kubectl create secret generic app-secrets --from-literal=DB_PASSWORD=... |
kubectl apply -f secret.yaml |
| 适用场景 | 临时创建、快速验证、CI 流水线中注入 | GitOps 工作流、需要版本化管理的配置 |
| 明文暴露风险 | 明文出现在 shell history / 命令行参数中 | 若用 stringData,明文存在于文件中;需配合加密的 Git 或 SealedSecrets 类工具 |
| 更新行为 | 通常不支持直接更新,需 kubectl delete 后重建(或 create 后 replace) |
kubectl apply 可声明式更新(immutable 除外) |
一个常见折中做法:用 stringData 在 YAML 中写明文便于维护,但该 YAML 不进入 Git,或进入 Git 前经 kubeseal 等工具加密。
5、Secret在Pod 中的使用方式
| 使用方式 | 配置写法 | 更新后是否自动生效 | 安全特征 |
|---|---|---|---|
| 环境变量(单键) | env[].valueFrom.secretKeyRef |
不自动生效,需重建 Pod | 值会出现在容器进程环境中,子进程可能继承;适合一次性读取的认证信息 |
| 环境变量(全部键) | envFrom[].secretRef |
同样不自动生效,需重建 Pod | 同上;便利但扩大了暴露面 |
| Volume 文件挂载 | volumes[].secret.secretName + volumeMounts |
可自动更新(kubelet 同步周期),但应用需重新读取文件 | 以文件形式出现在容器文件系统中,键为文件名,值为解码后内容;可配合 readOnly: true、defaultMode 收紧权限 |
imagePullSecrets |
Pod spec.imagePullSecrets[].name |
仅用于拉取镜像时 | 将仓库凭据传递给 kubelet,不注入业务容器 |
选择建议 :需要动态轮换且应用支持热加载时优先用 Volume 挂载;应用不支持文件监听、只能从环境变量读取时,用环境变量并接受"更新后需滚动重启"的代价。
5.1、通过环境变量引入Secret
bash
# 把mysql的root用户的password创建成secret
kubectl create secret generic mysql-password \
--from-literal=password='eminem**zhou66'
# 查看
kubectl get secret
kubectl get secret -o yaml
kubectl get secret mysql-password -o jsonpath='{.data.password}' | base64 -d
kubectl describe secret mysql-password
password的值是加密的,但secret的加密是一种伪加密,它仅仅是将数据做了base64的编码.
bash
# 创建 pod-secret.yaml
vi pod-secret.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,Pod 使用 v1
kind: Pod # 资源类型为 Pod
metadata: # Pod 的元数据,包括名称、标签等
name: pod-secret # Pod 名称为 pod-secret
labels: # 给 Pod 打标签
app: myapp # 标签键为 app,值为 myapp
spec: # Pod 的期望状态/规格
containers: # 定义 Pod 中的容器列表
- name: myapp # 容器名称为 myapp
image: docker.io/janakiramm/myapp:v1 # 容器使用的镜像及版本
ports: # 声明容器端口
- name: http # 端口名称为 http
containerPort: 80 # 容器声明监听 80 端口
env: # 定义容器环境变量列表
- name: MYSQL_ROOT_PASSWORD # 环境变量名为 MYSQL_ROOT_PASSWORD
valueFrom: # 环境变量的值不直接写死,而是从其他资源引用
secretKeyRef: # 从 Secret 中引用某个 key 的值
name: mysql-password # 引用的 Secret 名称为 mysql-password
key: password # 引用该 Secret 中的 password 键
bash
# 启动pod-secret
kubectl apply -f pod-secret.yaml
# 查看pod-secret
kubectl get pod pod-secret
# 查看pod-secret详细信息
kubectl describe pod pod-secret
# 查看环境变量
kubectl exec -it pod-secret -- env | grep MYSQL_ROOT_PASSWORD

环境变量方式不会自动更新 :Secret 更新后,已经运行的 Pod 中 MYSQL_ROOT_PASSWORD 不会变,需要删除重建 Pod。
5.2、通过volume挂载Secret
bash
# 创建Secret
# 手动加密,基于base64加密
echo -n 'eminem' | base64
echo -n 'eminem123456' | base64
# 尝试解码,不用做这个操作(验证一下而已)
echo -n ZW1pbmVt | base64 -d
echo -n ZW1pbmVtMTIzNDU2 | base64 -d

bash
# 创建secret.yaml
vi secret.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,Secret 使用 v1
kind: Secret # 资源类型为 Secret,用于保存敏感数据
metadata: # Secret 的元数据
name: mysecret # Secret 名称为 mysecret
type: Opaque # Secret 类型为 Opaque,表示通用的用户自定义键值对
data: # 存放 Base64 编码后的数据
username: ZW1pbmVt # username 的值,Base64 解码后为 eminem
password: ZW1pbmVtMTIzNDU2 # password 的值,Base64 解码后为 eminem123456
bash
# 创建secret
kubectl apply -f secret.yaml
# 查看mysecret
kubectl get secret mysecret -o yaml
kubectl describe secret mysecret


bash
# 将Secret挂载到Volume中
# 创建pod_secret_volume.yaml
vi pod_secret_volume.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,Pod 使用 v1
kind: Pod # 资源类型为 Pod
metadata: # Pod 的元数据
name: pod-secret-volume # Pod 名称为 pod-secret-volume
spec: # Pod 的期望状态/规格
containers: # 定义 Pod 中的容器列表
- name: myapp # 容器名称为 myapp
image: docker.io/janakiramm/myapp:v1 # 容器使用的镜像
volumeMounts: # 定义容器内挂载的存储卷
- name: secret-volume # 挂载的卷名,需与下方 volumes 中的名称一致
mountPath: /etc/secret # 卷挂载到容器内的路径
readOnly: true # 以只读方式挂载
volumes: # 定义 Pod 级别的存储卷
- name: secret-volume # 卷名称为 secret-volume
secret: # 卷类型为 Secret
secretName: mysecret # 引用的 Secret 名称为 mysecret
bash
kubectl apply -f pod_secret_volume.yaml
kubectl get pod pod-secret-volume
kubectl exec -it pod-secret-volume -- ls -l /etc/secret
kubectl exec -it pod-secret-volume -- cat /etc/secret/username
kubectl exec -it pod-secret-volume -- cat /etc/secret/password
挂载后的效果
Secret mysecret 中的每个 key 会变成 /etc/secret 目录下的一个文件,文件内容为 Base64 解码后的明文:
/etc/secret/username → eminem
/etc/secret/password → eminem123456

5.3、Secret 的 Volume 挂载 vs 环境变量(env)
| 对比维度 | Volume 挂载方式 | env 环境变量方式 |
|---|---|---|
| 配置写法 | volumes[].secret.secretName + volumeMounts[].mountPath |
env[].valueFrom.secretKeyRef(单键)或 envFrom[].secretRef(全部键) |
| 数据呈现形式 | Secret 每个 key 变成挂载目录下的一个文件,文件内容为 Base64 解码后的明文 | 每个 key 变成容器进程的环境变量,值为解码后的明文 |
| Secret 更新后是否传播 | 会,kubelet 在同步周期内更新挂载文件(有分钟级延迟) | 不会 ,已运行 Pod 的环境变量不变,必须重建 Pod |
| 应用如何感知更新 | 应用需重新读取文件(支持热加载的应用可直接生效) | 应用无法感知,只能通过滚动重启/重建 Pod 生效 |
| 是否需要重建 Pod | 不需要(文件自动更新) | 需要 |
| 安全暴露面 | 值在容器文件系统中,可配合 readOnly: true、defaultMode 收紧权限 |
值进入进程环境,子进程会继承 ;/proc/<pid>/environ、env、崩溃转储等都可能泄露 |
| 可见性/调试 | ls、cat 文件即可查看(注意明文输出风险) |
env、printenv 可查看 |
| 粒度控制 | 可挂载整个 Secret,也可用 items 只挂载指定 key 并重命名文件 |
secretKeyRef 精确到单个 key;envFrom 会注入全部 key |
| 键名限制 | 文件名受文件系统规则约束,可用 items[].path 映射成合法文件名 |
环境变量名必须符合标识符规则,非法键名无法直接注入 |
| 适用场景 | 证书、密钥文件、需要轮换且应用支持热加载的配置 | 应用只从环境变量读取、一次性使用的认证信息 |
| 典型缺点 | 应用需支持读文件/监听文件变化 | 更新不自动生效;环境变量泄露面更大 |
| 推荐优先级 | 机密数据优先考虑 | 应用不支持文件读取时的退路 |
一句话选择建议
-
需要轮换、应用支持读文件 → 用 Volume 挂载。
-
应用只能读环境变量 → 用 env,并接受"更新后需重建 Pod"的代价。
-
两者都要求 Secret 与 Pod 同 namespace ,且 Secret 默认在 etcd 中未加密存储,需配合 Encryption at Rest 与 RBAC。
6、与 ConfigMap 的核心区别
| 维度 | ConfigMap | Secret |
|---|---|---|
| 设计用途 | 非机密配置数据 | 机密数据(密码、Token、证书) |
| 存储形式 | 明文键值对 | data 中 Base64 编码,stringData 写入时合并 |
| 默认 etcd 存储 | 明文 | 默认同样明文(Base64 不是加密) |
| 安全能力 | 无内建保密/加密 | 可配置 静态加密 ;可配合 RBAC 做最小权限;可标记 immutable |
| 使用方式 | 环境变量 / Volume | 环境变量 / Volume / imagePullSecrets |
核心心智模型:Secret 是"为机密数据预留了安全加固钩子"的键值对对象,ConfigMap 是通用非机密配置对象。把机密放进 ConfigMap 是常见反模式。
7、安全风险与最佳实践(按优先级)
Secret 的安全需要多层叠加,任何单一措施都不充分。
| 风险 / 实践项 | 说明与操作 |
|---|---|
| 默认未加密存储在 etcd | 任何能访问 etcd 或具有 API get/list Secret 权限的主体都可还原明文。必须配置 Encryption at Rest |
| RBAC 最小权限 | 严格限制 get、list、watch。特别注意:能被授权创建 Pod 的用户,可以通过挂载 Secret 间接读取其值,即使该用户没有直接读 Secret 的权限 |
| etcd 访问管控 | 仅集群管理员可访问 etcd(包括只读);多 etcd 实例之间启用加密 SSL/TLS |
| 命名空间隔离 | 不同安全域使用独立命名空间,限制挂载 Secret 的跨域可达性 |
| 短期 Secret + 审计 | 尽量使用短生命周期凭据;对并发读取多个 Secret 等异常行为配置审计告警 |
immutable: true |
对无需轮换的 Secret,设为不可变可减少 kube-apiserver 的 watch 负载,同时防止意外篡改 |
| 外部 Secret 存储(进阶) | 通过 Secrets Store CSI Driver 将 Vault、云 KMS/Key Vault 等外部存储中的机密以 Volume 形式挂载,可避免 Secret 在 etcd 中长期驻留,并支持集中轮换与审计 |
总结 :Secret 的"安全"是一套配置责任 ,而非开箱即得的属性。Base64 编码只解决"不显眼",不解决"不可读"。真正让 Secret 可信的,是 静态加密 + 严格 RBAC + 最小暴露面的消费方式 三者同时到位。

