spring cloud config 分布式配置中心
Spring Cloud Config是Spring Cloud团队创建的一个全新项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持的,它分为服务端与客户端两个部分。其中服务端也成为分布式配置中心,它是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密/解密信息等访问接口;而客户端则是微服务架构中的各个微服务应用或基础设施,它们通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。Spring Cloud Config实现了对服务端和客户端中环境变量和属性配置的抽象映射,所以它除了适用于Spring构建的应用程序之外,也可以在任何其他语言运行的应用程序中使用。由于Spring Cloud Config实现的配置中心默认采用Git来存储配置信息,所以使用spring Cloud Config构建的配置服务器,天然就支持对微服务应用配置信息的版本管理,并且可以通过Git等客户端工具来方便地管理和访问配置内容。当然它也提供了对其他存储方式的支持,比如SVN仓库、本地化文件系统。接下来,我们从一个简单的入门示例开始学习Spring Cloud Config服务端以及客户端的详细构建与使用方法。
在本节中,我们将演示如何构建基于Git存储的分布式配置中心,同时对配置的详细规则进行讲解,并在客户端中演示如何通过配置指定微服务应用的所属配置中心,并让其能够从配置中心获取配置信息并绑定到代码中的整个过程。
通过Spring Cloud Config构建一个分布式配置中心非常简单,只需要以下三步。
▪️创建一个基础的Spring Boot工程,命名为config-server,并在pom.xml中引入下面的依赖:
![](https://img.haomeiwen.com/i8796251/38356afbd4eb6db4.png)
▪️创建Spring Boot的程序主类,并添加@EnableConfigServer注解,开启Spring Cloud Config的服务端功能。
![](https://img.haomeiwen.com/i8796251/3721fbaab498a802.png)
▪️在application.properties中添加配置服务的基本信息以及Git仓库的相关信息,如下所示:
![](https://img.haomeiwen.com/i8796251/e6399a1b26591dca.png)
其中Git等配置信息分别表示如下内容:
▪️spring.cloud.config.server.git.uri:配置Git仓库位置
▪️spring.cloud.config.server.git.searchPaths:配置仓库路径下单相对搜索位置,可以配置多个
▪️spring.cloud.config.server.git.username:访问Git仓库的用户名
▪️spring.cloud.config.server.git.password:访问Git仓库的用户密码
▪️spring.cloud.config.label:git仓库分支名称
到这里,使用一个通过Spring Cloud Config实现,并使用Git管理内容等分布式配置中心就完成了。我们可以将该应用先启动起来,确保没有错误产生,然后进入下面等地学习内容。
为了验证上面完全的分布式配置中心config-server,根据git配置信息中指定的仓库位置,在https://github.com/erghjmncq6643981/demo/tree/master下创建respo目录作为配置仓库,并根据不同环境新建下面4个配置文件:
▪️didispace.properties
▪️didispace-dev.properties
▪️didispace-test.properties
▪️didispace-prod.properties
![](https://img.haomeiwen.com/i8796251/505f27a371a7ef65.png)
在这4个配置文件中均设置了一个from属性,并为每个配置文件分别设置了不同的值,如下所示:
▪️from=git-default-1.0
▪️from=git-dev-1.0
▪️from=git-test-1.0
▪️from=git-prod-1.0
![](https://img.haomeiwen.com/i8796251/48b087cb595ac32f.png)
![](https://img.haomeiwen.com/i8796251/212dff605952afb6.png)
为了测试版本控制,在该Git仓库的master分支中,我们为from属性加入1.0的后缀,同时创建一个config-label-test分支,并将各配置文件中的值用2.0作为后缀。
完成了这些准备工作之后,我们就可以通过浏览器、POSTMAN或CURL等工具直接来访问我们的配置内容了。访问配置信息的URL与配置文件的映射关系如下所示:
▪️/{application}/{profile}[/{label}]
▪️/{application}-{profile}.yml
▪️/{application}-{profile}.properties
▪️/{label}/{application}-{profile}.properties
上面的url会映射{application}-{profile}.properties对应的配置文件,其中{label}对应Git上不同的分支,默认为master。我们可以尝试构造不同的url来访问不同的配置内容,比如要访问kyle分支,didispace应用的prod不同的配置内容,比如,要访问kyle分支,didispace应用的prod环境,就可以访问这个url:http://localhost:8888/didispace/prod/kyle,并获得如下返回信息。
![](https://img.haomeiwen.com/i8796251/15237dcf58f796f1.png)
![](https://img.haomeiwen.com/i8796251/9ef0093cbb45a562.png)
我们可以看到该JSON中返回了应用名didispace,环境名prod,分支名kyle,以及默认default环境和prod环境的配置内容。另外,之前没有提到过的version,从下雨我们可以观察到,它对应的是在Git上的commit号。
![](https://img.haomeiwen.com/i8796251/b20043d8c56ce40e.png)
同时,我们可以看到config-server的控制台中还输出了下面的内容,配置服务器在从Git中获取配置信息后,会存储一份在config-server的文件系统中,实质上config-sever是通过git clone的文件系统中,实质上config-server是通过git clone命令将配置内容复制了一份在本地存储,然后读取这些内容并返回给微服务应用进行加载。
![](https://img.haomeiwen.com/i8796251/4031956fee037d4a.png)
config-server通过Git在本地仓库暂存,可以有效防止当Git仓库出现故障而引起无法加载配置信息的情况。我们可以通过断开网络,再次发起http://localhost:8888/didispace/prod/kyle请求,在控制台中可输出Could not pull remote for kyle,但是它依然会为该请求返回配置内容,这些内容源于之前访问时存于config-server本地文件系统中的配置内容。
![](https://img.haomeiwen.com/i8796251/734647a0d546a60a.png)
在动手实践了上面关于Spring Cloud Config的基础入门内容之后,在这里我们深入理解一些它是如何运作起来的。下面所示的是上一节我们构建案例的基本结构。
其中,主要包含下面几个要素。
▪️远程Git仓库:用来存储配置文件的地方,上例中我们用来存储针对应用名为didispace的多环境配置文件:didispace-{profile}.properties。
▪️Config Server:这是我们上面构建的分布式配置中心,config-server工程,在该工程中指定了所要连接的Git仓库位置以及账户、密码等连接信息。
▪️本地Git仓库:在Config Server的文件系统中,每次客户端请求获取配置信息时,Config Server从Git仓库中获取最新配置到本地,然后客户端请求获取配置信息时,Config Server从Git仓库中获取最新配置到本地,然后在本地Git仓库中读取并返回。当远程仓库无法获取时,直接将本地内容返回。
▪️Service A、Service B:具体的微服务应用,它们指定了Config Server的地址,从而实现从外部获取应用自己要用的配置信息。这些应用在启动的时候,会向Config Server请求获取配置信息来进行加载。
![](https://img.haomeiwen.com/i8796251/942376c0f188fdc6.png)
客户端应用从配置管理中获取配置信息遵从下面的执行流程:
应用启动时,根据bootstrap.properties中配置的应用名{application}、环境名{profile}、分支名{label},向Config Server请求获取配置信息。
Config Server根据自己维护的Git仓库信息和客户端传递过来的配置定位信息去查找配置信息。
通过git clone命令将找到的配置信息下载到Config Server的文件系统中。
Config Server创建Spring的Applicationcontext实例,并从Git本地仓库中加载配置文件,最后将这些配置内容读取出来返回给客户端应用。
客户端应用在获得外部配置文件后加载到客户端的ApplicationContext实例,该配置内容的优先级高于客户端Jar包内部的配置内容,所以在Jar包中重复的内容将不再被加载。
Config Server巧妙地通过git clone将配置信息存于本地,起到缓存的作用,即使当Git服务端无法访问的时候,依赖可以取Config Server中的缓存内容进行使用。
在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仓库的网络地址和账户信息即可,比如在快速入门一节中的例子:
![](https://img.haomeiwen.com/i8796251/c7bc759a13dc6482.png)
如果我们将该值通过file://前缀来设置为一个文件地址(在Windows系统中,需要使用file:///来定位文件内容),那么它将以本地仓库的方式运行,这样我们就可以脱离Git服务端来进行调试与开发,比如:
![](https://img.haomeiwen.com/i8796251/da3b0f78a223bbf2.png)
其中,${user.home}代表当前用户的所属目录。file://配置的本地文件系统方式虽然对于本地开发调试时使用非常方便,但是该方式也仅用于开发与测试,在生产环境中,请务必大家自己的Git仓库来存储配置资源。
{application}、{profile}、{label}这些占位符除了用于标识配置文件的规则之外,还可以用于Config Server中对Git仓库地址的URI配置。比如,我们可以通过{application}占位符来实现一个应用对应一个Git仓库目录的配置效果,具体配置实现如下:
![](https://img.haomeiwen.com/i8796251/6c4f915004963e50.png)
其中,{application}代表了应用名,所以当客户端应用向Config Server发起获取配置的请求时,Config Server会根据客户端spring.application.name信息来填充{application}占位符以定位配置资源的存储位置,从而实现根据微服务应用的属性动态获取不同的位置的配置。另外,这些占位符中,{label}参数较为特别,如果Git的分支和标签名包含"/",那么{label}参数在HTTP的URL中应该使用"(_)"来替代,以避免改变了URI含义,指向到其他的URI资源。
![](https://img.haomeiwen.com/i8796251/012193e9f883598f.png)
github中目录结构必须如下所示
![](https://img.haomeiwen.com/i8796251/481e9095159c8e5f.png)
注意:假如使用yml文件,需要在占位符上加'',如下图所示
![](https://img.haomeiwen.com/i8796251/bc6ac6456667d4d2.png)
当我们使用Git作为配置中心来存储各个微服务应用配置文件的时候,该功能会变得非常有用,通过在URI中使用占位符可以帮助我们规划和实现通用的仓库配置。例如,我们可以对微服务应用做如下规划。
▪️代码库:使用服务名作为Git仓库名称,比如会员服务的代码库https://github.com/erghjmncq6643981/demo/respo/member-service。
▪️配置库:使用服务名加上-config后缀作为Git仓库名称,比如上面会员服务对应的配置库地址位置https://github.com/erghjmncq6643981/demo/respo/member-service-config。
这时,我们就可以通过使用spring.cloud.config.server.git.rui=https://github.com/erghjmncq6643981/demo/respo/{application}-config配置,来同时匹配多个不同服务的配置仓库。
Config Server除了可以通过application和profile模式来匹配配置仓库之外,还支持通过带有通配符的表达式来匹配,以实现更为复杂的配置需求。并且当我们有多个匹配规则的时候,还可以用逗号来分割多个{application}/{profile}配置规则,比如:
![](https://img.haomeiwen.com/i8796251/49a93d733d5a6336.png)
上述配置内容通过spring.cloud.config.server.git.uri属性,指定了一个默认的仓库位置,当使用{application}/{profile}模式未能匹配到合适的仓库时,就将在该默认仓库位置下获取配置信息。除此之外,还配置了三个仓库,分别是dev、test、prod。其中dev仓库匹配dev/*的模式,所以无论profile是什么,它都能匹配application名称为dev的应用。并且我们可以注意到,它存储的配置文件位置还采用了Config Server的本地文件系统中的内容。对于此配置,我们可以通过访问http://localhost8888/dev/profile的请求来验证到该仓库的配置内容,其中profile可以为任意值。而test和prod仓库均使用了Git仓库的存储,并且test仓库未配置匹配规则,所以它只匹配application名为test的应用;prod仓库则需要匹配application为prod并且profile为pp开头,或者application为online并且profile为oo开头的应用和环境。
当配置多个仓库的时候,Config Server在启动时会直接克隆第一个仓库的配置库,其他的配置库只有在请求时才会克隆到本地,所以对于仓库排列可以根据配置内容的重要程度有所区分。另外,如果表达式是以通配符开始的,那么需要使用引号将配置内容引起来。
由于配置中心存储的内容比较敏感,做一定的安全处理是必需的。为配置中心实现安全保护的方式有很多,比如物理网络限制、OAuth2授权等。不过,由于我们的微服务应用和配置中心都构建于Spring Boot基础上,所以与Spring Security结合使用会更加方便。
我们只需要在配置中心的pom.xml中加入spring-boot-starter-security依赖,不需要做任何其他改动就能实现对配置中心访问的安全保护。
![](https://img.haomeiwen.com/i8796251/1fdefbd07256fe00.png)
默认情况下,我们可以获得一个名为user的用户,并且在配置中心启动的时候,在日志中打印该用户的随机密码,具体如下:
![](https://img.haomeiwen.com/i8796251/ec0cde5b5fe3a62a.png)
大多数情况下,我们并不会使用随机生成密码的机制。我们可以在配置文件中指定用户和密码,比如:
![](https://img.haomeiwen.com/i8796251/81f9a8be51caf935.png)
由于我们已经为config-server设置了安全保护,如果这时候连接到配置中心的客户端中没有设置对应的安全信息,在获取配置信息时会返回401错误。所以,需要通过配置的方式在客户端中加入安全信息来通过校验,比如:
![](https://img.haomeiwen.com/i8796251/c9baaf6129bd74f9.png)
在微服务架构中,我们通常会采用DevOps的组织方式来降低因团队间沟通造成的巨大成本,以加速微服务应用的交付能力。这就使得原本由运维团队控制的线上信息将交由微服务所属组织的成员自行维护,其中将会包括大量的敏感信息,比如数据库的账户与密码等。显然,如果我们直接将敏感信息以明文的方式存储于微服务应用的配置文件中是非常危险的。针对这个问题,Spring Cloud Config提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。比如下面的例子:
![](https://img.haomeiwen.com/i8796251/18af6da1e55939db.png)
在Spring Cloud Config中通过在属性值前使用{cipher}前缀来标注该内容是一个加密值,当微服务客户端加载配置时,配置中心会自动为带有{cipher}前缀的值进行解密。通过该机制的实线,运维团队就可以放心地将线上信息的加密资源给到微服务团队,而不用担心这些敏感信息遭到泄露了。下面我们来具体介绍如何在配置中心使用该项功能。
在使用Spring Cloud Config的加密解密功能时,有一个必要的前提需要我们注意。为了启用该功能,我们需要在配置中心的运行环境中安装不限长度的JCE版本(Unlimited Strength Java Cryptography Extension)。虽然,JCE功能在JRE中自带,但是默认使用的是长度限制的版本。我们可以从Oracle的官方网站下载到它,它是一个压缩包,解压后可以看到下面三个文件:
![](https://img.haomeiwen.com/i8796251/43d0b7e21423aff5.png)
我们需要将local_policy.jar和US_export_policy.jar两个文件复制到$JAVA_HOME/jre/lib/security目录下,覆盖原来的默认内容。到这里,加密解密的准备工作就完成了。
![](https://img.haomeiwen.com/i8796251/03468bf7096206ab.png)
在完成了JCE的安装后,可以尝试启动配置中心。在控制台中,将会输出一些配置中心持有的端点,主要包括如下几个。
▪️/encrypt/status:查看加密功能状态的端点。
▪️/key:查看密钥的端点。
▪️/encrypt:对请求的body内容进行加密的端点。
▪️/decrypt:对请求的body内容进行解密的端点。
可以尝试通过GET请求访问/encrypt/status端点,我们将得到如下内容;
![](https://img.haomeiwen.com/i8796251/bbf9029f7ba46a50.png)
该返回信息说明当前配置中心的加密功能还不能使用,因为没有为加密服务配置对应的密钥。
我们可以通过encrpt.key属性在配置文件中直接指定密钥信息(对称性密钥),比如:
![](https://img.haomeiwen.com/i8796251/a64f760b2524e472.png)
加入上述配置信息后,重启配置中心,再访问/encrpt/status端点,我们将得到如下内容:
![](https://img.haomeiwen.com/i8796251/2220a7af02820596.png)
此时,我们配置中心的加密解密功能就已经可以使用了,不妨尝试访问一下/encrypt和/decrypt端点来使用加密和解密的功能。注意,这两个端点都说POST请求,加密和解密信息需要通过请求体来发送。比如,以curl命令为例,我们可以通过下面的方式调用加密与解密端点:
![](https://img.haomeiwen.com/i8796251/9e8229b9019a1e15.png)
![](https://img.haomeiwen.com/i8796251/a814eeeab4f7194b.png)
![](https://img.haomeiwen.com/i8796251/7fde4de10a547a70.png)
这里,我们通过配置encrypt.key参数来指定密钥的实现方式采用了对称性加密。这种方式实现起来比较简单,只需要配置一个参数即可。另外,我们也可以使用环境变量ENCRYPT_KEY来进行配置,让密钥信息外部化存储。
![](https://img.haomeiwen.com/i8796251/bce1f77981696289.png)
![](https://img.haomeiwen.com/i8796251/fb15994adb51d1c0.png)
在完成了上述验证之后,确定配置服务中心已经正常运作,下面我们尝试如何在服务应用中获取上述 配置信息。
▪️创建一个Spring Boot应用,命名config-client,并在pom.xml中引入下述依赖:
![](https://img.haomeiwen.com/i8796251/3c07d050a7d961fa.png)
▪️创建Spring Boot的应用主类,具体如下:
![](https://img.haomeiwen.com/i8796251/91bbf7894fdd140b.png)
▪️创建bootstrap.properties配置,来指定获取配置文件的config-server位置,例如:
![](https://img.haomeiwen.com/i8796251/9888ac15cf49437d.png)
上述配置参数与Git中存储的配置文件中各个部分的对应关系如下所示。
▪️spring.application.name:对应配置文件规则中的{application}部分
▪️spring.cloud.config.profile:对应配置文件规则中的{profile}部分
▪️spring.cloud.config.label:对应配置文件中的{label}部分
▪️spring.cloud.config.uri:配置中心config-server的地址
▪️spring.cloud.config.discovery.enabled:使用注册中心找寻config-server的地址
▪️spring.cloud.config.discovery.serviceId:配置中心在注册中心的applicationName
这里需要格外注意,上面这些属性必须配置在bootstrap.properties中,这样config-server中的配置信息才能被正确加载。在第2章中,我们详细说明了Spring Boot对配置文件的加载顺序,对于本应用jar包之外的配置文件加载会优先与应用jar包内的配置内容,而通过bootstrap.properties对config-server的配置,使得该应用会从实现了外部化配置。
▪️创建一个RESTful接口来返回配置中心的from属性,通过@Value("from")绑定配置服务中配置的from属性,具体实现如下:
![](https://img.haomeiwen.com/i8796251/304d791e354c8651.png)
▪️除了通过@Value注解绑定注入之外,也可以通过Environment对对象来获取配置属性,比如:
![](https://img.haomeiwen.com/i8796251/1fc688d0eff54d56.png)
启动config-client应用,并访问http://localhost:8881/from,我们就可以根据配置内容输出对应环境的from内容了。根据当前配置,我们可以获得如下返回的内容:
![](https://img.haomeiwen.com/i8796251/03091ce70ff19f58.png)
可以继续通过修改bootstrap.properties中的配置内容获取不同的配置信息来熟悉配置服务中的配置规则。
如果需要給我修改意见的发送邮箱:erghjmncq6643981@163.com
本博客的代码示例已上传GitHub:分布式配置中心https://github.com/erghjmncq6643981/SpringCloudConfig
资料参考:《Spring Cloud 微服务实战》
转发博客,请注明,谢谢。