5月27日 18:30

Kubernetes ConfigMap 和 Secret 有什么区别?

Kubernetes 中 ConfigMap 和 Secret 都用于将配置与容器镜像解耦,但它们在数据性质、存储方式和安全机制上有本质区别。理解两者的差异并正确使用,是管理 K8s 应用配置的基本功,也是面试高频考点。

ConfigMap 和 Secret 的核心区别是什么

ConfigMap 存储非敏感的配置数据,比如应用端口号、日志级别、功能开关;Secret 专门存储密码、证书、Token 等敏感信息。这是最根本的划分原则——如果你犹豫某个值该放哪里,问自己一个问题:泄露后会不会出安全事故?会就放 Secret。

两者在 API 层面的差异:

  • 编码方式:ConfigMap 的 data 字段存明文,Secret 的 data 字段存 Base64 编码。注意 Base64 只是编码不是加密,etcd 中 Secret 默认仍是明文存储,必须启用 EncryptionConfiguration 才能真正加密。
  • 访问控制:Secret 有更严格的 RBAC 建议策略,K8s 审计日志可以单独追踪 Secret 的访问记录。
  • etcd 存储:开启加密后,Secret 在 etcd 中以密文保存;ConfigMap 始终明文。
  • 大小限制:两者单条都限制 1 MiB,超限需要拆分或引入外部配置中心。

一个容易忽略的点:Secret 挂载到 Pod 后,kubelet 会将其写入 tmpfs(内存文件系统),Pod 删除后数据随之消失;而 ConfigMap 挂载的文件默认写入磁盘。

怎样创建 ConfigMap

实际项目中很少用命令行一条条创建,更多是通过 YAML 声明式管理,配合 GitOps 流程。以下覆盖常见的创建方式。

从字面值创建,适合少量键值对:

bash
kubectl create configmap app-config \ --from-literal=LOG_LEVEL=info \ --from-literal=MAX_RETRIES=3

从文件创建,适合将整个配置文件注入:

bash
kubectl create configmap nginx-config \ --from-file=nginx.conf=./nginx.conf

从 YAML 声明创建,这是推荐的做法,便于版本管理和审计:

yaml
apiVersion: v1 kind: ConfigMap metadata: name: app-config data: LOG_LEVEL: "info" MAX_RETRIES: "3" application.yml: | server: port: 8080 spring: datasource: url: jdbc:mysql://mysql-svc:3306/mydb

注意 ConfigMap 的 data 下所有值都是字符串类型。如果你写了 PORT: 8080,实际存储的是 "8080",这在某些语言框架中可能引发类型解析问题。

怎样在 Pod 中使用 ConfigMap

ConfigMap 有三种使用方式,选择哪种取决于配置的变更频率和消费方式。

作为环境变量——适合少量、启动时确定的配置:

yaml
spec: containers: - name: app image: my-app:latest env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL

也可以一次性注入所有键值对:

yaml
envFrom: - configMapRef: name: app-config

注意 envFrom 会把 ConfigMap 的所有键注入环境变量,如果键名冲突会被后面的覆盖,要确认命名规范一致。

挂载为卷——适合配置文件场景,如 Nginx 配置、Spring 的 application.yml:

yaml
spec: containers: - name: app volumeMounts: - name: config-volume mountPath: /etc/app/config readOnly: true volumes: - name: config-volume configMap: name: app-config

挂载为卷有一个重要特性:ConfigMap 更新后,挂载的文件会在几分钟内自动刷新(kubelet 的同步周期默认 60 秒 + 随机延迟)。但应用本身需要有能力感知文件变化并热加载,否则还是要滚动重启 Pod。

作为命令行参数——在容器启动命令中引用环境变量:

yaml
spec: containers: - name: app image: my-app:latest command: ["./app"] args: ["--log-level=$(LOG_LEVEL)"] env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL

这种方式本质上还是环境变量,只是被 command/args 引用了。

怎样创建 Secret

Secret 的创建方式与 ConfigMap 类似,但有一个关键区别:data 字段的值必须 Base64 编码。

yaml
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque data: username: YWRtaW4= # echo -n 'admin' | base64 password: c2VjcmV0MTIz # echo -n 'secret123' | base64

如果你不想手动编码,可以用 stringData 字段,K8s 会自动帮你转 Base64:

yaml
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque stringData: username: admin password: secret123

stringData 在写入 etcd 后会被转为 data 字段的 Base64 编码形式,所以通过 kubectl get -o yaml 看到的仍然是编码后的值。

创建 TLS 类型的 Secret,用于 Ingress 或 Pod 的 HTTPS 配置:

