Kubernetes ConfigMap 介绍与使用 – wiki基地


Kubernetes ConfigMap 介绍与使用:解耦应用配置的最佳实践

在现代分布式应用开发中,配置管理是一个核心挑战。应用程序需要读取数据库连接字符串、第三方服务地址、日志级别、功能开关等各种配置信息。传统的做法往往是将这些配置硬编码在代码中,或者打包到应用程序镜像中。然而,随着应用规模的扩大和部署环境的多样化(开发、测试、生产),这种方式带来了诸多问题:

  1. ** inflexibility:** 修改配置需要重新构建镜像或修改代码,流程繁琐且耗时。
  2. 可移植性差: 相同的应用镜像无法直接部署到不同配置的环境中。
  3. 安全性隐患: 敏感信息(尽管 ConfigMap 不是用于敏感信息)或环境特有的配置暴露在代码或镜像中。
  4. 管理复杂: 难以统一管理和审计不同环境的配置差异。
  5. 运维效率低: 配置变更需要停机或复杂的滚动更新流程来更新镜像。

Kubernetes 作为一个强大的容器编排平台,为解决这些问题提供了标准化的机制。其中,ConfigMap 就是专门用于存储非敏感配置数据,并将其注入到 Pod 中的 API 对象。它完美地体现了解耦的思想:将应用程序的配置与应用程序镜像本身分离,使得配置的修改、管理和分发变得更加灵活和高效。

本文将深入探讨 Kubernetes ConfigMap 的概念、工作原理、创建方法、在 Pod 中的使用方式,以及相关的最佳实践和注意事项。

1. 什么是 Kubernetes ConfigMap?

ConfigMap 是 Kubernetes 中的一种 API 对象,它主要用来存储非敏感的键值对配置数据。这些数据可以在 Pod 启动时以环境变量、命令行参数或者卷中文件的方式注入到容器中。

简单来说,ConfigMap 就是一个存放配置信息的“篮子”。这个篮子与运行应用程序的 Pod 是分开管理的。当 Pod 需要这些配置时,Kubernetes 会负责将篮子里的东西“递送”给 Pod 里面的容器。

核心特点:

  • 键值对存储: ConfigMap 内部以键值对的形式存储配置数据。键是字符串,值可以是短字符串,也可以是多行文本甚至整个配置文件内容。
  • 非敏感数据: ConfigMap 不应存储密码、密钥等敏感信息。对于敏感数据,Kubernetes 提供了另一个专门的 API 对象 Secret
  • 与 Pod 解耦: ConfigMap 是独立于 Pod 创建和管理的。一个 ConfigMap 可以被多个 Pod 引用。
  • 多种注入方式: ConfigMap 的数据可以通过环境变量、文件卷或者命令行参数等方式注入到 Pod 的容器中。
  • Namespace 作用域: ConfigMap 是 Namespace 级别的资源,通常只能被同一 Namespace 下的 Pod 引用。

2. ConfigMap 的工作原理

ConfigMap 本质上是 Kubernetes API Server 存储的一种资源对象,其数据保存在 Kubernetes 的 etcd 存储中。

当一个 Pod 需要使用 ConfigMap 中的数据时,Pod 的定义(YAML 文件)中会引用特定的 ConfigMap。Kubernetes 的各个组件协同工作来完成配置的注入:

  1. API Server: 负责接收和存储 ConfigMap 对象以及 Pod 定义。
  2. Scheduler: 将 Pod 调度到合适的 Node 上。
  3. Kubelet: 运行在每个 Node 上,负责管理该 Node 上的 Pod。当 Kubelet 启动一个引用了 ConfigMap 的 Pod 时,它会根据 Pod 定义中的要求,从 API Server 获取 ConfigMap 的数据,并按照指定的方式(环境变量或文件)将其提供给 Pod 中的容器。

  4. 环境变量方式: Kubelet 直接将 ConfigMap 的键值对设置为容器的环境变量。

  5. 文件卷方式: Kubelet 在 Node 上为 Pod 创建一个特殊的卷(通常是一个内存文件系统或 HostPath),并将 ConfigMap 的每个键值对作为文件写入到该卷中。然后将该卷挂载到容器的指定路径下。

