第3章
配置中心: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:[email protected]
或者:
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 通路
得到響應“key1: master-test-value1”。
步驟2 将configServerDemo-test.properties的内容修改為master-test-value2并通過Git送出對配置檔案的修改。
步驟3 請求Config Client的
,依舊是“key1: master-test-value1”。因為Client未接到任何通知進行本地配置更新。
步驟4 通過POST通路
http://localhost:8084/refresh得到響應["key1"],表明key1已被更新。
步驟5 通路
,相應内容已經變成了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,将會得到如下響應:
listen 80;
mykey master-dev-value-dev;
可以看到正常比對了我們上傳的自定義格式檔案,并替換了占位符。
3.5 其他倉庫的實作配置
1.配置Git
在應用配置檔案與特定配置檔案中可以通過正規表達式來支援更為複雜的情況。在{application}/{profile}中可以使用通配符進行比對,如果有多個值可以使用逗号分隔,配置檔案示例如下:
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數組格式來定義。這樣可以設定成多個配置檔案,示例如下:
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
每個資源庫有一個可選的配置,用來指定掃描路徑,示例如下:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
searchPaths: foo,bar*
這樣系統就會自動搜尋foo的子目錄,以及以bar開頭的檔案夾中的子目錄。
預設情況下,當第一次請求配置時,系統複制遠端資源庫。系統也可以配置成一啟動就複制遠端資源庫,示例如下:
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
如果遠端資源庫設定了權限認證,則可以如下配置:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
username: trolley
password: strongpassword
如果不使用HTTPS和使用者認證,可以使用SSH URI的格式。例如,[email protected]: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配置示例如下:
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程式進行動态化參數配置及熱生效等相關操作,進而實作程式與配置隔離,解耦編碼與環境之間的耦合。這同樣是微服務架構所需要的。接下來我們将進入下一章開始學習服務端之間的調用。