使用 `ConfigMap` `PropertySource`

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

以下所有内容主要通过使用 ConfigMap 的示例进行解释,但 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` 值命名的属性的内容作为 `yaml`(或 `properties`)应用(如果不存在,则为 `application.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 或属性文件时。在这种情况下,键的名称不必是 `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` 格式外部化到 config map 中,如下所示:

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:
        enableApi: true
        useNameAsPrefix: true
        namespace: spring-k8s
        sources:
          - labels:
              letter: a

这将搜索命名空间 `spring-k8s` 中具有标签 `{letter : a}` 的每个 configmap。需要注意的是,与按名称读取 configmap 不同,这可能导致读取 *多个* configmap。通常,Secrets 也支持相同的功能。

您还可以根据合并的活动配置文件配置 Spring Boot 应用程序,这些配置文件在读取 `ConfigMap` 时合并在一起。您可以通过使用 `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

在前面的情况下,加载到具有 `development` 配置文件的 Spring 应用程序中的配置如下所示:

  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` 中最后出现的属性将覆盖任何之前的属性值。

另一种方法是为每个配置文件创建一个不同的 config map,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 时激活特定配置文件的一种方法是使用可以在容器规范的 PodSpec 中定义的环境变量启动 Spring Boot 应用程序。部署资源文件,如下所示:

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"

您可能会遇到多个 config map 具有相同属性名称的情况。例如:

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` 中放置这些文件的顺序,您最终可能会得到意外的结果(最后一个 config map 获胜)。例如:

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 的前缀一样配置 secrets 的前缀;对于基于名称的 secrets 和基于标签的 secrets 都适用。例如

spring:
  application:
    name: prefix-based-secrets
  cloud:
    kubernetes:
      secrets:
        enableApi: 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

生成属性源时,相同的处理规则也适用于 config map。唯一的区别是,通过标签查找 secrets 可能会找到多个源。在这种情况下,前缀(如果通过useNameAsPrefix指定)将是为这些特定标签找到的所有 secrets 的名称。

还有一点需要注意的是,我们支持每个prefix,而不是每个 secret 的prefix。最简单的解释方法是通过一个例子

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

假设匹配此标签的查询将返回两个 secrets:secret-asecret-b。这两个 secrets 都有相同的属性名称:color=sea-bluecolor=ocean-blue。哪个color最终会成为属性源的一部分是不确定的,但它的前缀将是secret-a.secret-b(自然排序连接的 secrets 名称)。

如果您需要更细粒度的结果,可以添加更多标签来唯一标识 secret。

默认情况下,除了读取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;按照此特定顺序。每个活动的配置文件都会生成这样的配置文件感知的 config map。

尽管您的应用程序不应受此类 config map 的影响,但如果需要,可以禁用它。

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

请注意,和之前一样,您可以在两个级别指定此属性:对于所有 config map 或单个 config map;后者具有更高的优先级。

您应该检查安全配置部分。要从 Pod 内部访问 config map,您需要拥有正确的 Kubernetes 服务帐户、角色和角色绑定。

使用ConfigMap实例的另一种方法是,通过运行 Spring Cloud Kubernetes 应用程序并将 Spring Cloud Kubernetes 从文件系统中读取它们,将其安装到 Pod 中。

此功能已弃用,将在未来的版本中删除(请改用spring.config.import)。此行为由spring.cloud.kubernetes.config.paths属性控制。您可以将其与前面描述的机制一起使用,也可以代替它。spring.cloud.kubernetes.config.paths需要每个属性文件的完整路径列表,因为不会递归解析目录。例如
spring:
  cloud:
    kubernetes:
      config:
        paths:
          - /tmp/application.properties
          - /var/application.yaml
如果您使用spring.cloud.kubernetes.config.pathsspring.cloud.kubernetes.secrets.path,则自动重新加载功能将不起作用。您需要向/actuator/refresh端点发出POST请求,或者重新启动/重新部署应用程序。

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

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

如果您由于某种原因已经在类路径中拥有spring-retryspring-boot-starter-aop,并且想要启用快速失败,但不想启用重试;您可以通过设置spring.cloud.kubernetes.config.retry.enabled=false来禁用ConfigMap PropertySources的重试。
表 1. 属性
名称 类型 默认值 描述

spring.cloud.kubernetes.config.enabled

布尔型

true

启用 ConfigMaps PropertySource

spring.cloud.kubernetes.config.name

字符串

${spring.application.name}

设置要查找的ConfigMap的名称

spring.cloud.kubernetes.config.namespace

字符串

客户端命名空间

设置要在其中查找的 Kubernetes 命名空间

spring.cloud.kubernetes.config.paths

列表

null

设置安装ConfigMap实例的路径

spring.cloud.kubernetes.config.enableApi

布尔型

true

启用或禁用通过 API 使用ConfigMap实例

spring.cloud.kubernetes.config.fail-fast

布尔型

false

启用或禁用在加载ConfigMap时发生错误时使应用程序启动失败

spring.cloud.kubernetes.config.retry.enabled

布尔型

true

启用或禁用配置重试。

spring.cloud.kubernetes.config.retry.initial-interval

长整型

1000

以毫秒为单位的初始重试间隔。

spring.cloud.kubernetes.config.retry.max-attempts

整型

6

最大尝试次数。

spring.cloud.kubernetes.config.retry.max-interval

长整型

2000

回退的最大间隔。

spring.cloud.kubernetes.config.retry.multiplier

双精度型

1.1

下一个间隔的乘数。