这种解耦和动态注入机制是 ConfigMap 强大之处,它使得配置的变更可以在不修改或重建镜像的情况下实现。

3. 如何创建 ConfigMap

创建 ConfigMap 有多种方式,最常见的是使用 kubectl 命令行工具或编写 YAML manifest 文件。

3.1 使用 kubectl 命令行创建 ConfigMap

kubectl create configmap 命令提供了方便的方式从字面值、文件或目录创建 ConfigMap。

3.1.1 从字面值创建 (--from-literal)

适用于存储简单的键值对。

bash
kubectl create configmap my-config --from-literal=app.name=my-app --from-literal=log.level=INFO

这会创建一个名为 my-config 的 ConfigMap,包含两个键值对:app.name: my-applog.level: INFO

3.1.2 从文件创建 (--from-file)

适用于将单个文件内容作为 ConfigMap 的一个值。键默认是文件名。

假设你有一个名为 config.properties 的文件,内容如下:

properties
database.url=jdbc:mysql://mysql:3306/mydb
database.username=user
database.password=secret # 注意:实际不应存密码

可以使用以下命令创建 ConfigMap:

bash
kubectl create configmap app-config --from-file=config.properties

这会创建一个名为 app-config 的 ConfigMap,其中包含一个键 config.properties,其值是 config.properties 文件的全部内容。

你也可以指定不同的键名:

bash
kubectl create configmap app-config --from-file=my-config.properties=config.properties

这将创建 ConfigMap,键为 my-config.properties,值为文件内容。

3.1.3 从目录创建 (--from-file)

适用于将整个目录下的所有文件都作为 ConfigMap 的键值对,文件名作为键。

假设你有一个 config/ 目录,包含 config.propertiessettings.yaml 两个文件。

bash
kubectl create configmap app-configs-from-dir --from-file=config/

这会创建一个名为 app-configs-from-dir 的 ConfigMap,包含两个键值对:
* config.properties: 值是 config/config.properties 的内容。
* settings.yaml: 值是 config/settings.yaml 的内容。

3.1.4 从环境变量文件创建 (--from-env-file)

适用于从.env 格式的文件创建 ConfigMap。文件格式为 KEY=VALUE,每行一个变量。

假设你有一个 .env 文件,内容如下:

env
API_KEY=abcdef12345
FEATURE_FLAG=enabled

bash
kubectl create configmap env-vars-config --from-env-file=.env

这会创建一个名为 env-vars-config 的 ConfigMap,包含两个键值对:API_KEY: abcdef12345FEATURE_FLAG: enabled

3.2 使用 YAML Manifest 文件创建 ConfigMap

这是在生产环境中更推荐的方式,因为它具有版本控制、易于自动化和集成到 CI/CD 流程的优点。

ConfigMap 的 YAML 结构如下:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config-yaml
namespace: default # 可选,默认为当前上下文的 namespace
data:
# 键值对形式,值必须是字符串
app.name: my-app-from-yaml
log.level: DEBUG
# 多行文本或整个文件内容可以作为字符串值
complex.config: |
key1: value1
key2:
subkey1: subvalue1
subkey2: subvalue2
binaryData: # 如果配置是二进制数据,可以使用此字段,但更推荐使用 Secret
# 例如存储一个图片,但非常少见
# my_image.png: ...base64encodedvalue...

  • apiVersion: v1
  • kind: ConfigMap
  • metadata: 包含 namenamespace 等元信息。
  • data: 这是存储非二进制配置数据的主要字段。每个键对应一个配置项,值必须是字符串。多行文本可以使用 YAML 的块展开风格(|>)。
  • binaryData: (较少使用)用于存储二进制数据,值需要是 base64 编码的字符串。对于非文本配置(如证书、二进制文件),通常更推荐使用 Secret 或将其打包到镜像中。

