关于解决SpringBoot多环境配置管理混乱的问题

场景

项目有开发、测试、生产三个环境,每个环境的配置都不一样

一开始是把配置写在不同的application-{profile}.yml文件里,但是越来越多配置,管理起来很混乱

而且有时候会不小心把测试环境的配置提交到生产环境,挺危险的

所以决定统一管理配置

方案一:多Profile文件

这是最简单的方式,SpringBoot天然支持

配置文件结构

1
2
3
4
5
src/main/resources/
├── application.yml # 公共配置
├── application-dev.yml # 开发环境
├── application-test.yml # 测试环境
└── application-prod.yml # 生产环境

公共配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# application.yml
spring:
application:
name: my-app

server:
port: 8080

# 日志配置
logging:
level:
root: INFO
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

开发环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
username: root
password: dev123456
redis:
host: localhost
port: 6379

logging:
level:
com.example: DEBUG

测试环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# application-test.yml
spring:
datasource:
url: jdbc:mysql://192.168.1.100:3306/test_db
username: test
password: test123456
redis:
host: 192.168.1.101
port: 6379

logging:
level:
com.example: INFO

生产环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db.example.com:3306/prod_db
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
redis:
host: ${REDIS_HOST}
port: 6379

logging:
level:
root: WARN
com.example: INFO
file:
name: /var/log/my-app/app.log

激活Profile

1
2
3
4
5
6
7
# 启动时指定
java -jar my-app.jar --spring.profiles.active=prod

# 或者在application.yml中指定
spring:
profiles:
active: dev

方案二:环境变量

对于敏感信息(如密码),建议使用环境变量:

1
2
3
4
5
6
7
8
9
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/${DB_NAME}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
redis:
host: ${REDIS_HOST}
password: ${REDIS_PASSWORD}

启动时设置环境变量:

1
2
3
4
5
6
7
export DB_HOST=prod-db.example.com
export DB_NAME=prod_db
export DB_USERNAME=prod_user
export DB_PASSWORD=secure_password
export REDIS_HOST=redis.example.com

java -jar my-app.jar --spring.profiles.active=prod

或者:

1
2
3
4
5
6
DB_HOST=prod-db.example.com \
DB_NAME=prod_db \
DB_USERNAME=prod_user \
DB_PASSWORD=secure_password \
REDIS_HOST=redis.example.com \
java -jar my-app.jar --spring.profiles.active=prod

方案三:外部配置文件

把配置文件放到jar包外部,方便修改:

1
2
3
4
5
6
7
/opt/my-app/
├── config/
│ ├── application.yml
│ ├── application-dev.yml
│ ├── application-test.yml
│ └── application-prod.yml
└── my-app.jar

启动时指定配置文件目录:

1
java -jar /opt/my-app/my-app.jar --spring.config.location=file:/opt/my-app/config/

方案四:配置中心(推荐)

如果项目比较大,建议使用配置中心,比如Nacos、Apollo等

Nacos配置中心

添加依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

配置bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: my-app
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: dev # 开发环境命名空间
group: DEFAULT_GROUP
file-extension: yml
refresh-enabled: true
profiles:
active: dev

在Nacos中创建配置

在Nacos控制台创建配置文件:

  • Data ID: my-app-dev.yml
  • Group: DEFAULT_GROUP
  • 配置内容:
1
2
3
4
5
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
username: root
password: dev123456

动态刷新

使用@RefreshScope注解实现配置动态刷新:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RefreshScope
public class ConfigController {

@Value("${custom.config:value}")
private String config;

@GetMapping("/config")
public String getConfig() {
return config;
}
}

在Nacos控制台修改配置后,调用/actuator/refresh端点刷新:

1
curl -X POST http://localhost:8080/actuator/refresh

Apollo配置中心

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>2.2.0</version>
</dependency>

配置

1
2
3
4
5
6
7
8
app:
id: my-app

apollo:
meta: http://192.168.1.100:8080
bootstrap:
enabled: true
namespaces: application,dev.mysql

使用

1
2
3
4
5
6
7
8
@ApolloConfig
private Config config;

@ApolloConfig("dev.mysql")
private Config mysqlConfig;

@ApolloValue("${timeout:100}")
private int timeout;

方案五:Spring Cloud Config

如果项目用Spring Cloud,可以用Spring Cloud Config

服务端

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
1
2
3
4
5
6
7
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-repo/config-repo
username: your-username
password: your-password
search-paths: configs

客户端

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
1
2
3
4
5
6
7
8
spring:
application:
name: my-app
cloud:
config:
uri: http://config-server:8888
profile: dev
label: main

配置加密

对于敏感配置,可以使用加密:

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

生成密钥:

1
2
keytool -genkeypair -alias mytestkey -keyalg RSA \
-keysize 2048 -keystore config-server.jks -validity 3650

配置:

1
2
3
4
5
6
encrypt:
key-store:
location: classpath:config-server.jks
alias: mytestkey
password: changeme
secret: changeme

加密配置:

1
curl config-server:8888/encrypt -d "my-password"

在配置文件中使用:

1
2
db:
password: '{cipher}AQA8J...' # 加密后的密文

最佳实践

  1. 敏感信息用环境变量,不要写在配置文件里
  2. 每个环境独立的命名空间或分组
  3. 配置变更要走审批流程
  4. 定期更新密钥和密码
  5. 做好配置备份
  6. 配置文档化,说明每个配置项的作用

总结

配置管理很重要,好的配置管理能减少很多问题

建议:

  • 小项目用多Profile文件 + 环境变量
  • 大项目用配置中心(Nacos、Apollo)
  • 敏感信息用环境变量或加密
  • 配置变更要有流程和记录

暂时就先记录这么多