跳到主要内容
版本:5.0.1

使用 ConfigMap PropertySource

DeepSeek V3 中英对照 Using a ConfigMap PropertySource Using a ConfigMap PropertySource

Kubernetes 提供了一种名为 ConfigMap 的资源,用于将传递给应用程序的参数外部化,形式可以是键值对或嵌入的 application.properties 或 application.yaml 文件。Spring Cloud Kubernetes Config 项目使得 Kubernetes ConfigMap 实例在应用程序启动时可用,并在检测到被观察的 ConfigMap 实例发生更改时,触发 bean 或 Spring 上下文的热重载。

以下内容主要参考使用 ConfigMaps 的示例进行解释,但同样适用于 Secrets,即:所有功能两者均支持。

默认行为是基于一个 Kubernetes ConfigMap 创建 Fabric8ConfigMapPropertySource(或 KubernetesClientConfigMapPropertySource),该 ConfigMap 的 metadata.name 为以下两者之一:

  • spring.cloud.kubernetes.config.name 的值

  • 你的 Spring 应用程序的值(由 spring.application.name 属性定义)

  • 字符串字面量 "application"

然而,更高级的配置是可能的,您可以使用多个 ConfigMap 实例。spring.cloud.kubernetes.config.sources 列表使这成为可能。例如,您可以定义以下 ConfigMap 实例:

spring:
application:
name: cloud-k8s-app
cloud:
kubernetes:
config:
name: default-name
namespace: default-namespace
sources:
# Spring Cloud Kubernetes looks up a ConfigMap named c1 in namespace default-namespace
- name: c1
# Spring Cloud Kubernetes looks up a ConfigMap named default-name in whatever namespace n2
- namespace: n2
# Spring Cloud Kubernetes looks up a ConfigMap named c3 in namespace n3
- namespace: n3
name: c3

在前面的示例中,如果未设置 spring.cloud.kubernetes.config.namespace,则会在应用程序运行的命名空间中查找名为 c1 的 ConfigMap。请参阅命名空间解析以更好地理解应用程序的命名空间是如何解析的。

任何匹配到的 ConfigMap 都将按以下方式处理:

  • 应用单独的配置属性。

  • 将任何以 spring.application.name 值命名的属性内容(如果不存在,则使用 application.yaml/properties)作为 yaml(或 properties)应用。

  • 将上述名称加上每个活动配置文件的属性文件内容作为属性文件应用。

举个例子应该会更清楚。假设 spring.application.name=my-app,并且我们有一个名为 k8s 的活动配置文件。对于如下配置:

kind: ConfigMap
apiVersion: v1
metadata:
name: my-app
data:
my-app.yaml: |-
...
my-app-k8s.yaml: |-
..
my-app-dev.yaml: |-
..
not-my-app.yaml: |-
..
someProp: someValue

这就是我们最终要加载的内容:

  • my-app.yaml 被视为文件

  • my-app-k8s.yaml 被视为文件

  • my-app-dev.yaml 被忽略,因为 dev 不是活动配置文件

  • not-my-app.yaml 被忽略,因为它与 spring.application.name 不匹配

  • someProp: someValue 普通属性

加载属性的顺序如下:

  • 首先从 my-app.yaml 加载所有属性

  • 然后从基于配置文件的来源加载所有属性:my-app-k8s.yaml

  • 最后加载所有纯属性 someProp: someValue

这意味着基于配置文件的源优先于非基于配置文件的源(就像在原生Spring应用中一样);而普通属性则优先于基于配置文件和非基于配置文件的源。以下是一个示例:

kind: ConfigMap
apiVersion: v1
metadata:
name: my-app
data:
my-app-k8s.yaml: |-
key1=valueA
key2=valueB
my-app.yaml: |-
key1=valueC
key2=valueA
key1: valueD

处理完这样的 ConfigMap 后,您将在属性中得到:key1=valueD、key2=valueB。

上述流程的唯一例外情况是,当 ConfigMap 包含一个单一键,且该键表明文件是 YAML 或 properties 文件时。在这种情况下,键的名称不必是 application.yaml 或 application.properties(可以是任何名称),并且属性的值会被正确处理。这一特性便于处理通过以下类似方式创建的 ConfigMap 的使用场景:

kubectl create configmap game-config --from-file=/path/to/app-config.yaml

假设我们有一个名为 demo 的 Spring Boot 应用程序,它使用以下属性来读取其线程池配置。

  • pool.size.core

  • pool.size.maximum

这可以外部化为 yaml 格式的配置映射,如下所示:

kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
pool.size.core: 1
pool.size.max: 16

在大多数情况下,单个属性配置已经足够。然而,有时嵌入 yaml 格式会更加方便。在这种情况下,我们使用一个名为 application.yaml 的属性来嵌入我们的 yaml 配置,如下所示:

kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
application.yaml: |-
pool:
size:
core: 1
max:16

以下示例同样适用:

kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
custom-name.yaml: |-
pool:
size:
core: 1
max:16

你也可以定义基于标签进行搜索,例如:

spring:
application:
name: labeled-configmap-with-prefix
cloud:
kubernetes:
config:
enabled: true
useNameAsPrefix: true
namespace: spring-k8s
sources:
- labels:
letter: a

