Nacos 启动失败问题
ENV
| 类别 | 信息 |
|---|---|
| 环境 | 基地A · Kubernetes 预发集群 |
| 节点 OS | Ubuntu 20.04.6 LTS,Kernel 5.4.0-216-generic |
| 容器运行时 | Docker 25.0.5 |
| Kubernetes | v1.24.6(Client/Server) |
| 节点 | 7 台(3 Master + 4 Worker),均 Ready |
| Nacos | Helm 部署,nacos/nacos-server:v2.2.1,StatefulSet 3 副本 |
| MySQL | Bitnami MySQL Cluster(mysql 命名空间),主库 mysql-cluster-primary |
| 网络 | Calico,CoreDNS |
1. 问题现象
| 对象 | 状态 |
|---|---|
nacos-0/1/2 |
CrashLoopBackOff,重启约 1.4 万次 |
StatefulSet nacos |
1/3 Ready(巡检时),后自行恢复为 3/3 |
| 异常事件 | BackOff restarting failed container |
关键日志:
Nacos Server did not start because dumpservice bean construction failure : No DataSource set
Caused by: java.lang.IllegalStateException: No DataSource set
2. 问题原因
2.1 定位推导过程
Step 1 — 确认容器退出原因
timeout 10 kubectl describe pod nacos-0 -n nacos | tail -n 60
关键输出:Exit Code: 1,环境变量 SPRING_DATASOURCE_PLATFORM=mysql,MySQL 参数来自 ConfigMap nacos-cm。
Step 2 — 查看崩溃日志
timeout 10 kubectl logs nacos-0 -n nacos --previous --tail=60
关键输出:No DataSource set,ExternalDumpService.init 失败。
Step 3 — 核对 MySQL 配置
timeout 10 kubectl get configmap nacos-cm -n nacos -o yaml | tail -n 60
关键输出:host/port/user/password/db 均存在,配置完整。
Step 4 — 验证 MySQL 服务与连通性
timeout 10 kubectl get pod,svc,endpoints -n mysql -o wide 2>&1 | head -n 60
timeout 10 kubectl exec -n mysql mysql-cluster-primary-0 -- \
mysql -unacos -p'password' -Dnacos_config -e "SELECT 1 AS connectivity_ok;" 2>&1
关键输出:MySQL Pod/Endpoints 正常;connectivity_ok = 1。
Step 5 — 分析 nacos.log 时序
timeout 10 kubectl exec -n nacos nacos-0 -c nacos -- \
sh -c "grep -iE 'datasource|Hikari|No DataSource' /home/nacos/logs/nacos.log | tail -n 60" 2>&1
关键输出:
09:53:28 findMapper dataSource: mysql → No DataSource set # HikariPool 尚未启动
09:59:07 HikariPool-1 - Starting...
09:59:07 HikariPool-1 - Start completed. # MySQL 连接实际正常
Step 6 — 集群互连日志
Fail to connect server ... Connection refused: nacos-X.nacos-hs.nacos.svc.cluster.local:9849
2.2 根因结论
| 层级 | 原因 |
|---|---|
| 直接原因 | Spring Bean 初始化竞态:ExternalDumpService 在 HikariPool 就绪前访问 DB → No DataSource set → 退出码 1 |
| 放大因素 | mysql.param 中 connectTimeout=1000(1 秒)过短,加剧数据源初始化失败概率 |
| 连带问题 | 3 节点同时 CrashLoop,8848/9848 gRPC 互连 Connection refused,集群无法形成,形成死锁 |
MySQL 服务、账号、网络均正常,非 MySQL 故障。
2.3 形成原因推测
运维在集群网络抖动或 CoreDNS 短暂异常期间,未做单节点恢复而任由 3 副本同时 CrashLoop;叠加 开发/运维 部署 Nacos 时 MySQL 连接超时仅 1 秒,启动窗口过窄。多次异常重启后 3 节点互相不可达,集群死锁,重启次数累积至 1.4 万+。后期节点偶发启动顺序正确时自行恢复为 3/3 Ready,但配置缺陷未消除,仍有复发风险。
3. 解决方案
3.1 预防性加固 ConfigMap(当前已 Ready,勿主动滚动重启)
kubectl patch configmap nacos-cm -n nacos --type merge -p \
'{"data":{"mysql.param":"characterEncoding=utf8&connectTimeout=30000&socketTimeout=30000&autoReconnect=true&useSSL=false"}}'
验证:
timeout 10 kubectl get configmap nacos-cm -n nacos -o yaml | grep -A1 'mysql.param'
3.2 若再次全员 CrashLoop — 打破集群死锁
kubectl scale statefulset nacos -n nacos --replicas=1
首节点稳定后:
kubectl scale statefulset nacos -n nacos --replicas=3
3.3 验证服务正常
timeout 10 kubectl get pod -n nacos -o custom-columns=\
NAME:.metadata.name,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount
timeout 10 kubectl exec -n nacos nacos-0 -- curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8848/nacos/
期望:3 个 Pod READY=true,HTTP 200/302;RESTARTS 不再持续增长。
4. 后续预防措施
开发
- Nacos 集群模式启动脚本中,确保
ExternalDumpService依赖DataSource就绪后再初始化(或升级至已修复竞态的版本)。 - Helm Chart 默认值中
connectTimeout不低于 10 秒。 - 集群模式部署文档中明确:首次启动建议单节点 bootstrap 再扩至 3 副本。
运维
- 监控 Nacos Pod
RESTARTS增量,连续增长时优先scale replicas=1而非反复 delete Pod。 - 维护 MySQL 前先确认 Nacos 连接参数与 Endpoints 可用。
- ConfigMap 变更后择低峰滚动重启,并逐 Pod 观察日志。
- 对
nacos命名空间配置 Pod 重启告警(如 1 小时内重启 > 3 次)。
5. 配置参数建议
| 参数 | 当前值 | 建议值 | 原因/后果 |
|---|---|---|---|
mysql.param → connectTimeout |
1000(1s) |
30000(30s) |
过短易在 DNS/网络抖动时连接失败,触发 DataSource 竞态崩溃 |
mysql.param → socketTimeout |
3000(3s) |
30000(30s) |
读超时过短导致连接中断,加剧启动失败 |
| StatefulSet replicas(故障时) | 3 |
临时 1 |
3 节点同时崩溃时集群 gRPC 死锁,单节点可打破循环 |
| Pod 内存 request | 2Gi |
保持或升至 4Gi |
集群模式 + JVM 1g 堆,节点内存紧张时 OOM 风险 |
附录:关键 ConfigMap 参考
mysql.db.host: mysql-cluster-primary.mysql.svc.cluster.local
mysql.db.name: nacos_config
mysql.user: nacos
mysql.password: password
mysql.port: "3306"
mysql.param: characterEncoding=utf8&connectTimeout=30000&socketTimeout=30000&autoReconnect=true&useSSL=false