1. 为什么本地跑 Nacos 总在重启后丢配置
很多人第一次在本地用 Docker 起 Nacos,流程都差不多:拉个镜像、跑个容器、打开控制台、建几个配置,用着挺顺。结果第二天电脑重启,或者手滑把容器删了重建,之前配的东西全没了,控制台里空空如也。这不是 Nacos 的 bug,而是默认情况下它把数据写在了容器内部的嵌入式数据库里,容器一销毁,数据跟着一起走。
Nacos 默认用的是内嵌的 Derby 数据库,单机模式开箱即用,适合快速验证功能。但只要你希望配置能留下来、能跨容器生命周期存在,就必须把存储切到外部数据库,通常选 MySQL。这一步做完,Nacos 才真正具备“持久化存储”的能力,而不是一个用完即弃的玩具。
这篇内容面向的是需要在本地或测试环境搭建 Nacos 的开发者,尤其是那些被“重启丢数据”坑过一次、想一次性把环境搭稳的人。我会从镜像选型、MySQL 初始化、容器编排、参数配置到常见报错排查,完整走一遍。所有操作都基于 Docker 和 Docker Compose,命令可以直接复制执行,参数含义我会逐个解释清楚,避免你只知道敲命令却不知道在配什么。
需要提前说明的是,Nacos 的版本迭代比较快,不同版本对 MySQL 驱动、表结构、环境变量的要求有差异。我下面以目前社区使用较广的 Nacos 2.x 系列为例,MySQL 用 8.x,这套组合在实际使用中比较稳。如果你用的是其他版本,思路一致,细节参数对照官方文档微调即可。
2. 镜像与数据库选型:先把地基定下来
2.1 Nacos 镜像的两种来源与取舍
Docker Hub 上能搜到好几个 Nacos 镜像,常见的有官方维护的nacos/nacos-server,也有一些第三方打包的版本。我的建议是优先用官方镜像,原因很直接:标签规范、更新及时、环境变量支持完整。第三方镜像有时候会预装一些额外组件,看起来方便,但版本对应关系不透明,出问题不好排查。
拉取命令很简单:
docker pull nacos/nacos-server:v2.3.2这里我特意指定了具体版本号,而不是用latest。latest标签在不同时间拉到的可能是不同版本,今天搭好的环境,过段时间同事再拉一次,版本就变了,配置和表结构可能对不上。固定版本号是保证环境可复现的基本操作,这一点在团队协作里尤其重要。
2.2 为什么持久化要选 MySQL 而不是其他方案
Nacos 支持把数据存到外部 MySQL,这是官方推荐的生产级方案。有人会问,能不能挂个数据卷把 Derby 的数据目录映射出来?理论上可以,但实践中有几个问题:Derby 是嵌入式数据库,多实例共享同一份数据文件会冲突;数据文件格式不透明,备份和迁移都麻烦;而且 Nacos 集群模式下根本不支持 Derby。所以只要涉及持久化,直接上 MySQL 是最省心的路径。
MySQL 这边,我用的是 8.x 版本。需要注意 MySQL 8 默认的认证插件是caching_sha2_password,而部分旧版 Nacos 镜像里带的 JDBC 驱动版本较低,连接时会报认证失败。解决办法有两个:要么把 MySQL 用户改成mysql_native_password认证,要么确保 Nacos 镜像里的驱动足够新。我下面会给出兼容性更好的配置方式。
2.3 版本搭配的坑:驱动、表结构、字符集
版本搭配是这套环境里最容易翻车的地方,我列一个对照表,把关键点说清楚:
| 组件 | 推荐版本 | 关键注意点 |
|---|---|---|
| Nacos | 2.2.x - 2.3.x | 2.x 需要额外开放 gRPC 端口 |
| MySQL | 8.0.x | 注意认证插件与驱动兼容 |
| JDBC 驱动 | 8.0.x | 旧驱动连 MySQL 8 易报错 |
| 字符集 | utf8mb4 | 避免中文配置乱码 |
字符集这块单独提一句。Nacos 的建表脚本里默认用的是 utf8,如果你在配置内容里写了中文、emoji 或者特殊符号,utf8 三字节的存储方式可能不够用,会出现乱码或插入失败。稳妥的做法是建库时直接指定utf8mb4,建表脚本里的字符集也统一改掉。这个细节很多人会忽略,等到配置里出现中文注释才追悔莫及。
3. 用 Docker Compose 编排 MySQL 与 Nacos
3.1 目录结构规划
在动手写编排文件之前,先把目录结构理清楚。我习惯这样组织:
nacos-stack/ ├── docker-compose.yml ├── mysql/ │ ├── conf/ │ │ └── my.cnf │ └── init/ │ └── nacos-mysql.sql └── nacos/ └── logs/mysql/init目录放初始化 SQL,容器首次启动时会自动执行;mysql/conf放自定义配置;nacos/logs用来把 Nacos 日志挂出来,方便排查问题。这种结构的好处是数据、配置、日志分离,迁移或备份时目标明确。
3.2 MySQL 服务的编排细节
先看 MySQL 这段编排:
services: mysql: image: mysql:8.0.36 container_name: nacos-mysql restart: always environment: MYSQL_ROOT_PASSWORD: nacos_root_pwd MYSQL_DATABASE: nacos_config TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --default-authentication-plugin=mysql_native_password ports: - "3306:3306" volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d - ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf networks: - nacos-net几个参数值得展开说。MYSQL_DATABASE: nacos_config会在容器初始化时自动建好这个库,省去手动建的步骤。--default-authentication-plugin=mysql_native_password这行是为了兼容性,让 MySQL 8 用旧版认证方式,避免 Nacos 连接时报认证错误。docker-entrypoint-initdb.d这个挂载点是 MySQL 官方镜像的约定,放在里面的.sql文件会在数据库首次初始化时自动执行,注意是“首次”,如果数据目录已经有数据了,它不会再跑。
restart: always保证容器异常退出或宿主机重启后自动拉起,本地开发环境加上这个能省不少事。
3.3 Nacos 服务的编排与依赖关系
Nacos 这段编排:
nacos: image: nacos/nacos-server:v2.3.2 container_name: nacos-standalone restart: always depends_on: - mysql environment: MODE: standalone SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: nacos_root_pwd MYSQL_SERVICE_DB_PARAM: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai NACOS_AUTH_ENABLE: "true" NACOS_AUTH_TOKEN: "SecretKey012345678901234567890123456789012345678901234567890123456789" NACOS_AUTH_IDENTITY_KEY: "nacos_identity_key" NACOS_AUTH_IDENTITY_VALUE: "nacos_identity_value" JVM_XMS: 512m JVM_XMX: 512m JVM_XMN: 256m ports: - "8848:8848" - "9848:9848" - "9849:9849" volumes: - ./nacos/logs:/home/nacos/logs networks: - nacos-netMYSQL_SERVICE_HOST: mysql这里用的是服务名而不是 IP,因为两个容器在同一个自定义网络nacos-net里,Docker 内置的 DNS 会把服务名解析成对应容器 IP。这就是为什么必须定义 networks,用默认的 bridge 网络虽然也能通,但服务名解析不稳定。
端口映射里8848是控制台和 HTTP 接口,9848和9849是 Nacos 2.x 新增的 gRPC 端口。很多人升级到 2.x 后发现客户端连不上,八成是忘了映射这两个端口。9848是客户端 gRPC 请求端口,9849是服务端之间同步用的,单机模式主要用到9848。
MYSQL_SERVICE_DB_PARAM这一长串连接参数里,allowPublicKeyRetrieval=true和useSSL=false是配合 MySQL 8 认证用的,serverTimezone不设的话可能报时区错误。这些参数看着琐碎,但少一个就可能连不上。
3.4 网络与数据卷的收尾配置
编排文件末尾补上网络定义:
networks: nacos-net: driver: bridge数据卷这里我用的是绑定挂载(bind mount)而不是命名卷,原因是本地开发时我想直接看到数据文件和日志,方便排查。生产环境更推荐命名卷,由 Docker 统一管理,权限和备份更规范。这个取舍看你的使用场景。
4. 初始化 SQL 与关键参数逐条拆解
4.1 建表脚本从哪来、怎么用
Nacos 的建表脚本在官方仓库的distribution/conf目录下,文件名类似mysql-schema.sql。你需要把它下载下来,放到mysql/init/目录,重命名为nacos-mysql.sql。容器首次启动时,MySQL 会自动执行这个脚本,建好config_info、config_info_beta、his_config_info、users、roles等表。
这里有个容易踩的坑:脚本里默认的数据库名可能和你在编排里设的不一致。脚本开头通常有CREATE DATABASE或USE语句,如果你已经在环境变量里指定了MYSQL_DATABASE: nacos_config,就要确保脚本里的库名也是nacos_config,否则表会建到别的库里,Nacos 启动时找不到表,报错退出。
4.2 字符集统一改成 utf8mb4
拿到脚本后,我习惯做一次全局替换,把utf8改成utf8mb4,把utf8_general_ci改成utf8mb4_unicode_ci。用编辑器批量替换就行,几秒钟的事。这一步不做,后面配置里写中文就可能出问题。
替换完可以检查一下,脚本里每张表的ENGINE=InnoDB DEFAULT CHARSET=...都应该是utf8mb4。InnoDB 引擎是必须的,MyISAM 不支持事务,Nacos 的某些操作依赖事务保证一致性。
4.3 连接参数里每个选项的作用
回到MYSQL_SERVICE_DB_PARAM那串参数,我逐个解释:
characterEncoding=utf8:告诉驱动用 UTF-8 编码传输,注意这里写 utf8 即可,驱动层面会处理成 utf8mb4。connectTimeout=1000:连接超时 1 秒,本地环境够用,网络差的环境可以调大。socketTimeout=3000:读写超时 3 秒,防止长时间阻塞。autoReconnect=true:连接断开后自动重连,MySQL 空闲连接被回收时有用。useSSL=false:本地环境不启用 SSL,省去证书配置。allowPublicKeyRetrieval=true:允许从服务器获取公钥,配合 MySQL 8 认证。serverTimezone=Asia/Shanghai:指定时区,避免时间字段错乱。
这些参数不是随便堆的,每一个都对应一类常见报错。比如不加serverTimezone,可能报The server time zone value is unrecognized;不加allowPublicKeyRetrieval,可能报Public Key Retrieval is not allowed。
4.4 认证相关的环境变量
Nacos 2.x 默认开启了鉴权,需要配置NACOS_AUTH_ENABLE、NACOS_AUTH_TOKEN、NACOS_AUTH_IDENTITY_KEY、NACOS_AUTH_IDENTITY_VALUE。NACOS_AUTH_TOKEN要求是 Base64 编码的字符串,长度有要求,太短会启动失败。我上面给的那串是示例,实际使用建议自己生成一个足够长的随机串。
如果你只是本地测试,不想折腾鉴权,可以把NACOS_AUTH_ENABLE设为false。但要注意,关闭鉴权后控制台不需要登录,任何能访问端口的人都能改配置,仅限本地使用。一旦暴露到公网就是严重的安全隐患,这一点必须清楚。
5. 启动验证与常见报错排查链路
5.1 分步启动与日志观察
不要一上来就docker compose up -d然后干等。我的习惯是先起 MySQL,确认它初始化完成,再起 Nacos。这样出问题时能快速定位是哪一层的锅。
docker compose up -d mysql docker compose logs -f mysql看到日志里出现ready for connections并且初始化脚本执行完毕,再起 Nacos:
docker compose up -d nacos docker compose logs -f nacosNacos 启动日志里,重点看有没有Nacos started successfully以及数据库连接相关的信息。如果看到Communications link failure,说明连不上 MySQL;如果看到Table 'nacos_config.config_info' doesn't exist,说明建表脚本没执行或库名不对。
5.2 连不上 MySQL 的几种典型原因
排查连接问题,我一般按这个顺序走:
- 网络是否通:进 Nacos 容器
docker exec -it nacos-standalone bash,然后ping mysql,看服务名能否解析。 - 端口是否对:确认 MySQL 容器确实在 3306 监听,
docker exec -it nacos-mysql mysql -uroot -p能登进去。 - 账号密码是否正确:环境变量里的密码和 MySQL 实际密码是否一致。
- 认证插件是否兼容:MySQL 8 默认插件和旧驱动不匹配,看日志里有没有认证相关报错。
- 连接参数是否完整:
serverTimezone、allowPublicKeyRetrieval这些是否漏了。
这个顺序是从底层到上层,先确认网络通不通,再确认服务在不在,最后看认证和参数。按这个链路走,基本能定位到具体环节。
5.3 表不存在与字符集报错的处理
表不存在的情况,多半是初始化脚本没跑。原因可能是:数据目录./mysql/data里已经有数据了,MySQL 不会重复执行初始化脚本。解决办法是停掉容器,删掉数据目录,重新启动。注意这会清空所有数据,操作前确认没有重要内容。
字符集报错通常表现为插入中文时失败,或者查询出来是乱码。检查三个地方:建库时的字符集、建表时的字符集、连接参数里的characterEncoding。三处都统一成 utf8mb4 系列,基本能解决。
5.4 端口占用与 gRPC 连接失败
8848端口被占用是本地常见问题,可能你之前起过别的 Nacos 或者别的服务占了这个端口。用lsof -i:8848或netstat -ano | findstr 8848查一下,把占用进程处理掉,或者改映射端口。
gRPC 连接失败是 Nacos 2.x 特有的。客户端报Client not connected或server is DOWN,先检查9848端口有没有映射。另外,如果你在容器里跑客户端,9848的地址解析可能有问题,需要在客户端配置里显式指定nacos.server.grpc.port。这个坑在 2.x 升级时很常见。
6. 数据持久化的验证与日常维护
6.1 怎么确认数据真的落到了 MySQL
搭好之后,别急着用,先做一次持久化验证。步骤是:在 Nacos 控制台新建一个配置,然后进 MySQL 查config_info表,看有没有对应的记录。有记录,说明数据确实写到了外部数据库。
SELECT data_id, group_id, content FROM nacos_config.config_info;然后做一次“破坏性测试”:docker compose down把容器全删了,再docker compose up -d重新起。起来后看控制台里之前的配置还在不在。在,说明持久化生效了。这个测试虽然简单,但能给你实打实的信心。
6.2 备份与迁移的实操思路
数据都在 MySQL 里,备份就变成了备份数据库。用mysqldump导出:
docker exec nacos-mysql mysqldump -uroot -pnacos_root_pwd nacos_config > nacos_config_backup.sql恢复时把 SQL 导回去就行。迁移到另一台机器,把编排文件、初始化脚本、备份 SQL 一起带过去,改一下密码和端口,重新起一遍即可。因为数据在外部数据库,Nacos 容器本身是无状态的,迁移起来很轻。
6.3 资源限制与 JVM 参数调优
本地环境资源有限,Nacos 默认的 JVM 参数可能偏大。我在编排里设了JVM_XMS: 512m、JVM_XMX: 512m、JVM_XMN: 256m,把堆内存控制在 512M,对本地开发足够。如果你的机器内存紧张,可以再调小,但XMN不要设得太小,否则频繁 GC 影响响应。
MySQL 这边也可以限制资源,在my.cnf里调innodb_buffer_pool_size,本地给 256M 到 512M 就够。这些参数不是必须调,但调了之后本地跑起来更轻快。
6.4 日志挂载与问题回溯
把 Nacos 日志挂到宿主机,好处是容器删了日志还在,排查历史问题时有据可查。日志目录里比较重要的有nacos.log(主日志)、protocol-raft.log(集群相关)、config-server.log(配置模块)。出问题时先看nacos.log里的 ERROR 和 WARN,大部分线索都在里面。
我个人的习惯是,每次环境搭好后,先跑一遍完整流程,把正常启动的日志留一份。以后出问题,拿异常日志和正常日志对比,差异点往往就是问题所在。这个笨办法在实际排查中特别管用。
7. 我在多次搭建中总结的几个实用经验
第一个经验是关于版本锁定的。我吃过一次亏,用latest标签搭好的环境,过了一个月同事重新拉镜像,Nacos 从 2.2 升到了 2.3,gRPC 端口行为有细微变化,客户端连不上,排查了半天才发现是版本问题。从那以后,所有镜像标签我都写死具体版本号,编排文件进版本控制,谁搭都是同一套。
第二个经验是关于初始化脚本的幂等性。MySQL 的docker-entrypoint-initdb.d只在数据目录为空时执行,这个特性要利用好。如果你需要修改表结构,不要直接改初始化脚本然后指望它重跑,得手动进数据库执行 ALTER,或者删数据目录重建。理解这个机制,能避免很多“为什么我改了脚本没生效”的困惑。
第三个经验是关于密码管理的。示例里的密码都是明文写在编排文件里的,本地测试无所谓,但如果这套环境要给团队共用,密码就不该硬编码。可以用.env文件配合 Docker Compose 的变量替换,把敏感信息从编排文件里抽出来,.env加入.gitignore。这是个小习惯,但能避免密码泄露到代码仓库。
第四个经验是关于健康检查的。Docker Compose 的depends_on只保证启动顺序,不保证服务真正就绪。MySQL 启动到能接受连接有几十秒的窗口,Nacos 如果起太快会连不上。稳妥的做法是给 MySQL 加healthcheck,Nacos 的depends_on里加上condition: service_healthy。这样 Nacos 会等 MySQL 真正健康后再启动,避免启动阶段的连接失败。
healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-pnacos_root_pwd"] interval: 10s timeout: 5s retries: 5这个配置加上之后,启动过程会稳很多,尤其是机器性能一般、MySQL 启动慢的时候。
最后说一个关于数据目录权限的细节。在 Linux 上,MySQL 容器里的进程以特定用户运行,如果宿主机挂载目录的权限不对,MySQL 会启动失败,报权限拒绝。解决办法是确保挂载目录对容器内用户可写,或者用命名卷让 Docker 自己管理权限。这个坑在 macOS 和 Windows 上不太会遇到,但在 Linux 服务器上很常见,值得留意。