
最近在为线上系统迁移到 Nacos 的时候,遇到了一个问题——开发、测试、生产环境的配置文件散落在各个角落,每次发版都要手动替换配置,还经常搞混环境。后来用上了 Nacos,才体会到什么叫真正的"配置管理"。顺便说一下,这次迁移我用 Docker 一键起了 Nacos,还摸透了控制台,就想着把这些经验总结下来分享给大家。
这篇文章就来聊一下我是怎么从零开始部署 Nacos 配置中心,逐步解决多环境隔离、配置动态刷新、以及最关键的敏感信息加密这些痛点的。
说实话,之前没用之前我也没想太多。项目初期,配置就放在 application.properties 里,修改配置得重新打包发布,线上环境有个紧急调整都要走一遍打包、推送、重启的流程,效率低得要死。
后来数据库连接信息、Redis 密码、第三方 API key 这些敏感信息也都混在配置里,明文存储,心里总是忐忑不安。再加上微服务越来越多,配置管理变成了噩梦——要改一个配置,得登上十几台服务器一个一个改。
Nacos 就是为了解决这个问题而来的。核心价值很简单:
说实话,自己编译部署 Nacos 其实不麻烦,但用 Docker 就更方便了。我的做法是用 docker-compose 搞定,这样开发环境、测试环境都能快速启动。
创建一个 docker-compose.yml 文件:
version: '3'
services:
nacos:
image: nacos/nacos-server:v2.3.0
container_name: nacos-server
environment:
- MODE=standalone # 单机模式,测试环境够用
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos_config
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=nacos123
- NACOS_AUTH_ENABLE=true # 启用认证
- NACOS_AUTH_TOKEN_EXPIRE_SECONDS=18000
- NACOS_AUTH_TOKEN_SECRET_KEY=your_secret_key_32_chars_min
ports:
- "8848:8848" # 配置中心端口
- "9848:9848" # gRPC 端口
depends_on:
- mysql
networks:
- nacos-net
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: nacos-mysql
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=nacos_config
- MYSQL_USER=nacos
- MYSQL_PASSWORD=nacos123
ports:
- "3306:3306"
volumes:
- nacos-mysql-data:/var/lib/mysql
- ./nacos-mysql.sql:/docker-entrypoint-initdb.d/nacos-mysql.sql
networks:
- nacos-net
restart: unless-stopped
networks:
nacos-net:
driver: bridge
volumes:
nacos-mysql-data:然后只需要一条命令启动:
docker-compose up -d等待 30 秒左右,Nacos 就跑起来了。访问 http://localhost:8848/nacos 就能进控制台。默认用户名密码是 nacos/nacos,建议第一时间改掉。
生产环境的话,建议改成集群模式(MODE=cluster),并且用外部数据库(比如云数据库),这样才能保证高可用。但对于初学者和开发测试环境,上面这个单机配置就够用了。
有一个坑要注意:MySQL 初始化需要 Nacos 的数据库脚本。我通常会在 MySQL 启动时执行一个 nacos-mysql.sql 脚本(这个文件 Nacos 官方镜像里有),或者直接进 MySQL 执行:
docker exec nacos-mysql mysql -u root -proot123 < nacos-mysql.sql这样数据库就初始化完了,Nacos 就能正常连接。
这是 Nacos 配置管理的核心概念,也是我最开始最容易搞混的地方。
Namespace 用来实现环境隔离。大多数团队的做法是给每个环境建一个 Namespace:
dev-namespace:开发环境test-namespace:测试环境prod-namespace:生产环境Group 用来对配置进行逻辑分组,通常按应用或模块来划分:
order-service:订单服务的配置user-service:用户服务的配置common-config:公共配置DataId 就是具体的配置文件名。比如:
application.yamldatabase.yamlredis.yaml三者的关系可以这样理解:要找一个配置,你需要指定"在哪个环境(Namespace)、哪个应用(Group)下的某个配置文件(DataId)"。
实际操作的时候,我的习惯是这样的:
# bootstrap.yml 配置
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: dev-namespace # 开发环境的 namespace ID
group: order-service # 服务所属的 group
file-extension: yaml
auto-refresh: true # 开启自动刷新
application:
name: order-service
profiles:
active: dev关键的是 namespace 要用 ID 而不是名称,在 Nacos 控制台新建 Namespace 时会生成一个 UUID,复制那个过来就行。
登进 Nacos 控制台后(http://localhost:8848/nacos),首先映入眼帘的是一个相对简洁的界面。左边是菜单栏,右边是主要内容区。
第一件事:创建 Namespace
点击左边菜单的"命名空间"(或在某些版本叫"Namespace"),然后点右上角的"新建命名空间"按钮。
dev-env、prod-env系统会为你生成一个 UUID 形式的"命名空间 ID",这个很重要,后面在 bootstrap.yml 中要用这个 ID。
新建完之后,点击这个 Namespace,你会发现界面有个下拉框,后面所有的操作都是在选中的 Namespace 下进行的。这就是环境隔离的第一层。
第二件事:配置管理
点左边菜单的"配置列表",此时你在某个 Namespace 下了(通常默认在 public)。点右上角的"+"号创建新配置。
弹出的表单有这几个关键字段:
application.yaml、database.yamlorder-service、user-service一个实际的例子是这样的:
# Data Id: database.yaml
# Group: order-service
spring:
datasource:
url: jdbc:mysql://mysql.dev.internal:3306/order_db
username: dev_user
password: dev_password_123
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true填完后点"发布",配置就保存了。
第三件事:快速查看和编辑
回到"配置列表",你会看到刚才创建的配置出现在表格里,有个 Search 框可以按 Data Id 或 Group 搜索。点配置那一行,就能看到详细内容和最后修改时间。
需要修改配置时,点"编辑"按钮,改完再点"发布"。改完之后,所有订阅这个配置的应用都会在 1-2 秒内收到推送(通过 Nacos 的长连接机制),无需重启应用。
第四件事:历史版本追踪
这是我比较喜欢的一个功能。点配置详情页面的"历史版本"标签,你可以看到这个配置的所有修改历史,甚至可以对比两个版本之间的差异,或者直接回滚到某个历史版本。这对于排查"为什么线上配置突然出问题了"特别有用。
第五件事:权限和访问控制
如果你是集群部署或者多人协作环境,点左边菜单的"权限管理",可以给不同用户分配不同的权限。比如让某个用户只能看 dev 环境的配置,不能看 prod 环境。虽然功能不算很复杂,但对于安全性还是有一定帮助的。
一个小技巧是,如果你的 Nacos 开启了认证(就是那个 NACOS_AUTH_ENABLE=true),那么 API 请求的时候需要带上 token。但在控制台里不需要担心这个,登进去就自动有权限了。
我们以数据库连接配置为例。
在 Nacos 控制台,按以下步骤创建配置:
1. 切换到 dev-namespace
点击左上角的 Namespace 下拉框,选择 dev-namespace。
2. 创建配置
进入"配置列表"页面,点击"+"按钮新增配置:
database.yamlorder-servicespring:
datasource:
url: jdbc:mysql://localhost:3306/order_db_dev
username: dev_user
password: dev_password123
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update然后对 test-namespace 和 prod-namespace 重复这个过程,改成对应环境的数据库地址。
在服务启动时,Nacos 客户端会根据 bootstrap.yml 的配置,自动从对应的 Namespace 和 Group 中拉取配置。
// 在任何 Spring Bean 中就可以直接用
@RestController
public class OrderController {
@Autowired
private DataSource dataSource;
@GetMapping("/health")
public String health() {
// dataSource 的配置会自动从 Nacos 拉取
return "OK";
}
}这样做的好处是,开发人员本地启动服务的时候,会自动连接到 dev-namespace,测试人员部署的时候只需要改 bootstrap.yml 中的 namespace 和 profiles.active,就能自动切换到对应环境的配置,不用修改任何业务代码。
配置集中管理了,但真正的威力在于能够动态刷新。修改配置之后,服务无需重启就能感知到变化。
这里最常用的做法是 @RefreshScope 注解。但我在实际应用中踩过坑——如果用法不对,改了配置也不会生效。
错误的用法:
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Value("${order.max-retry-times:3}")
private Integer maxRetryTimes; // 这样不行!
@GetMapping("/retry-times")
public Integer getRetryTimes() {
return maxRetryTimes;
}
}即使加了 @RefreshScope 在类上,这样也不会动态刷新。为什么?因为 @Value 注入的是一个原始值,Spring 无法对其进行动态更新。
正确的用法 - 方法一:
@Configuration
public class OrderConfig {
@Bean
@RefreshScope
public OrderProperties orderProperties() {
return new OrderProperties();
}
}
@Data
@Component
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
private Integer maxRetryTimes =;
private String retryStrategy = "exponential";
}
// 控制器中注入对象
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private OrderProperties orderProperties;
@GetMapping("/retry-times")
public Integer getRetryTimes() {
// 这样就会动态刷新
return orderProperties.getMaxRetryTimes();
}
}关键是用 @ConfigurationProperties + @Component 的方式,Nacos 的监听器能捕获到配置变化,然后 Spring 会重新创建这个 Bean,从而实现动态刷新。
正确的用法 - 方法二:
如果你用的是 Spring Cloud Alibaba(即 Nacos 的官方 Spring Boot 集成),还可以用 @NacosValue 直接注入:
@Configuration
public class OrderConfig {
@NacosValue(value = "${order.max-retry-times:3}", autoRefreshed = true)
private Integer maxRetryTimes;
public Integer getMaxRetryTimes() {
return maxRetryTimes;
}
}这种方式更直接,但需要显式指定 autoRefreshed = true,否则也不会生效。
在 Nacos 控制台修改配置后,你会看到一条日志:
2026-01-15 14:22:33 [NacosConfigListener] Refresh config from nacos...
2026-01-15 14:22:33 [OrderProperties] Properties refreshed: maxRetryTimes=5然后再访问接口,得到的就是新的值了,不需要重启服务。这就是配置中心的魅力所在。
说起敏感信息加密,这是很多团队容易忽视的地方。我之前在一个项目里,数据库密码和 API key 直接存在配置文件里,代码仓库一不小心就暴露到公网,后来还被 GitHub 的安全扫描工具给警告了。
Nacos 从 2.0 版本开始提供了配置加密功能。虽然原理比较复杂(涉及加密插件机制),但使用起来很简单。
第一步:启用加密
在 Nacos 配置中心的控制台,新增配置时,有一个"保密字段"的选项。勾选它之后,这个配置的值就会被加密存储。
但实际操作中我发现,这个功能依赖于 Nacos 服务端的加密插件支持,如果你是自建的 Nacos 集群,可能需要额外配置。
第二步:更实用的方案 - 应用层加密
与其依赖 Nacos 的加密功能(有时候不是很稳定),我更建议在应用层自己处理:
定义一个加密配置类:
@Configuration
public class EncryptedPropertyResolver {
// 这里用一个固定的密钥,生产环境应该从环境变量或密钥管理系统取
private static final String SECRET_KEY = "your-secret-key-here-32-chars";
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setPlaceholderPrefix("${");
configurer.setPlaceholderSuffix("}");
return configurer;
}
/**
* 解密被 ENC() 包装的属性值
*/
public static String decrypt(String encryptedValue) {
if (encryptedValue == null || !encryptedValue.startsWith("ENC(") || !encryptedValue.endsWith(")")) {
return encryptedValue;
}
String encrypted = encryptedValue.substring(, encryptedValue.length() -);
// 使用 AES 或其他加密算法解密
// 这里用伪代码表示
return AesUtil.decrypt(encrypted, SECRET_KEY);
}
}
// 自定义环境预处理器,在配置加载时进行解密
@Component
public class DecryptEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 遍历所有的 property source,对 ENC(...) 的值进行解密
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
for (String name : enumerable.getPropertyNames()) {
String value = (String) enumerable.getProperty(name);
if (value != null && value.startsWith("ENC(")) {
// 解密后替换
String decrypted = EncryptedPropertyResolver.decrypt(value);
// 这里的替换方式取决于具体的实现...
}
}
}
}
}
}在 Nacos 中存储敏感配置时,改成这样:
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db_prod
username: prod_user
password: ENC(x7y8z9a0b1c2d3e4f5g6h7i8j9k0l1m2n3) # 加密后的密码
driver-class-name: com.mysql.cj.jdbc.Driver这样即使有人能访问到 Nacos 的配置存储,看到的也是加密过的密码,没有密钥是没法解密的。
在生产环境实际应用 Nacos 的时候,我还踩过一些坑,想分享给大家以避免重复:
坑一:忘记在 bootstrap.yml 中配置 Nacos 服务器地址
很多同学在迁移到 Nacos 时,会把 Nacos 的配置放在 application.yml 中,但实际上需要放在 bootstrap.yml(或 bootstrap.yaml)中。因为 bootstrap 文件的加载优先级更高,Spring Cloud 会先加载它,才能连接到 Nacos 去拉取其他配置。
# bootstrap.yml - 正确的位置
spring:
cloud:
nacos:
config:
server-addr: nacos-server.internal:8848
namespace: prod-namespace-id
group: my-service坑二:Namespace ID 和 Namespace 名称混淆
在 bootstrap.yml 中的 namespace 要填 Namespace 的 ID(UUID 格式),而不是 Namespace 的名称。新建 Namespace 时会自动生成一个 ID,复制那个就行。
坑三:配置刷新不生效
这个很常见。原因通常是:
@RefreshScope 或 @Component@Value 却没有用 @ConfigurationProperties 包装坑四:集群环境下的配置同步延迟
如果你的 Nacos 是集群部署(多个节点),某个节点可能会因为网络抖动导致配置同步延迟。这时候客户端连到不同节点可能看到不同的配置。解决方案是在客户端加重试机制或用健康检查确保连接到主节点。
坑五:监听器在动态刷新时失效
@Component
public class OrderConfigListener implements ApplicationListener<RefreshEvent> {
@Override
public void onApplicationEvent(RefreshEvent event) {
// 这个监听器会在配置刷新时被触发
System.out.println("配置已刷新");
}
}这个监听器有时候会因为 Spring 的事件机制问题而不被触发。最稳妥的做法是在 @PostConstruct 中手动注册 Nacos 的配置监听器。
Nacos 配置中心的核心价值就是"中心化管理 + 动态刷新 + 安全加密",配合好 Namespace、Group、DataId 的三层结构,就能构建一个相对完善的配置管理系统。
实际项目中,建议按照这个步骤来:
@RefreshScope最后的最后,Nacos 配置中心本身也需要高可用部署、权限控制、备份恢复等运维工作,这些内容就留到下一篇文章再聊了。