中间件 运维复盘

Nacos 启动失败:是MySQL 依赖、容器网络还是启动顺序问题?

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 在某次创建容器时分配的地址。

结合日志,最需要警惕的有两种情况:

  1. MySQL 还没有准备好:所有容器一起启动时,Nacos 比 MySQL 更早开始连接;Nacos 的有限重试用完后就退出。
  2. Docker IP 发生变化:MySQL 容器重建后,原来的 172.18.0.9 可能不再属于 MySQL,硬编码地址随之失效。

即便后来把地址改成了容器名,配置里仍可能同时保留 networks 与 network_mode 两种网络声明,存在冗余和可维护性隐患——这部分在第七节统一处理。

这两个问题都属于”容器基础设施依赖”,和上传到 Nacos 的 zhiguang-runtime.yaml 业务运行期配置没有直接关系。

五、为什么重启所有容器后又成功

重启后,MySQL 最终先恢复了监听,Nacos 在后续启动/重试时恰好能够连接数据库,因此启动完成。

整个恢复过程可以理解成这样一条时序:

  1. MySQL 容器:初始化数据库与 3306 端口;
  2. Nacos 容器:连接 nacos 数据库,MySQL 返回连接成功;
  3. Nacos 容器:加载配置与历史版本;
  4. 知光后端:向 Nacos 订阅 zhiguang-runtime.yaml;
  5. 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 已经接受连接。更可靠的做法:

  1. 给 MySQL 加健康检查(healthcheck);
  2. 让 Nacos 等待 MySQL service_healthy 后再启动;
  3. 给 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:

  1. 看 Nacos 容器是否还在运行。
  2. 查看 Nacos 日志,搜索 Connection refused、Access denied、jdbc。
  3. 确认 MySQL 容器状态和 3306 是否健康。
  4. 确认两个容器都在同一个 Docker 网络。
  5. 检查 Nacos 指向的是服务名/容器名,还是容易变化的固定 IP。
  6. MySQL 正常后,再单独重启 Nacos,最后检查控制台中的配置列表和知光后端日志。

九、总结本文

Nacos 出现启动失败时,先通过日志区分”配置内容错误”和”基础设施依赖错误”。本次日志显示的是 MySQL Connection refused,根因在 Nacos 到 MySQL 的连接链路,而不是业务 Data ID。临时通过恢复容器启动顺序解决后,进一步把冗余的 network_mode 去掉、改用声明式 networks,并把固定 Docker IP 替换为容器名或服务发现别名,用健康检查保证 Nacos 只在 MySQL 就绪后启动,避免容器重建或冷启动时再次故障。

十、反思

回顾这次故障,有几个点值得在长期运维里反复提醒自己:

  1. 为什么 Nacos 的 MySQL 配置不能放进 Nacos 自己的 Data ID?——因为会形成”要先启动才能读配置,但启动前又需要配置”的循环依赖。
  2. Connection refused、Access denied 分别说明故障在哪一层?——前者是网络/端口不可达,后者是凭据错误,定位方向完全不同。
  3. 为什么容器服务名通常比 172.x.x.x 更适合写进 Compose?——容器重建后 IP 会变,服务名由 Docker DNS 稳定解析。
  4. 为什么 network_mode 与 networks 并存是冗余的?——network_mode 是命令式、networks 是声明式,二者并存增加维护复杂度,应只保留声明式。
  5. 为什么”重启全部容器后恢复”不是永久修复?——它只说明当下 MySQL 可达,硬编码地址与启动顺序的隐患并未消除,环境一变就会复现。