Docker 容器访问宝塔 MySQL 接口超时事故解决报告
1. 事故基本信息
| 项目 | 内容 |
|---|---|
| 事故名称 | Docker 后端容器访问宝塔 MySQL 接口超时 |
| 发生日期 | 2026年7月31日 |
| 影响服务 | sca-backend、fds-backend |
| 数据库服务 | 宝塔容器内 MySQL 5.7 |
| 主要现象 | 后端接口响应超过40秒,部分请求直接超时 |
| 最终状态 | 已解决,应用接口恢复正常 |
| 根本原因 | 应用实际连接了错误的 Docker 网关地址 172.17.0.1,未通过容器服务名直接访问 MySQL 容器 |
2. 事故概述
sca-backend 和 fds-backend 均以 Docker 容器方式运行,MySQL 数据库运行在同一台宿主机的 baota 容器中。
事故发生后,应用接口在读取 MySQL 数据时出现长时间等待,部分接口耗时超过40秒,并出现 MySQL 初始通信阶段断开的问题。
典型异常如下:
1 | pymysql.err.OperationalError: |
但直接在 MySQL 中执行相关 SQL 时,查询耗时不足1秒。因此,故障初期容易被误认为是 Docker 网络、MySQL 权限、数据库性能或接口代码性能问题。
3. 系统部署背景
事故发生时,主要容器部署关系如下:
1 | Docker Host |
相关容器位于用户自定义 Docker 网络 app_net 中:
1 | app_net |
在该网络结构下,后端容器应通过 Docker 内部 DNS 和容器服务名访问数据库:
1 | baota:3306 |
不应通过宿主机地址、Docker 默认网关地址或动态容器 IP 绕行访问。
4. 事故现象
4.1 应用接口异常
应用接口调用数据库时出现以下问题:
- 接口响应时间超过40秒;
- 部分接口直接超时;
- 后端偶发 MySQL 握手阶段连接中断;
sca-backend和fds-backend均受到影响。
4.2 数据库直接查询正常
在 MySQL 中直接执行 SQL 时,查询可以快速完成。
测试结果:
1 | execute: 0.013 |
该结果说明:
- SQL 执行速度正常;
- MySQL 数据读取正常;
- 数据库本身不存在明显性能瓶颈;
- 85861条数据的统计查询仅耗时约0.013秒。
因此,接口超时不是由 SQL 执行缓慢造成的。
5. 排查过程
5.1 检查 MySQL 监听状态
执行:
1 | ss -ntlp | grep 3306 |
返回:
1 | LISTEN 0 150 *:3306 *:* users:(("mysqld",pid=1385,fd=19)) |
确认结果:
- MySQL 已监听
3306端口; - 监听地址为全部网卡,而非仅监听
127.0.0.1; - MySQL 监听配置不是本次故障的主要原因。
5.2 检查数据库账号和远程权限
通过以下 SQL 检查账号来源限制:
1 | SELECT user, host |
查询结果中,相关业务账号存在 host='%' 的记录,说明数据库具备远程连接账号。
排查过程中,phpMyAdmin 复制结果中出现了类似以下内容:
1 | server_privileges.php?username=xxx&hostname=%25&token=... |
该内容更可能是 phpMyAdmin 页面中用户名超链接的地址被一同复制,而不能单独据此认定 MySQL 用户名已经被污染。
虽然排查期间重新创建了部分远程账号,但后续测试证明,账号权限并不是造成接口超时的根本原因。
5.3 使用容器服务名进行手工连接测试
分别进入两个后端容器,直接通过 baota 服务名连接 MySQL。
sca-backend 测试结果:
1 | host=baota |
fds-backend 测试结果:
1 | host=baota |
该测试确认:
- Docker 内部 DNS 解析正常;
app_net网络通信正常;- MySQL 监听正常;
- 数据库账号和密码正常;
- 两个后端容器均能快速连接
baota:3306。
5.4 发现手工测试与应用接口使用的配置不一致
虽然手工测试连接 baota:3306 仅需约0.045秒,但应用接口仍然超时。
这说明手工测试所使用的连接参数,与应用进程实际读取的连接参数并不相同。
随后直接读取应用运行时配置:
1 | python3 - <<'PY' |
实际输出:
1 | host: 172.17.0.1 |
至此确认,应用接口实际访问的是:
1 | 172.17.0.1:3306 |
而不是已经验证正常的:
1 | baota:3306 |
6. 根本原因分析
6.1 直接原因
应用容器的数据库环境变量配置错误:
1 | MYSQL_HOST=172.17.0.1 |
其中:
1 | 172.17.0.1 |
是 Docker 默认 bridge 网络的网关地址,不是 MySQL 容器在 app_net 中的服务地址。
6.2 错误访问路径
错误配置产生的访问路径为:
1 | sca-backend / fds-backend |
该路径绕过了 Docker 用户自定义网络中的容器直连机制,增加了网关转发、端口映射和网络回流环节,并在实际应用请求中出现 MySQL 初始通信等待和连接中断。
6.3 正确访问路径
正确路径应为:
1 | sca-backend ─┐ |
应用应通过容器服务名 baota 直接连接数据库,而不是使用网关地址或固定容器 IP。
6.4 配置加载机制放大了问题
应用配置文件中存在以下逻辑:
1 |
|
settings 会在 Python 模块加载时读取一次环境变量。
因此,即使后续修改了 .env 或 Docker Compose 配置,如果没有重新创建或重启后端进程,应用仍可能继续使用旧的数据库地址。
7. 促成因素
除直接配置错误外,以下因素增加了故障定位难度:
手工测试参数与应用运行参数不一致
手工测试使用
baota:3306,而应用实际使用172.17.0.1:3306。手工测试成功只能证明指定参数可用,不能证明应用当前配置正确。配置文件存在不安全的默认连接地址
当环境变量缺失时,程序会自动使用代码中的默认公网地址、端口和账号。这会使配置错误被静默掩盖,而不是在启动阶段立即报错。
修改环境变量后未确认运行时配置
只修改配置文件但未重新创建容器,可能导致应用继续使用旧值。
启动日志未输出实际数据库目标
服务启动时没有明确记录当前数据库的
host、port、database和user,导致无法快速确认接口实际连接了哪个数据库。最初排查范围过宽
排查过程先后涉及监听端口、账号权限、数据库性能、连接池、SQL执行和业务代码,直到读取应用运行时配置后才锁定真正原因。
8. 解决措施
8.1 修改数据库连接地址
将后端服务中的数据库地址由:
1 | MYSQL_HOST=172.17.0.1 |
修改为:
1 | MYSQL_HOST=baota |
数据库密码通过 Docker Secret 或环境变量安全注入,不在报告中记录明文。
8.2 确认容器处于同一网络
确认以下容器均已加入 app_net:
1 | sca-backend |
检查命令:
1 | docker inspect sca-backend |
8.3 重新创建后端容器
修改 Docker Compose 配置后,重新创建相关服务:
1 | docker compose up -d --force-recreate sca-backend fds-backend |
如服务分别位于不同 Compose 项目中,则分别执行重新创建命令。
仅在确认环境变量已经写入现有容器的情况下,才可使用:
1 | docker restart sca-backend |
通常修改 Compose 环境变量后,应优先使用 --force-recreate,而不是只执行普通重启。
8.4 验证应用实际配置
重新创建容器后执行:
1 | docker exec -it sca-backend python3 - <<'PY' |
预期结果:
1 | host: baota |
fds-backend 使用同样方式验证。
9. 修复后验证结果
9.1 sca-backend 数据库连接
1 | MySQL Host: baota |
9.2 fds-backend 数据库连接
1 | MySQL Host: baota |
9.3 SQL 查询验证
1 | execute: 0.013s |
9.4 应用验证
修复并重新加载配置后:
- 两个后端容器均可正常连接 MySQL;
- MySQL 初始通信阶段异常消失;
- 应用接口恢复正常;
- 未再出现原有的长时间等待和接口超时问题。
10. 前因后果总结
前因
后端服务和 MySQL 均运行在 Docker 容器中,且已经接入同一个用户自定义网络。按照正确架构,后端应通过 baota:3306 访问数据库。
但后端环境变量仍保留了旧配置:
1 | MYSQL_HOST=172.17.0.1 |
该地址是 Docker 默认网关,不是 MySQL 容器服务地址。
经过
应用请求通过错误的网关路径访问 MySQL,导致连接需要经过宿主机网络和端口转发,最终出现握手等待、连接中断和接口超时。
排查过程中依次验证了:
- MySQL 监听状态;
- 3306端口连通性;
- 数据库账号权限;
- Docker 容器网络;
- MySQL 连接速度;
- SQL 查询速度;
- 应用运行时数据库配置。
前六项均未发现足以解释40秒超时的异常,最终在读取 app.config.settings 时发现,应用实际使用的地址仍是 172.17.0.1。
结果
将数据库地址修改为 Docker 服务名:
1 | MYSQL_HOST=baota |
并重新创建后端容器,使应用重新加载环境变量。
修复后,两个后端容器连接 MySQL 的耗时均约为0.045秒,应用接口恢复正常,事故关闭。
11. 整改与预防措施
| 序号 | 整改措施 | 状态 |
|---|---|---|
| 1 | 所有后端统一使用 baota 服务名连接 MySQL |
已完成 |
| 2 | 禁止使用 172.17.0.1 等 Docker 网关地址连接容器数据库 |
已完成 |
| 3 | 确认后端和数据库容器统一加入 app_net |
已完成 |
| 4 | 修改环境变量后强制重新创建容器 | 已完成 |
| 5 | 删除代码中的公网数据库默认地址和默认账号 | 待落实 |
| 6 | 环境变量缺失时让服务启动失败,禁止静默使用默认值 | 待落实 |
| 7 | 服务启动时输出脱敏后的数据库连接目标 | 待落实 |
| 8 | 增加数据库连接和接口耗时监控 | 待落实 |
| 9 | 增加容器网络及数据库连通性健康检查 | 待落实 |
| 10 | 建立统一的 Docker 部署配置检查清单 | 待落实 |
12. 建议的配置改进
12.1 禁止使用公网默认值
不建议:
1 | mysql_host = os.getenv("MYSQL_HOST", "某个公网IP") |
建议在缺少必要配置时直接终止启动:
1 | import os |
12.2 启动时输出脱敏配置
1 | print( |
严禁输出:
1 | MYSQL_PASSWORD |
12.3 增加 Docker 健康检查
1 | services: |
实际配置应根据当前 Docker Compose 版本和数据库容器环境进行调整。
13. 标准排查方法
今后遇到“数据库直接查询快,但接口超时”的问题,应按以下顺序排查:
1 | 1. 查看应用运行时实际配置 |
关键原则:
测试命令必须使用与应用进程完全相同的主机、端口、账号、数据库和网络环境。否则测试成功不能代表应用链路正常。
14. 最终结论
本次事故不是 MySQL 性能问题,也不是 SQL 查询效率问题。
根本原因是:
1 | 应用数据库连接地址配置错误 |
应用使用了 Docker 默认网关:
1 | 172.17.0.1:3306 |
而不是通过 Docker 用户自定义网络直接访问:
1 | baota:3306 |
修复数据库环境变量并重新创建后端容器后,MySQL 连接耗时恢复至约0.045秒,应用接口恢复正常。
事故已解决并关闭。