这将搜索命名空间 spring-k8s 中所有具有标签 {letter : a} 的 ConfigMap。这里需要注意的重要一点是,与按名称读取 ConfigMap 不同,这可能导致读取多个 ConfigMap。通常,对于 Secret 也支持相同的功能。

您还可以根据读取 ConfigMap 时合并的活动配置文件,以不同方式配置 Spring Boot 应用程序。您可以通过使用 application.properties 或 application.yaml 属性,为不同的配置文件提供不同的属性值,每个配置文件特定的值都在其自己的文档中(由 --- 序列表示),如下所示:

kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
application.yml: |-
greeting:
message: Say Hello to the World
farewell:
message: Say Goodbye
---
spring:
profiles: development
greeting:
message: Say Hello to the Developers
farewell:
message: Say Goodbye to the Developers
---
spring:
profiles: production
greeting:
message: Say Hello to the Ops

在前述案例中,加载到你的 Spring 应用程序中并启用 development 配置文件的配置如下:

greeting:
message: Say Hello to the Developers
farewell:
message: Say Goodbye to the Developers

然而,如果 production 配置文件处于激活状态,配置将变为:

greeting:
message: Say Hello to the Ops
farewell:
message: Say Goodbye

如果两个配置文件都处于激活状态,ConfigMap 中最后出现的属性将覆盖之前的所有值。

另一种选择是为每个配置文件创建不同的配置映射,Spring Boot 将根据活动配置文件自动获取它

kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
application.yml: |-
greeting:
message: Say Hello to the World
farewell:
message: Say Goodbye
kind: ConfigMap
apiVersion: v1
metadata:
name: demo-development
data:
application.yml: |-
spring:
profiles: development
greeting:
message: Say Hello to the Developers
farewell:
message: Say Goodbye to the Developers
kind: ConfigMap
apiVersion: v1
metadata:
name: demo-production
data:
application.yml: |-
spring:
profiles: production
greeting:
message: Say Hello to the Ops
farewell:
message: Say Goodbye

要告诉 Spring Boot 应该启用哪个 profile,请参阅 Spring Boot 文档。在部署到 Kubernetes 时激活特定 profile 的一种方法是在启动 Spring Boot 应用程序时使用一个环境变量,你可以在容器规范的 PodSpec 中定义该变量。Deployment 资源文件如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-name
labels:
app: deployment-name
spec:
replicas: 1
selector:
matchLabels:
app: deployment-name
template:
metadata:
labels:
app: deployment-name
spec:
containers:
- name: container-name
image: your-image
env:
- name: SPRING_PROFILES_ACTIVE
value: "development"

你可能会遇到多个配置映射具有相同属性名的情况。例如:

kind: ConfigMap
apiVersion: v1
metadata:
name: config-map-one
data:
application.yml: |-
greeting:
message: Say Hello from one

和

kind: ConfigMap
apiVersion: v1
metadata:
name: config-map-two
data:
application.yml: |-
greeting:
message: Say Hello from two

根据你在 bootstrap.yaml|properties 中放置这些配置的顺序,可能会产生意想不到的结果(最后一个配置映射会生效)。例如:

spring:
application:
name: cloud-k8s-app
cloud:
kubernetes:
config:
namespace: default-namespace
sources:
- name: config-map-two
- name: config-map-one

将导致属性 greetings.message 变为 Say Hello from one。

可以通过指定 useNameAsPrefix 来更改此默认配置。例如:

spring:
application:
name: with-prefix
cloud:
kubernetes:
config:
useNameAsPrefix: true
namespace: default-namespace
sources:
- name: config-map-one
useNameAsPrefix: false
- name: config-map-two

这样的配置将生成两个属性:

  • greetings.message 等于 Say Hello from one。

  • config-map-two.greetings.message 等于 Say Hello from two

请注意,spring.cloud.kubernetes.config.useNameAsPrefix 的优先级低于 spring.cloud.kubernetes.config.sources.useNameAsPrefix。这允许您为所有配置源设置一个“默认”策略,同时仅覆盖其中少数几个。

如果无法使用配置映射名称,你可以指定另一种策略,称为:explicitPrefix。由于这是你选择的显式前缀,它只能提供给 sources 层级。同时,它的优先级高于 useNameAsPrefix。假设我们有第三个配置映射,包含以下条目:

kind: ConfigMap
apiVersion: v1
metadata:
name: config-map-three
data:
application.yml: |-
greeting:
message: Say Hello from three

类似下面的配置:

spring:
application:
name: with-prefix
cloud:
kubernetes:
config:
useNameAsPrefix: true
namespace: default-namespace
sources:
- name: config-map-one
useNameAsPrefix: false
- name: config-map-two
explicitPrefix: two
- name: config-map-three

将生成三个属性:

  • greetings.message 等于 Say Hello from one。

  • two.greetings.message 等于 Say Hello from two。

  • config-map-three.greetings.message 等于 Say Hello from three。

与配置 ConfigMap 前缀的方式相同,你也可以为 Secret 配置前缀;这适用于基于名称的 Secret 和基于标签的 Secret。例如:

