最近在帮团队梳理 Kubernetes(简称 K8s)集群的配置治理规范,发现一个很有意思的现象:不少同学对 K8s 的 Secret 对象理解得比较浅,要么把它当成万能保险箱,以为数据放进去就绝对安全;要么干脆不用,把数据库密码、API 密钥、证书私钥直接写死在 Pod 的 yaml 里。这两种极端,在真正的生产环境里都挺危险的。
这篇文章不会泛泛地讲 Secret 是什么,而是从我自己的实战经验出发,把 Secret 的设计逻辑、创建方式、注入姿势、安全加固措施,以及我踩过的坑完整串一遍。无论你是刚把 K8s 集群搭起来、正在规范配置管理方式,还是准备面试 K8s 相关岗位,这篇文章应该都能给你一些用得上的东西。顺便说一句,Secret 是 K8s 原生的资源对象,不管你的集群是用 kubeadm 装的、二进制方式搭的还是云厂商托管的,使用方式完全一样,不存在哪套集群“不支持”的问题。
1. 先搞懂 Secret 的本质:它不是加密,只是不让你一眼看到
1.1 K8s 为什么要单独造一个 Secret 对象
先回答一个很多新手都会问的问题:K8s 里已经有 ConfigMap 了,为什么还要再搞一个 Secret?它们俩看起来都是把配置数据从 Pod 里抽离出来,区别到底在哪?
最直接的答案:Secret 专门用来放“敏感信息”。这个定位决定了它和 ConfigMap 在用途上有本质差异。ConfigMap 里放的是端口号、日志级别、开关配置这类不敏感的数据,就算被人看到了也无所谓,甚至很多团队会直接把 ConfigMap 提交到 Git 仓库里做版本管理。但 Secret 里放的是数据库密码、第三方 API Token、TLS 私钥、镜像仓库凭证这些东西,一旦泄露,轻则数据被拖库,重则整个集群被攻破。
K8s 把 Secret 单独设计成一个 API 对象,核心目的有三个:一是从机制上引导用户把敏感信息与非敏感信息分开管理;二是配合 RBAC(基于角色的权限控制)体系,给 Secret 单独做权限隔离,比如普通开发人员可以看 ConfigMap,但只有运维人员能看 Secret;三是作为后续安全增强的载体,比如 K8s 支持对写入 etcd 的 Secret 做加密,这类功能如果直接加在 ConfigMap 上,影响面就太大了。
还要顺带提一点,经常有人把 K8s 和 Docker 放在一起比较,实际上这俩不是同一个层次的东西。Docker 解决的是“单个容器怎么打包和运行”,K8s 解决的是“一堆容器怎么编排、调度、管理”。Secret 是 K8s 这一层的概念,它作用于 Pod 和集群层面,而不是 Docker 镜像层面。你把镜像推到镜像仓库,镜像里的环境变量如果写死了密码,那不管用不用 Secret,密码都在镜像里躺着,这也是很多人容易忽略的泄露途径。
1.2 Secret 到底长什么样:结构拆解
直接看一个最小化的 Secret YAML,这样比讲一堆理论更直观。
apiVersion: v1 kind: Secret metadata: name: myapp-db-secret namespace: production type: Opaque data: DB_HOST: bXktZGF0YWJhc2UuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbA== DB_USERNAME: YWRtaW4= DB_PASSWORD: c3VwZXItc2VjcmV0LXBhc3N3b3Jk先看type字段,Secret 不是只有一种类型,Opaque是默认类型,表示任意键值对,绝大多数场景下你只需要用这种。另外还有两种高频类型:kubernetes.io/dockerconfigjson用来存私有镜像仓库凭证,kubernetes.io/tls用来存 TLS 证书和私钥。这三种类型覆盖了日常工作中 90% 的诉求,后面我会分别演示怎么创建。
再看data和stringData这两个字段的区别。data里面的值必须是 Base64 编码后的字符串,也就是说你放进去之前要自己先 base64 一下。而stringData允许你直接写明文,K8s 的 API Server 会在写入 etcd 之前帮你自动编码成 Base64,最后实际保存的时候还是编码后的。我个人建议,在 YAML 里写stringData更舒服,可读性好很多,尤其是在多人协作的仓库里,别人 review 代码时不需要先解码才能看懂这个 Key 到底是什么。
metadata里的namespace也很关键。Secret 是命名空间级别的资源,同一个名字可以同时存在于default、production、staging这些不同的命名空间里,互不影响。但这也意味着,你在production里创建的 Secret,不能直接被staging下的 Pod 引用,这是新手最容易踩的坑之一,后面排查问题部分我会详细说。
2. 创建 Secret 的几种方式,总有一种适合你
2.1 命令行一键创建:适合快速调试和临时验证
如果你的集群已经跑起来了,想最快速度创建一个 Secret 来验证某个功能,那kubectl create secret是最直接的方式,不需要写 YAML 文件。
最常用的是generic子命令,它有几个--from系列参数可以组合使用。
# 方式一:直接给字面量,适合单个或少量键值 kubectl create secret generic app-secret \ --from-literal=DB_PASSWORD='MyP@ssw0rd!' \ --from-literal=API_TOKEN='token-xxxxx' # 方式二:从文件加载,整个文件内容会成为某个 Key 的值 kubectl create secret generic app-secret \ --from-file=config.yaml=./conf/config.yaml # 方式三:从目录加载,目录下每个文件名会自动成为 Key kubectl create secret generic app-secret \ --from-file=./configs/方式一是最直观的,--from-literal后面用Key=Value的格式,多个键值用多个参数就行。方式二和三适合场景是:你有一份现成的配置文件需要完整塞进 Secret 里,然后以文件形式挂载到 Pod。这里有一个比较实用的细节:--from-file=config.yaml=./conf/config.yaml这种写法表示“把本地的conf/config.yaml内容放进 Secret,Key 为config.yaml”;如果省略前面的config.yaml=,则会用文件名本身作为 Key。
命令行创建还有个好处:--dry-run=client -o yaml参数可以让你在真正创建之前把最终 YAML 打印出来,方便审查。
kubectl create secret generic app-secret \ --from-literal=DB_PASSWORD='MyP@ssw0rd!' \ --dry-run=client -o yaml这样输出出来的 YAML,就是 API Server 实际会接受到的内容,你可以确认data字段已经被 Base64 编码。
2.2 YAML 声明式管理:生产环境更推荐
命令行创建方便归方便,但在生产环境里我更推荐把 Secret 定义放进 Git 仓库,走 GitOps 流程统一管理。原因很简单,你的 Secret 不能只存在于集群里,它需要有版本历史、需要被 review、需要能在故障时快速重建。
用 YAML 文件管理时,我推荐用stringData而不是data。
apiVersion: v1 kind: Secret metadata: name: app-config namespace: production type: Opaque stringData: DB_HOST: my-database.default.svc.cluster.local DB_USERNAME: admin DB_PASSWORD: super-secret-password注意,stringData只是创建或更新时的便捷写法,当你通过kubectl get secret app-config -o yaml查看实际对象时,API Server 返回的仍然是data字段,值已经变成 Base64 编码了。
这里要特别提醒一个安全习惯:尽量不要用kubectl get secret <name> -o yaml然后把输出直接拷贝到剪贴板或者聊天工具里发送。命令本身没有问题,但你的 shell 历史、终端日志、企业内部的即时通讯记录都可能因此留下敏感信息。我一般都会在拿到 YAML 后立即用 sed 或者编辑器把data段替换掉,只保留我要关注的 metadata 部分。
2.3 私有镜像仓库凭证:一行命令解决拉取问题
集群要拉取私有镜像仓库里的镜像,你就必须给节点提供凭证。这种凭证也是通过 Secret 创建的,类型是kubernetes.io/dockerconfigjson。命令行创建非常简单:
kubectl create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=deploy-user \ --docker-password='SuperSecret' \ --docker-email=deploy@example.com \ --namespace=production创建完成之后,在 Deployment 的 Pod 模板里通过imagePullSecrets引用,Kubernetes 在拉取镜像时就会自动携带这个凭证。
spec: template: spec: imagePullSecrets: - name: regcred containers: - name: myapp image: registry.example.com/myapp:latest这里常见的坑是:imagePullSecrets是 Pod 级别的字段,如果你写在了 Deployment 顶层,kubectl apply会直接报错。另外,regcred和 Deployment 必须在同一个 namespace,否则 Pod 调度到该节点时无法找到对应的 Secret,依然会报ImagePullBackOff。
2.4 TLS 证书类型:给 Ingress 用
如果你的集群用 Ingress 对外暴露 HTTPS 服务,那 TLS 证书和私钥也需要放到 Secret 里,类型是kubernetes.io/tls。
kubectl create secret tls myapp-tls \ --cert=certs/tls.crt \ --key=certs/tls.key \ --namespace=production要注意--cert和--key必须配对,证书里面包含的域名信息要和你的 Ingress 规则匹配,否则浏览器会报证书错误。Ingress 里引用时,只需在tls段指定 secretName 即可。
到这里,Secret 的创建方式基本覆盖全了。做一个简单对比表格帮大家记:
| Secret 类型 | 典型用途 | 创建命令 | 常见引用位置 |
|---|---|---|---|
| Opaque | 数据库密码、API Token、任意键值 | kubectl create secret generic | 环境变量、挂载文件 |
| dockerconfigjson | 私有镜像仓库登录凭证 | kubectl create secret docker-registry | Pod 的 imagePullSecrets |
| tls | TLS 证书和密钥 | kubectl create secret tls | Ingress、Ingress Controller |
3. 把 Secret 真正用起来:三种访问姿势详解
3.1 环境变量注入:简单,但更新不实时
把 Secret 的内容注入到 Pod 的环境变量,是大多数业务应用最习惯的读取方式,因为应用代码不用做任何特殊处理,直接os.Getenv("DB_PASSWORD")就行。
apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: production spec: template: spec: containers: - name: myapp image: myapp:latest env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: app-config key: DB_PASSWORDvalueFrom.secretKeyRef指向集群里已经存在的一个 Secret 对象,name是 Secret 名称,key是 Secret 数据里的键。你也可以用envFrom把整个 Secret 暴露成环境变量:
envFrom: - secretRef: name: app-config这样 Secret 里的每个键值都会变成环境变量,变量名就是 Key 名。看起来挺省事,但我不太建议在大规模生产环境这么干。理由是:你失去了对变量名的显式控制,如果 Secret 里某个 Key 的命名不符合环境变量规范(比如包含中划线),K8s 会自动忽略并产生告警事件,应用读不到这个变量,排错成本就增加了。用env逐条声明虽然啰嗦,但每个变量来源都清晰可见,谁都能看懂。
需要特别记住的是:环境变量方式注入的 Secret,只在 Pod 启动时生效,Pod 运行期间就算你更新了 Secret,Pod 里的环境变量也不会跟着变。想要新值生效,必须重建 Pod。这一点和文件挂载方式不同,后面我会展开讲。
3.2 文件挂载:改动后自动同步,但有例外
文件挂载的方式是把 Secret 挂载到容器内的一个目录,每个键对应一个文件,文件名就是键名,文件内容就是键值。应用读取指定路径的文件即可。
spec: template: spec: containers: - name: myapp image: myapp:latest volumeMounts: - name: app-config mountPath: /etc/myapp readOnly: true volumes: - name: app-config secret: secretName: app-config在这个配置里,如果 Secretapp-config里有一个DB_PASSWORD键,容器内就会生成/etc/myapp/DB_PASSWORD文件,内容就是该键的值。应用只需要读取这个文件就能拿到配置。
文件挂载相比环境变量有个很大的优势:更新 Secret 后,kubelet 会周期性同步这些文件,默认大约每 1 分钟同步一次,不需要重启 Pod 就能拿到新值。如果你的应用做了文件监听或定时重读,就可以实现配置热更新。
但是,这里有一个非常隐蔽的坑:如果volumeMounts里加了subPath,那挂载行为就变了,Secret 更新后文件不会自动同步。
volumeMounts: - name: app-config mountPath: /etc/myapp/DB_PASSWORD subPath: DB_PASSWORD readOnly: truesubPath是把 Secret 里的某个键挂载成单个文件,而不是挂载整个目录。这样做的好处是容器内其他文件不会被覆盖,但代价是 kubelet 不会对这个文件做自动更新。我见过不止一次事故:应用配置文件是 nginx.conf,通过 subPath 从 Secret 挂载,运维改了 Secret,nginx 始终加载的还是旧配置,排查了半天才发现是这个机制问题。所以我的建议是:需要热更新的场景不要用 subPath;必须用 subPath 的场景,就接受“改 Secret 之后需要重启 Pod”这个现实,别在更新机制上白费劲。
3.3 静态 Pod / 控制器自动挂载:ServiceAccount 与 Secret 的关系
还有一类 Secret 你可能没主动创建过,但每个命名空间里都存在,那就是 ServiceAccount 对应的 Token。每个 namespace 在创建defaultServiceAccount 时会自动生成一个类型为kubernetes.io/service-account-token的 Secret,它会被自动挂载到该命名空间下所有 Pod 的/var/run/secrets/kubernetes.io/serviceaccount/目录。这个目录下包含 CA 证书和 JWT Token,供 Pod 内应用与 K8s API Server 做身份认证。
这个机制本身没问题,但实际运维中我发现,有些团队把所有 Pod 都放在同一个 namespace,也没有把automountServiceAccountToken显式设为false,导致业务容器里白白挂载了一个具备一定权限的 ServiceAccount Token。万一业务容器被入侵,攻击者拿到这个 Token 就能尝试调用 API Server。这个点虽然不算 Secret 的核心用法,但和 Secret 权限安全强相关,我在下一节展开讲 RBAC 时会再提一次。
4. 安全加固:这才是生产环境该有的姿势
4.1 别让 Secret 裸奔:给 etcd 里的数据加一层真正的加密
先说一个很多人都会误解的事实:Secret 里的值只是 Base64 编码,而 Base64 不是加密。你随手写一个字符串然后 base64 一下,任何人都能直接解码还原。K8s 默认配置下,Secret 的数据是以 Base64 编码的形式存在 etcd 里的,这意味着如果备份文件泄露,或者 etcd 被拖走,里面的密码、Token 等于全裸。
我见过很多团队用默认配置跑了一年多,直到安全审计才发现这个问题。解决方案是给 API Server 配置EncryptionConfiguration,让 Secret 在写入 etcd 之前真正加密。
具体操作分三步。
第一步,生成一个 32 字节的随机密钥,并 Base64 编码:
head -c 32 /dev/urandom | base64第二步,写一个加密配置文件/etc/kubernetes/encryption/encryption-config.yaml:
apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: <第一步生成的base64字符串> - identity: {}注意 providers 的顺序:aescbc排在前面表示新写入的数据都用它加密,identity: {}排在最后表示读取数据时,对于未加密的存量数据也能兼容解密。
第三步,修改 kube-apiserver 的启动参数,加上:
--encryption-provider-config=/etc/kubernetes/encryption/encryption-config.yaml改完重启 API Server,新写入的 Secret 就会以密文形式存到 etcd 了。这一步如果集群是 kubeadm 装的,可以直接编辑/etc/kubernetes/manifests/kube-apiserver.yaml添加参数,kubelet 会自动检测到并重启 API Server 静态 Pod。
要特别提醒的是:这个配置不会对已有的存量 Secret 做加密。已经存在 etcd 里的老 Secret 在更新之前仍然是明文编码状态。想要全部加密,可以遍历所有 namespace 下的 Secret,重新写入一次,或者借助开源工具强制轮换。我当时的做法是写了个简单脚本,把每个 Secretget -o yaml再apply一遍,强制 API Server 走一次加密路径,实测有效。
4.2 权限收敛:Secret 的 RBAC 管控
比加密更前置的一道防线是权限控制。如果你的集群任何人都能kubectl get secrets -A,那你加密做得再好也没用。K8s 默认的 RBAC 策略是,如果你给某个用户绑定了cluster-admin或view权限,他都能看到 Secret 内容。注意view这个角色看起来很无害,但它对 Secret 是有读取权限的,这是 K8s 出于兼容性做的默认设定,实际生产里容易被忽略。
我的建议是给 Secret 定义一个独立的最小权限 Role:
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: production name: production-secret-reader rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch"]然后只给确实需要读取 Secret 的 ServiceAccount 或用户绑定这个 Role:
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: production name: prod-deployer-secret-reader subjects: - kind: ServiceAccount name: deploy-robot namespace: production roleRef: kind: Role name: production-secret-reader apiGroup: rbac.authorization.k8s.io这里要给一个更安全的建议:普通开发者的只读权限,不要给整个 namespace 级的view,而是可以单独给他 ConfigMap 和 Pod 的只读权限,Secret 一律不给。真需要看的时候走一次性临时授权。
同时,在 API Server 开启审计日志后,谁看了哪个 Secret、什么时候看的,都会被记录。排查安全事件时这些日志是重要依据。如果你的集群还没开审计,建议尽早研究一下审计策略配置,这个投入非常值得。
还有一个小细节:命名空间里自动挂载的 ServiceAccount Token 也是 Secret。如果你不想让某个 Pod 自动持有集群权限,在 Pod 模板里显式关闭:
spec: template: spec: automountServiceAccountToken: false认认真真做一遍 RBAC 梳理之后,你可能会发现集群里长期存在一堆实际用不到的超级权限账号,这也是我每次安全巡检必查的一项。
4.3 更进一步:Sealed Secrets 与 External Secrets
就算把 Secret 纳入 Git 仓库管理,stringData仍然把明文写在了仓库里,这对于私有仓库还好,如果是开源项目或者对代码权限管控比较松的团队,风险依然很大。有两个工具能解决这个问题。
第一个是 Bitnami Sealed Secrets。它的思路是:在集群里装一个 Controller,Controller 持有一对密钥,公钥用于加密,私钥用于解密。你本地用kubeseal工具把明文 Secret 加密成一个 SealedSecret 对象,这个对象可以安全地提交到 Git 仓库。部署时把 SealedSecret apply 到集群,Controller 拿到后用私钥解密,自动帮你创建真正的 Secret 对象。由于只有 Controller 有私钥,即使 SealedSecret YAML 泄露,别人也还原不出明文。
# 本地用一个普通的 Secret YAML 作为输入 kubeseal --controller-namespace kube-system --controller-name sealed-secrets-controller \ < my-secret.yaml > my-sealed-secret.yaml生成出来的my-sealed-secret.yaml可以直接提交到 Git。这个工具使用成本很低,但安全收益很大,强烈推荐给使用 GitOps 流程的团队。
第二个是 External Secrets Operator。它的思路更彻底:Secret 的明文根本不在 K8s 集群里持久化,而是存在第三方密钥管理系统里,比如云厂商的 KMS、HashiCorp Vault、AWS Secrets Manager。集群内的 External Secrets Controller 按需从这些系统拉取最新的敏感值,然后生成 K8s Secret。这样做的好处是密钥的源头在 KMS 或 Vault 里,轮换、审计都在那套成熟体系里完成,K8s 只是消费方。如果你的公司已经有密钥管理系统,这个方案是长期最优解。
这两个工具不冲突,可以结合使用。我的经验是:敏感程度一般的配置用 Sealed Secrets 解决 Git 明文问题,核心数据库密码、支付相关的密钥就直接接 Vault 或云 KMS。
另外还有一个技巧:K8s 从 1.21 开始支持 Secret 不可变(immutable)。给 Secret 加上immutable: true后,这个 Secret 就不能被修改或删除,只能重建。这对于防止误操作、保证运行稳定性很有帮助。代价是不能热更新了,需要配合外部系统自动重建 Secret 才能用,比如 External Secrets Operator 的轮换模式。对于关键业务的数据库凭证,我觉得不可变是一件好事,任何人想改都必须显式重建并走一次发布流程。
5. 常见问题与排查实录
5.1 Secret 更新了,Pod 里怎么还是旧值
这个问题我在 3.1 和 3.2 里都提到了,这里再把判断流程串一下,方便你遇到问题时快速定位:
- 如果 Secret 通过环境变量注入,那 Pod 不会热更新,强制删除 Pod 让 Deployment 重新创建新 Pod 即可生效。
- 如果 Secret 通过文件挂载,且没有用 subPath,那么 kubelet 会在同步周期内更新文件,通常最多等 1~2 分钟。如果你等不了,手动
kubectl rollout restart deployment/myapp也能立即生效。 - 如果用了 subPath,文件不会自动更新,同样需要重启 Pod。
还有一个细节容易被忽略:kubectl rollout restart重启 Deployment 后,新旧 Pod 会短暂共存,这时如果应用启动时有强校验逻辑,可能会出现短暂启动失败。建议在业务低峰期操作。
5.2 Secret 已经创建了,Pod 还报 Secret not found / ImagePullBackOff
这个问题我在前面已经提过一次,核心原因多半是 namespace 不一致。Secret 和 Pod 必须处于同一个 namespace。排查步骤我总结出来给你参考:
kubectl get secret <name> -n <namespace>确认目标 namespace 下确实存在这个 Secret。kubectl describe pod <pod-name> -n <namespace>查看 Events 里的具体报错,通常会直接告诉你secret "xxx" not found。- 如果确认存在,检查 Deployment 里的
imagePullSecrets或secretKeyRef拼写,有没有大小写、中划线之类的差异,这类问题最常见也最隐蔽。 - 顺手检查一下
metadata.namespace是不是被前一个命令的历史参数污染了,比如你上一个命令在productionnamespace 创建了 Secret,下一个 Deployment 写在staging,就非常容易出现这种问题。
5.3 不小心把 Secret 提交到 Git 仓库了,怎么办
这个问题我处理过好几次,方案其实分两部分。
第一部分是把明文从 Git 历史里清掉。老项目的 Git 历史往往已经很长,直接用git filter-branch逐条改会很痛苦,推荐用git filter-repo或者 BFG Repo-Cleaner,可以批量把历史中出现的某个字符串或者某个文件路径全部重写掉。改完历史后强制推送,然后所有团队成员重新 clone 新的仓库,原来的仓库副本全部作废。
但比清理 Git 历史更重要的是第二部分:轮换密钥。只要 Secret 内容已经出现在任何人的本地仓库、CI 日志、聊天记录里,就应该默认它已经泄露。Git 历史清理只是防止后续继续传播,真正的止损是修改所有受影响的密码、Token、证书。这一步不能省,也别抱有侥幸心理。我的建议是,从这次事件开始,给团队配一个 gitleaks 之类的预提交检查工具,在代码 push 之前先扫描一遍,从源头拦截。
5.4 Secret 的 1MiB 大小限制
K8s 对单个 Secret 对象的大小上限是 1MB(确切说是 1 MiB),这个限制其实是继承了 etcd 单对象大小上限。如果你试图往 Secret 里塞一个几十 MB 的配置文件,API Server 会拒绝写入。
遇到这种情况,先想一下是不是设计问题:这么大的数据本身不适合放进 Secret,可以考虑拆分成多个 Secret 按需挂载,或者把大文件放到对象存储,在应用启动时拉取。如果只是用于 Kubernetes 集群自身的某些资源(比如镜像证书链比较长),也可以考虑把证书链拆成多个键分别存储,挂载时拼起来用,但这要改应用逻辑,不是很推荐。总的来说,越是接近但这个上限,越要审视一下你的方案是否合理。
5.5 Pod 启动后看不到 Secret 对应的环境变量
如果你的应用容器里printenv查不到某个变量,先确认 Deployment 的 env 配置里secretKeyRef的key和 Secret 里的data键名是否完全一致,大小写敏感,一个字母都不能差。还有一个容易被忽略的点:如果 Secret 里的 Key 名不符合 POSIX 环境变量命名规范(只能包含字母、数字、下划线,不能以数字开头),envFrom批量导入时该键会被静默跳过,并且会生成一个InvalidEnvironmentVariableNames事件。用kubectl describe deployment就能看到这个事件。所以我在前面建议用逐条的env,就是为了避免这种“看起来没生效,实际上被规范过滤了”的诡异情况。
最后说几句实在话
Secret 这个东西,本身用法并不复杂,复杂的是你对它的安全意识和管理规范。我早年也吃过亏,图省事把数据库密码直接写在 ConfigMap 里,等出了问题再回头看,才发现整个集群的敏感信息防护形同虚设。后来逐步把 Secret 的权限收敛、etcd 加密、Git 安全存储这些事情一项项补起来,才真正觉得集群有了点“生产可用”的样子。
如果你现在正准备在团队里推行 Secret 规范化管理,我的建议是从最小权限做起:先把不需要访问 Secret 的默认权限收紧,再考虑上 etcd 加密和工具链。步子不用一下迈太大,但每一步都要走稳。希望这篇文章能帮你少踩几个坑。