Nacos 启动失败:是MySQL 依赖、容器网络还是启动顺序问题?
一、引言
你有没有遇到过这种情况:Nacos 控制台突然打不开,或者容器启动后没过几秒就悄悄退出了?你重启了一遍所有容器,它又奇迹般地好了——但你心里清楚,这事儿迟早还会再来。
很多人第一反应是”是不是我刚改的业务 YAML 写错了”,于是埋头去查 Data ID。其实,这次故障和你的业务配置半点关系都没有。
其实,这次故障和业务配置半点关系都没有——根因在 Nacos 启动阶段连不上 MySQL。
那到底是怎么连不上的?为什么”重启就好”只是假象?下面从日志证据到永久修复,把这条链路拆开。
二、这次到底发生了什么
现象是 Nacos 控制台打不开,或者服务启动后很快退出;把所有容器重启后,它又恢复了。
这不是 zhiguang-runtime.yaml 的内容写错,也不是 RAG 热更新配置本身导致的。Nacos 在启动阶段连不上它用于持久化配置的 MySQL,所以直接启动失败。
一句话记忆:Nacos 不是只靠容器自身就能工作;在当前部署模式下,它先要连上 MySQL,才能加载和保存配置。MySQL 不可达,Nacos 就起不来。
三、日志给出的直接证据
这次日志的关键链路是:
load jdbc.properties error
-> Could not create connection to database server
-> Communications link failure
-> Connection refused
它说明 Nacos 进程已经启动到”连接外部数据库”这一步,但目标地址的 3306 端口当时没有可用的 MySQL 服务。
用一张表概括这条判断链路:
| 阶段 | 结果 |
|---|---|
| Nacos 容器启动 | 进入”连接外部数据库”步骤 |
| 能连上 MySQL | 正常启动 → 读取/保存 Data ID、历史版本等配置 |
| 连不上(Connection refused) | Nacos 启动失败 |
注意区分三类报错,它们指向完全不同的层级:
| 报错 | 含义 | 常见原因 |
|---|---|---|
Connection refused |
网络目标到了,但该端口没有服务接受连接 | MySQL 尚未启动完成、地址指错、容器重建后 IP 变化 |
Access denied |
通常是用户名或密码错误 | 凭据配错或被改 |
| 配置内容格式错误 | Nacos 能启动,只是应用读某个 Data ID 时出错 | YAML 缩进/字段写错 |
四、为什么会失败
当前 Nacos 的 Compose 配置中,数据库地址曾写成一个 Docker 内网 IP:
MYSQL_SERVICE_HOST: 172.18.0.9
MYSQL_SERVICE_PORT: 3306
这类写法的风险在于:172.18.0.9 不是 MySQL 的稳定名字,而是 Docker 在某次创建容器时分配的地址。
结合日志,最需要警惕的有两种情况:
- MySQL 还没有准备好:所有容器一起启动时,Nacos 比 MySQL 更早开始连接;Nacos 的有限重试用完后就退出。
- Docker IP 发生变化:MySQL 容器重建后,原来的
172.18.0.9可能不再属于 MySQL,硬编码地址随之失效。
即便后来把地址改成了容器名,配置里仍可能同时保留 networks 与 network_mode 两种网络声明,存在冗余和可维护性隐患——这部分在第七节统一处理。
这两个问题都属于”容器基础设施依赖”,和上传到 Nacos 的 zhiguang-runtime.yaml 业务运行期配置没有直接关系。
五、为什么重启所有容器后又成功
重启后,MySQL 最终先恢复了监听,Nacos 在后续启动/重试时恰好能够连接数据库,因此启动完成。
整个恢复过程可以理解成这样一条时序:
- MySQL 容器:初始化数据库与
3306端口; - Nacos 容器:连接 nacos 数据库,MySQL 返回连接成功;
- Nacos 容器:加载配置与历史版本;
- 知光后端:向 Nacos 订阅
zhiguang-runtime.yaml; - Nacos:向知光后端推送运行期配置。
这次恢复只说明”当前时刻 MySQL 可达”,不代表硬编码地址和启动顺序已经彻底可靠。下次 Docker 重建、服务器重启或 MySQL 启动变慢时,仍可能复现。
六、Nacos 与知光配置的关系
现在知光读取的运行期配置是:
namespace: public
Data ID: zhiguang-runtime.yaml
Group: ZHIGUANG_GROUP
其中 RAG 模型、检索开关、候选数量、超时等属于”应用运行期配置”。Nacos 正常启动后,知光后端才能订阅它们并接收更新。
但 Nacos 自己连接 MySQL 的地址、端口、账号、容器网络,属于”Nacos 启动期配置”。它们不能依赖 Nacos 再动态下发,否则会形成循环依赖:
Nacos 要先启动
-> 才能读取 Nacos 配置
-> 但启动前又需要数据库配置
因此要分层记忆:
| 配置层级 | 例子 | 是否适合放 Nacos 热更新 |
|---|---|---|
| Nacos 自身启动期 | MySQL 地址、容器网络、端口 | 否,需要重启 Nacos |
| 知光运行期 | RAG 开关、模型名、TopK、超时、提示词 | 是,前提是应用已接入动态刷新 |
| 敏感凭据 | 数据库密码、第三方密钥 | 不应提交到 Git;应通过服务器环境变量或受控密钥管理注入 |
七、永久修复应该怎么做
临时重启能救急,但不能根治。下面从”网络声明方式”到”启动顺序”逐层收口。
7.1 用 networks 替代 network_mode(消除冗余)
原配置里常同时出现 networks(声明使用哪个网络)和 network_mode(直接指定容器网络模式)。两者并存既冗余,又可能因为 network_mode 覆盖 networks 的声明而增加维护复杂度。
network_mode 是命令式写法,networks 才是 Docker Compose 推荐的声明式写法。删掉 network_mode,只保留 networks 让容器加入目标网络即可:
services:
nacos:
# ... 其他配置
networks:
- 1panel-network
7.2 移除 external: true(让 Compose 托管网络)
external: true 表示依赖一个手动创建好的外部网络(docker network create 1panel-network)。一旦该网络不存在,启动时直接报错,配置的可移植性变差。
如果这个网络本就是由 Compose 管理的,直接删掉 external: true,让 Compose 在 up 时自动创建:
networks:
1panel-network:
# 不写 external: true,由 Compose 自动创建
只有当网络必须跨多个 Compose 项目共享、且由外部统一创建时,才保留 external: true。
7.3 用容器名 / 服务名通信,不要用 IPv4
当前 MYSQL_SERVICE_HOST=1Panel-mysql-3KNh 用容器名是正确的:在用户自定义网络中,Docker 内置 DNS 会把容器名解析到当前 IP,不需要手工维护 IP。
绝不要用 IPv4(如 172.18.0.9):容器重建或重启后 IP 会变,硬编码地址立刻失效,容器名稳定得多。
7.4 加网络别名,简化访问
如果 MySQL 容器名较长(如 1Panel-mysql-3KNh),可以在 networks 下加别名,让 Nacos 用更短、更语义化的名字访问:
mysql:
networks:
1panel-network:
aliases:
- mysql # Nacos 用 MYSQL_SERVICE_HOST=mysql 即可
7.5 处理”启动了”与”已可用”的时间差
仅有 depends_on 只保证”先启动 MySQL 容器”,不保证 MySQL 已经接受连接。更可靠的做法:
- 给 MySQL 加健康检查(
healthcheck); - 让 Nacos 等待 MySQL
service_healthy后再启动; - 给 Nacos 配自动重启策略,避免短暂连接失败后永久退出。
优化后的配置示例
把上面五步合起来,一个可读、可移植的写法如下:
services:
nacos:
# ... 镜像、环境变量等
environment:
MYSQL_SERVICE_HOST: mysql # 用别名,而非 IP 或长容器名
MYSQL_SERVICE_PORT: 3306
networks:
- 1panel-network
depends_on:
mysql:
condition: service_healthy
restart: unless-stopped
mysql:
# ... 镜像、环境变量等
networks:
1panel-network:
aliases:
- mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
1panel-network:
# 由 Compose 自动创建,不写 external: true
如果 MySQL 也在同一份 Compose 里,把 MySQL 服务也加入 1panel-network,并直接用服务名 mysql 作为主机名,可读性最好,也彻底摆脱容器名长度问题。这通常是我们的目标形态。
八、下次怎么排查
看到 Nacos 不可用时,按这个顺序判断,不要先怀疑业务 YAML:
- 看 Nacos 容器是否还在运行。
- 查看 Nacos 日志,搜索
Connection refused、Access denied、jdbc。 - 确认 MySQL 容器状态和
3306是否健康。 - 确认两个容器都在同一个 Docker 网络。
- 检查 Nacos 指向的是服务名/容器名,还是容易变化的固定 IP。
- MySQL 正常后,再单独重启 Nacos,最后检查控制台中的配置列表和知光后端日志。
九、总结本文
Nacos 出现启动失败时,先通过日志区分”配置内容错误”和”基础设施依赖错误”。本次日志显示的是 MySQL
Connection refused,根因在 Nacos 到 MySQL 的连接链路,而不是业务 Data ID。临时通过恢复容器启动顺序解决后,进一步把冗余的network_mode去掉、改用声明式networks,并把固定 Docker IP 替换为容器名或服务发现别名,用健康检查保证 Nacos 只在 MySQL 就绪后启动,避免容器重建或冷启动时再次故障。
十、反思
回顾这次故障,有几个点值得在长期运维里反复提醒自己:
- 为什么 Nacos 的 MySQL 配置不能放进 Nacos 自己的 Data ID?——因为会形成”要先启动才能读配置,但启动前又需要配置”的循环依赖。
Connection refused、Access denied分别说明故障在哪一层?——前者是网络/端口不可达,后者是凭据错误,定位方向完全不同。- 为什么容器服务名通常比
172.x.x.x更适合写进 Compose?——容器重建后 IP 会变,服务名由 Docker DNS 稳定解析。 - 为什么
network_mode与networks并存是冗余的?——network_mode是命令式、networks是声明式,二者并存增加维护复杂度,应只保留声明式。 - 为什么”重启全部容器后恢复”不是永久修复?——它只说明当下 MySQL 可达,硬编码地址与启动顺序的隐患并未消除,环境一变就会复现。