Spring Cloud第十篇 | 分布式配置中心Config

877 阅读13分钟

本文是Spring Cloud专栏的第十篇文章,了解前九篇文章内容有助于更好的理解本文:

  1. Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览

  2. Spring Cloud第二篇 | 使用并认识Eureka注册中心

  3. Spring Cloud第三篇 | 搭建高可用Eureka注册中心

  4. Spring Cloud第四篇 | 客户端负载均衡Ribbon

  5. Spring Cloud第五篇 | 服务熔断Hystrix

  6. Spring Cloud第六篇 | Hystrix仪表盘监控Hystrix Dashboard

  7. Spring Cloud第七篇 | 声明式服务调用Feign

  8. Spring Cloud第八篇 | Hystrix集群监控Turbin

  9. Spring Cloud第九篇 | 分布式服务跟踪Sleuth

一、介绍

Spring Cloud Config是Spring Cloud团队创建的一个全新项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端(config server)与客户端(config client)两个部分。其中服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密/解密信息等访问接口,而客户端则是微服务架构中的各个微服务应用或基础设施,它们通过指定的配置中心来管理应用资源与业务相关的配置内容, 并在启动的时候从配置中心获取和加载配置信息。Spring Cloud Config实现了对服务端和客户端中环境变量和属性配置的抽象映射,所以它除了适用于Spring构建的应用程序之外, 也可以在任何其他语言运行的应用程序中使用。由于Spring Cloud Config实现的配置中心默认采用Git来存储配置信息,所以使用Spring Cloud Config构建的配置服务器,天然就支持对微服务应用配置信息的版本管理,并且可以通过Git客户端工具来方便地管理和访问配置内容。当然它也提供了对其他存储方式的支持,比如SVN仓库、本地化文件系统。

二、构建Config Server

1、添加依赖

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

2、在启动类上添加注解@EnableConfigServer

3、在application.yml文件中添加配置

spring:
  application:
    name: springcloud-config-server
  cloud:
    config:
      server:
        git:
          #配置git仓库地址
          uri: https://gitee.com/coding-farmer/config-center
          #配置仓库路径
          search-paths: "{profile}"
          #访问git仓库的用户名
          username:
          #访问git仓库的密码
          password:
          #配置中心通过git从远程git库,有时本地的拷贝被污染,
          #这时配置中心无法从远程库更新本地配置,设置force-pull=true,则强制从远程库中更新本地库
          force-pull: true
          #默认从git仓库克隆下载的在C:/Users/<当前用户>/AppData/Local/Temp
          #basedir:

server:
  port: 8888

如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写,本案例使用的是码云公开仓库。

4、配置中心仓库目录结构为:按环境拆分

远程码云仓库gitee.com/coding-farm…中有3个文件夹,分别是dev,prod,test,里面存放着相应环境的配置文件

5、测试返回数据

http请求地址和资源文件映射如下:

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

此时启动我们的配置中心,通过/{application}/{profile}/[{label}]就能访问到我们的配置文件了,其中application表示配置文件的名字,对应我们上面的配置文件就是config;profile表示环境,我们有dev、test、prod还有默认,label表示分支,默认我们都是放在master分支上

访问:http://localhost:8888/configclient/dev,默认分支为master分支

如下图则证明配置服务中心可以从远程程序获取配置信息:

从浏览器上可以看到我们放在仓库中的配置文件信息。JSON中的name表示配置文件名application的部分,profiles表示环境部分,label表示分支,多了一个version,实际上就是我们码云上提交信息时产生的版本号,当我们访问成功后,我们还可以看到控制台打印了如下日志:

INFO 6600 --- [nio-8888-exec-1] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5c4fca8a: startup date [Thu Aug 15 15:12:07 CST 2019]; root of context hierarchy
INFO 6600 --- [nio-8888-exec-1] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/Administrator/AppData/Local/Temp/config-repo-6372945341655107732/dev/configclient-dev.yml
INFO 6600 --- [nio-8888-exec-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5c4fca8a: startup date [Thu Aug 15 15:12:07 CST 2019]; root of context hierarchy

实际上是配置中心通过git clone命令将配置文件在本地保存了一份,这样可以确保在git仓库挂掉的时候我们的应用还可以继续运行,当微服务A/B尝试去从Config Server中加载配置信息的时候,Config Server会先通过git clone命令克隆一份配置文件保存到本地,此时我们断掉网络,再访问http://localhost:8888/configclient/dev一样还可以拿到数据,此时的数据就是从本地获取的。

如果有两个前缀名相同文件,例如一个configclient.yml,一个configclient-dev.yml。那么在访问相同前缀的文件时,config-server会对这两个文件进行一个合并。例如configclient.yml有一段配置是configclient-dev.yml没有的,理应访问configclient-dev.yml的时候是没有那段配置的,但最终的访问的结果却是它俩合并之后的内容,即configclient-dev.yml会拥有configclient.yml里所配置的内容。

到此config server构建完成。

三、构建Config Client

Config Client也就是你的微服务应用例如(springcloud-service-consumer、springcloud-service-feign、springcloud-service-provider等等,这些模块相对于Config Server来说都是Config Client),但是为了保持其他案例模块的纯洁干净,这里就单独构建一个Config Client命名为:springcloud-config-client

1、引入依赖

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

2、新建bootstrap.yml

不了解bootstrap.yml可以参考,SpringCloud入门之常用的配置文件application.yml和 bootstrap.yml区别:https://www.cnblogs.com/BlogNetSpace/p/8469033.html

spring:
  application:
    name: springcloud-config-client
  cloud:
    config:
      #uri则表示配置中心的地址
      uri: http://localhost:8888
      #注:config 客户端在没有 spring.cloud.config.name属性的时候,服务端{application} 获取的是客户端
      #spring.application.name的值,否则,获取的是 spring.cloud.config.name的值。
      #1)、当没有spring.cloud.config.name时,客户端获取的是spring.application.name 所对应的git库中的文件,并且只能获取一个文件
      #2)、当一个项目中有需求要获取多个文件时,就需要用到spring.cloud.config.name这个属性,以逗号分割
      name: configclient
      profile: dev
      #label对应了label部分
      label: master
server:
  port: 8881

3、编写Controller

@RestController
public class MyController {

    //配置中心里面配置的env属性
    @Value("${env}")
    private String env;

    @RequestMapping("/index")
    public String env(){
        return env;
    }
}

4、访问http://localhost:8881/index

四、安全保护

开发环境中我们的配置中心肯定是不能随随便便被人访问的,我们可以加上适当的保护机制,由于微服务是构建在Spring Boot之上,所以整合Spring Security是最方便的方式。

1、添加依赖

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

2、默认情况下,我们可以获得一个名为user的用户,并且在配置中心启动的时候,在日志中打印出该用户的随机密码

Using generated security password: 9ca37d4c-c786-44be-8919-1825a0baaadb

大多数情况下,我们并不会使用随机生成密码的机制,我们可以在配置中心服务端application.yml文件中指定用户名密码

spring:
  security:
    user:
      name: coding-farmer
      password: 123456

当再次访问http://localhost:8888/configclient/dev配置中心服务端的时候需要输入密码,显示如下图

3、由于我们已经为config server设置了安全保护,如果我们这个时候连接到配置中心的客户端中没有设置响应对应的安全信息,在获取配置信息时会返回401错误,所以需要通过配置的方式在客户端中加入安全信息来通过校验,比如:

spring:
  cloud:
    config:
      username: coding-farmer
      password: 123456

五、加密解密

1、对称加密

在微服务架构中,我们通常会采用DevOps的组织方式来降低因团队间沟通造成的巨大成本,以加速微服务应用的交付能力。这就使得原本由运维团队控制的线上信息将交由微服务所属组织的成员自行维护,其中将会包括大量的敏感信息,比如数据库的账户与密码等。显然,如果我们直接将敏感信息以明文的方式存储于微服务应用的配置文件中是非常危险的。针对这个问题, Spring Cloud Config提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。比如下面的例子

spring.datasource.username=root
spring.datasource.password={cipher}22fedb745505ffcd1dec962bee3c1f0f3af8a3e6b6930eee9afb8659b16a0c630fd256a181319704b806df90f38e7371

在Spring Cloud Config中通过在属性值前使用{cipher)前缀来标注该内容是一个加密值,当微服务客户端加载配置时,配置中心会自动为带有{cipher}前缀的值进行解密。通过该机制的实现,运维团队就可以放心地将线上信息的加密资源给到微服务团队,而不用担心这些敏感信息遭到泄漏的风险。

1-1、使用前提

在使用 Spring Cloud Config的加密解密功能时,有一个必要的前提需要我们注意。为了启用该功能,我们需要在配置中心的运行环境中安装不限长度的JCE版本(Unlimited Strength Java Cryptography Extension)。虽然,JCE功能在JRE中自带,但是默认使用的是有长度限制的版本。我们可以从 Oracle的官方网站下载到它,它是一个压缩包,解压后可以看到下面三个文件:

我们需要将local_policy.jar和US_export_policy.jar两个文件复制到%JAVA_HOME%\jre\lib\security目录下,覆盖原来的默认内容。注意:我这里使用的是JDK8,应该下载对应版本的JCE

1-2、相关端点

在完成了JCE的安装后,可以尝试启动配置中心。在控制台中,将会输出一些配置中心特有的端点,主要包括如下几个

  • /encrypt/status:查看加密功能状态的端点。

  • /key:查看密钥的端点。

  • /encrypt:对请求的body内容进行加密的端点。

  • /decrypt:对请求的body内容进行解密的端点。

可以尝试通过GET请求访问/encrypt/status端点,我们将得到如下内容

该返回信息说明当前配置中心的加密功能还不能使用,因为没有为加密服务配置对应的密钥。

1-3、配置秘钥

我们可以通过encrypt.key属性在bootstrap.yml(为什么要写bootstrap.yml文件里查看,关于spring cloud config加密EncryptionTooWeakException异常说明:https://www.iteye.com/blog/357029540-2433259)配置文件中直接指定密钥信息(对称性密钥),比如:

encrypt:
  key: coding-farmer

加入上述配置信息后,重启配置中心,再访问/encrypt/status端点,我们将得到 如下内容:

此时,我们配置中心的加密解密功能就已经可以使用了,不妨尝试访问一下/encrypt 和/decrypt端点来使用加密和解密的功能。注意,这两个端点都是POST请求,加密和解密信息需要通过请求体来发送。

1-4、使用postman进行加密:http://localhost:8888/encrypt

1-5、使用postman解密:http://localhost:8888/decrypt

1-6、把加密的内容复制到你的配置文件中,提交到仓库即可

如下图,加密内容前面要有{cipher}开头表示该值是一个加密字符,配置中心config-server在获取到这个值之后会先对值进行解密,解密之后才会返回给客户端使用,如果是yml文件key=value,value值要加单引号

1-7、访问:http://localhost:8888/configclient/dev,显示如下则加密成功

2、非对称加密

Spring Cloud Config的配置中心不仅可以使用对称性加密,也可以使用非对称性加密 (比如RSA密钥对)。虽然非对称性加密的密钥生成与配置相对复杂一些,但是它具有更高的安全性。下面,我们来具体介绍一下如何使用非对称加密

首先,需要通过keytool工具来生成密钥对。keytool是JDK中的一个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向其他用户/服务认证自己)或数据完整性以及认证服务。在JDK1.4以后的版本中都包含了这一工具,它的位置在%JAVA_HOME%\bin\keytool.exe。

生成密钥的具体命令如下所示,在DOS窗口输入:

keytool -genkeypair -alias config-server -keyalg RSA -keystore config-server.keystore
C:\Program Files\Java\jdk1.8.0_101\bin>keytool -genkeypair -alias config-server -keyalg RSA -keystore config-server.keystore
输入密钥库口令: 123456
再次输入新口令: 123456
您的名字与姓氏是什么?
  [Unknown]:  coding-farmer
您的组织单位名称是什么?
  [Unknown]:  company
您的组织名称是什么?
  [Unknown]:  organization
您所在的城市或区域名称是什么?
  [Unknown]:  city
您所在的省/市/自治区名称是什么?
  [Unknown]:  province
该单位的双字母国家/地区代码是什么?
  [Unknown]:  china
CN=coding-farmer, OU=company, O=organization, L=city, ST=province, C=china是否正确?
  [否]:  y

输入 <config-server> 的密钥口令
        (如果和密钥库口令相同, 按回车): 456789
再次输入新口令: 456789

另外,如果不想逐步输入那些提示信息,可以使用-dname来直接指定,而秘钥库口令可使用-storepass和-keypass来直接指定。所以,我们可以通过下面命令直接创建与上述命令一样的秘钥库。

keytool -genkeypair -alias config-server -keyalg RSA \
 -dname "CN=coding-farmer, OU=company, O=organization, L=city, ST=province,C=china" \
 -storepass 123456 \
 -keystore config-server.keystore \
 -keypass 456789 \

默认情况下,使用上述命令创键的秘钥只有90天有效期,如果想要调整它的有效期,可以通过增加-validity参数来实现,比如我们可以通过下面的命令,让秘钥的有效期延长到一年:

 keytool -genkeypair -alias config-server -keyalg RSA \
 -dname "CN=coding-farmer, OU=company, O=organization, L=city, ST=province, C=china" \
 -storepass 123456 \
 -keystore config-server.keystore \
 -keypass 456789 \
 -validity 365 \

上述的三种命令生成方式,最终都会在命令的当前执行目录下生成一个config-server. keystore文件。下面,我们需要将它保存在配置中心的文件系统中的某个位置, 比如将该文件拷贝到config-server的src\main\resources目录下,然后在配置中心中加入相关的配置信息

encrypt:
  key-store:
    location: config-server.keystore
    alias: config-server
    password: 123456
    secret: 456789

非对称加密的配置信息也可以通过环境变量的方式进行配置,它们对应的具体变量名如下:

ENCRYPT_KEY_STORE_LOCATION
ENCRYPT_KEY_STORE_ALIAS
ENCRYPT_KEY_STORE_PASSWORD
ENCRYPT_KEY_STORE_SECRET

测试方式和对称加密的测试方式一致

详细参考案例源码:gitee.com/coding-farm…