使用Spring Cloud Config统一管理微服务配置

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Spring Cloud Config统一管理微服务配置相关的知识,希望对你有一定的参考价值。

使用Spring Cloud Config统一管理微服务配置

为什么要统一管理微服务配置

对于传统的单体应用,常使用配置文件管理所有配置。例如一个Spring Boot开发的单体应用,可将配置内容放在application.yml文件中。如果需要切换环境,可设置多个Profile,并在启动应用时指定spring.profiles.active={profile}。当然也可借助Profile实现环境切换。
然而,在微服务架构中,微服务的配置管理一般有以下需求:

  • 集中管理配置。一个使用微服务架构的应用系统可能会包含成百上千个微服务,因此集中管理配置是非常有必要的。
  • 不同环境,不同配置。例如,数据源配置在不同的环境(开发、测试、预发布、生产等)中是不同的。
  • 运行期间可动态调整。例如,可根据各个微服务的负载情况,动态调整数据源连接池大小或熔断阈值,并且在调整配置时不停止微服务。
  • 配置修改后可自动更新。如配置内容发生变化,微服务能够自动更新配置。

Spring Cloud Config简介

Spring Cloud Config为分布式系统外部化配置了服务器端和客户端的支持,它包括Config Server和Config Client两部分。由于Config Server和Config Client都实现类对Spring Environment和PropertySource抽象的应映射,因此Spring Cloud Config非常适合Spring应用程序,当然也可与任何其他语言编写的应用程序配合使用。
Config Server是一个可横向拓展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置内容(可以使用Subversion、本地文件系统或Vault存储配置),因此可以很方便地实现对配置的版本控制与内容审计。
Config Client是Config Server的客户端,用于操作存储在Config Server中的配置属性。所有的微服务都指向Config Server。各个微服务在启动时,会请求Config Server以获取所需要的配置属性,然后缓存这些属性以提高性能。

编写Config Server

1)在Git仓库https://gitee.com/persimmon_me/spring-cloud-config-repo.git中新建几个配置文件。

内容分别是

profile=default-1.0
profile=dev-1.0
profile=test-1.0
profile=production-1.0

为了测试版本控制,为该Git仓库创建config-label-v2.0分支,并将各个配置文件中的1.0改为2.0。
2)创建一个Maven工程,ArtifactId是microservice-config-server,并为项目添加以下依赖。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

3)编写启动类,在启动类上添加注解@EnableConfigServer,声明这是一个Config Server。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class);
    }
}

4)编写配置文件application.yml,并在其中添加

server:
  port: 8080
spring:
  application:
    name: microservice-config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/********/spring-cloud-config-repo
          username: ***********
          password: ******

这样,一个Config Server就完成了。
可以使用Config Server的端点获取配置文件的内容。端点与配置文件的映射规则如下

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

以上端点都可映射到{application}-{profile}.properties这个配置文件,{application}表示微服务的名称,{label}对应Git仓库的分支,默认是master
按照以上规则,对于本例,可使用以下URL访问到Git仓库master分支的microservice-foo-dev.properties。

http://localhost:8080/microservice-foo/dev
http://localhost:8080/microservice-foo-dev.properties
http://localhost:8080/microservice-foo-dev.yml

测试
1)访问http://localhost:8080/microservice-foo/dev

从结果可以直观地看到应用名称、项目profile、Git label、 Git version、配置文件URL、配置详情等信息。
2)访问http://localhost:8080/microservice-foo-dev.properties,返回配置文件中的属性

3)访问http://localhost:8080/config-labal-v2.0/microservice-foo-dev.properties,可获得

说明获得了Git仓库config-label-v2.0分支中的配置信息。
至此,已成功构建了Config Server,并通过构造URL的方式,获取了Git仓库中的配置信息。

编写Config Client

1)创建一个Maven工程,ArtifactId是microservice-config-client,并为项目添加以下依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

2)创建一个基本的Spring Boot启动类

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConfigClientApplication {
    public static void main(String[] args){
        SpringApplication.run(ConfigClientApplication.class,args);
    }
}

3)编写配置文件application.yml

server:
  port: 8081

4)创建配置文件bootstrap.yml,并在其中添加如下内容

spring:
  application:
    name: microservice-foo
  cloud:
    config:
      uri: http://localhost:8080/
      profile: dev
      label: master