spring:
application:
name: prefix-based-secrets
cloud:
kubernetes:
secrets:
enabled: true
useNameAsPrefix: true
namespace: spring-k8s
sources:
- labels:
letter: a
useNameAsPrefix: false
- labels:
letter: b
explicitPrefix: two
- labels:
letter: c
- labels:
letter: d
useNameAsPrefix: true
- name: my-secret

生成属性源时,应用与配置映射相同的处理规则。唯一的区别在于,通过标签查找密钥时,可能会找到多个源。在这种情况下,前缀(如果通过 useNameAsPrefix 指定)将是针对这些特定标签找到的密钥的名称。

还有一点需要注意,我们支持每个密钥的 prefix。通过一个例子来解释这一点是最简单的:

spring:
application:
name: prefix-based-secrets
cloud:
kubernetes:
secrets:
enabled: true
useNameAsPrefix: true
namespace: spring-k8s
sources:
- labels:
color: blue
useNameAsPrefix: true

假设一个匹配此标签的查询将返回两个密钥作为结果:secretA 和 secretB。这两个密钥具有相同的属性名称:color=sea-blue 和 color=ocean-blue。由于 useNamesAsPrefix=true,将加载两个属性源:

  • secretA.color=sea-blue

  • secretB.color=ocean-blue

默认情况下,除了读取 sources 配置中指定的配置映射外,Spring 还会尝试从“配置文件感知”源读取所有属性。通过一个例子来解释这一点是最简单的方式。假设你的应用程序启用了一个名为 "dev" 的配置文件,并且你有一个如下所示的配置:

spring:
application:
name: spring-k8s
cloud:
kubernetes:
config:
namespace: default-namespace
sources:
- name: config-map-one

除了读取 config-map-one,Spring 还会尝试读取 config-map-one-dev;按照这个特定的顺序。每个激活的配置文件都会生成这样一个支持配置文件的 ConfigMap。

你可以禁用这个“基于配置文件的源”,这样就不会尝试使用 config-map-one-dev:

spring:
application:
name: spring-k8s
cloud:
kubernetes:
config:
includeProfileSpecificSources: false
namespace: default-namespace
sources:
- name: config-map-one
includeProfileSpecificSources: false
备注

自版本 5.0.0 起,includeProfileSpecificSources 仅支持命名源(spring.cloud.kubernetes.sources.name=XXX);对标记源的支持已被移除。

请注意,与之前一样,你可以在两个层级上指定此属性:针对所有配置映射或针对单个配置映射;后者的优先级更高。

备注

您应该查看安全配置部分。要从 pod 内部访问配置映射,您需要拥有正确的 Kubernetes 服务账户、角色和角色绑定。

在某些情况下,您的应用程序可能无法通过 Kubernetes API 加载部分 ConfigMaps。如果您希望在这种情况下让应用程序的启动过程失败,可以设置 spring.cloud.kubernetes.config.fail-fast=true,使应用程序在启动时因异常而失败。

你也可以让应用程序在失败时重试加载 ConfigMap 属性源。首先,你需要设置 spring.cloud.kubernetes.config.fail-fast=true。然后,你需要将 spring-retry 和 spring-boot-starter-aspectj 添加到你的类路径中。你可以通过设置 spring.cloud.kubernetes.config.retry.* 属性来配置重试属性,例如最大尝试次数、退避选项(如初始间隔、乘数、最大间隔)。

备注

如果你由于某些原因已经在类路径中包含了 spring-retry 和 spring-boot-starter-aspectj,并且希望启用快速失败(fail-fast),但不想启用重试功能;你可以通过设置 spring.cloud.kubernetes.config.retry.enabled=false 来禁用 ConfigMap PropertySources 的重试功能。

备注

自版本 5.0.0 起,我们引入了单独读取来源的可能性。在此之前,我们会访问命名空间并读取所有可用的 ConfigMap / Secret,然后过滤出请求的那些。自 5.0.0-M3 起,您可以通过设置属性 spring.cloud.kubernetes.config.read-type=SINGLE 来指定单独读取它们。在命名空间中读取所有配置的先前选项由 spring.cloud.kubernetes.config.read-type=BATCH 控制,并且它是默认选项。

表 1. 属性:

名称类型默认值描述
spring.cloud.kubernetes.config.enabledBooleantrue启用 ConfigMaps PropertySource
spring.cloud.kubernetes.config.nameString${spring.application.name}设置要查找的 ConfigMap 名称
spring.cloud.kubernetes.config.namespaceString客户端命名空间设置要查找的 Kubernetes 命名空间
spring.cloud.kubernetes.config.fail-fastBooleanfalse在加载 ConfigMap 发生错误时,启用或禁用应用程序启动失败
spring.cloud.kubernetes.config.retry.enabledBooleantrue启用或禁用配置重试。
spring.cloud.kubernetes.config.retry.initial-intervalLong1000初始重试间隔(毫秒)。
spring.cloud.kubernetes.config.retry.max-attemptsInteger6最大尝试次数。
spring.cloud.kubernetes.config.retry.max-intervalLong2000退避的最大间隔。