1 Preface
Configurability is a feature that a mature software system should provide, and configuration management is important for large systems, especially for microservices systems with multiple applications. Happily, Spring
provides us with good configuration management, such as the powerful configuration of Springboot
. For Spring Cloud
, there is the powerful Spring Cloud Config
which is very useful for distributed system configuration management by providing a configuration management outside the application, such as a file or Git
repository.
2 Quick Experience
The Spring Cloud Config
server is a Springboot
application that is very easy to start and deploy.
The overall architecture is shown in the following figure.
2.1 The server side is a Springboot
Add a dependency to Springboot
as follows.
1
2
3
4
5
|
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
|
That’s all, it already contains web
and actuator
.
Add the Java
main class.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package com.pkslow.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class,args);
}
}
|
Compared to a normal Springboot
application it just has one more annotation @EnableConfigServer
.
2.2 Configuration repositories
We will manage the configuration through version control, generally using the Git
repository, but for simplicity we will use a local repository as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 创建目录
mkdir git-repo
# 初始化一个git目录
git init
# 新建文件
touch application.properties
# 添加变更
git add .
# 提交变更
git commit -m "init"
|
Configure the project’s application.properties
, note that it’s for the Config Server
project and not in the git-repo
directory.
1
2
3
|
server.port=8888
spring.application.name=config-server
spring.cloud.config.server.git.uri=/Users/pkslow/IdeaProjects/pkslow-modules/config-server/git-repo
|
Then you can start Config Server
.
But there is nothing in the git
repository for the config file, so we add the following and commit it (you have to commit it or it won’t be available).
1
2
3
|
pkslow.webSite=www.pkslow.com
pkslow.age=18
pkslow.email=admin@pkslow.com
|
2.3 Configuration path matching
So how do we get these configurations? It can be read from the following URL
s.
1
2
3
4
5
|
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
|
label
, which refers to a branch of code, such as master
, feature-1
, etc.
application
, the name of the application, which will be used later when the client reads it.
profile
, generally used to specify the environment, such as prod
, dev
, uat
, etc.
So, we can use the following URL
to get the configuration information we just added.
1
2
3
4
|
http://localhost:8888/application/default
http://localhost:8888/application/default/master
http://localhost:8888/master/application.properties
http://localhost:8888/application-default.properties
|
Use curl to access configuration information.
1
2
|
$ curl http://localhost:8888/application/default/master
{"name":"application","profiles":["default"],"label":"master","version":"8796f39b35095f6e9b7176457eb03dd6d62b1783","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/git-repo/application.properties","source":{"pkslow.webSite":"www.pkslow.com","pkslow.age":"18","pkslow.email":"admin@pkslow.com"}}]}
|
The last address /{label}/{application}-{profile}.properties
returns the result in a different format, returning the profile contents directly.
1
2
3
4
|
$ curl http://localhost:8888/application-default.properties
pkslow.age: 18
pkslow.email: admin@pkslow.com
pkslow.webSite: www.pkslow.com
|
If we first create a branch release-20200809
and change age
to 9
, it will look like this.
1
2
|
$ curl http://localhost:8888/application/default/release-20200809
{"name":"application","profiles":["default"],"label":"release-20200809","version":"7e27e6972ed31ee1a51e9277a2f5c0a628cec67a","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/git-repo/application.properties","source":{"pkslow.webSite":"www.pkslow.com","pkslow.age":"9","pkslow.email":"admin@pkslow.com"}}]}
|
You can see that the corresponding pkslow.age
has changed to 9
, but access to /application/default/master
is still 18
, and the branches do not affect each other.
2.4 Remote repositories
The local repository is for simple demonstration purposes only. In real projects, you can create a new repository in GitHub
as follows.
A private
repository was created to check if the authentication behind it is correct.
The address of the reconfigured repository is as follows.
1
2
3
4
5
|
spring.cloud.config.server.git.uri=https://github.com/pkslow/pkslow-config
spring.cloud.config.server.git.username=admin@pkslow.com
spring.cloud.config.server.git.password=***
spring.cloud.config.server.git.default-label=master
spring.cloud.config.server.git.search-paths=demo
|
Create a demo
directory to hold the configuration, so search-paths
is configured as demo
. Finish the configuration and restart the server, and you can read the configuration of the remote repository normally.
2.5 Multiple code configuration repositories
There are times when our configuration may not be in just one repository, but in the code repository of the respective client, for example, we have the following three services.
- (1) Service discovery: discovery, code repository pkslow-discovery-service
- (2) API gateway: gateway, code repository pkslow-gateway-service
- (3) Order service: order, code repository pkslow-order-service
Their respective configuration files are placed in their own code base, that requires the configuration of multiple code bases. We also configure one more default configuration library pkslow-default, if it does not match, the configuration of the default code base will be selected. The specific configuration is as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: /Users/pkslow/multiple-repos/pkslow-default
repos:
pkslow-discovery-service:
pattern: pkslow-discovery-*
cloneOnStart: true
uri: /Users/pkslow/multiple-repos/pkslow-discovery-service
search-paths: config
pkslow-gateway-service:
pattern: pkslow-gateway-*/dev
cloneOnStart: true
uri: /Users/pkslow/multiple-repos/pkslow-gateway-service
search-paths: config
pkslow-order-service:
pattern: pkslow-order-*
cloneOnStart: true
uri: /Users/pkslow/IdeaProjects/pkslow-order-service
search-paths: config
|
You can define the directory search-paths
where the profiles are placed, and the default is the root directory if not configured.
The configuration rule for pattern
here is {application}/{profile}
, which supports the regular symbol *
. Note that only one result is matched, if both are satisfied, only the first matching repository is taken.
After starting, let’s see the configuration result.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# 默认profile和label,正确匹配
$ curl http://localhost:8888/pkslow-order-service/default/master
{"name":"pkslow-order-service","profiles":["default"],"label":"master","version":"9d86e5d11974f0a0e7c20cd53d8f062755193e70","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-order-service/config/application.properties","source":{"pkslow.webSite":"www.pkslow.com","pkslow.app.name":"order-service"}}]}
# 正确匹配,但不存在的Label,配置库没有对应代码分支,404
$ curl http://localhost:8888/pkslow-order-service/default/release
{"timestamp":"2020-08-13T06:58:38.722+0000","status":404,"error":"Not Found","message":"No such label: release","path":"/pkslow-order-service/default/release"}
# profile为dev,正确匹配
$ curl http://localhost:8888/pkslow-order-service/dev/master
{"name":"pkslow-order-service","profiles":["dev"],"label":"master","version":"9d86e5d11974f0a0e7c20cd53d8f062755193e70","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-order-service/config/application.properties","source":{"pkslow.webSite":"www.pkslow.com","pkslow.app.name":"order-service"}}]}
# 对于gateway只能匹配profile=dev,无法匹配,读取默认配置
$ curl http://localhost:8888/pkslow-gateway-service/default/master
{"name":"pkslow-gateway-service","profiles":["default"],"label":"master","version":"8358f2b4701fac21a0c7776bc46cec6d9442c549","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-base/application.properties","source":{"pkslow.birthDate":"2020-08-10"}}]}
# 对于gateway只能匹配profile=dev,正确匹配
$ curl http://localhost:8888/pkslow-gateway-service/dev/master
{"name":"pkslow-gateway-service","profiles":["dev"],"label":"master","version":"1a4e26849b237dc2592ca0d391daaa1a879747d2","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-gateway-service/config/application.properties","source":{"pkslow.webSite":"www.pkslow.com","pkslow.app.name":"gateway-service"}}]}
# 不存在的服务名,无法匹配,读取默认配置
$ curl http://localhost:8888/unknown-service/dev/master
{"name":"unknown-service","profiles":["dev"],"label":"master","version":"8358f2b4701fac21a0c7776bc46cec6d9442c549","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-base/application.properties","source":{"pkslow.birthDate":"2020-08-10"}}]}
|
3 Client-side use of configuration
After the previous examples we have learned how the server side can get the configuration from the code base, but it is always important to enable the client side to get the corresponding configuration and produce the effect. Let’s demonstrate this.
3.1 Project preparation
Build the simplest Springboot Web
project with Spring Cloud Config
support and add the following dependencies.
1
2
3
4
5
6
7
8
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
|
Add the configuration file: bootstrap.properties
(although we want to read the configuration from the server, some configurations must still be added at the client, such as the server address), with the following content.
1
2
3
|
server.port=8080
spring.application.name=pkslow-gateway-service
spring.cloud.config.uri=http://localhost:8888
|
Here we configure the port of the client, the address of the server and the name of the client application, the name is very critical and will be explained later.
Add Controller
to expose the API
to display the read configuration content.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@RestController
public class PkslowController {
@Value("${pkslow.age}")
private Integer age;
@Value("${pkslow.email}")
private String email;
@Value("${pkslow.webSite}")
private String webSite;
@GetMapping("/pkslow")
public Map<String, String> getConfig() {
Map<String, String> map = new HashMap<>();
map.put("age", age.toString());
map.put("email", email);
map.put("webSite", webSite);
return map;
}
}
|
Then start the client and access the Controller. The result is as follows.
1
2
|
$ curl http://localhost:8080/pkslow
{"webSite":"default.pkslow.com","age":"9","email":"admin@pkslow.com"}
|
These configuration contents are not on the client side, indicating that the configuration information can be obtained from the server side.
3.2 How the client matches
The client has gotten the configuration information, is it correct? And how does the client match it? Actually, as mentioned in the previous section, there are three main pieces of information to match.
label
refers to the code branch.
application
is the name of the application.
profile
is generally used to specify the environment.
The client name in the previous example is pkslow-gateway-service
; label
is not specified and defaults to master
; profile
is not specified and defaults to default
.
Our server-side matching rule is pattern: pkslow-gateway-*/dev
, so we can match the name, but not the profile
, so the default repository configuration is read.
1
2
3
4
|
$ cat pkslow-base/application.properties
pkslow.webSite=default.pkslow.com
pkslow.age=9
pkslow.email=admin@pkslow.com
|
Modify the client configuration by configuring profile
to dev
as follows.
1
2
3
4
|
server.port=8080
spring.application.name=pkslow-gateway-service
spring.profiles.active=dev
spring.cloud.config.uri=http://localhost:8888
|
Get the client configuration again as follows.
1
2
|
$ curl http://localhost:8080/pkslow
{"webSite":"gateway-master.pkslow.com","age":"9","email":"admin@pkslow.com"}
|
You can see that the configuration of the pkslow-gateway-service
repository has been read.
1
2
3
4
|
$ cat pkslow-gateway-service/config/application.properties
pkslow.webSite=gateway-master.pkslow.com
pkslow.age=9
pkslow.email=admin@pkslow.com
|
Create a new code branch release
in the pkslow-gateway-service
repository and add the configuration, then modify the client configuration as follows.
1
2
3
4
5
|
server.port=8080
spring.application.name=pkslow-gateway-service
spring.profiles.active=dev
spring.cloud.config.label=release
spring.cloud.config.uri=http://localhost:8888
|
After rebooting and accessing again, the configuration of the new branch was read correctly.
1
2
|
$ curl http://localhost:8080/pkslow
{"webSite":"gateway-release.pkslow.com","age":"9","email":"admin@pkslow.com"}
|
3.3 Client configuration takes effect instantly
When we modify the configuration, the result of accessing again is as follows.
1
2
3
4
5
6
7
8
9
|
$ git commit -a -m "update"
[release 0e489fe] update
1 file changed, 2 insertions(+), 2 deletions(-)
$ curl http://localhost:8888/pkslow-gateway-service/dev/release
{"name":"pkslow-gateway-service","profiles":["dev"],"label":"release","version":"0e489fec5de73b1a6d11befa3f65e44836979e23","state":null,"propertySources":[{"name":"/Users/pkslow/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-gateway-service/config/application.properties","source":{"pkslow.webSite":"gateway-release.pkslow.com","pkslow.age":"10","pkslow.email":"admin@pkslow.com"}}]}
$ curl http://localhost:8080/pkslow
{"webSite":"gateway-release.pkslow.com","age":"9","email":"admin@pkslow.com"}
|
It turns out that the server side has taken effect, but the client side has not. This is because in this mode the client will only read the configuration at startup to make it effective. If we want the client to take effect as well, we need to use the /refresh
endpoint provided by the Springboot actuator
to do so.
Start by adding the actuator
dependency.
1
2
3
4
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
|
Activate and open the /refresh
endpoint. Add the following configuration.
1
|
management.endpoints.web.exposure.include=refresh
|
Specify the scope of the refresh by adding the annotation @RefreshScope
to the Controller
.
1
2
3
4
5
|
@RefreshScope
@RestController
public class PkslowController {
//xxx
}
|
Restart the application. The operation and results are as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 修改配置并提交
$ git commit -a -m "update age to 18"
[release fc863bd] update age to 18
1 file changed, 1 insertion(+), 1 deletion(-)
# 服务端配置生效
$ curl http://localhost:8888/pkslow-gateway-service/dev/release
{"name":"pkslow-gateway-service","profiles":["dev"],"label":"release","version":"fc863bd8849fa1dc5eaf2ce0a97afb485f81c2f0","state":null,"propertySources":[{"name":"/Users/larry/IdeaProjects/pkslow-modules/config-server/multiple-repos/pkslow-gateway-service/config/application.properties","source":{"pkslow.webSite":"gateway-release.pkslow.com","pkslow.age":"18","pkslow.email":"admin@pkslow.com"}}]}
# 客户端没有生效
$ curl http://localhost:8080/pkslow
{"webSite":"gateway-release.pkslow.com","age":"10","email":"admin@pkslow.com"}
# 发送POST请求到客户端/refresh
$ curl -X POST http://localhost:8080/actuator/refresh
["config.client.version","pkslow.age"]
# 客户端已经生效
$ curl http://localhost:8080/pkslow
{"webSite":"gateway-release.pkslow.com","age":"18","email":"admin@pkslow.com"}
|
3.4 Automatic configuration update
Every time you commit a configuration, you need to manually send a POST
request to the client to update the configuration, which is obviously not friendly enough. GitHub provides a webhook
function that sends a request to a specified URL
after an event is triggered, so that automatic updates can be made.
Automatic updates via the webhook
function are one-to-one, and cannot be implemented directly this way if there are many clients (which is usually the case). There are two options.
- Implement a port to accept requests from
Git
and distribute them to the servers. This is not recommended because it’s a bit tricky.
- Introduce
Spring Cloud Bus
to refresh multiple clients. This requires the introduction of MQ
, such as kafka
or RabbitMQ
.
3.5 Services Discovery
In a microservices architecture, if both the server and the client are configured to register with service discovery (e.g. eureka
), the client does not need to configure the address of the server anymore and will get the identification from the service discovery center. This is similar to Springboot Admin
in the case of eureka
.
There is nothing special about the code, it just registers both the server and the client to eureka
.
4 Summary
This article shows how to use Spring Cloud Config
step by step with examples, mainly to understand the interaction process and matching rules, everything else is code details, just refer to the official documentation.
Reference https://cloud.tencent.com/developer/news/680840