Spring Cloud(十):分布式配置管理 Config 之 Git 实现

  在微服务架构集群部署的环境中,可能会有十几甚至几十个服务,若要修改项目属性参数,手动的方式是很糟糕的,所以就有了分布式配置管理这个概念。

  将服务器的配置外部化,可以存在文件或数据库中,一个专门用于管理应用服务配置的应用,可以随时修改和自动更新到目标服务器上。目前已有开源项目,如,smconfdisconfQConf

  Spring Cloud Config 为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Config Server,可以在所有环境中管理应用程序的外部属性。Spring Cloud Config 官方文档spring-cloud-config Github

概述

Spring Cloud Config Server 默认使用 Git 来存储配置,使用 Git 管理配置文件自动支持文件的版本管理。

Config Server 特性

  • 提供基于 HTTP API 的外部配置(key-value 或 yaml 内容)。
  • 加密和解密属性值(对称或非对称)。
  • 使用 @EnableConfigServer 轻松地将 Config Server 嵌入到 Spring Boot 应用中。

Spring Cloud Config Server 默认的Git存储库方式,是将 spring.cloud.config.server.git.uri 指定的Git配置仓库克隆到本地,客户端的配置文件存储在此Git存储库中或子目里,并将之用于客户端初始化。

1
spring.cloud.config.server.git.uri=https://github.com/spring-cloud-samples/config-repo

Config Server 获取以下形式的配置文件供客户端应用使用:

1
2
3
4
5
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
  • application:指 spring.config.name 属性设置的 Config Client 名称,不存在则使用 spring.application.name 的值。
  • profile:应用需要使用的 profile,若没有指定,则使用默认 default。
  • label:Git 分支、标签,若没有指定,则使用默认 master。

注意spring.application.name 的值不要使用 application- 作为前缀,会导致无法正确地解析配置文件。

Config Client 特性

  • 绑定到配置服务器,并使用远程属性源初始化 Spring 环境。
  • 加密和解密属性值(对称或非对称)。
  • Spring Bean 上添加 @RefreshScope 注解即可实现在配置更改时重新初始化。
  • 使用和管理端点:
    • /env:用于更新 Environment 和重新绑定 @ConfigurationProperties 及日志级别。
    • /refresh:用于刷新 @RefreshScope beans。
    • /restart:重启 Spring Context(默认情况下禁用)。
    • /pause/resume:用于调用 Lifecycle 方法(在 ApplicationContext 上的 stop() 和 start() )。

Config Git 存储库

Config Server

  1. 创建 Config Server 项目并引入 spring-cloud-config-server 依赖。
    pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springcloud.configserver</groupId>
    <artifactId>config-server-jdbc</artifactId>
    <version>v1.0.0</version>
    <name>config-server-git</name>
    <description>Demo project for Spring Boot</description>

    <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>
  2. 在启动类上添加 @EnableConfigServer 注解来启用Config Server。

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableConfigServer
    public class ConfigServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConfigServerApplication.class, args);
    }
    }

    Spring Cloud Config Server 为外部配置(key-val 或等效 yaml 内容)提供基于 HTTP 资源的 API。 通过使用 @EnableConfigServer 注解,服务器可嵌入 Spring Boot 应用程序中。

  3. 创建远程 Git 仓库,用于存储客户端应用的配置文件。
    这里在 GitHub 创建配置存储库用于,库名最好与服务应用名相同。实际生产会创建私有仓库,需要授权认证才可访问。
    Config Server Git 配置存储库:https://github.com/gxing19/config-repo

  4. Config Server 配置文件
    application.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 端口
    server.port=9010
    # 应用名
    spring.application.name=config-repo
    # Git URI 使用占位符
    spring.cloud.config.server.git.uri=https://github.com/gxing19/config-repo
    #spring.cloud.config.server.git.uri=https://github.com/gxing19/${spring.application.name}
    # 设置 HTTP 连接超时时长, 单位:秒
    spring.cloud.config.server.git.timeout=10
    # 删除本地未跟踪的库
    spring.cloud.config.server.git.delete-untracked-branches=true