示例 YAML 文件 (configmap.yaml):

“`yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-yaml
data:
# 简单的键值对
application.environment: production
logging.level: INFO

# 模拟一个完整的配置文件内容
settings.properties: |
db.host=prod-db.example.com
db.port=5432
service.timeout=30s
feature.enabled=true

# 另一个配置文件内容
nginx.conf: |
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
“`

创建 ConfigMap 使用 kubectl apply -f configmap.yaml 命令。

查看 ConfigMap:

创建完成后,可以使用以下命令查看 ConfigMap 的内容:

“`bash
kubectl get configmap -o yaml

或者

kubectl describe configmap
“`

4. 在 Pod 中使用 ConfigMap

ConfigMap 创建后,就可以被同一个 Namespace 下的 Pod 引用了。主要有两种方式将 ConfigMap 中的数据注入到 Pod 的容器中:作为环境变量或作为文件卷。

4.1 作为环境变量注入

这是最常见的用法之一,特别是对于简单的键值对配置。

4.1.1 注入 ConfigMap 中的单个键值对

在 Pod 定义的容器 spec 中,使用 env 字段,并通过 valueFrom 引用 ConfigMap 中的特定键。

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-env
spec:
containers:
- name: my-app-container
image: my-app-image:latest
env:
# 注入 ConfigMap 'app-config-yaml' 中的 'application.environment' 键的值
- name: APP_ENV # 容器中的环境变量名
valueFrom:
configMapKeyRef:
name: app-config-yaml # 引用的 ConfigMap 名称
key: application.environment # ConfigMap 中的键名
# optional: true # 如果设置为 true,即使 ConfigMap 或键不存在也不会导致 Pod 启动失败
- name: LOG_LEVEL # 容器中的环境变量名
valueFrom:
configMapKeyRef:
name: app-config-yaml
key: logging.level

4.1.2 注入 ConfigMap 中的所有键值对

如果你想将 ConfigMap 中的所有键值对都作为环境变量注入到容器中,可以使用 envFrom 字段。ConfigMap 的键将直接成为环境变量名。

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-envfrom
spec:
containers:
- name: my-app-container
image: my-app-image:latest
envFrom:
# 注入 ConfigMap 'app-config-yaml' 中所有键值对作为环境变量
- configMapRef:
name: app-config-yaml # 引用的 ConfigMap 名称
# optional: true # 如果设置为 true,即使 ConfigMap 不存在也不会导致 Pod 启动失败

使用 envFrom 需要注意,ConfigMap 的键名必须是合法的环境变量名(字母、数字、下划线,不能以数字开头)。如果 ConfigMap 包含非法的键名,整个 envFrom 注入将会失败,Pod 将无法启动。此外,如果多个 envFromenv 条目定义了相同的环境变量名,后定义的会覆盖先定义的。

4.2 作为文件卷注入

这种方式适用于将 ConfigMap 中存储的整个配置文件或多行文本注入到容器的特定文件路径下。ConfigMap 的键会成为文件名。

在 Pod 定义中,需要在 spec.volumes 中定义一个类型为 configMap 的卷,并在容器的 spec.containers.volumeMounts 中挂载该卷。

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-volume
spec:
containers:
- name: my-app-container
image: my-app-image:latest
volumeMounts:
- name: config-volume # 容器内挂载卷的名称,与 spec.volumes 中的名称对应
mountPath: /etc/config # 容器内部配置文件的存放路径
readOnly: true # ConfigMap 卷默认是只读的,建议保持只读
volumes:
- name: config-volume # 卷的名称
configMap:
name: app-config-yaml # 引用的 ConfigMap 名称
# optional: true # 如果设置为 true,即使 ConfigMap 不存在也不会导致 Pod 启动失败
# defaultMode: 0644 # 可选,设置文件的权限,默认是 0644

