- 推荐序一
- 推荐序二
- 推荐序三
- 推荐语
- 前言
- 第1章 基础知识
- 第2章 微服务构建:Spring Boot
- 第3章 服务治理:Spring Cloud Eureka
- 第4章 客户端负载均衡:Spring Cloud Ribbon
- 第5章 服务容错保护:Spring Cloud Hystrix
- 第6章 声明式服务调用:Spring Cloud Feign
- 第7章 API网关服务:Spring Cloud Zuul
- 第8章 分布式配置中心:Spring Cloud Config
- 第9章 消息总线:Spring Cloud Bus
- 第10章 消息驱动的微服务:Spring Cloud Stream
- 附录 A Starter POMs
- 后记
服务端详解
在上一节中,我们实现了一个具备基本结构的配置管理服务端和客户端,同时讲解了其中一些配置的基本原理和规则。在本节中,我们将进一步介绍Spring Cloud Config服务端的一些相关知识和用法。
基础架构
在动手实践了上面关于Spring Cloud Config的基础入门内容之后,在这里我们深入理解一下它是如何运作起来的。下图所示的是上一节我们所构建案例的基本结构。
其中,主要包含下面几个要素。
- 远程 Git 仓库:用来存储配置文件的地方,上例中我们用来存储针对应用名为didispace的多环境配置文件:didispace-{profile}.properties。
- Config Server:这是我们上面构建的分布式配置中心,config-server 工程,在该工程中指定了所要连接的Git仓库位置以及账户、密码等连接信息。
- 本地Git仓库:在Config Server的文件系统中,每次客户端请求获取配置信息时,Config Server从Git仓库中获取最新配置到本地,然后在本地Git仓库中读取并返回。
当远程仓库无法获取时,直接将本地内容返回。
- Service A、Service B:具体的微服务应用,它们指定了Config Server的地址,从而实现从外部化获取应用自己要用的配置信息。这些应用在启动的时候,会向Config Server请求获取配置信息来进行加载。
客户端应用从配置管理中获取配置信息遵从下面的执行流程:
1.应用启动时,根据bootstrap.properties中配置的应用名{application}、环境名{profile}、分支名{label},向Config Server请求获取配置信息。
2.Config Server根据自己维护的Git仓库信息和客户端传递过来的配置定位信息去查找配置信息。
3.通过git clone命令将找到的配置信息下载到Config Server的文件系统中。
4.Config Server创建Spring的ApplicationContext实例,并从Git本地仓库中加载配置文件,最后将这些配置内容读取出来返回给客户端应用。
5.客户端应用在获得外部配置文件后加载到客户端的ApplicationContext实例,该配置内容的优先级高于客户端Jar包内部的配置内容,所以在Jar包中重复的内容将不再被加载。
Config Server巧妙地通过git clone将配置信息存于本地,起到了缓存的作用,即使当Git服务端无法访问的时候,依然可以取Config Server中的缓存内容进行使用。
Git配置仓库
在Spring Cloud Config的服务端,对于配置仓库的默认实现采用了Git。Git非常适用于存储配置内容,它可以非常方便地使用各种第三方工具来对其内容进行管理更新和版本化,同时Git仓库的Hook功能还可以帮助我们实时地监控配置内容的修改。其中,Git自身的版本控制功能正是其他一些配置中心所欠缺的,通过 Git 进行存储意味着,一个应用的不同部署实例可以从Spring Cloud Config的服务端获取不同的版本配置,从而支持一些特殊的应用场景。
由于Spring Cloud Config中默认使用Git,所以对于Git的配置也非常简单,只需在Config Server的application.properties中设置spring.cloud.config.server.git.uri属性,为其指定Git仓库的网络地址和账户信息即可,比如在快速入门一节中的例子:
spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/SpringCloud-Learning/
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=password
如果我们将该值通过 file://前缀来设置为一个文件地址(在 Windows 系统中,需要使用file:///来定位文件内容),那么它将以本地仓库的方式运行,这样我们就可以脱离Git服务端来快速进行调试与开发,比如:
spring.cloud.config.server.git.uri=file://${user.home}/config-repo
其中,${user.home}代表当前用户的所属目录。file://配置的本地文件系统方式虽然对于本地开发调试时使用非常方便,但是该方式也仅用于开发与测试,在生产环境中请务必搭建自己的Git仓库来存储配置资源。
占位符配置URI
{application}、{profile}、{label}这些占位符除了用于标识配置文件的规则之外,还可以用于 Config Server 中对 Git 仓库地址的 URI 配置。比如,我们可以通过{application}占位符来实现一个应用对应一个Git仓库目录的配置效果,具体配置实现如下:
spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/{application}
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=password
其中,{application}代表了应用名,所以当客户端应用向Config Server发起获取配置的请求时,Config Server会根据客户端的spring.application.name信息来填充{application}占位符以定位配置资源的存储位置,从而实现根据微服务应用的属性动态获取不同位置的配置。另外,在这些占位符中,{label}参数较为特别,如果Git的分支和标签名包含“/”,那么{label}参数在HTTP的URL中应该使用“(_)”来替代,以避免改变了URI含义,指向到其他的URI资源。
当我们使用 Git 作为配置中心来存储各个微服务应用配置文件的时候,该功能会变得非常有用,通过在URI中使用占位符可以帮助我们规划和实现通用的仓库配置。例如,我们可以对微服务应用做如下规划。
- 代码库: 使用服务名作为Git 仓库名称,比如会员服务的代码库http://git.oschina.net/didispace/member-service。
- 配置库: 使用服务名加上-config后缀作为Git仓库名称,比如上面会员服务对应的配置库地址位置http://git.oschina.net/didispace/member-serviceconfig。
这时,我们就可以使用 spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/{application}-config 配置,来同时匹配多个不同服务的配置仓库。
配置多个仓库
Config Server除了可以通过application和profile模式来匹配配置仓库之外,还支持通过带有通配符的表达式来匹配,以实现更为复杂的配置需求。并且当我们有多个匹配规则的时候,还可以用逗号来分割多个{application}/{profile}配置规则,比如:
spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/config-repo
spring.cloud.config.server.git.repos.dev.pattern=dev/*
spring.cloud.config.server.git.repos.dev.uri=file://home/git/config-repo
spring.cloud.config.server.git.repos.test=http://git.oschina.net/test/config-repo
spring.cloud.config.server.git.repos.prod.pattern=prod/pp*,online/oo*
spring.cloud.config.server.git.repos.prod.uri=http://git.oschina.net/prod/confi g-repo
上述配置内容通过 spring.cloud.config.server.git.uri 属性,指定了一个默认的仓库位置,当使用{application}/{profile}模式未能匹配到合适的仓库时,就将在该默认仓库位置下获取配置信息。除此之外,还配置了三个仓库,分别是dev、test、prod。其中,dev 仓库匹配 dev/*的模式,所以无论 profile 是什么,它都能匹配application名称为dev的应用。并且我们可以注意到,它存储的配置文件位置还采用了 Config Server 的本地文件系统中的内容。对于此配置,我们可以通过访问http://localhost:7001/dev/profile 的请求来验证到该仓库的配置内容,其中profile可以为任意值。而test和prod仓库均使用了Git仓库的存储,并且test仓库未配置匹配规则,所以它只匹配application名为test的应用;prod仓库则需要匹配application为prod并且profile为pp开头,或者application为online并且profile为oo开头的应用和环境。
当配置多个仓库的时候,Config Server在启动时会直接克隆第一个仓库的配置库,其他的配置库只有在请求时才会克隆到本地,所以对于仓库的排列可以根据配置内容的重要程度有所区分。另外,如果表达式是以通配符开始的,那么需要使用引号将配置内容引起来。
子目录存储
除了支持占位符配置、多仓库配置之外,Config Server 还可以将配置文件定位到 Git仓库的子目录中。有心的读者,或许还能记得在快速入门中,我们除了配置spring.cloud.config.server.git.uri 属性之外,还配置了另外一个参数:spring.cloud.config.server.git.searchPaths,通过该参数我们实现了在http://git.oschina.net/didispace/SpringCloud-Learning/仓库的spring_cloud_in_action/config-repo子目录下实现配置的存储。
对于 spring.cloud.config.server.git.searchPaths 参数的配置也支持使用{application}、{profile}和{label}占位符,比如:
spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/SpringCloud-Learning/
spring.cloud.config.server.git.searchPaths={application}
通过上面的配置,我们可以实现在http://git.oschina.net/didispace/SpringCloud-Learning/仓库中,一个应用一个目录的效果。
访问权限
Config Server在访问Git仓库的时候,若采用HTTP的方式进行认证,那么我们需要增加username和password属性来配置账户(快速入门中也是如此实现),比如:
spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/application
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=password
若不使用HTTP的认证方式,我们也可采用SSH的方式,通过生成Key并在Git仓库中进行配置匹配以实现访问。
SVN配置仓库
Config Server除了支持Git仓库之外,也能使用SVN仓库,只需要做如下配置。
- 在pom.xml中引入SVN的依赖配置,让Config Server拥有读取SVN内容的能力:
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.8.10</version>
</dependency>
- 在application.properties中使用SVN的配置属性来指定SVN服务器的位置,以及访问的账户名与密码:
spring.cloud.config.server.svn.uri=svn://localhost:443/didispace/config-repo
spring.cloud.config.server.svn.username=username
spring.cloud.config.server.svn.password=password
通过上面的配置修改,Config Server就可以使用SVN作为仓库来存储配置文件了,而对于客户端来说,这个过程是透明的,所以不需要做任何变动。
本地仓库
在使用了Git或SVN仓库之后,文件都会在Config Server的本地文件系统中存储一份,这些文件默认会被存储于以 config-repo 为前缀的临时目录中,比如名为/tmp/config-repo-<随机数>的目录。由于其随机性以及临时目录的特性,可能会有一些不可预知的后果,为了避免将来可能会出现的问题,最好的办法就是指定一个固定的位置来存储这些重要信息。我们只需要通过spring.cloud.config.server.git.basedir或spring.cloud.config.server.svn.basedir来配置一个我们准备好的目录即可。
本地文件系统
Spring Cloud Config也提供了一种不使用Git仓库或SVN仓库的存储方式,而是使用本地文件系统的存储方式来保存配置信息。实现方式也非常简单,我们只需要设置属性spring.profiles.active=native,Config Server 会默认从应用的 src/main/resource 目录下搜索配置文件。如果需要指定搜索配置文件的路径,我们可以通过spring.cloud.config.server.native.searchLocations 属性来指定具体的配置文件位置。
虽然Spring Cloud Config提供了这样的功能,但是为了支持更好的内容管理和版本控制等强大功能,还是推荐使用Git仓库的方式。
健康监测
当使用占位符来配置 URI 的时候,很有可能在控制台中出现类似这样的警告信息(以spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/{application}-config配置为例):
o.s.c.c.s.e.JGitEnvironmentRepository : Could not fetch remote for master remote:
http://git.oschina.net/didispace/app-config
而引起该警告的原因是由于 Spring Cloud Config 的服务端为spring-bootactuator 模块的/health 端点实现了对应的健康检测器:org.springframework.cloud.config.server.config.ConfigServerHealthIndicator。在该检测器中,默认构建了一个application为app的仓库。而根据之前的配置规则:{application}config.git,该检测器会不断地检查http://git.oschina.net/didispace/app-config 仓库是否可以连通。此时,我们可以访问配置中心的/health 端点来查看它的健康状态,具体返回信息如下:
"configServer": {
"status": "DOWN",
"repository": {
"application": "app",
"profiles": "default"
},
"error": "org.springframework.cloud.config.server.environment.NoSuchLabelException:No such label: master"
}
从/health 端点的返回信息中,我们可以看到,由于无法连通http://git.oschina.net/didispace/app-config仓库,使得配置中心的可用状态是DOWN。虽然我们依然可以通过URI的方式访问该配置中心,但是当将配置中心服务化使用的时候,该状态将影响它的服务可用性判断。所以,我们需要了解它的健康检测配置,并让它的健康检测器正常工作起来。
根据默认配置规则,我们可以直接在Git仓库中创建一个名为app-config的配置库,让健康检测器能够访问到它。另外,也可以配置一个实际存在的仓库来进行连通检测,比如下面的配置,它实现了通过连接check-repo-config仓库来进行健康监测:
spring.cloud.config.server.git.uri=http://git.oschina.net/didispace/{applicatio n}-config
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.username=password
spring.cloud.config.server.health.repositories.check.name=check-repo
spring.cloud.config.server.health.repositories.check.label=master
spring.cloud.config.server.health.repositories.check.profiles=default
由于健康检测的repositories是个Map对象,所以实际使用时我们可以配置多个。而每个配置中包含了与定位仓库地址时类似的三个元素。
- name:应用名。
- label:分支名。
- profiles:环境名。
如果我们不想使用该健康检测器,也可以通过使用 spring.cloud.config.server.health.enabled=false参数设置来关闭它。
属性覆盖
Config Server还有一个“属性覆盖”的特性,它可以让开发人员为所有的应用提供配置属性,只需要通过spring.cloud.config.server.overrides属性来设置键值对的参数,这些参数会以Map的方式加载到客户端的配置中。比如:
spring.cloud.config.server.overrides.name=didi
spring.cloud.config.server.overrides.from=shanghai
通过该属性配置的参数,不会被Spring Cloud的客户端修改,并且Spring Cloud客户端从Config Server中获取配置信息时,都会取得这些配置信息。利用该特性可以方便地为Spring Cloud应用配置一些共同属性或是默认属性。当然,这些属性并非强制的,我们可以通过改变客户端中更高优先级的配置方式(比如,配置环境变量或是系统属性),来选择是否使用Config Server提供的默认值。
安全保护
由于配置中心存储的内容比较敏感,做一定的安全处理是必需的。为配置中心实现安全保护的方式有很多,比如物理网络限制、OAuth2授权等。不过,由于我们的微服务应用和配置中心都构建于Spring Boot基础上,所以与Spring Security结合使用会更加方便。
我们只需要在配置中心的pom.xml中加入spring-boot-starter-security依赖,不需要做任何其他改动就能实现对配置中心访问的安全保护。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
默认情况下,我们可以获得一个名为user 的用户,并且在配置中心启动的时候,在日志中打印出该用户的随机密码,具体如下:
INFO 22028---[ main]b.a.s.AuthenticationManagerConfiguration : Using
default security password: 1a32a848-da0c-4590-9c58-e860be8c50dd
大多数情况下,我们并不会使用随机生成密码的机制。我们可以在配置文件中指定用户和密码,比如:
security.user.name=user
security.user.password=37cc5635-559b-4e6f-b633-7e932b813f73
由于我们已经为config-server设置了安全保护,如果这时候连接到配置中心的客户端中没有设置对应的安全信息,在获取配置信息时会返回401错误。所以,需要通过配置的方式在客户端中加入安全信息来通过校验,比如:
spring.cloud.config.username=user
spring.cloud.config.password=37cc5635-559b-4e6f-b633-7e932b813f73
加密解密
在微服务架构中,我们通常会采用 DevOps 的组织方式来降低因团队间沟通造成的巨大成本,以加速微服务应用的交付能力。这就使得原本由运维团队控制的线上信息将交由微服务所属组织的成员自行维护,其中将会包括大量的敏感信息,比如数据库的账户与密码等。显然,如果我们直接将敏感信息以明文的方式存储于微服务应用的配置文件中是非常危险的。针对这个问题,Spring Cloud Config提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。比如下面的例子:
spring.datasource.username=didi
spring.datasource.password={cipher}dba6505baa81d78bd08799d8d4429de499bd4c2053c0 5f029e7cfbf143695f5b
在Spring Cloud Config中通过在属性值前使用{cipher}前缀来标注该内容是一个加密值,当微服务客户端加载配置时,配置中心会自动为带有{cipher}前缀的值进行解密。通过该机制的实现,运维团队就可以放心地将线上信息的加密资源给到微服务团队,而不用担心这些敏感信息遭到泄露了。下面我们来具体介绍如何在配置中心使用该项功能。
使用前提
在使用Spring Cloud Config的加密解密功能时,有一个必要的前提需要我们注意。为了启用该功能,我们需要在配置中心的运行环境中安装不限长度的 JCE 版本(Unlimited Strength Java Cryptography Extension)。虽然,JCE功能在JRE中自带,但是默认使用的是有长度限制的版本。我们可以从Oracle的官方网站下载到它,它是一个压缩包,解压后可以看到下面三个文件:
README.txt
local_policy.jar
US_export_policy.jar
我们需要将 local_policy.jar 和 US_export_policy.jar 两个文件复制到$JAVA_HOME/jre/lib/security目录下,覆盖原来的默认内容。到这里,加密解密的准备工作就完成了。
相关端点
在完成了JCE的安装后,可以尝试启动配置中心。在控制台中,将会输出一些配置中心特有的端点,主要包括如下几个。
- /encrypt/status:查看加密功能状态的端点。
- /key:查看密钥的端点。
- /encrypt:对请求的body内容进行加密的端点。
- /decrypt:对请求的body内容进行解密的端点。
可以尝试通过GET请求访问/encrypt/status端点,我们将得到如下内容:
{
}
"description": "No key was installed for encryption service",
"status": "NO_KEY"
该返回信息说明当前配置中心的加密功能还不能使用,因为没有为加密服务配置对应的密钥。
配置密钥
我们可以通过encrypt.key属性在配置文件中直接指定密钥信息(对称性密钥),比如:
encrypt.key=didispace
加入上述配置信息后,重启配置中心,再访问/encrypt/status端点,我们将得到如下内容:
{
"status": "OK"
}
此时,我们配置中心的加密解密功能就已经可以使用了,不妨尝试访问一下/encrypt和/decrypt端点来使用加密和解密的功能。注意,这两个端点都是POST请求,加密和解密信息需要通过请求体来发送。比如,以 curl 命令为例,我们可以通过下面的方式调用加密与解密端点:
$ curl localhost:7001/encrypt-d didispace
3c70a809bfa24ab88bcb5e1df51cb9e4dd4b8fec88301eb7a18177f1769c849ae9c9f29400c9204
80be2c99406ae28c7
$ curl localhost:7001/decrypt-d
3c70a809bfa24ab88bcb5e1df51cb9e4dd4b8fec88301eb7a18177f1769c849ae9c9f29400c920480be
2c99406ae28c7
didispace
这里,我们通过配置encrypt.key参数来指定密钥的实现方式采用了对称性加密。这种方式实现起来比较简单,只需要配置一个参数即可。另外,我们也可以使用环境变量ENCRYPT_KEY来进行配置,让密钥信息外部化存储。
非对称加密
Spring Cloud Config的配置中心不仅可以使用对称性加密,也可以使用非对称性加密(比如RSA密钥对)。虽然非对称性加密的密钥生成与配置相对复杂一些,但是它具有更高的安全性。下面,我们来具体介绍一下如何使用非对称加密。
首先,需要通过keytool工具来生成密钥对。keytool是JDK中的一个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向其他用户/服务认证自己)或数据完整性以及认证服务。在JDK 1.4以后的版本中都包含了这一工具,它的位置在%JAVA_HOME%\bin\keytool.exe。
生成密钥的具体命令如下所示:
$ keytool-genkeypair-alias config-server-keyalg RSA-keystore config-server.keystore
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
[Unknown]: zhaiyongchao
您的组织单位名称是什么?
[Unknown]: company
您的组织名称是什么?
[Unknown]: organization
您所在的城市或区域名称是什么?
[Unknown]: city
您所在的省/市/自治区名称是什么?
[Unknown]: province
该单位的双字母国家/地区代码是什么?
[Unknown]: china
CN=zhaiyongchao,OU=company,O=organization,L=city,ST=province,C=china是否正确?
[否]: y
输入 <config-server> 的密钥口令
(如果和密钥库口令相同,按回车):
再次输入新口令:
另外,如果不想逐步输入那些提示信息,可以使用-dname 来直接指定,而密钥库口令与密钥口令可使用-storepass和-keypass来直接指定。所以,我们可以通过下面的命令直接创建出与上述命令一样的密钥库:
$ keytool-genkeypair-alias config-server-keyalg RSA \
-dname "CN=zhaiyongchao,OU=company,O=organization,L=city,ST=province,C=china" \
-keypass 222222 \
-keystore config-server.keystore \
-storepass 111111 \
默认情况下,使用上述命令创建的密钥只有90天有效期。如果想要调整它的有效期,可以通过增加-validity参数来实现,比如我们可以通过下面的命令,让密钥的有效期延长到一年:
$ keytool-genkeypair-alias config-server-keyalg RSA \
-dname "CN=zhaiyongchao,OU=company,O=organization,L=city,ST=province,C=china" \
-keypass 222222 \
-keystore config-server.keystore \
-storepass 111111 \
-validity 365 \
上述的三种命令生成方式,最终都会在命令的当前执行目录下生成一个 configserver.keystore文件。下面,我们需要将它保存在配置中心的文件系统中的某个位置,比如放在当前的用户目录下,然后在配置中心中加入相关的配置信息:
encrypt.key-store.location=file://${user.home}/config-server.keystore
encrypt.key-store.alias=config-server
encrypt.key-store.password=111111
encrypt.key-store.secret=222222
如果我们将config-server.keystore放在配置中心的src/main/resource目录下,也可以直接这样配置:encrypt.key-store.location=config-server.keystore。另外,非对称加密的配置信息也可以通过环境变量的方式进行配置,它们对应的具体变量名如下:
ENCRYPT_KEY_STORE_LOCATION
ENCRYPT_KEY_STORE_ALIAS
ENCRYPT_KEY_STORE_PASSWORD
ENCRYPT_KEY_STORE_SECRET
通过环境变量来配置密钥库相关信息可以获得更好的安全性,所以我们将敏感的口令信息存储在配置中心的环境变量中是一种不错的选择。
高可用配置
当要将配置中心部署到生产环境中时,与服务注册中心一样,我们也希望它是一个高可用的应用。Spring Cloud Config实现服务端的高可用非常简单,主要有以下两种方式。
- 传统模式: 不需要为这些服务端做任何额外的配置,只需要遵守一个配置规则,将所有的Config Server都指向同一个Git仓库,这样所有的配置内容就通过统一的共享文件系统来维护。而客户端在指定Config Server位置时,只需要配置Config Server上层的负载均衡设备地址即可,就如下图所示的结构。
- 服务模式: 除了上面这种传统的实现模式之外,我们也可以将Config Server作为一个普通的微服务应用,纳入Eureka的服务治理体系中。这样我们的微服务应用就可以通过配置中心的服务名来获取配置信息,这种方式比起传统的实现模式来说更加有利于维护,因为对于服务端的负载均衡配置和客户端的配置中心指定都通过服务治理机制一并解决了,既实现了高可用,也实现了自维护。由于这部分的实现需要客户端的配合,具体示例读者可详细阅读“客户端详解”一节中的“服务化配置中心”小节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论