K8s Secret 管理实战:创建、挂载与安全更新
在 Kubernetes 里管理密码、Token、证书这类敏感信息,标准做法是使用 Secret 资源,而不是把它们写死在镜像或 Deployment 的明文环境变量里。
下面从零开始讲清楚 Secret 的创建、挂载、更新和权限控制,命令可以直接在测试集群执行。
先分清 Secret 和 ConfigMap 的适用边界
Secret 和 ConfigMap 都是用来把配置从镜像里解耦出来的,但用途不同:
- ConfigMap 存普通配置,比如日志级别、超时时间、功能开关,内容不需要保密。
- Secret 存敏感数据,比如数据库密码、API Key、TLS 证书。Kubernetes 会把 Secret 的内容以 base64 编码存储,并且在节点上默认挂载到 tmpfs 内存文件系统,减少落盘风险。
需要先明确一点:base64 只是编码,不是加密。
任何能读取 Secret 对象的人都能解码出原文,所以真正的安全边界在于 RBAC 权限控制和 etcd 的加密配置,而不是 base64 本身。
准备条件
开始之前确认环境满足以下条件:
- 一个可用的 Kubernetes 集群,
kubectl已配置好 kubeconfig 并能正常执行kubectl get nodes。 - 当前账号拥有在目标命名空间创建 Secret 和 Pod 的权限。如果不确定,先用
kubectl auth can-i create secret -n default检查。 - 建议先在测试命名空间操作,避免误改生产环境的敏感配置。
三种常用创建方式
方式一:从命令行字面量创建
适合快速测试,把用户名密码直接写进命令:
kubectl create secret generic db-credential \
--from-literal=username=admin \
--from-literal=password='S3cureP@ss' \
-n default
执行后可以用 kubectl get secret db-credential -o yaml 查看,data 字段里每个值都是 base64 编码后的结果。
方式二:从文件创建
证书、密钥文件这类内容更适合用文件方式导入:
kubectl create secret generic tls-file \
--from-file=./server.crt \
--from-file=./server.key \
-n default
--from-file 会把文件名作为 key,文件内容作为 value。
如果希望自定义 key 名,可以写成 --from-file=tls.crt=./server.crt。
方式三:用 YAML 声明式管理
生产环境更推荐用 YAML 纳入版本控制。
注意 data 里的值必须是 base64 编码,stringData 则可以写明文,Kubernetes 会在创建时自动转码:
apiVersion: v1
kind: Secret
metadata:
name: app-secret
namespace: default
type: Opaque
stringData:
api-key: "my-plain-text-key"
db-password: "p@ssw0rd"
保存为 app-secret.yaml 后执行:
kubectl apply -f app-secret.yaml
用 stringData 可以避免手动 base64 编码时算错字符。
在 Pod 里使用 Secret
Secret 创建好之后,有两种常见消费方式。
挂载为环境变量
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo user=$DB_USER; sleep 3600"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credential
key: username
这种方式适合少量配置项。
缺点是环境变量在容器进程启动后就固定了,Secret 更新后不会自动同步。
挂载为文件
apiVersion: v1
kind: Pod
metadata:
name: file-pod
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "cat /etc/secret/api-key; sleep 3600"]
volumeMounts:
- name: secret-vol
mountPath: /etc/secret
readOnly: true
volumes:
- name: secret-vol
secret:
secretName: app-secret
文件方式的好处是:kubelet 会定期同步 Secret 变化到挂载的文件,应用如果支持热加载配置文件,就能不重启 Pod 拿到新值。
默认同步周期受 kubelet 配置影响,通常在分钟级别。
更新 Secret 后的验证方法
更新 Secret 有两种常见做法:
- 用
kubectl edit secret app-secret -n default直接改。 - 修改 YAML 后重新
kubectl apply -f app-secret.yaml。
更新完成后按使用方式验证:
- 环境变量方式:需要重建 Pod 才能生效。执行
kubectl delete pod demo-pod,让控制器重新拉起。 - 文件挂载方式:进入容器执行
kubectl exec -it file-pod -- cat /etc/secret/api-key,观察内容是否已更新。如果没有变化,先确认 kubelet 同步周期,再检查 Secret 是否真的更新成功。
几个容易踩的坑
base64 编码换行问题。 手动编码时如果用了 echo 'pass' | base64,会带上换行符,导致解码后的值多一个 \n。
正确做法是 printf '%s' 'pass' | base64 或直接用 stringData。
把 Secret 提交到 Git 仓库。 用 YAML 管理 Secret 时,不要把包含真实密码的文件直接推到公开仓库。
可以考虑 Sealed Secrets、External Secrets 等方案,或者至少把 Secret 文件加入 .gitignore 并单独保管。
权限放得太宽。 默认情况下 namespace 内的账号可能有权读取 Secret。
建议按最小权限原则收紧 RBAC,例如只允许特定 ServiceAccount 在特定命名空间读取特定 Secret:
kubectl auth can-i get secret -n default --as=system:serviceaccount:default:my-sa
用这条命令确认某个 ServiceAccount 是否具备读取权限,再决定是否调整 Role 和 RoleBinding。
删除 Secret 前没确认引用。 如果某个 Deployment 正引用该 Secret,删除后新创建的 Pod 会因找不到 Secret 而启动失败。
删除前先用 kubectl get pods -o yaml | grep secretName 排查引用关系。
关于 Secret 安全的常见疑问
Secret 存储在 etcd 里是加密的吗? 取决于集群是否启用了 EncryptionConfiguration。
默认不一定加密,生产集群建议确认 etcd 静态加密已开启,并限制 etcd 的访问权限。
Secret 和镜像里的环境变量哪个更安全? Secret 至少把敏感数据和镜像解耦,便于轮换和权限控制。
直接把密码打进镜像层,任何能拉取镜像的人都能提取出来,风险更高。
更新 Secret 后 Pod 会自动重启吗? 不会。
环境变量方式必须重建 Pod,文件挂载方式由 kubelet 同步,但应用是否重读文件取决于应用自身实现。
能不能跨命名空间引用 Secret? 默认不能。
Secret 是命名空间级别的资源,跨命名空间引用需要额外方案,比如在目标命名空间复制一份,或使用支持跨命名空间的工具。
服务器 K8s 秘钥管理的核心思路是:敏感数据放 Secret,用 RBAC 控制谁能读,用文件挂载配合热加载减少重启,更新后按实际使用方式验证生效情况。
先在测试环境把创建、挂载、更新这条链路走通,再迁移到生产环境会更稳妥。