前段时间我在搭一个内部服务的注册与配置中心,选型定在 Nacos 2.2.3。网上关于 Docker 部署 Nacos 的教程一抓一大把,但大多停留在docker run nacos/nacos-server这种级别——容器能起来就觉得完事了,存储用默认的嵌入式数据库,鉴权不开,端口只映射一个 8848。按这一套思路放到生产环境,基本就是给后面埋雷:配置数据跟着容器一起没了、控制台没有任何防护直接暴露、新版客户端用 gRPC 连接又因为端口不通反复重试。这篇就把我在 Nacos 2.2.3 生产级单机部署上验证过的完整方案写出来:Docker Compose 编排、数据落到 MySQL、开启鉴权并做安全加固,每一步都说明为什么这么做。适合刚接手 Nacos、或者正准备把配置中心从 demo 状态推向生产环境的同学参考。
1. 在此之前,先把“生产级单机”这件事拆清楚
1.1 单机不等于开发机,生产级有一条硬底线
很多人一听“单机”,就觉得和高可用、生产级不沾边,随便跑跑就能用。这是个误区。生产级单机和开发环境单机之间差的不是节点数量,而是数据安全、访问控制、可运维性这三条底线。
- 数据安全:配置中心的全部价值都在配置数据上,如果数据跟着容器生命周期走,一次
docker compose down或者换镜像 tag 升级,历史配置全部蒸发,这在生产上是不可接受的。 - 访问控制:Nacos 控制台和 OpenAPI 默认不开启鉴权,任何人只要能访问到 8848 端口,就能读取甚至修改所有配置、查看所有服务实例信息。
- 可运维性:日志没有持久化,容器一删日志就没了,出了问题根本没法排查;内存参数完全跟着镜像默认值走,小机器很容易被 OOM 干掉。
所以这篇文章里的“生产级”,不是指扛住多大流量,而是指把这三条底线补齐。
1.2 Nacos 默认的存储方式为什么不能用
Nacos 默认的存储模式是嵌入式存储,2.2.3 版本里用的是内嵌的 Derby 数据库。这个设计对开发环境非常友好:一条命令拉起来就能用,零依赖。但它的致命问题在于数据只存在容器内部的可写层里,容器一旦被删除、重建或者迁移,数据就彻底丢了。就算只是docker compose down再up,如果 Volume 没做对,同样会从零开始。
生产环境要接外部 MySQL,核心目的只有一个:把配置数据、用户数据、服务元数据的生命周期从容器中剥离出来。容器可以随时销毁重建,数据始终在数据库里躺着,这才具备最基本的可恢复性。
1.3 部署形态与数据流梳理
整个部署的形态很清晰:一个 Nacos Server 容器 + 一个 MySQL 实例(可以是独立部署的 MySQL,也可以是云数据库)。Nacos 容器通过 JDBC 连接 MySQL,配置数据、用户表、权限表全部落在 MySQL;容器文件系统只保留日志和少量运行时数据。客户端通过 Nacos 的 HTTP 端口和 gRPC 端口访问服务。单机模式下没有集群节点之间的 Raft 通信,所以部署重点集中在“正确连接数据库”和“正确暴露端口”这两件事上。下面从 MySQL 准备开始,一步步往下走。
2. MySQL 侧准备:建库、导表、连接串,按顺序来
2.1 版本选择和账号规划
Nacos 2.2.3 对 MySQL 的兼容性覆盖了 5.7 和 8.0 两个大版本,实际生产中选哪个主要看你的存量设施。如果公司有现成的云数据库,直接在上面建一个独立库即可;如果是自己维护的 MySQL 8.0,也没问题。建议不要选太冷门的版本,避免驱动兼容问题。
账号规划上的原则是最小权限 + 独立账号。不要给 Nacos 用 MySQL 的 root 账号,单独创建一个专用账号,只授一个库的权限就够了。这样即使 Nacos 侧配置泄露,攻击者能影响的也只是一个库,而不是整台数据库服务器。
建库和建账号的语句大致如下:
CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'nacos'@'%' IDENTIFIED BY 'YourStrongPassword'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES;这里有个以前踩过的细节:字符集选utf8mb4时,如果 MySQL 是 5.7,导入官方 SQL 后建索引可能会报“索引长度超限”的错误,这是因为utf8mb4下组合索引的字节数超过了旧版本的限制。遇到这种情况,把库字符集改成utf8再导一次基本就能过。MySQL 8.0 一般没这个问题。
2.2 拿到官方 SQL 并初始化表结构
Nacos 官方明确要求:使用外部 MySQL 时,必须手动执行官方提供的建表脚本,因为服务端的spring.jpa.hibernate.ddl-auto是none,不会自动建表。也就是说,如果数据库是空的,启动时你会在日志里看到一堆Table 'nacos_config.config_info' doesn't exist之类的报错。
官方 SQL 脚本的获取方式一般有两种:
- 从 Nacos 官方发布的
nacos-server-2.2.3.zip安装包中,解压后在conf目录下找到nacos-mysql.sql(部分版本文件名是mysql-schema.sql)。 - 从官方 Docker 部署仓库的
docker目录下拿对应的 SQL 脚本。
拿到脚本后,在命令行里执行导入:
mysql -h127.0.0.1 -unacos -p nacos_config < nacos-mysql.sql导入完成后,数据库里会出现config_info、his_config_info、tenant_info、users、roles、permissions等核心表。config_info存配置主数据,his_config_info存配置历史变更,users和roles存控制台用户和角色,这些都是生产环境必须提前建好的。
2.3 连接参数里的时区、SSL 和超时
Nacos 通过 JDBC 连 MySQL 时,连接串上能带一堆参数。官方 Docker 镜像把这些参数收敛成了一个环境变量MYSQL_SERVICE_DB_PARAM,我实际使用的参数是这样的:
characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai逐个说下为什么需要它们:
connectTimeout=1000:连接建立超过 1 秒就放弃,避免 MySQL 不可用时 Nacos 线程被长时间卡住。socketTimeout=3000:读超时 3 秒,防止网络异常时 JDBC 长时间挂起。autoReconnect=true:数据库连接闪断后自动重连,减少短暂故障对配置读写的影响。useSSL=false:内网环境一般不需要 SSL 加密链路,关掉能省一次握手开销;如果你的网络环境要求加密,再按需开启。serverTimezone=Asia/Shanghai:这是最容易忽略的。MySQL 8.0 的 JDBC 驱动对时区很敏感,不显式指定时区,启动阶段就可能直接报The server time zone value '...' is unrecognized,就算不报错,Nacos 控制台里显示的配置发布时间和实际时间也会差 8 小时。
这里顺带说一下,Docker 容器本身也建议加上TZ=Asia/Shanghai,让容器内日志时间和宿主同步,排查问题的时候少绕弯子。
3. Docker Compose 编排:一份可以直接落地的配置
3.1 为什么我倾向 Compose 而不是裸 docker run
裸docker run拉一个 Nacos 容器很简单,但要落地的生产部署通常会同时包含 MySQL 初始化、健康检查、端口映射、目录挂载、环境变量注入,这些参数堆在一行命令里非常不可读,也不容易提交到版本库复用。Compose 的好处是能把整个部署拓扑写进一个 YAML 文件里,团队成员 clone 下来直接docker compose up -d就能拉起一套环境,参数变更走 git 记录,比大家在群里共享 shell 命令要可靠得多。
3.2 完整配置(Nacos + MySQL)
下面这份配置可以直接抄。它包含一个 MySQL 8.0 容器和一个 Nacos 2.2.3 容器,MySQL 首次启动时会自动执行./mysql/init目录下的 SQL 脚本完成建表,免去手动导表的步骤。
services: mysql: image: mysql:8.0 container_name: nacos-mysql restart: always environment: MYSQL_ROOT_PASSWORD: RootStrongPassword MYSQL_DATABASE: nacos_config MYSQL_USER: nacos MYSQL_PASSWORD: NacosStrongPassword command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci ports: - "3306:3306" volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"] interval: 10s timeout: 3s retries: 5 start_period: 30s nacos: image: nacos/nacos-server:v2.2.3 container_name: nacos-server restart: always depends_on: mysql: condition: service_healthy 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: NacosStrongPassword MYSQL_SERVICE_DB_PARAM: "characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai" NACOS_AUTH_ENABLE: "true" NACOS_AUTH_TOKEN: "VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=" NACOS_AUTH_IDENTITY_KEY: "serverIdentity" NACOS_AUTH_IDENTITY_VALUE: "YourUniqueIdentityValue" JVM_XMS: 1024m JVM_XMX: 1024m JVM_XMN: 512m TZ: Asia/Shanghai ports: - "8848:8848" - "9848:9848" - "9849:9849" volumes: - ./nacos/logs:/home/nacos/logs - ./nacos/data:/home/nacos/data启动前创建好目录结构:
mkdir -p ./mysql/init ./mysql/data ./nacos/logs ./nacos/data然后把官方 SQL 脚本放到./mysql/init目录下。首次执行docker compose up -d时,MySQL 容器会在初始化阶段自动导入脚本。如果 MySQL 数据目录已经是第二次使用,脚本不会重新执行,所以务必在真正初始化之前把文件放对位置。
3.3 环境变量逐个说清楚
Compose 里有几个环境变量起到了决定性作用,理解它们才能绕过生产环境的大坑:
MODE决定了 Nacos 的运行模式,单机必须填standalone,如果填cluster而NACOS_SERVERS没有正确配置,启动会直接失败。
SPRING_DATASOURCE_PLATFORM是数据源类型开关,写成mysql才会走外部数据库,不写的默认走内嵌 Derby,等于前面 MySQL 全白准备。
MYSQL_SERVICE_*这一组是为了拼接 JDBC URL 的。官方镜像启动脚本会把 host、port、dbname、user、password 和MYSQL_SERVICE_DB_PARAM拼成完整的 JDBC 地址。注意MYSQL_SERVICE_DB_PARAM里的&符号在 YAML 中会被解析成别的语法,所以这一行务必用双引号把整个值包起来,这也是我最开始写 Compose 时踩过的小坑。
NACOS_AUTH_*是鉴权相关的四个变量,对应关系我会在下一章详述,这里先记住它们一定都要配。
JVM_XMS、JVM_XMX、JVM_XMN控制堆内存。官方镜像默认值是 2g,在小内存机器上非常容易爆,我给出的 1024m 是低配机器起步值,建议根据实际内存调整。
TZ控制容器时区,不配的话日志和控制台时间会与北京时间差 8 小时。
3.4 端口不只是 8848:gRPC 偏移端口
Nacos 从 2.0 开始,客户端和服务端之间的通信从纯粹的 HTTP 增加了 gRPC 通道。服务端默认监听的端口由一个主端口通过固定偏移计算出来:主端口 8848,gRPC 客户端端口是 9848,服务端之间的通信端口是 9849。
很多人在 Docker 部署时只映射了8848:8848,结果客户端日志反复报连接异常、配置拉取失败、服务注册心跳掉线。原因就是 9848 根本没暴露出来。所以ports里必须同时映射 8848、9848、9849 三个端口。在云服务器上部署时,安全组的入方向规则也需要同步放行这三个 TCP 端口。
判断 9848 是否真的通了,可以在客户端机器上执行:
telnet <服务器IP> 9848能连通就说明 gRPC 通道是通的。如果只通 8848 不通 9848,基本就是安全组或 Docker 端口映射的问题。
3.5 启动、看日志、判定成功
在 Compose 文件所在目录执行:
docker compose up -d然后观察 Nacos 容器日志:
docker logs -f nacos-server当日志中出现类似下面这一行时,说明服务已经成功启动:
Nacos started successfully in 5498 ms.如果日志一直在刷数据库连接异常,先确认 MySQL 是否健康、MYSQL_SERVICE_HOST是否正确、账号密码是否匹配。用docker compose ps看容器的状态,应该能看到healthy或者running。
4. 鉴权:四个配置项和一个容易忽略的版本约束
4.1 配置项映射关系
Nacos 开启鉴权并不是设一个布尔值就完事的,至少涉及四个关键配置。我先把它们在配置文件里的名称和 Docker 环境变量的对应关系列出来:
| 用途 | 配置文件属性 | Docker 环境变量 |
|---|---|---|
| 鉴权总开关 | nacos.core.auth.enabled | NACOS_AUTH_ENABLE |
| JWT Token 密钥 | nacos.core.auth.plugin.nacos.token.secret.key | NACOS_AUTH_TOKEN |
| 服务间身份标识 Key | nacos.core.auth.server.identity.key | NACOS_AUTH_IDENTITY_KEY |
| 服务间身份标识 Value | nacos.core.auth.server.identity.value | NACOS_AUTH_IDENTITY_VALUE |
官方 Docker 镜像的启动脚本会自动把这些环境变量注入到application.properties对应的属性上,所以使用镜像时不需要手动改配置文件,只需要把环境变量写对。
4.2 生成强密钥并安全保管
NACOS_AUTH_TOKEN是用来签名 JWT 的密钥,它直接决定了鉴权体系的可靠性。如果这个值还是官方文档里的默认值,等于攻击者也能自己伪造一个合法的 accessToken,那整个鉴权形同虚设。
生产环境务必自己生成随机密钥。推荐用 OpenSSL 生成一个经过 Base64 编码、原始内容长度不低于 32 字节的随机串:
openssl rand -base64 48输出类似:
Zo8y9x2KcVQFqWvE3mN0pLxHj7A5uBdRgT4sYcXkWnM=把这个值填到NACOS_AUTH_TOKEN中。需要注意两点:
- 集群环境中,所有 Nacos 节点的
NACOS_AUTH_TOKEN必须保持一致,否则不同节点签发的 token 无法互相识别。 - 这个密钥一定要妥善备份,比如放到团队的密码管理工具里。密钥丢失后,所有已签发的 token 都会失效,用户需要重新登录。
Compose 示例里那个VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=只是方便理解的演示值,真实环境必须替换。
4.3 2.2.1 之后必须设置的 server identity
这是我在升级到 2.2.3 时遇到的一个比较隐蔽的坑。从 Nacos 2.2.1 开始,只要开启鉴权,就必须同时配置nacos.core.auth.server.identity.key和nacos.core.auth.server.identity.value。如果这两个值为空,服务启动时会直接报错退出,日志里会明确提示需要设置 server identity。
这对标识的作用是让 Nacos 服务端节点之间互相识别“自己人”。在集群环境里,节点间的内部 HTTP 请求也要通过这个标识验证身份;如果留空,无法通过启动检查;如果沿用官方文档里的演示值serverIdentity/security,又等于告诉别人内部通信的凭据。所以生产环境要改成自己定义的一对随机字符串。
同样地,集群部署时所有节点要使用同一对 identity key/value。
4.4 用 OpenAPI 实测鉴权
部署完成并开启鉴权后,我习惯先用 curl 做一轮接口验证,确认鉴权确实生效,而不是只看登录页面有没有出现。
第一步,登录拿 accessToken:
curl -s -X POST 'http://127.0.0.1:8848/nacos/v1/auth/login' -d 'username=nacos&password=nacos'正常情况下会返回一个 JSON,里面包含accessToken字段。
第二步,不带 token 访问配置查询接口,预期返回 403:
curl -i 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=test&group=DEFAULT_GROUP'第三步,带上第一步拿到的 accessToken 再访问一次:
curl -i 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=test&group=DEFAULT_GROUP&accessToken=<上一步拿到的token>'这次预期返回 200。执行完这三步,鉴权链路才算真正验证通过。
4.5 控制台初始账号的处理
Nacos 开启鉴权后,控制台默认管理员账号是nacos / nacos。这个初始密码是公开的,生产环境如果放着不管,等于给任何能访问控制台的人留了后门。
登录控制台后,第一件事是进入“权限控制”菜单,在用户管理里修改nacos用户的密码。更稳妥的做法是新建一个自己的管理员账号,把默认nacos账号停用或改成一个复杂度足够高的随机密码,再也不会有人拿着初始口令登进来。用户和权限数据存在 MySQL 的users、roles、permissions表里,所以这些修改不会因为容器重建而丢失。
5. 排错实录:部署期最容易踩的五个坑
5.1 空库启动只会反复报 SQL 异常
现象:Nacos 容器启动后一直重启,docker logs里反复出现CannotGetJdbcConnectionException或者Table 'nacos_config.config_info' doesn't exist。
原因:MySQL 库是新建的空库,官方 SQL 脚本没有执行,而 Nacos 服务端不会自动建表。
处理:手动执行官方 SQL 脚本完成建表,然后重启 Nacos 容器。如果你用的是上面 Compose 里的 MySQL 初始化目录方式,那就检查一下./mysql/init里的 SQL 文件是否在 MySQL 数据目录第一次初始化之前就放好了。
5.2 端口映射不全导致客户端连不上
现象:Nacos 控制台能打开,服务也能注册到 Nacos,但客户端日志里频繁报 gRPC 连接失败、配置变更收不到通知。
原因:Docker 只映射了8848:8848,gRPC 端口 9848 没有暴露,客户端虽然能通过 HTTP 接口拿到数据,但无法建立长连接。
处理:在ports中补上 9848 和 9849,并同步在防火墙/安全组放行这两个端口。修改后重启容器,再用telnet验证连通性。
5.3 开鉴权反而启动失败
现象:按照教程设置了NACOS_AUTH_ENABLE=true,容器无法启动,日志提示和nacos.core.auth.server.identity相关的内容。
原因:2.2.1 之后,鉴权开启时强制要求配置 server identity 的 key 和 value,两者不能为空。
处理:在环境变量里补上NACOS_AUTH_IDENTITY_KEY和NACOS_AUTH_IDENTITY_VALUE,并且使用自定义的随机字符串,不要用官方示例值。这正好提醒我们,鉴权不是“开一个开关”这么简单,配套参数必须一起上。
5.4 默认 JVM 参数把小机器内存吃光
现象:容器运行一段时间后突然消失,docker inspect nacos-server里能看到OOMKilled: true,或者宿主机的dmesg里出现进程被 kill 的记录。
原因:官方镜像默认的JVM_XMX是 2g,加上 Metaspace、线程栈等开销,实际占用会明显超过 2g。如果宿主机内存只有 2g 或 4g,很容易被系统 OOM Killer 干掉。
处理:在 Compose 环境变量里设置JVM_XMS=1024m、JVM_XMX=1024m、JVM_XMN=512m,并观察docker stats nacos-server的实际占用情况,再针对负载做调整。生产单机建议给这个容器预留 3~4G 内存空间,具体堆大小按业务配置数量和服务数量来评估。
5.5 挂载目录权限导致的启动闪退
现象:容器刚启动就在初始化阶段报Permission denied,日志指向/home/nacos/logs或/home/nacos/data,容器随即退出。
原因:镜像内默认以nacos用户运行,UID 一般是 1000,而宿主机上新建的挂载目录属主是 root,容器内用户没有写权限。
处理:启动前把挂载目录的属主改成容器内用户的 UID:
chown -R 1000:1000 ./nacos/logs ./nacos/data如果不确定容器内用户的 UID,先不挂载目录启动一次,再执行docker exec -it nacos-server id nacos查看。这个坑在 Linux 宿主机上高发,macOS 和 Windows Docker Desktop 下通常不明显,容易让人忽略。
6. 部署后的验收与长期运维
6.1 八项自检清单
部署完成后,我习惯按下面的清单逐项过一遍,避免“以为配好了,实际没生效”的情况:
| 检查项 | 操作方法 | 预期结果 |
|---|---|---|
| 容器状态 | docker compose ps | nacos 和 mysql 都是 running/healthy |
| 启动日志 | docker logs nacos-server | 出现Nacos started successfully |
| 控制台访问 | 浏览器打开http://IP:8848/nacos/ | 出现登录页 |
| 鉴权开启 | 不带 token 访问配置查询接口 | 返回 403 |
| 登录验证 | 用账号密码登录控制台 | 能正常登录并看到配置列表 |
| 数据持久化 | 发布一条测试配置,docker restart nacos-server后再次查看 | 配置仍然存在 |
| gRPC 端口 | telnet IP 9848 | 能连通 |
| 时间显示 | 查看配置列表中的修改时间 | 与本地时间一致 |
6.2 备份和升级的实操流程
有了 MySQL 之后,Nacos 的备份核心就是备份数据库。配置数据、用户数据、历史变更全在库里,日常备份直接用mysqldump即可:
mysqldump -h127.0.0.1 -unacos -p nacos_config > nacos_backup_$(date +%Y%m%d).sql升级版本前,先备份数据库,再备份 Compose 文件和当前环境变量,然后修改镜像 tag 并重新创建容器。Nacos 升级有可能伴随数据库表结构的变化,所以升级前看看官方 release notes 里有没有 SQL 变更,升级后做一轮第 6.1 节的验收清单。特别提醒:不要轻易降级,高版本数据落到低版本上很可能不兼容。
6.3 从单机演进出集群的几个前提
单机部署跑稳之后,迟早会考虑扩展成三节点集群。因为数据都在 MySQL,单机到集群的演进不会丢配置,但有几个硬性前提:
- 所有节点连接同一个 MySQL。
MODE从standalone改为cluster,并配置NACOS_SERVERS列出所有节点地址。- 所有节点的
NACOS_AUTH_TOKEN、NACOS_AUTH_IDENTITY_KEY、NACOS_AUTH_IDENTITY_VALUE必须一致。 - 安全组除了 8848、9848、9849,还要放行集群节点间使用的协议端口,具体端口偏移以官方文档为准。
另外要明确一点:三节点 Nacos 集群解决的是 Nacos 服务本身的高可用,MySQL 仍然可能是单点。生产环境如果对可用性要求高,数据库这一层也需要做主从或者直接使用高可用的云数据库服务。
从实际使用感受来说,这套单机 + MySQL 的部署方式最大的价值不是“跑起来”,而是让 Nacos 变成了一个可以重建、可以备份、可以升级的基础设施组件。我在几个环境里按这套流程部署过,最深的体会是:环境变量和密钥一定要一开始就在本地留存一份,容器重建时照抄就行,否则时间一长,当初那个随机密钥生成完就忘了放哪,后面每次动 Nacos 都得小心翼翼。建议把所有部署参数整理成一个 env 文件纳入版本管理,密码类内容用占位符或者加密工具处理,这样后续不管是谁接手,都能在十分钟内复现一套同样的环境。