Config Client

  1. 创建 Spring Boot 应用,引入 spring-cloud-starter-config 依赖和 spring-boot-starter-actuator 依赖。
    这里创建一个订单服务的应用(orderService)来演示
    pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springcloud</groupId>
    <artifactId>service-order</artifactId>
    <version>v1.0.0</version>
    <name>service-order</name>
    <description>Demo project for Spring Boot</description>

    <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>

  2. 作为 Config Client ,必须使用 bootstrap.properties 或 bootstrap.yml 作为引导配置,在应用上下文的引导阶段起作用。
    bootstrap.properties

    1
    2
    3
    4
    5
    6
    7
    8
    # Config Server 地址
    spring.cloud.config.uri=http://localhost:9010
    # 应用名
    spring.application.name=orderService
    # 使用的 profile
    spring.profiles.active=dev
    # Actuator 演示开放所有端点(生产只开放少数必要的端点)
    management.endpoints.web.exposure.include=*
  3. 在远程 Git 仓库创建客户端应用的配置文件
    在 Git 配置存储库:https://github.com/gxing19/config-repo 创建配置文件 orderService-dev.properties
    orderService-dev.properties

    1
    2
    3
    server.port=8010
    common.properties.app-id=QWERTYUIO
    #logging.level.root=debug
  4. 启动客户端应用,查看配置客户端和服务端的日志
    客户端应用(Config Client)在启动时需要向配置服务器(Config Server) 发送请求获取配置属性来完成应用初始化。看日志是在打印出 Banner 后就向配置服务器发送请求。

    Config Client 启动日志:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    2019-04-15 18:09:17.622  INFO 8944 --- [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : 
    Fetching config from server at : http://localhost:9010

    2019-04-15 18:09:20.669 INFO 8944 --- [ restartedMain] c.c.c.ConfigServicePropertySourceLocator :
    Located environment: name=orderService, profiles=[dev], label=null, version=5780a5068b7241badfebf68fffdc006c4a185545, state=null

    2019-04-15 18:09:20.669 INFO 8944 --- [ restartedMain] b.c.PropertySourceBootstrapConfiguration :
    Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'},
    MapPropertySource {name='https://github.com/gxing19/config-repo/orderService-dev.properties'}]}

    2019-04-15 18:09:20.673 INFO 8944 --- [ restartedMain] c.s.s.order.OrderServiceApplication : The following profiles are active: dev

    客户端应用启动成功,使用了远程配置文件中的 8010 端口。

    Config Server 接收到请求日志:
    克隆远程配置仓库到本地,在 Windows 系统默认是克隆到系统临时文件目录。

    1
    2
    3
    4
    5
    2019-04-16 09:17:52.292  INFO 10176 --- [nio-9010-exec-5] .c.s.e.MultipleJGitEnvironmentRepository : 
    Fetched for remote master and found 1 updates

    2019-04-16 09:17:52.517 INFO 10176 --- [nio-9010-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository :
    Adding property source: file:/C:/Users/gxing/AppData/Local/Temp/config-repo-4123568461296240052/orderService-dev.properties

Client 运行环境

客户端添加 spring-boot-starter-actuator 依赖,才可以通过调用端点来更新 Environment 。

根端点:http://localhost:8010/actuator

  1. 环境变量端点:http://localhost:8010/actuator/env
    从该端点可以看到客户端应用完整的运行环境,包括环境变量。bootstrap.properties 在 /env 端点显示为高优先级属性源,端点数据如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    {
    "activeProfiles": [
    "dev"
    ],
    "propertySources": [
    {
    "name": "server.ports",
    "properties": {
    "local.server.port": {
    "value": 8010
    }
    }
    },
    {
    "name": "configService:configClient",
    "properties": {
    "config.client.version": {
    "value": "5780a5068b7241badfebf68fffdc006c4a185545"
    }
    }
    },
    {
    "name": "configService:https://github.com/gxing19/config-repo/orderService-dev.properties",
    "properties": {
    "server.port": {
    "value": "8010"
    },
    "common.properties.app-id": {
    "value": "QWERTYUIO"
    }
    }
    },
    {...省略...},
    {
    "name": "applicationConfig: [classpath:/bootstrap.properties]",
    "properties": {
    "spring.cloud.config.uri": {
    "value": "http://localhost:9010",
    "origin": "class path resource [bootstrap.properties]:1:25"
    },
    "spring.application.name": {
    "value": "orderService",
    "origin": "class path resource [bootstrap.properties]:2:25"
    },
    "spring.profiles.active": {
    "value": "dev",
    "origin": "class path resource [bootstrap.properties]:3:24"
    },
    "management.endpoints.web.exposure.include": {
    "value": "*",
    "origin": "class path resource [bootstrap.properties]:4:43"
    }
    }
    },
    {
    "name": "defaultProperties",
    "properties": {

    }
    }
    ]
    }

动态更新属性

客户端应用的远程配置文件 orderService-dev.properties 有个自定义属性,写个 Controller 从环境中获取该值,然后改变自定义属性的值,调用 /refresh 刷新环境和变量,重新请求该属性。

  1. 创建获取自定义属性的 Controller 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @RestController
    @RequestMapping("/config")
    public class ConfigController {

    @Autowired
    private Environment environment;

    @GetMapping("/refresh")
    public String refreshProperties(){
    String appId = environment.getProperty("common.properties.app-id");
    return appId;
    }
    }

    返回结果:QWERTYUIO

  2. 修改远程配置文件该属性值
    orderService-dev.properties

    1
    common.properties.app-id=abcdefghijk112233
  3. 调用刷新环境的端点 /refresh,POST 请求

    1
    2
    # post 请求
    http://localhost:8010/actuator/refresh

    返回结果:

    1
    2
    3
    4
    [
    "config.client.version",
    "common.properties.app-id"
    ]
  4. 调用执行步骤 1 创建的方法,会重新拉取远程的配置文件。
    返回结果:abcdefghijk112233
    不用重启应用,调用端点刷新环境和变量,即完成了配置文件属性重载。

与 Eureka 集成

在上面示例的基础上整合 Eureka ,通过服务发现来与 Config Server 通信获取远程配置源。

注册 Config Server

  1. Config Server 作为 Eureka Server 的客户端,需要引入 eureka-client 依赖
    pom.xml

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. Config Server 的配置文件添加注册到 Eureka Server 的属性
    application.properties

    1
    2
    # 添加注册Eureka Server
    eureka.client.service-url.defaultZone=http://admin:123456@eureka.master.com:8761/eureka,http://admin:123456@eureka.slave.com:8762/eureka

注册 Config Client

  1. Config Client 作为 Eureka Server 的客户端,需要引入 eureka-client 依赖

    pom.xml

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. Config Client 的引导启动配置文件 bootstrap.properties 添加开启服务发现和设置 Eureka 服务地址:

    bootstrap.properties

    1
    2
    3
    4
    5
    6
    7
    8
    # 添加开启服务发现 discovery
    spring.cloud.config.discovery.enabled=true
    # 添加注册Eureka Server
    eureka.client.service-url.defaultZone=http://admin:123456@eureka.master.com:8761/eureka,http://admin:123456@eureka.slave.com:8762/eureka
    # 添加设置 Config Server 在 Eureka Server 中的 service ID
    spring.cloud.config.discovery.service-id=config-repo
    # 注释掉显式指定的 Config Server uri
    #spring.cloud.config.uri=http://localhost:9010
  3. 重启 Config Server 和 Config Client,查看日志

    在 Eureka Server 的 Web 控制台可以看到 Config Server 和 Config Client 注册实例。

    Config Client 在引导启动阶段,通过服务发现获取远程配置源。

    调用 Config Client 获取环境变量的 Controller 方法成功。

Config Server 高可用

Spring Cloud Config Server 实现高可用主要有两种方式:

  1. 部署多个 Config Server 实例,指向同一个存储库(Git 或 JDBC),在上层通过负载均衡器来反向代理到 Config Server。
  2. 部署多个 Config Server,集成 Eureka Client,注册到 Eureka Server,通过服务治理来实现,即可高可用,也实现了自维护。

Config 详细配置

请参阅 Spring Cloud系列(十二):分布式配置管理 Config 之 配置详解

其它参考

Spring Coud Config Server 也支持使用 VaultCredHub作为存储后端,还支持复合环境存储(同时使用不同的存储库),支持通过代理访问存储后端。详细使用参考官网。

可以使用 UAA 作为提供者,通过 OAuth2.0 进行身份验证。

Spring Cloud(十):分布式配置管理 Config 之 Git 实现

http://blog.gxitsky.com/2019/04/06/SpringCloud-10-config-git-impl/

作者

光星

发布于

2019-04-06

更新于

2022-06-17

许可协议

评论