Spring Cloud Config 是用來集中管理應用設定的利器,但預設用 Git backend,在開發時有時候有點麻煩。我就來介紹怎麼用本地檔案模式搞定開發環境的設定管理。

為什麼要用本地檔案模式?

預設的 Config Server 是用 Git repository 當後端,這對生產環境來說很棒,但開發時有點費事:

  • 要維護一個 Git repo
  • 改設定要 push、pull
  • 不方便快速迭代

用本地檔案模式就簡單多了,直接在電腦上改檔案,重新整理一下就行。

Config Server 的設定

首先建立一個 Spring Boot application 當 Config Server。

1. 加依賴

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

2. 啟動類別標注 @EnableConfigServer

1
2
3
4
5
6
7
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

3. application.yml 設定

這是重點。用 native profile 來啟用本地檔案模式:

1
2
3
4
5
6
7
8
spring:
profiles:
active: native
cloud:
config:
server:
native:
searchLocations: file:///Users/cheng/config/

或者用環境變數:

1
2
3
4
5
6
7
8
spring:
profiles:
active: native
cloud:
config:
server:
native:
searchLocations: file://${user.home}/config/

幾個重點

  • file:/// 是本地檔案路徑(三個 slash)
  • 路徑最後要加 /
  • 可以指定多個路徑,用逗號分隔:file:///path1/,file:///path2/

如果你想要更簡潔的設定,也可以這樣:

1
2
3
4
5
6
7
8
9
10
spring:
profiles:
active: native
config:
import: configserver:
cloud:
config:
server:
native:
searchLocations: classpath:/config/

classpath:/ 的話設定檔會放在 JAR 裡面(通常是 src/main/resources/config/)。

設定檔的命名和組織

Config Server 預期的設定檔名稱有特定格式。假設 client 端的 application name 是 user-service,profiles 是 dev,那麼 Config Server 會依序查找:

  1. user-service-dev.ymluser-service-dev.properties
  2. user-service.ymluser-service.properties

所以假設你的設定檔目錄結構是這樣:

1
2
3
4
5
6
7
config/
├── user-service-dev.yml
├── user-service-prod.yml
├── user-service.yml
├── order-service-dev.yml
├── order-service-prod.yml
└── order-service.yml

然後你有個檔案在 /Users/cheng/config/user-service-dev.yml

1
2
3
4
5
6
7
8
server:
port: 8001

app:
database:
url: jdbc:mysql://localhost:3306/user_db_dev
username: root
password: password

Config Server 的 Endpoint

Config Server 提供了一些 REST endpoint 來取得設定。格式是:

1
/{application}/{profile}[/{label}]

比如:

1
2
3
4
5
6
7
8
9
# 取得 user-service 在 dev profile 的設定
GET http://localhost:8888/user-service/dev

# 取得 user-service 預設 profile 的設定
GET http://localhost:8888/user-service/default

# 如果用 Git backend,{label} 就是 branch 名稱
# 本地模式通常不用 {label}
GET http://localhost:8888/user-service/dev/master

實際運作

假設 Config Server 跑在 http://localhost:8888,我們用 curl 試試:

1
curl http://localhost:8888/user-service/dev

會回傳類似這樣的 JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "user-service",
"profiles": ["dev"],
"label": null,
"version": null,
"state": null,
"propertySources": [
{
"name": "file:///Users/cheng/config/user-service-dev.yml",
"source": {
"server.port": 8001,
"app.database.url": "jdbc:mysql://localhost:3306/user_db_dev",
"app.database.username": "root",
"app.database.password": "password"
}
}
]
}

Client 端的設定

現在來設定 Config Client。假設你的應用叫 user-service,想要讀 dev 的設定。

bootstrap.yml 裡面設定(不是 application.yml!重要):

1
2
3
4
5
6
7
8
spring:
application:
name: user-service
profiles:
active: dev
cloud:
config:
uri: http://localhost:8888

然後在 application.yml 就可以這樣用:

1
2
3
4
5
6
# 這些是預設值,如果 Config Server 有就會被覆蓋
server:
port: 8080

app:
version: 1.0.0

用 @Value 取得設定

在程式裡面用 @Value 取得設定值:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserService {

@Value("${app.database.url}")
private String databaseUrl;

@Value("${app.database.username}")
private String username;

public void connect() {
System.out.println("Connecting to: " + databaseUrl);
}
}

或者用 @ConfigurationProperties 綁定整個設定物件(這個我在另外一篇文章講過了)。

本地模式的限制

要注意本地模式有些限制:

  1. 沒有版本控制 - native 模式不支援 Git 的 label(branch)概念
  2. 不適合生產 - 生產環境還是要用 Git backend,確保可追蹤和回滾
  3. 熱重載有限制 - Config Server 本身改了設定檔,client 端要呼叫 refresh endpoint 才能更新

從 Git 轉到本地模式

開發時用本地模式,生產時切回 Git,可以這樣設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
profiles:
active: ${CONFIG_PROFILE:native}
cloud:
config:
server:
# native profile 設定
native:
searchLocations: file://${CONFIG_PATH:~/config}/

# git profile 設定
git:
uri: https://github.com/myorg/config-repo
searchPaths: "config/{application}"

然後用環境變數決定用哪個:

1
2
3
4
5
6
# 開發環境
export CONFIG_PROFILE=native
export CONFIG_PATH=/Users/cheng/config/

# 生產環境
export CONFIG_PROFILE=git

完整例子

一個完整的開發設定流程:

  1. 建立設定檔目錄 /Users/cheng/config/
  2. 建立 user-service-dev.yml
  3. Config Server 的 application.yml
1
2
3
4
5
6
7
8
9
10
11
server:
port: 8888

spring:
profiles:
active: native
cloud:
config:
server:
native:
searchLocations: file:///Users/cheng/config/
  1. Client 的 bootstrap.yml
1
2
3
4
5
6
7
8
spring:
application:
name: user-service
profiles:
active: dev
cloud:
config:
uri: http://localhost:8888
  1. 啟動 Config Server,然後啟動 client 應用
  2. Client 應用會自動從 Config Server 拉設定

重點整理

  • 開發用 native profile + 本地檔案很方便
  • 設定檔命名:{application}-{profile}.yml
  • 設定要在 bootstrap.yml,不是 application.yml
  • Config Server endpoint 是 /{application}/{profile}
  • 生產環境還是建議用 Git backend
  • native 模式沒有版本控制,只適合開發

下次要快速調整開發設定,就用本地檔案模式,爽度直接升天。