其中

  • spring.application.name:对应Config Server所获取的配置文件中的{application}。
  • spring.cloud.config.uri:指定Config Server的地址,默认是http://localhost:8888。
  • spring.cloud.config.profile:profile对应的Config Server所获取的配置文件中的{profile}。
  • spring.cloud.config.label:指定Git仓库的分支,对应Config Server所获取配置文件的{label}。
    注:以上属性应配置在bootstrap.yml,而不是application.yml中。如果配置在application.yml中,该部分配置就不能正常工作。例如,Config Client会连接spring.cloud.config.uri的默认值http://localhost:8888,而并非配置的http://localhost:8080
    Spring Cloud有一个"引导上下文"的概念,这是主应用程序的父上下文。引导上下文负责从配置服务器加载配置属性,以及解密外部配置文件中的属性。和著应用程序加载application.*(yml或properties)中的属性不同,引导上下文加载bootstrap.*中的属性。配置在bootstrap.*中的属性有更高的优先级,因此默认情况下它们不能被本地配置覆盖。
    若需禁用引导过程,可设置spring.cloud.bootstrap.enabled=false。
    5)编写Controller
package com.example.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ConfigClientController {
    @Value("${profile}")
    private String profile;

    @GetMapping("/profile")
    public String hello() {
        return this.profile;
    }
}

在Controller中,通过注解@Value("${profile}"),绑定Git仓库配置文件中的profile属性。
测试
1)启动microservice-config-server
2)启动microservice-config-client
3)访问http://localhost:8081/profile

Config Server的Git仓库配置详情

前文使用spring.cloud.config.server.git.uri指定了一个Git仓库,事实上,该属性非常灵活。

占位符支持

Config Server的占位符支持{application}、{profile}和{label}。
示例

server:
  port: 8080
spring:
  application:
    name: microservice-config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/*******/{application}
          username: 18332104715
          password: zbn456123

模式匹配

模式匹配指的是带有通配符的{application}/{profile}名称的列表。如果{application}/{profile}不匹配任何模式,他将使用spring.cloud.config.server.git.uri定义的URI。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          repos:
            simple: https://github.com/simple/config-repo
            special:
              pattern: special*/dev*,*special*/dev*
              uri: https://github.com/special/config-repo
            local:
              pattern: local*
              uri: file:/home/configsvc/config-repo

该例中,对于simple仓库,它只匹配所有配置文件中名为simple的应用程序。local仓库则匹配所有配置文件中以local开头的所有应用程序的名称。

搜索目录

很多场景下,可能把配置文件放在了Git仓库子目录中,此时可以使用search-path指定,search-path同样支持占位符。

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          searchPaths: foo,bar*

这样,Config Server就会在Git仓库根目录、foo子目录以及所有以bar开始的子目录中查找配置文件。

启动时加载配置文件

默认情况下,在配置被首次请求时,Config Server才会clone Git仓库。也可让Config Server在启动时就clone Git仓库

spring:
  cloud:
    config:
      server:
        git:
          uri: https://git/common/config-repo.git
          repos:
            team-a:
                pattern: microservice-*
                cloneOnStart: true
                uri: http://git/microservice/config-repo.git

将属性spring.cloud.config.server.git.repos.*.clone-on-start设为true,即可让Config Server启动时clone指定Git仓库。
当然,也可使用spring.cloud.config.git.clone-on-start=true进行全局配置。
配置clone-on-start=true,可帮助Config Server启动时快速识别错误的配置源(例如无效的Git仓库)。
将以下包的日志级别设置为DEBUG,就可打印Config Server请求Git仓库的细节。可通过这种方式,更好地理解Config Server的Git仓库配置,同时也便于快速定位问题。

logging:
  level:
    org.springframework.cloud: DEBUG
    org.springframework.boot: DEBUG

Config Server的健康状态指示器

Config Server自带了一个健康状况指示器,用于检查所配置的EnvironmentRepository是否正常工作。可使用Config Server的/health端点查询当前健康状态。默认情况下,健康指示器向EnvironmentRepository请求的{application}是app,{profile}和{label}是对应EnvironmentRepository实现的默认值。对于Git,{profile}是default,{label}是master。

server:
  port: 8080
spring:
  application:
    name: microservice-config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/**********/spring-cloud-config-repo
          username: *********
          password: *******
        health:
          repositories: 
            a-foo:
              label: config-label-v2.0
              name: microservice-foo
              profiles: dev

若需禁用健康状态指示器,可设置spring.cloud.config.server.health.enabled=false

配置内容的加解密

前文是在Git仓库中明文存储配置属性的。很多场景下,对于某些敏感的配置内容(例如数据库账号、密码)应当加密存储。
Config Server为配置内容的加密与解密提供了支持

安装JCE

