配置中心:Config
对于传统的单体应用,配置文件可以解决配置问题,但是当多机部署时,修改配置依然是烦琐的问题。
在微服务中,由于系统拆分的粒度更小,微服务的数量比单体应用要多得多(基本上多一个数量级),通过配置文件来管理配置变得更不可行。
所以,对于微服务架构而言,一个通用的分布式配置管理是必不可少的。在大多数微服务系统中,都会有一个名为“配置文件”的功能模块来提供统一的分布式配置管理。
在研发流程中有测试环境、UAT环境、生产环境等隔离,因此每个微服务又对应至少三个不同环境的配置文件。这么多的配置文件,如果需要修改某个公共服务的配置信息,如缓存、数据库等,难免会产生混乱,这时就需要引入Spring Cloud的另外一个组件:Spring Cloud Config。
Spring Could Config是一个提供了分布式配置管理功能的Spring Cloud子项目。在以往的单体应用中往往是代码与配置文件放在一个应用包中,但是随着系统的体量越来越大,我们会将系统分成多个服务,对于这么多服务的配置管理以及热生效等方面的支持将会越来越麻烦。Spring Cloud Config完美解决了这些问题。
在市面上有一些开源产品,如百度的DisConf、淘宝的Diamond,以及很多基于ZooKeeper的各个公司自主开发的产品。这些产品可能由于某些问题已经停止维护,导致文档资料不全、重复造轮子等各种问题。而Spring Cloud Config由于可与Spring无缝集成、功能强大、社区活跃等各方面原因,成为开发中不可不着重考虑的一项技术。
3.1 Spring Cloud Config的组成
Spring Cloud Config项目提供了如下的功能支持:
- 提供服务端和客户端支持;
- 集中式管理分布式环境下的应用配置;
- 基于Spring环境,与Spring应用无缝集成;
- 可用于任何语言开发的程序;
- 默认实现基于Git仓库,可以进行版本管理;
- 可替换自定义实现;
- Spring Cloud Config Server作为配置中心服务端;
- 拉取配置时更新Git仓库副本,保证是最新结果;
- 支持数据结构丰富,包括yml、json、properties等;
- 配合Eureka可实现服务发现,配合Spring Cloud Bus可实现配置推送更新;
- 配置存储基于Git仓库,可进行版本管理;
- 简单可靠,有丰富的配套方案;
- Spring Cloud Config Client提供(如SVN、Local等)开箱即用的客户端实现;
- Spring Boot项目不需要改动任何代码,加入一个启动配置文件指明使用Config Server中哪个配置文件即可。
下面分别从配置仓库、Config Server、Config Client的使用与概念解释,以及Config Server的高可用、全局通知、安全性、加解密等方面来介绍Spring Cloud Config。
3.2 使用Config Server配置服务端
本节先使用Git作为配置文件存储仓库,后文中会介绍使用SVN、本地目录以及自行扩展等方式。
首先,我们需要在以Maven作为依赖管理的项目pom.xml中添加spring-cloud-starter-config、spring-cloud-config-server两项依赖,以及以spring-boot-starter-parent作为父项目。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId> spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
在项目中创建ConfigServerApplication类,其中@EnableConfigServer注解表示允许该服务以HTTP形式对外提供配置管理服务。
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
添加application.yml,新增如下内容指定Git仓库的地址:
server:
port: 8888
spring:
cloud:
config:
application:
name: myConfigServer
server:
git:
#Git仓库地址
uri: https://git.oschina.net/wawzw123/SpringCloudBookCode.git
search-paths: config-repo
如下为配置文件中的配置项。
1)spring.cloud.config.server.git.uri:配置Git仓库位置。
2)spring.cloud.config.server.git.searchPaths:配置仓库路径下的相对搜索位置,可以配置多个。
3)spring.cloud.config.server.git.username:访问Git仓库的用户名。
4)spring.cloud.config.server.git.password:访问Git仓库的用户密码。
读者在自行测试的时候需要自行创建Git仓库,并根据Git仓库信息自行替换application.properties中的内容。我们已经事先在实例的仓库中添加了如下几个文件,用于进行不同分支的不同key的读取测试。
在Master分支中添加如下文件和内容。
1)configServerDemo.properties :key1=master-default-value1;
2)configServerDemo-dev.properties:key1=master-dev-value1;
3)configServerDemo-test.properties:key1=master-test-value1;
4)configServerDemo-prd.properties:key1=master-prd-value1。
在Branch分支中添加如下文件和内容。
1)configServerDemo.properties:key1=branch-prd-value1;
2)configServerDemo-dev.properties:key1=branch-dev-value1;
3)configServerDemo-test.properties:key1=branch-test-value1;
4)configServerDemo-prd.properties:key1=branch-prd-value1。
在服务端启动后,可以基于HTTP请求访问如下URL进行配置获取。可以通过如下几种格式的HTTP向配置中心发起配置文件获取的请求:
1)/{application}/{profile}[/{label}];
2)/{application}-{profile}.yml;
3)/{application}-{profile}.json;
4)/{label}/{application}-{profile}.yml;
5)/{label}/{application}-{profile}.json;
6)/{application}-{profile}.properties;
7)/{label}/{application}-{profile}.properties。
- application:应用名称,默认取spring.application.name,也可以通过spring.cloud.config.name指定。
- profile:环境,如dev(开发)、test(测试)、prd(生产)等,通过spring.cloud.config.profile指定。
- label:版本,对应不同的分支,通过spring.cloud.config.label指定。
比如,尝试通过curl或者浏览器等发起HTTP请求“http://localhost:8888/configServer-Demo/test/master ”,将会得到如下响应内容。
{
"name": "configServerDemo",
"profiles": [
"test"
],
"label": null,
"version": "32d326655ae7d17be752685f29d017ba42e8541a",
"propertySources": [
{
"name": "https://git.oschina.net/wawzw123/Spring CloudBookCode.git/config-repo/configServerDemo-test.properties",
"source": {
"key1": "master-test-value1"
}
},
{
"name":"https://git.oschina.net/wawzw123/Spring CloudBookCode.git/config-repo/configServerDemo.properties",
"source": {
"key1": "master-default-value1"
}
}
]
}
访问http://localhost:8888/configServerDemo-test.yml ,则会得到如下结果:
key1: master-test-value1
在尝试了手动从配置中心获取配置项之后,我们接下来尝试启动一个示例项目来自动从配置中心获取配置项。
3.3 使用Config Client配置客户端
接下来创建一个Spring Boot应用作为配置管理的客户端来读取Config Server中提供的配置,如图3-1所示。
在pom.xml中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
添加bootstrap.yml:
server:
port: 7002
spring:
application:
name: cloudConfigDemo${server.port}
cloud:
config:
profile: dev
label: master
name: configServerDemo
uri: http://localhost:8888/
上面这些属性必须配置在bootstrap.properties中,config部分才能被正确加载。因为config的相关配置会先于application.properties,而bootstrap.properties的加载也先于application.properties。
创建ConfigClientApplication类并添加@EnableAutoConfiguration注解,表示自动获取项目中的配置变量:
@SpringBootApplication
@RestController
@EnableAutoConfiguration
public class ConfigClientApplication {
@Value("${key1}")
String foo;
@RequestMapping("/say")
@ResponseBody
public String say(){
return foo;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在ConfigClientApplication中我们创建了一个RestController提供Web服务,输出读取到的key1的值。
启动main方法,看到控制台信息中将有如下一行日志,包含了配置的相关信息:
[main]c.c.c.ConfigServicePropertySourceLocator:Located-environment:name=configServerDemo,profiles=[test],label=branch1,version=575f8f8ded872700c7abcfb6bbbecf02f9271a17, state=null
现在我们尝试访问http://localhost:8084/say ,会得到branch1-test-value1的响应。
下面我们来一起了解Spring Cloud Config Client可能用到的常用配置。
(1)客户端快速失败
有的时候,需要在Config Server连接不上时直接启动失败。需要这个特性时可以设置bootstrap配置项spring.cloud.config.failFast=true来开启。
(2)客户端重试
可以在Config Server不可用时,让客户端重试。可以通过设置“spring.cloud.config.failFast=false;”在classpath中增加spring-retry、spring-boot-starter-aop依赖。默认情况下会重试6次,每次间隔1000ms并以1.1乘以次数方式递增。也可以通过'spring.cloud.config.retry'系列配置来修改相关配置。而且我们可以自己实现一个RetryOperations-Interceptor来详细地自定义重试策略。
(3)HTTP权限
如果要对HTTP请求进行账号密码的权限控制,可以配置服务器URI或单独的用户名和密码属性,bootstrap.yml配置文件如下:
spring:
cloud:
config:
uri: https://user:secret@myconfig.mycompany.com
或者:
spring:
cloud:
config:
uri: https://myconfig.mycompany.com
username: user
password: secret
spring.cloud.config.password和spring.cloud.config.username值覆盖URI中提供的内容。
3.4 进阶场景
3.4.1 热生效
在应用运行时经常会有修改配置的需求,那么在Spring Cloud Config中如何让修改Git仓库的配置动态生效呢?我们在ConfigClientApplication类上加上@RefreshScope注解并在Config Client的pom.xml中添加spring-boot-starter-actuator监控模块,其中包含了/refresh刷新API,并启动Config Client。如下为pom文件中的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在配置完之后,我们进行如下尝试来验证配置时进行了热生效。
步骤1 访问http://localhost:8084/say 得到响应“key1: master-test-value1”。
步骤2 将configServerDemo-test.properties的内容修改为master-test-value2并通过Git提交对配置文件的修改。
步骤3 请求Config Client的http://localhost:8084/say ,依旧是“key1: master-test-value1”。因为Client未接到任何通知进行本地配置更新。
步骤4 通过POST访问http://localhost:8084/refresh 得到响应["key1"],表明key1已被更新。
步骤5 访问http://localhost:8084/say ,相应内容已经变成了master-test-value2。
然而,如果每次修改了配置文件就要手动请求/refresh,这一定不是我们想要的效果。在第11章中我们将介绍如何使用Bus来通知Spring Cloud Config,如图3-2所示。
3.4.2 高可用
在上文中我们以在配置文件中指定配置中心Config Server的实例地址的方式来定位Config Server。一旦遇到Config Server宕机,Config Client将无法继续获取配置,且对Config Server进行横向扩展时也需要修改每一个Config Client的配置文件。当单台Config Server压力过大时,客户端也无法做到负载均衡。
Spring Cloud针对Config Server同样支持通过Eureka进行服务注册的方式。我们将Config Server的所有实例以服务提供方的形式注册在Eureka上。Config Client以服务消费方的形式去Eureka获取Config Server的实例,这样也就同时支持由Eureka组件提供的故障转移、服务发现、负载均衡等功能。
我们接下来对之前的Config Server进行改造,在Config Server的Maven pom.xml上增加Eureka的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
在配置文件application.yml中追加Eureka的注册中心地址的配置:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8989/eureka/
在ConfigServerApplication.java主类中标注@EnableEurekaClient注解。
接下来,启动Config Server,并查看Eureka控制台,可以看见已经注册的服务提供方。之后需要让Config Client去Eureka获取Config Server地址。我们来对Config Server进行改造。
在Maven中新增对Eureka的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
在bootstrap.yml中新增Eureka注册中心地址的配置,并去掉spring.cloud.config.uri的静态指定。通过spring.cloud.config.discovery.enabled指定打开Spring Cloud Config的动态发现服务功能的开关,通过spring.cloud.config.discovery.serviceId指定在注册中心的配置中心中的ServiceId。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8989/eureka/
spring:
application:
name: cloudConfigDemo${server.port}
cloud:
config:
profile: dev
label: master
name: configServerDemo
discovery:
enabled: true
service-id: myConfigServer
#uri: http://localhost:8888/
在ConfigClientApplication.java主类中增加@EnableDiscoveryClient注解,使其打开服务发现客户端功能。
启动Config Client时可以发现,如果配置服务部署多份,通过负载均衡,可以实现高可用。
3.4.3 安全与加解密
使用Spring Cloud Config时,可能有些场景需要将配置服务暴露在公网或者其他需要加权限安全控制的场景。可以使用Spring Security来整合Spring Cloud Config。
使用Spring Boot默认的基于HTTP安全方式,仅仅需要引入Spring Security依赖(如:可以通过spring-boot-starter-security)。引入此依赖的默认情况是使用一个用户名和一个随机产生的密码,这种方式并不是很靠谱,因此,建议通过spring-boot-starter-security配置密码,并对其进行加密处理。
在默认情况下,启动Config Server时,会看到启动日志中有如下类似信息:
b.a.s.AuthenticationManagerConfiguration :
Using default security password: 7bbc28c2-b60f-4996-8eb5-87b4f57e976c
这就是Spring Security默认生成的密码,同样也可以通过配置文件自定义账号和密码:
security:
user:
name: testuser
password: testpassword
1.服务端加解密
在实际生产环境使用过程中,就算加入了账号和密码等方式的权限控制,数据存储在Config Server中依旧可能被泄露,那么可以对数据加密后再存储在Config Server中。
如果远程资源是一个经过加密的内容(以{cipher}开头),在发送给客户端之前运行时会被解密。这样,配置内容就不用明文存放了。
我们先来使用JDK自带的keytool工具生成加解密时所需要用到的密钥:
keytool
-genkey
-alias cloudtest (别名)
-keypass 123456 (别名密码)
-keyalg RSA (算法)
-validity 365 (有效期,天)
-keystore mykey.keystore (指定生成证书的位置和证书名称)
-storepass mypass (获取keystore信息的密码)
接下来需要填入一些无关紧要的信息:
你的名字与姓氏是什么?
[Unknown]: hjh
你的组织单位名称是什么?
[Unknown]: spring
你的组织名称是什么?
[Unknown]: spring
你所在的城市或区域名称是什么?
[Unknown]: shanghai
你所在的省/市/自治区名称是什么?
[Unknown]: cn
该单位的双字母国家/地区代码是什么?
[Unknown]: cn
CN=hjh, OU=spring, O=spring, L=shanghai, ST=cn, C=cn是否正确?
[否]: y
接下来将密钥信息复制进Resources目录并配置进Config Server配置文件中:
encrypt:
key-store:
location: classpath:mykey.keystore
password: mypass
alias: cloudtest
secret: 123456application.yml
在location中也可以使用file://来配置文件路径。
接下来尝试访问http://localhost:8888/encrypt 并以POST方式提交需要加密的内容。
$ curl localhost:8888/encrypt -d mysecret
AQATZVzrgr9M+doCEiRdL44JD2rB+A2HzX/I6Sec6w04+VW+znApTHZoiJhL0Fn4+3u73aUi5euejvokwmAx+ttBPX8UrhxMcDHZmqj1ADm2XAqX1/NEJtkcfVSFCrkyAztzlT/u+6/uzHRUMZhiJDn41yYtGKtt9/zlni9WKcEBxhSb2XMYuJL21EL2q4w2rD9awLYfJBy4MD6fbPC2mlZ0XCFuCDR7mslneLQtB/bkKcVUR/p5g8GJ8qWUt9T6DGQ52QgxTCoRvJcUFzulRD+A3b4UhuHmumdP0i7wM+hnTI+6h/HXVZ33Ju8SGRtnYXp7Bnz69T4NPZRT7Ov6S/4/IJMObwrSNSfZv7tAV2BSRj4U6xhBCCAcXdVrTHQzlpM=
请求返回的内容就是服务端根据我们配置的密钥加密后的结果。
同样,以POST方式请求http://localhost:8888/decrypt 并传入密文,将会返回解密后的结果。
$ curl localhost:8888/decrypt -d AQATZVzrgr9M+doCEiRdL44JD2rB+A2HzX/I6Sec6w04+VW+znApTHZoiJhL0Fn4+3u73aUi5euejvokwmAx+ttBPX8UrhxMcDHZmqj1ADm2XAqX1/NEJtkcfVSFCrkyAztzlT/u+6/uzHRUMZhiJDn41yYtGKtt9/zlni9WKcEBxhSb2XMYuJL21EL2q4w2rD9awLYfJBy4MD6fbPC2mlZ0XCFuCDR7mslneLQtB/bkKcVUR/p5g8GJ8qWUt9T6DGQ52QgxTCoRvJcUFzulRD+A3b4UhuHmumdP0i7wM+hnTI+6h/HXVZ33Ju8SGRtnYXp7Bnz69T4NPZRT7Ov6S/4/IJMObwrSNSfZv7tAV2BSRj4U6xhBCCAcXdVrTHQzlpM=
asdasd
如果在请求/encrypt和/decrypt的时候服务端抛出“Illegal key size”异常,则表明JDK中没有安装Java Cryptography Extension。
Java Cryptography Extension(JCE)是一组包,提供用于加密、密钥生成和协商以及MAC(Message Authentication Code)算法的框架和实现,提供对对称、不对称、块和流密码的加密支持,还支持安全流和密封的对象。它不提供对外出口,用它开发并完成封装后将无法调用。
下载地址为http://www.oracle.com/technetwork/java/javase/downloads/index.html ,下载并解压完成后,将其复制到JDK/jre/lib/security中即可。
在实际使用过程中,只需要将生成好的密文以{cipher}开头填入配置中即可。
spring:
datasource:
username: dbuser
password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
如果使用properties格式配置文件,则加密数据不要加上双引号。可以在application.properties中加入如下配置:
spring.datasource.username: dbuser
spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
这样就可以安全共享此文件,同时可以保护密码。
2.客户端解密
有的时候需要客户端自行对密文进行解密,而不是在服务端解密。这就需要明确指定配置数据在服务端发出时不解密:spring.cloud.config.server.encrypt.enabled=false。
3.4.4 自定义格式文件支持
在某些场景可能不总是以YAML、Properties、JSON等格式获取配置文件,可能内容是自定义格式的,希望Config Server将其以纯文本方式来处理而不做其他加工。Config Server提供了一个访问端点/{name}/{profile}/{label}/{path}来支持这种需求,这里的{path}是指文件名。
当资源文件被找到后,与常规的配置文件一样,也会先处理占位符。例如,在上文案例的Git仓库中上传:
nginx.conf
server {
listen 80;
mykey ${key1};
}
接下来重启Config Server并请求Nginx的配置文件。如尝试请求http://localhost:8888/configServerDemo/dev/master/nginx.conf ,将会得到如下响应:
server {
listen 80;
mykey master-dev-value-dev;
}
可以看到正常匹配了我们上传的自定义格式文件,并替换了占位符。
3.5 其他仓库的实现配置
1.配置Git
在应用配置文件与特定配置文件中可以通过正则表达式来支持更为复杂的情况。在{application}/{profile}中可以使用通配符进行匹配,如果有多个值可以使用逗号分隔,配置文件示例如下:
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
如果{application}/{profile}没有匹配到任何资源,则使用spring.cloud.config.server.git.uri配置的默认URI。
上面例子中pattern属性是一个YAML数组,也可以使用YAML数组格式来定义。这样可以设置成多个配置文件,示例如下:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
repos:
development:
pattern:
- */development
- */staging
uri: https://github.com/development/config-repo
staging:
pattern:
- */qa
- */production
uri: https://github.com/staging/config-repo
每个资源库有一个可选的配置,用来指定扫描路径,示例如下:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
searchPaths: foo,bar*
这样系统就会自动搜索foo的子目录,以及以bar开头的文件夹中的子目录。
默认情况下,当第一次请求配置时,系统复制远程资源库。系统也可以配置成一启动就复制远程资源库,示例如下:
spring:
cloud:
config:
server:
git:
uri: https://git/common/config-repo.git
repos:
team-a:
pattern: team-a-*
cloneOnStart: true
uri: http://git/team-a/config-repo.git
team-b:
pattern: team-b-*
cloneOnStart: false
uri: http://git/team-b/config-repo.git
team-c:
pattern: team-c-*
uri: http://git/team-a/config-repo.git
上面的例子中team-a的资源库会在启动时就从远程资源库进行复制,其他的则等到第一次请求时才从远程资源库复制。
2.配置权限与HTTPS
如果远程资源库设置了权限认证,则可以如下配置:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
username: trolley
password: strongpassword
如果不使用HTTPS和用户认证,可以使用SSH URI的格式。例如,git@github.com:configuration/cloud-configuration,这就需要先有SSH的key。这种方式系统会使用JGit库进行访问,可以去查看相关文档。可以在~/.git/config中设置HTTPS代理配置,也可以通过JVM参数-Dhttps.proxyHost、-Dhttps.proxyPort来配置代理。
用户不知道自己的~/.git目录时,可以使用git config --global来指定。例如:git config --global http.sslVerify false。
3.配置SVN
如果希望使用SVN充当配置仓库来替换Git,配置也与Git类似,同样支持账户、密码、搜索路径等配置,这里不再赘述,SVN配置示例如下:
spring:
cloud:
config:
server:
svn:
uri: https://subversion.assembla.com/svn/spring-cloud-config-repo/
#git:
# uri: https://github.com/pcf-guides/configuration-server-config-repo
default-label: trunk
profiles:
active: subversion
4.配置本地仓库
如果希望配置仓库从本地classpath或者文件系统加载配置文件,可以通过spring.profiles.active=native开启。默认从classpath中加载,如果使用“file:”前缀加载文件系统,则从本地路径中加载。当然也可以使用${}样式的环境占位符,例如:file:///${user.home}/config-repo。
3.6 小结
有了Spring Cloud Config,可以实现对任意一个集成过的Spring程序进行动态化参数配置及热生效等相关操作,从而实现程序与配置隔离,解耦编码与环境之间的耦合。这同样是微服务架构所需要的。接下来我们将进入下一章开始学习服务端之间的调用。