ConfigMap 与 Secret:配置注入的四种姿势与安全边界

文章目录

    • 痛点先行
    • 一、ConfigMap:配置注入的四种姿势
      • [1.1 环境变量注入(最常用,但不支持热更新)](#1.1 环境变量注入(最常用,但不支持热更新))
      • [1.2 环境变量批量注入(envFrom)](#1.2 环境变量批量注入(envFrom))
      • [1.3 Volume 挂载(可热更新,但 subPath 有陷阱)](#1.3 Volume 挂载(可热更新,但 subPath 有陷阱))
        • [subPath 挂载的陷阱](#subPath 挂载的陷阱)
      • [1.4 四种注入方式对比](#1.4 四种注入方式对比)
      • [1.5 ConfigMap 的版本管理盲区](#1.5 ConfigMap 的版本管理盲区)
    • 二、Secret:不是加密的,但有办法加固
      • [2.1 Secret 默认只做 Base64 编码](#2.1 Secret 默认只做 Base64 编码)
      • [2.2 Secret 的类型体系](#2.2 Secret 的类型体系)
      • [2.3 etcd 静态加密:Secret 的第一道防线](#2.3 etcd 静态加密:Secret 的第一道防线)
      • [2.4 云厂商的 Secret 加密方案](#2.4 云厂商的 Secret 加密方案)
      • [2.5 Secret 安全边界的完整视图](#2.5 Secret 安全边界的完整视图)
    • [三、RBAC:控制谁能访问 Secret](#三、RBAC:控制谁能访问 Secret)
      • [3.1 默认 ServiceAccount 的陷阱](#3.1 默认 ServiceAccount 的陷阱)
      • [3.2 禁用自动挂载:最小权限原则的第一步](#3.2 禁用自动挂载:最小权限原则的第一步)
      • [3.3 最小权限 RBAC 配置示例](#3.3 最小权限 RBAC 配置示例)
      • [3.4 审计 Secret 访问](#3.4 审计 Secret 访问)
    • [四、外部 Secret 管理:生产级实践](#四、外部 Secret 管理:生产级实践)
      • [外部 Secret Manager 方案对比](#外部 Secret Manager 方案对比)
    • 五、配置注入完整决策树
    • 六、验证命令清单
    • 总结

前置知识:本文假设读者已掌握 Pod、Deployment、Namespace 的基本概念。命令行示例默认在已连接集群的终端中执行。

环境说明:示例使用 Kubernetes 1.28,ConfigMap/Secret 机制在不同版本间保持向后兼容。


痛点先行

生产环境中因配置注入方式选择错误导致的问题远比想象中多:

  • ConfigMap 更新了,但 Pod 里的应用读到的还是旧配置 ------ 以为用了 Volume 挂载就能热更新,结果忽略了 subPath 陷阱
  • Secret 泄露了,以为是加密的 ------ 其实 Kubernetes Secret 只是 Base64 编码,任何能访问 etcd 的人都能直接读取
  • Pod 莫名其妙拿到了集群 admin 权限 ------ 原因只是没有禁用默认 ServiceAccount 的 Token 自动挂载

这些问题看似简单,但根因分散在 ConfigMap 挂载机制、Kubernetes Secret 的安全模型、以及 RBAC 权限体系三个不同维度。本文逐一拆解。


一、ConfigMap:配置注入的四种姿势

ConfigMap 将配置数据以键值对的形式存储,供 Pod 以四种不同方式使用。选择哪种方式,决定了配置变更是否需要重启 Pod。

1.1 环境变量注入(最常用,但不支持热更新)

通过 env 字段将 ConfigMap 的指定键注入为容器环境变量:

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  DATABASE_HOST: "db.example.com"
yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
    - name: nginx
      image: nginx:1.25
      env:
        - name: LOG_LEVEL        # 容器内的环境变量名
          valueFrom:
            configMapKeyRef:
              name: app-config   # ConfigMap 名称
              key: LOG_LEVEL      # ConfigMap 中的键名

env 方式的特点:Pod 启动时固化,ConfigMap 更新后环境变量值不会同步变化。如果需要新配置生效,必须重建 Pod(删除后重新 apply,或者配合 Deployment 的滚动更新)。

这种方式适合不经常变化的配置,比如数据库连接地址、API 端点等基础设施级参数。

1.2 环境变量批量注入(envFrom)

当 ConfigMap 中有大量配置项时,逐个用 env 声明会非常冗长。envFrom 可以将整个 ConfigMap 的所有键值对一次性注入为环境变量,前缀可自定义:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
    - name: nginx
      image: nginx:1.25
      envFrom:
        - configMapRef:
            name: app-config    # 引用整个 ConfigMap
          prefix: APP_         # 可选:为所有变量加前缀

注入后,容器内会出现 APP_LOG_LEVELAPP_DATABASE_HOST 等环境变量。同样不支持热更新,ConfigMap 变更后需要重建 Pod。

1.3 Volume 挂载(可热更新,但 subPath 有陷阱)

通过 Volume 将 ConfigMap 内容作为文件挂载到容器内,这是唯一支持热更新(无需重启 Pod)的注入方式:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  containers:
    - name: nginx
      image: nginx:1.25
      volumeMounts:
        - name: config
          mountPath: /etc/config
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: app-config

挂载后,ConfigMap 中的每个键对应一个同名的文件,文件内容就是该键的值:

复制代码
/etc/config/
├── LOG_LEVEL       # 内容: "info"
└── DATABASE_HOST   # 内容: "db.example.com"

热更新机制 :Kubernetes 控制平面会在 ConfigMap 变更后将新内容同步到 Volume 中,容器内的文件随之更新。默认同步间隔(syncInterval)为 60 秒,因此配置不会立即生效,但整个过程不需要重启 Pod,应用可以继续处理请求。

bash 复制代码
# 验证热更新:修改 ConfigMap 后,检查文件时间戳
kubectl exec web-server -- ls -la /etc/config/
kubectl exec web-server -- cat /etc/config/LOG_LEVEL
subPath 挂载的陷阱

使用 subPath 将 ConfigMap 中的单个键挂载为特定路径时,热更新不会生效

yaml 复制代码
volumeMounts:
  - name: config
    mountPath: /etc/nginx/conf.d/default.conf
    subPath: nginx.conf    # 只挂载单个键,危险!

subPath 的本质是将文件"钉"在容器的层(layer)中,挂载时不会触发镜像层的重新绑定。因此即使用 kubectl apply 更新了 ConfigMap,容器内看到的仍然是挂载时的旧内容。这是生产环境中一个非常隐蔽的坑。

正确的做法是:将整个 ConfigMap 挂载为目录,然后让应用读取该目录下的对应文件。如果应用必须读取固定路径的文件,可以结合符号链接或初始化容器来间接实现。

1.4 四种注入方式对比

注入方式 热更新支持 粒度 配置变更影响 适用场景
env 单键 需要重建 Pod 基础设施参数(不常变更)
envFrom 全部键 需要重建 Pod 大量配置项统一管理
Volume 挂载 单文件 自动同步(约60秒延迟) 需要热更新的运行时配置
Volume + subPath 单键 无效(subPath 陷阱) 禁止使用

1.5 ConfigMap 的版本管理盲区

Kubernetes 没有内置 ConfigMap 版本历史。如果需要在不重建 Pod 的前提下回滚 ConfigMap,有几种实践方式:

方式一:通过 labels 和 kubectl apply 管理版本

bash 复制代码
# 应用带版本标签的 ConfigMap
kubectl apply -f configmap-v1.yaml --record
kubectl apply -f configmap-v2.yaml --record

# 查看版本历史
kubectl rollout history configmap/app-config

方式二:配合 Git 管理 ConfigMap

将 ConfigMap YAML 纳入 Git 仓库,配合 ArgoCD 或 Flux 实现配置版本化。外部 secret manager(如 AWS Secrets Manager、HashiCorp Vault)可以通过 External Secrets Operator 自动同步到集群,进一步提升配置的安全性。


二、Secret:不是加密的,但有办法加固

这是 Kubernetes 入门最常被误解的概念之一:Secret 不是加密的

2.1 Secret 默认只做 Base64 编码

创建一个 Secret:

bash 复制代码
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=super_secret_pass

查看其内容:

bash 复制代码
kubectl get secret db-credentials -o yaml

输出类似:

yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: default
type: Opaque
data:
  username: YWRtaW4=       # "admin" 的 Base64 编码
  password: c3VwZXJfc2VjcmV0X3Bhc3M=   # "super_secret_pass" 的 Base64 编码

Base64 不是加密。任何能访问 etcd 的人(集群管理员、etcd 节点物理访问者、云厂商的元数据服务)都能直接解码:

bash 复制代码
echo "YWRtaW4=" | base64 -d    # 输出: admin
echo "c3VwZXJfc2VjcmV0X3Bhc3M=" | base64 -d  # 输出: super_secret_pass

这意味着:将密码放进 Secret 不等于"加密存储"。在多层防御体系中,Secret 充其量是"不让它在明文 YAML 中出现"------真正的加密需要额外配置。

2.2 Secret 的类型体系

Kubernetes Secret 并非铁板一块,通过 type 字段区分不同的用途:

类型 用途 特殊处理
Opaque(默认) 通用键值对 无特殊处理,Base64 编码
kubernetes.io/tls TLS 证书和私钥 字段固定为 tls.crttls.key,专用于 Ingress TLS termination
kubernetes.io/dockerconfigjson 私有镜像仓库认证 kubectl create secret docker-registry 自动生成,用于 imagePullSecrets
kubernetes.io/basic-auth HTTP Basic Auth 凭证 字段固定为 usernamepassword
kubernetes.io/ssh-auth SSH 私钥 字段固定为 ssh-privatekey
bash 复制代码
# 创建 TLS Secret(直接从证书文件)
kubectl create secret tls my-tls \
  --cert=tls.crt \
  --key=tls.key

# 创建镜像仓库 Secret
kubectl create secret docker-registry my-registry \
  --docker-server=registry.example.com \
  --docker-username=admin \
  --docker-password=pass \
  --docker-email=admin@example.com

2.3 etcd 静态加密:Secret 的第一道防线

要让 Secret 真正"加密存储",需要在 etcd 层配置静态加密(Encryption at Rest)。这会将 etcd 中的 Secret 数据用密钥加密后写入磁盘:

yaml 复制代码
# encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>  # 手动生成并妥善保管
      - identity: {}    # 兜底:不加密(用于未迁移的数据)

启用加密:

bash 复制代码
# 编辑 kube-apiserver 的启动参数,添加:
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml

# 重启 kube-apiserver 后,所有新写入的 Secret 会自动加密
# 旧有的明文 Secret 需要重新写入才会加密:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

这里有个关键细节:加密密钥的轮换 。如果密钥长期不轮换,攻击者拿到了旧快照就能解密所有历史 Secret。生产环境应配置自动密钥轮换(--encryption-provider-config 中的 resources[].providers 可以配置多个 provider,第一个用于写入,所有 provider 依次尝试读取)。

2.4 云厂商的 Secret 加密方案

主流云厂商的托管 K8s 服务通常默认开启 etcd 加密:

云厂商 默认加密 密钥管理
AWS EKS ✅ 启用 KMS 加密(默认) AWS KMS(CMK 自定义密钥)
GCP GKE ✅ 启用 Google 管理密钥 Google Cloud KMS
阿里云 ACK ✅ 启用 KMS 加密(可选) 阿里云 KMS
自建集群 ❌ 默认不加密 需手动配置 etcd 加密或 KMS Provider

云厂商方案的优势在于使用专用硬件(HSM)管理密钥,密钥不会出现在 kube-apiserver 的配置文件中。但如果对数据主权有严格要求,仍然需要自建集群并配置 etcd 加密。

2.5 Secret 安全边界的完整视图

理解 Secret 的安全边界,需要区分三个不同的威胁模型:
防御层次
威胁模型
加密密钥泄露
SA Token 泄露
威胁1:etcd 未授权访问
威胁2:API Server 鉴权漏洞
威胁3:Pod 内应用日志泄露
etcd 静态加密(应对 T1)
RBAC + 最小权限(应对 T2)
应用层:日志脱敏 / 审计(应对 T3)

防御层次一:etcd 静态加密防止磁盘层面的泄露,但加密密钥本身的安全是另一个问题。

防御层次二:RBAC 控制谁能通过 API Server 读取 Secret,这是最重要的纵深防线。

防御层次三:即使防御层一和二层都正常,恶意应用也可能将 Secret 写入日志或外传到外部服务器。这需要应用层自身的安全实践来解决。


三、RBAC:控制谁能访问 Secret

Secret 的权限控制依赖 Kubernetes 的 RBAC 体系。错误的 RBAC 配置是生产环境权限泄露最常见的根因。

3.1 默认 ServiceAccount 的陷阱

每个 Namespace 创建时,Kubernetes 会自动创建一个名为 default 的 ServiceAccount:

bash 复制代码
kubectl get sa -n default
# NAME      SECRETS   AGE
# default   1         3d

kubectl get secret -n default
# NAME                             TYPE      DATA   AGE
# default-token-xxxxx              kubernetes.io/service-account-token   4   3d

更危险的是:在 Kubernetes 1.21 之前,所有 Pod 默认会自动挂载这个 Token。应用代码中一个简单的请求就能拿到这个 Token:

python 复制代码
# 恶意应用只需要这段代码就能窃取 Pod 的身份
import requests

token = open("/var/run/secrets/kubernetes.io/serviceaccount/token").read()
namespace = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()

# 用这个 Token 访问 API Server(默认有 default SA 的权限)
r = requests.get(
    f"https://kubernetes.default.svc/api/v1/namespaces/{namespace}/secrets",
    headers={"Authorization": f"Bearer {token}"},
    verify="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
print(r.json())

如果 default ServiceAccount 被赋予了超出预期的权限,这个 Token 就是一把钥匙。

3.2 禁用自动挂载:最小权限原则的第一步

方案一:Pod 级别禁用

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  serviceAccountName: web-sa   # 使用专用 ServiceAccount
  automountServiceAccountToken: false   # 禁用自动挂载 Token

方案二:ServiceAccount 级别禁用(推荐)

yaml 复制代码
apiVersion: v1
kind: ServiceAccount
metadata:
  name: web-sa
automountServiceAccountToken: false   # 该 SA 创建的所有 Pod 默认不挂载 Token

方案三:全局准入控制(OPA/Gatekeeper)

对于多团队集群,可以在 admission 层面强制要求所有 Pod 禁用自动挂载:

yaml 复制代码
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAutomountServiceAccountToken
metadata:
  name: no-automount-default
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces: ["kube-system"]
  parameters:
    serviceAccountName: "default"
    automount: "false"

3.3 最小权限 RBAC 配置示例

为每个应用分配专用 ServiceAccount,并仅授予其所需的最小权限:

yaml 复制代码
# Step 1: 创建专用 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payment-service-sa
  namespace: production
---
# Step 2: 定义只读 ConfigMap 的 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: payment-service-config-reader
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["payment-service-config"]   # 只允许访问指定的 ConfigMap
    verbs: ["get", "list"]
---
# Step 3: 将 Role 绑定到 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payment-service-config-reader-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: payment-service-sa
    namespace: production
roleRef:
  kind: Role
  name: payment-service-config-reader
  apiGroup: rbac.authorization.k8s.io

注意 resourceNames 字段------这是资源名称级别的权限控制 ,比单纯控制 resources 更精确。结合 verbs: ["get"](只有 get,没有 list),这个 ServiceAccount 只能读取指定的单个 ConfigMap,不能列出该 Namespace 下其他资源。

3.4 审计 Secret 访问

即使配置了严格的 RBAC,也需要审计来发现异常访问:

bash 复制代码
# 启用 API Server 审计日志后,查找 Secret 访问事件
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: RequestResponse   # 记录 Secret 的完整请求和响应
    resources:
      - group: ""
        resources: ["secrets"]
bash 复制代码
# 查询 Secret 访问最多的 ServiceAccount(生产中应接入 Prometheus + Grafana)
kubectl get events -n production \
  --field-selector involvedObject.kind=Secret \
  --sort-by='.lastTimestamp' | tail -20

四、外部 Secret 管理:生产级实践

当 Secret 数量增多、跨集群管理需求出现时,原生 Secret 的局限性就暴露出来了:

  • 每次密钥轮换需要手动更新集群中的 Secret
  • 密钥轮换时需要同步更新引用它的 Pod(除非用了 RBAC 缓存策略)
  • 多集群场景下,Secret 分布在不同集群的 etcd 中,无法集中管理

External Secrets Operator(ESO) 是 CNCF 官方孵化的项目,用于将外部 Secret Manager(AWS Secrets Manager、HashiCorp Vault、GCP Secret Manager 等)的数据同步到 Kubernetes 原生 Secret:

yaml 复制代码
# 安装 ESO(Helm)
helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --create-namespace

# 创建 AWS Secrets Manager 的 Provider
apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
  name: shared-secrets
spec:
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  externalSecretSpec:
    secretStoreRef:
      name: aws-secrets-manager
      kind: ClusterSecretStore
    target:
      name: payment-service-secrets   # 在集群中创建名为 payment-service-secrets 的原生 Secret
      creationPolicy: Owner
    data:
      - secretKey: db-password    # 集群内 Secret 的键名
        remoteRef:
          key: production/payment/db   # AWS Secrets Manager 中的密钥 ARN 或路径

这种方式的优势在于:外部 Secret Manager 成为唯一的真相来源(Single Source of Truth),密钥轮换只需要在外部平台操作,ESO 自动将新值同步到集群 Pod 可见的原生 Secret 中。GitOps 团队只需要维护 ExternalSecret 资源的 YAML 文件,而不需要将任何真实密钥写入集群。

外部 Secret Manager 方案对比

维度 K8s 原生 Secret HashiCorp Vault AWS Secrets Manager GCP Secret Manager
加密 需额外配置 etcd 加密 ✅ 默认加密(Transit Engine) ✅ 默认加密(KMS) ✅ 默认加密(KMS)
密钥轮换 手动更新 Secret 对象 ✅ 自动轮换 + 插件 ✅ 自动轮换 Lambda ✅ IAM 轮换
多集群 各自维护 ✅ Centralized Server ✅ IAM 跨账户 ✅ 项目级别共享
K8s 集成 原生 Vault Agent / ESO ESO ESO
学习曲线
成本 免费 自建 / Vault Cloud 按 API 调用计费 按版本数计费

五、配置注入完整决策树

根据实际需求,选择正确的配置注入方案:








需求场景
配置是否包含

敏感信息?
是否在多个

集群间共享?
配置是否需要

热更新?
External Secrets Operator

  • 外部 Secret Manager

(AWS / Vault / GCP)
RBAC 最小权限

  • etcd 加密
    Volume 挂载

(非 subPath)
env / envFrom 注入

  • Deployment 滚动更新
    是否需要跨

集群共享?
原生 ConfigMap

Volume 挂载
Secret 最小权限方案


六、验证命令清单

bash 复制代码
# 1. 检查 Secret 是否被 etcd 加密(etcd 数据目录为加密状态则无法直接 cat)
kubectl get secret -A -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'

# 2. 查看 Pod 是否挂载了默认 ServiceAccount Token
kubectl get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}'
kubectl get pod <pod-name> -o jsonpath='{.spec.automountServiceAccountToken}'

# 3. 检查 ServiceAccount 的 RBAC 权限
kubectl auth can-i get secret --as=system:serviceaccount:production:payment-sa -n production

# 4. 查看 ConfigMap 的同步状态(热更新生效时间)
kubectl get configmap app-config -o jsonpath='{.metadata.resourceVersion}'
# 修改后等待约 60 秒,再次查看 resourceVersion 变化

# 5. 验证 Volume 挂载(非 subPath)生效
kubectl exec <pod-name> -- cat /etc/config/LOG_LEVEL

# 6. 审计 Secret 访问(需要开启 audit policy)
kubectl get events -n production --field-selector involvedObject.kind=Secret

总结

ConfigMap 与 Secret 是 Kubernetes 中最容易被"用错"的基础资源:

  • ConfigMap 不是配置加密方案,它是配置管理方案。热更新≠加密存储,两者解决的是完全不同的问题
  • Secret 不是加密的,Base64 编码只是编码,不是加密。生产环境必须配置 etcd 静态加密或使用云厂商托管加密
  • RBAC 是 Secret 安全的核心,禁用不必要的 Token 自动挂载、为每个应用分配最小权限 ServiceAccount,比加密更重要
  • subPath 挂载会破坏热更新,这是 ConfigMap 使用中最隐蔽的陷阱

实际生产中,推荐以 ConfigMap + Volume 非 subPath 挂载 作为配置管理标准,以 RBAC + etcd 加密 作为安全基线,以 External Secrets Operator + 外部 Secret Manager 作为大规模多集群密钥管理的终态方案。三者配合,才能真正实现配置的安全存储与高效分发。


如果这篇文章对你有帮助,欢迎点赞、关注!

日常 Kubernetes 运维中你还遇到过哪些配置管理或 Secret 安全的坑?欢迎在评论区交流。

相关推荐
飘忽不定的bug2 小时前
记录:RK3576 适配开源GPU驱动(panfrost)
linux·gpu·rk3576·panfrost
Lentou2 小时前
部署项目之systemd部署
linux·运维·服务器
郭庆汝2 小时前
华为昇腾服务器安装docker
运维·服务器·docker
zz0723202 小时前
Seata ——微服务分布式事务
分布式·微服务·架构·seata
o丁二黄o2 小时前
若依部署Nginx和Tomcat
运维·nginx·tomcat
鄃鳕2 小时前
ubuntu下将DHCP动态分配改成静态ip
linux·tcp/ip·ubuntu
凭X而动2 小时前
CentOS7搭建SFTP
linux·运维·服务器
wanhengidc2 小时前
服务器能干什么?
运维·服务器·网络·安全·web安全
Watink Cpper2 小时前
Ubuntu24.04网络图标消失导致无法上网--排查得到原因:内核和驱动版本不匹配
运维·网络·linux内核·运维开发·debug·ubuntu24.04