Config Server的加解密功能依赖Java Cryptography Extension(JCE)
Java 8 JCE的地址:

Config Server的加解密端点

Config Server提供了加密与解密的端点,分别是/encrypt与/decrypt。可使用以下代码来加密明文:

curl $CONFIG_SERVER_URL/encrypt -d 想要加密的密文

使用以下代码来解密密文

curl $CONFIG_SERVER_URL/decrypt -d 想要加密的密文

对称加密

1)复制项目microservice-config-service,将ArtifactId修改为microservice-config-server-encryption。
2)创建bootstrap.yml

encrypt:
  key: foo  #对称加密密钥

测试
1)输入以下命令。

curl http://localhost:8080/encrypt -d mysecret

可返回8c1f87805fd3cea100e50df3ccd9aad592f3104fa79325edbf16833f79bec29f,说明mysecret已被加密。

2)输入以下命令

curl http://localhost:8080/decrypt -d 8c1f87805fd3cea100e50df3ccd9aad592f3104fa79325edbf16833f79bec29f


注:encrypt开头的配置,必须放在bootstrap.yml中,否则将无法正常加解密
在早期的Spring Cloud版本中,并无此限制,例如Spring Cloud Camden SR4

存储加密的内容

加密后的内容,可使用{cipher}密文的形式存储。
1)准备一个配置文件,命名为encryption.yml

spring:
  datasource:
    username: dbuser
    password: '{cipher}2********************9c1d53908afbb'

并将其push到Git仓库。此处需要注意spring.datasource.password上的单引号不能少。如果使用properties格式管理配置,则不能使用单引号,否则该值不会被解密。
2)使用http://localhost:8080/encryption-default.yml可获得如下结果。

说明Config Server能自动解密配置内容
一些场景下,想要让Config Server直接返回密文本身,可设置spring.cloud.config.server.encrypt.enable=false,这时可由Config Client自行解密。

非对称加密

1)复制项目microservice-config-server,将ArtifactId修改为microservice-config-server-encryption-rsa。
2)执行以下命名,并按照提示操作,即可创建一个Key Store

keytool -genkeypair -alias mytestkey -keyalg RSA -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" -keypass changeme -keystore D:/server.jks -storepass letmein


3)将生成的server.jks文件复制到项目的classpath下。
4)创建bootstrap.yml

encrypt:
  key-store:
    location: classpath:/server.jks		#jks文件的路径
    password: letmein					#storepass
    alias: mytestkey					#alias
    secret: changeme					#keypass

这样,使用以下命令

curl http://localhost:8080/encrypt -d mysecret

使用/refresh端点手动刷新配置

很多场景下,需要在运行期间动态调整配置。如果配置发生了修改,微服务要如何实现配置的刷新呢?
要想实现配置刷新,需对之前的代码进行一点改造。
1)复制项目microservice-config-client,将ArtifactId修改为microservice-config-client-refresh。
2)为项目添加spring-boot-starter-actuator的依赖,该依赖包含了/refresh端点,用于配置的刷新。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

3)在Controler上添加注解@RefreshScope。添加@RefreshScope的类会在配置更改时得到特殊的处理

package com.example.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope
public class ConfigClientController {
    @Value("${profile}")
    private String profile;

    @GetMapping("/profile")
    public String hello() {
        return this.profile;
    }
}

测试
1)启动microservice-config-server。
2)启动microservice-config-client-refresh。
3)访问http://localhost:8081/profile,获得结果:dev-1.0

4)修改Git仓库中microservice-foo-dev.properties文件内容为profile=dev-1.0-change。
5)重新访问http://localhost:8081/profile,发现结果依然是dev-1.0,说明配置尚未刷新。
6)发送POST请求到http://localhost:8081/refresh

7)再次访问http://localhost:8081/profile,返回dev-1.0-change,说明配置已经刷新。

使用Spring Cloud Bus自动属性配置

前文讨论了使用/refresh端点手动刷新配置,但如果所有微服务节点的配置都需要手动去刷新,工作量可想而知。不仅如此,随着系统的不断扩张,会越来越难以维护。因此实现配置的自动刷新是很有必要的。

Spring Cloud Bus简介

Spring Cloud Bus使用轻量级的消息代理(例如RabbitMQ、Kafka等)连接分布式系统的节点,这样就可以广播传播状态的更改(例如配置的更新)或者其他的管理指令。可将Spring Cloud Bus想象成一个分布式的Spring Boot Actuator。
微服务A的所有实例都通过消息总线连接到了一起,每个实例都会订阅配置更新事件。当其中一个微服务节点的/bus/refresh端点被请求时,该实例就会向消息总线发送一个配置更新事件,其他实例获得该事件后也会更新配置。