使用上述配置,app-config-yaml ConfigMap 中的每个键都会在 /etc/config/ 目录下创建一个同名文件。例如,application.environment 键的内容会出现在 /etc/config/application.environment 文件中,settings.properties 的内容会出现在 /etc/config/settings.properties 文件中。

4.2.1 注入 ConfigMap 中的指定键作为文件 (items)

有时候你可能只想注入 ConfigMap 中的部分键,或者想给注入的文件指定不同的名字。可以使用 items 字段实现。

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-volume-items
spec:
containers:
- name: my-app-container
image: my-app-image:latest
volumeMounts:
- name: config-volume-items
mountPath: /app/settings # 将指定文件挂载到此目录
readOnly: true
volumes:
- name: config-volume-items
configMap:
name: app-config-yaml
items: # 只注入指定的键
- key: settings.properties # ConfigMap 中的键
path: config.props # 容器中对应的文件名 (位于 mountPath 下)
- key: nginx.conf
path: conf/nginx.conf # 可以在 mountPath 下创建子目录
# defaultMode: 0600 # 可以为这些文件设置不同的权限

使用此配置,app-config-yaml 中的 settings.properties 内容会出现在容器的 /app/settings/config.props 文件中,nginx.conf 的内容会出现在 /app/settings/conf/nginx.conf 文件中。其他键则不会被注入。

4.2.2 使用 subPath 将单个文件挂载到容器的特定路径

如果你只想将 ConfigMap 中的 一个 特定键的内容挂载到容器中的 一个 特定文件路径,并且不希望这个文件被放在一个中间目录(比如上面的 /etc/config/),可以使用 subPath

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-subpath
spec:
containers:
- name: my-app-container
image: my-app-image:latest
volumeMounts:
- name: config-volume # 卷的名称
mountPath: /app/config.properties # 直接挂载到容器内的文件路径
subPath: settings.properties # 对应 ConfigMap 中的键
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config-yaml
# optional: true

使用此配置,app-config-yaml 中的 settings.properties 内容会直接出现在容器的 /app/config.properties 文件中。

重要注意事项:

  • 当使用 subPath 挂载单个文件时,ConfigMap 的更新不会自动反映到 Pod 中。要获取更新,需要重新创建 Pod。这与将整个 ConfigMap 作为卷挂载时的自动更新行为不同。
  • 使用 subPath 挂载的文件是单个文件,无法通过卷的 defaultMode 设置权限,需要确保 ConfigMap 文件本身的权限设置合理。

4.3 作为命令行参数注入

ConfigMap 的数据也可以用来构建容器的启动命令参数。

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod-args
spec:
containers:
- name: my-app-container
image: my-app-image:latest
args: ["--log-level=$(LOG_LEVEL)", "--env=$(APP_ENV)"] # 使用环境变量
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config-yaml
key: application.environment
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config-yaml
key: logging.level

这里通过先将 ConfigMap 的值注入为环境变量,然后在 argscommand 中引用这些环境变量来实现。

5. ConfigMap 的更新与 Pod 的反应

ConfigMap 的一个强大之处在于,当 ConfigMap 对象本身被更新时,引用它的 Pod 在一定条件下可以动态地获取最新的配置。然而,不同的注入方式对更新的反应是不同的。

5.1 作为环境变量注入时的更新

如果 ConfigMap 作为环境变量注入到 Pod 中,更新 ConfigMap 对象本身 不会 自动更新容器中的环境变量。

要让 Pod 使用更新后的环境变量,必须通过删除并重新创建 Pod (例如使用 Deployment 的滚动更新) 来实现。

这是因为容器的环境变量是在容器启动时由 Kubelet 一次性设置的,容器内部通常没有机制去监听外部环境变量的变化。

5.2 作为文件卷注入时的更新