bash
kubectl create secret tls tls-cert \ --cert=./tls.crt \ --key=./tls.key

创建镜像拉取凭据:

bash
kubectl create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=user \ --docker-password=pass

怎样在 Pod 中使用 Secret

作为环境变量——最简单但要注意安全隐患:

yaml
spec: containers: - name: app env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-credentials key: username

环境变量方式的缺点:应用日志或调试输出可能意外打印敏感值;子进程会继承所有环境变量。如果对安全性要求高,优先用卷挂载。

挂载为卷——推荐方式,Secret 以文件形式存在 tmpfs 中:

yaml
spec: containers: - name: app volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true volumes: - name: secret-volume secret: secretName: db-credentials

挂载方式的好处是应用可以按需读取,不会意外泄露到环境变量或日志中。

imagePullSecrets——用于拉取私有镜像仓库:

yaml
spec: imagePullSecrets: - name: regcred containers: - name: app image: registry.example.com/my-app:latest

ConfigMap 和 Secret 的更新机制有什么坑

这是一个面试常考、实战也常踩的要点。

ConfigMap/Secret 更新后,Pod 不会自动重启。对于 Deployment 管理的 Pod,你需要在模板中引用 ConfigMap/Secret 的某个标注(比如通过 subPath 或 env 注入资源版本号),触发滚动更新。否则新配置只会通过卷挂载的方式在 Pod 内部刷新文件内容。

环境变量方式更加局限——容器的环境变量在启动时就固定了,ConfigMap 更新后,已经运行的 Pod 的环境变量不会变,必须重建 Pod 才能生效。

还有一个容易忽略的坑:如果你用了 subPath 挂载 ConfigMap 或 Secret 的某个键,该文件不会随着 ConfigMap/Secret 更新而自动刷新,因为它脱离了符号链接的更新机制。

immutable 不可变配置有什么用

Kubernetes 1.21 起,ConfigMap 和 Secret 支持 immutable: true 字段:

yaml
apiVersion: v1 kind: ConfigMap metadata: name: app-config immutable: true data: LOG_LEVEL: "info"

标记为不可变后,无法修改 data 字段,只能删除重建。好处有两点:

  • 性能提升:kubelet 不需要 watch 不可变 ConfigMap/Secret,减轻了 API Server 和 kubelet 的负担。集群中 ConfigMap/Secret 数量多时效果明显。
  • 安全性:防止配置被意外或恶意篡改。

生产环境中,如果配置在发布后确实不会变更,建议加上 immutable: true

Secret 的安全加固方案有哪些

Base64 编码不等于加密,这是一个必须强调的事实。以下是生产环境中需要落实的安全措施。

启用 etcd 加密——配置 EncryptionConfiguration,让 Secret 在 etcd 中以密文存储:

yaml
apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: <base64-encoded-32-byte-key> - identity: {}

配置 RBAC 最小权限——只授权必要的 Secret 访问:

yaml
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader rules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["db-credentials"] verbs: ["get"]

使用外部密钥管理系统——对于安全等级高的场景,引入 HashiCorp Vault、AWS Secrets Manager 或 Sealed Secrets 等:

  • Vault Agent Injector:通过 Mutating Admission Webhook 自动将 Vault 中的密钥注入 Pod
  • External Secrets Operator:将外部密钥同步为 K8s Secret
  • Sealed Secrets:加密后可以安全地存储在 Git 中

启用审计日志——追踪谁在什么时候访问了哪些 Secret:

yaml
apiVersion: audit.k8s.io/v1 kind: Policy rules: - level: RequestResponse resources: - group: "" resources: ["secrets"]

定期轮换密钥——建立轮换机制,避免长期使用同一密钥。可以结合 CI/CD 流程在部署时自动更新 Secret。

面试中如何回答 ConfigMap 和 Secret 的相关问题

面试官问这个问题,通常不只是要你背概念,而是在考察你对 K8s 配置管理的整体理解。一个合格的回答应该涵盖以下层次:

  1. 先说清楚本质区别:非敏感 vs 敏感,明文 vs Base64 编码,不同的安全机制
  2. 再说实际使用:创建方式、三种消费方式(环境变量/卷挂载/命令行参数)及各自的适用场景
  3. 然后说踩坑经验:更新机制的限制、环境变量不热更新、subPath 不自动刷新
  4. 最后说安全加固:etcd 加密、RBAC 最小权限、外部密钥管理、审计日志

如果你只答了第一层,面试官会认为你对 K8s 的理解停留在表面。能把第四层讲清楚,才能体现出生产环境的实战经验。

标签:Kubernetes