实现自动刷新

安装RabbitMQ后,接下来为项目整合Spring Cloud Bus并实现自动刷新。
1)复制项目microservice-config-client-refresh,将ArtifactId修改为microservice-config-client-refresh-cloud-bus。
2)为项目添加spring-cloud-starter-bus-amqp的依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

3)在bootstrap.yml中添加以下内容

spring:
  application:
    name: microservice-foo
  cloud:
    config:
      uri: http://localhost:8080/
      profile: dev
      label: master
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

测试
1)启动microservice-config-server
2)启动microservice-config-client-refresh-cloud-bus
3)将microservice-config-client-refresh-cloud-bus的端口改为8082,再启动一个节点。
4)访问http://localhost:8081/profile,可获得结果:dev-1.0-changa

5)将Git仓库中的microservice-foo-dev.proerties文件内容修改为

prifile=dev-1.0-bus

6)发送POST请求到其中一个Config Client实例的/bus/refresh端点

curl -X POST http://localhost:8081/bus/refresh

7)访问两个Config Client节点的/profile端点,发现两个节点都会返回dev-1.0-bus,说明配置内容已被刷新。
借助Git仓库的WebHooks,即可轻松实现配置的自动刷新。


局部刷新

某些场景下(例如灰度发布等),若只想刷新部分微服务的配置,可通过/bus/refresh端点的destination参数来定位要刷新的应用程序。
例如/bus/refresh?destination=customers:9000,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要刷新。其中,customers:9000指的是各个微服务的ApplicationContextID。
destination参数也可以用来定位特定的微服务。例如/bus/refresh?destination=customers:**,这样就可以触发customers微服务所有实例的配置刷新。

架构改进

在前面的示例中,通过请求某个微服务/bus/refresh端点的方式来实现配置刷新,但这种方式并不优雅。

  • 破坏了微服务的职责单一原则。业务微服务只应关注自身业务,不应承担配置配置刷新的职责。
  • 破坏了微服务各节点的对等性。
  • 有一定的局限性。例如,微服务在迁移时,网络地址常常会发生变化。此时如果想自动刷新配置,就不得不修改WebHook的配置。
    将Config Server也加入消息总线中,并使用Config Server的/bus/refresh端点来实现配置的刷新。这样,各个微服务只需要关注自身的业务,而不再承担配置刷新的职责。

跟踪总线事件

在一些场景下,如果希望知道Spring Cloud Bus事件传播的细节,可以跟踪总线事件。
想要跟踪总线事件非常简单,只需设置spring.cloud.bus.trace.enable=true,这样在/bus/refresh端点被请求后,访问/trace端点

Spring Cloud Config与Eureka配合使用

前文在微服务中指定了Config Server地址,这种方式无法利用服务发现组件的优势。
1)将Config Server和ConfigClient都注册到Eureka Server上。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-client</artifactId>
        </dependency>

Config Client application.yml

server:
  port: 8081
management:
  security:
    enabled: false
spring:
  application:
    name: microservice-config-client-eureka

Config Server application.yml

server:
  port: 8080
spring:
  application:
    name: microservice-config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/persimmon_me/spring-cloud-config-repo
          username: *********
          password: ******
        health:
          repositories: 
            a-foo:
              label: config-labal-v2.0
              name: microservice-foo
              profiles: dev
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

2)Config Client的bootstrap.yml

spring:
  application:
    name: microservice-foo
  cloud:
    config:
      profile: dev
      label: master
      discovery:
        enabled: true
        service-id: microservice-config-server-eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

Spring Cloud Config的用户认证

在前文的示例中,Config Server是允许匿名访问的。为了防止配置内容的外泄,应该保护Config Server的安全。有多种方式做到这一点,例如通过物理网络安全,或者为Config Server添加用户认证等。
本节来为Config Server添加基于HTTP Basic的用户认证
先来构建一个需要用户认证的Config Server
1)复制项目microservice-config-server,将ArtifactId修改为microservice-config-server-authenticating
2)为项目添加以下依赖

        <dependency>
            以上是关于使用Spring Cloud Config统一管理微服务配置的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Cloud Config 统一管理配置,别再到处放配置文件了!

springcloud-spring cloud config统一配置中心

开启springcloud全家桶7:使用spring cloud config来统一管理配置文件

Spring Cloud Config(统一配置中心)

spring cloud config

Spring Cloud Config(统一配置中心服务端和客户端)