如果 ConfigMap 作为卷挂载到 Pod 中,更新 ConfigMap 对象时,Kubelet 会定期检查引用的 ConfigMap 是否有变化,并在检测到变化时自动更新 Pod 中的文件。

  • 更新延迟: 这个更新不是即时的。Kubelet 会每隔一定时间(默认为 1 分钟,可以通过 Kubelet 配置 configMapAndSecretChangeDetectionStrategysyncFrequency 进行调整,但通常不建议修改默认值)轮询 API Server 检查 ConfigMap 是否有更新。
  • 文件更新方式: Kubelet 通常通过软链接(symbolic link)的方式来实现文件更新。当 ConfigMap 更新时,Kubelet 会在一个新的目录下写入新的文件内容,然后原子性地更新 Pod 卷挂载路径下的软链接,使其指向新的目录。大多数应用程序在读取文件时会跟随软链接,因此可以读取到最新的配置。
  • 应用程序感知: 应用程序需要能够感知到文件的变化。有些应用会监听配置文件的变更并热加载,而有些则只在启动时读取配置。如果你的应用不能热加载配置,那么即使文件更新了,应用也可能不会使用新配置,直到应用重启。
  • subPath 挂载: 只有将整个 ConfigMap 或使用 items 将多个键挂载到目录时,这种自动更新机制才有效。
  • subPath 挂载的特殊性: 如前所述,使用 subPath 将 ConfigMap 中的单个键挂载为容器中的单个文件时,该文件实际上是直接绑定到 Pod 卷的子路径,而不是通过软链接。因此,ConfigMap 的更新不会通过这种方式传播到 Pod 文件中。

如何处理配置更新?

鉴于环境变量更新不自动,文件卷更新有延迟且依赖应用行为,最可靠且通用的方式是:

  1. 更新 ConfigMap 对象。
  2. 触发引用该 ConfigMap 的 Deployment (或 StatefulSet) 进行滚动更新。

Deployment 的滚动更新策略会创建新的 Pod,这些新的 Pod 在启动时会加载最新的 ConfigMap 数据(无论是作为环境变量还是文件),然后逐步替换旧的 Pod。这是确保所有 Pod 都使用最新配置的标准做法。

许多 Helm Chart 和其他部署工具在 ConfigMap 更新时会自动执行滚动更新,通常通过在 Pod 模板中包含 ConfigMap 的哈希值作为注解来实现。当 ConfigMap 内容变化时,哈希值变化,从而触发 Pod 模板变化,进而触发 Deployment 的滚动更新。

6. ConfigMap 的最佳实践和注意事项

  • ConfigMap vs Secret: ConfigMap 用于存储非敏感配置(如日志级别、应用地址、配置文件的文本内容)。Secret 用于存储敏感信息(如密码、API 密钥、TLS 证书)。不要将敏感信息放入 ConfigMap。
  • 命名规范: 给 ConfigMap 起一个清晰、有意义的名字,最好能反映其用途或关联的应用。
  • 版本控制: 使用 YAML 文件创建 ConfigMap,并将这些文件纳入版本控制系统(如 Git),这样可以方便地追踪配置变更。
  • 粒度: ConfigMap 的粒度可以根据需要调整。可以将所有配置放在一个大的 ConfigMap 中,也可以将相关配置分组到多个 ConfigMap 中。小的 ConfigMap 更易于管理和复用,但也可能导致 Pod 引用多个 ConfigMap。没有绝对的最佳答案,根据实际情况选择。
  • Immutability (不可变性): Kubernetes 1.18 版本引入了 ConfigMap 和 Secret 的不可变性特性。通过在 ConfigMap 的 metadata 中设置 immutable: true,可以防止对其进行修改。这提高了安全性(防止意外修改),并可能提高性能(API Server 不需要 watch 这些不可变对象的变化)。一旦设置为不可变,就不能再修改,只能删除重建。适用于配置稳定、不常变更的场景。

yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-immutable-config
immutable: true # 设置为不可变
data:
key1: value1
key2: value2

  • Limit ConfigMap Size: ConfigMap 的大小是有限制的(默认为 1MB)。不要将非常大的文件或大量配置项放入 ConfigMap。如果配置过大,考虑其他方式,如打包到镜像中、使用外部配置服务或使用 Downward API 注入 Pod 自身信息。
  • 应用程序感知文件更新: 如果通过文件卷注入 ConfigMap 并希望应用能够自动获取更新,需要确保应用程序具备监听文件变化并热加载配置的能力。否则,仍然需要滚动更新 Pod 来获取最新配置。
  • 错误处理: 在 Pod 定义中使用 optional: true 可以让 Pod 在 ConfigMap 或其引用的键不存在时也能启动,这在某些场景下(如开发环境)可能有用,但生产环境通常希望配置是齐全的,缺少配置应视为错误,此时不设置 optional: true 是更好的选择。
  • 与其他工具集成: ConfigMap 经常与 Helm、Kustomize 等配置管理工具结合使用,以便更方便地管理不同环境或不同版本应用的配置。

7. 示例:使用 ConfigMap 配置一个简单的 Nginx Pod

我们来创建一个 ConfigMap,包含一个自定义的 Nginx 配置文件,并将其注入到一个 Nginx Pod 中。

7.1 创建 ConfigMap YAML 文件 (nginx-configmap.yaml):

“`yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-custom-config
data:
nginx.conf: | # 键名为 nginx.conf,值为多行字符串
worker_processes 1;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;

    location / {
      root /usr/share/nginx/html; # Nginx 默认静态文件目录
      index index.html index.htm;
    }

    location /health { # 添加一个健康检查路径
      return 200 'OK';
      add_header Content-Type text/plain;
    }
  }
}

“`

7.2 创建 Pod YAML 文件 (nginx-pod.yaml):

yaml
apiVersion: v1
kind: Pod
metadata:
name: custom-nginx-pod
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config-volume # 挂载卷的名称
mountPath: /etc/nginx/nginx.conf # Nginx 配置文件的标准路径
subPath: nginx.conf # 引用 ConfigMap 中的 nginx.conf 键
readOnly: true # 建议只读
volumes:
- name: nginx-config-volume # 卷的名称
configMap:
name: nginx-custom-config # 引用的 ConfigMap 名称

7.3 部署和验证:

  1. 创建 ConfigMap:
    bash
    kubectl apply -f nginx-configmap.yaml
  2. 创建 Pod:
    bash
    kubectl apply -f nginx-pod.yaml
  3. 等待 Pod 启动并运行 (kubectl get pods custom-nginx-pod)。
  4. 进入 Pod 内部检查文件内容(或者通过 Service 暴露 Pod 访问):
    bash
    kubectl exec -it custom-nginx-pod -- cat /etc/nginx/nginx.conf

    你应该能看到你在 nginx-configmap.yaml 中定义的自定义 Nginx 配置。
  5. 如果暴露了 Pod 的端口,可以尝试访问 /health 路径,应该返回 ‘OK’,这证明自定义配置已生效。

这个例子展示了如何将自定义配置文件通过 ConfigMap 注入到容器的特定路径,覆盖默认配置。

8. 总结

Kubernetes ConfigMap 是一个强大且基础的 API 对象,它通过将应用程序的配置与镜像和 Pod 定义分离,极大地提高了应用的灵活性、可管理性和可移植性。

通过将非敏感配置存储在 ConfigMap 中,我们可以:

  • 简化应用镜像: 镜像中不再需要包含环境相关的配置。
  • 加速部署和配置变更: 修改配置只需更新 ConfigMap,无需重建镜像。
  • 提高可移植性: 同一个应用镜像可以部署到不同的环境,只需关联不同的 ConfigMap。
  • 集中管理配置: Kubernetes 提供了统一的配置管理入口。

理解 ConfigMap 的创建方式、不同的注入机制(环境变量 vs 文件卷)、以及更新行为对于有效地在 Kubernetes 上管理应用程序配置至关重要。结合 Secret 处理敏感数据,以及采用滚动更新策略来应用配置变更,是构建健壮、可维护的云原生应用的基石。

通过熟练掌握 ConfigMap 的使用,你可以更好地实践配置与代码分离的原则,提升应用的部署和运维效率。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部