Docker容器访问宝塔MySQL超时问题排查与解决实战

Docker 容器访问宝塔 MySQL 接口超时事故解决报告

1. 事故基本信息

项目 内容
事故名称 Docker 后端容器访问宝塔 MySQL 接口超时
发生日期 2026年7月31日
影响服务 sca-backendfds-backend
数据库服务 宝塔容器内 MySQL 5.7
主要现象 后端接口响应超过40秒,部分请求直接超时
最终状态 已解决,应用接口恢复正常
根本原因 应用实际连接了错误的 Docker 网关地址 172.17.0.1,未通过容器服务名直接访问 MySQL 容器

2. 事故概述

sca-backendfds-backend 均以 Docker 容器方式运行,MySQL 数据库运行在同一台宿主机的 baota 容器中。

事故发生后,应用接口在读取 MySQL 数据时出现长时间等待,部分接口耗时超过40秒,并出现 MySQL 初始通信阶段断开的问题。

典型异常如下:

1
2
3
pymysql.err.OperationalError:
(2013, "Lost connection to MySQL server at
'waiting for initial communication packet'")

但直接在 MySQL 中执行相关 SQL 时,查询耗时不足1秒。因此,故障初期容易被误认为是 Docker 网络、MySQL 权限、数据库性能或接口代码性能问题。


3. 系统部署背景

事故发生时,主要容器部署关系如下:

1
2
3
4
5
6
7
8
9
10
Docker Host

├── sca-backend
│ └── Python / FastAPI 后端

├── fds-backend
│ └── Python / FastAPI 后端

└── baota
└── MySQL 5.7:3306

相关容器位于用户自定义 Docker 网络 app_net 中:

1
2
3
4
5
6
7
8
app_net

├── sca-backend

├── fds-backend

└── baota
└── MySQL:3306

在该网络结构下,后端容器应通过 Docker 内部 DNS 和容器服务名访问数据库:

1
baota:3306

不应通过宿主机地址、Docker 默认网关地址或动态容器 IP 绕行访问。


4. 事故现象

4.1 应用接口异常

应用接口调用数据库时出现以下问题:

  • 接口响应时间超过40秒;
  • 部分接口直接超时;
  • 后端偶发 MySQL 握手阶段连接中断;
  • sca-backendfds-backend 均受到影响。

4.2 数据库直接查询正常

在 MySQL 中直接执行 SQL 时,查询可以快速完成。

测试结果:

1
2
3
execute: 0.013
fetch: 0.0
result: 85861

该结果说明:

  • 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
2
SELECT user, host
FROM mysql.user;

查询结果中,相关业务账号存在 host='%' 的记录,说明数据库具备远程连接账号。

排查过程中,phpMyAdmin 复制结果中出现了类似以下内容:

1
server_privileges.php?username=xxx&hostname=%25&token=...

该内容更可能是 phpMyAdmin 页面中用户名超链接的地址被一同复制,而不能单独据此认定 MySQL 用户名已经被污染。

虽然排查期间重新创建了部分远程账号,但后续测试证明,账号权限并不是造成接口超时的根本原因。

5.3 使用容器服务名进行手工连接测试

分别进入两个后端容器,直接通过 baota 服务名连接 MySQL。

sca-backend 测试结果:

1
2
3
host=baota
port=3306
time=0.044s

fds-backend 测试结果:

1
2
3
host=baota
port=3306
time=0.045s

该测试确认:

  • Docker 内部 DNS 解析正常;
  • app_net 网络通信正常;
  • MySQL 监听正常;
  • 数据库账号和密码正常;
  • 两个后端容器均能快速连接 baota:3306

5.4 发现手工测试与应用接口使用的配置不一致

虽然手工测试连接 baota:3306 仅需约0.045秒,但应用接口仍然超时。

这说明手工测试所使用的连接参数,与应用进程实际读取的连接参数并不相同。

随后直接读取应用运行时配置:

1
2
3
4
5
6
7
8
python3 - <<'PY'
from app.config import settings

print("host:", settings.mysql_host)
print("port:", settings.mysql_port)
print("db:", settings.mysql_database)
print("user:", settings.mysql_user)
PY

实际输出:

1
2
3
4
host: 172.17.0.1
port: 3306
db: glue_sca
user: bandlinksca

至此确认,应用接口实际访问的是:

1
172.17.0.1:3306

而不是已经验证正常的:

1
baota:3306

6. 根本原因分析

6.1 直接原因

应用容器的数据库环境变量配置错误:

1
2
MYSQL_HOST=172.17.0.1
MYSQL_PORT=3306

其中:

1
172.17.0.1

是 Docker 默认 bridge 网络的网关地址,不是 MySQL 容器在 app_net 中的服务地址。

6.2 错误访问路径

错误配置产生的访问路径为:

1
2
3
4
5
6
7
8
9
10
11
12
13
sca-backend / fds-backend


172.17.0.1


Docker bridge 网关


宿主机网络及端口转发


baota / MySQL

该路径绕过了 Docker 用户自定义网络中的容器直连机制,增加了网关转发、端口映射和网络回流环节,并在实际应用请求中出现 MySQL 初始通信等待和连接中断。

6.3 正确访问路径

正确路径应为:

1
2
3
4
5
sca-backend ─┐

fds-backend ─┼── app_net ── baota:3306

└── Docker 内部 DNS

应用应通过容器服务名 baota 直接连接数据库,而不是使用网关地址或固定容器 IP。

6.4 配置加载机制放大了问题

应用配置文件中存在以下逻辑:

1
2
3
4
5
6
@dataclass(frozen=True)
class Settings:
mysql_host: str = os.getenv("MYSQL_HOST", "默认地址")
mysql_port: int = int(os.getenv("MYSQL_PORT", "默认端口"))

settings = Settings()

settings 会在 Python 模块加载时读取一次环境变量。

因此,即使后续修改了 .env 或 Docker Compose 配置,如果没有重新创建或重启后端进程,应用仍可能继续使用旧的数据库地址。


7. 促成因素

除直接配置错误外,以下因素增加了故障定位难度:

  1. 手工测试参数与应用运行参数不一致

    手工测试使用 baota:3306,而应用实际使用 172.17.0.1:3306。手工测试成功只能证明指定参数可用,不能证明应用当前配置正确。

  2. 配置文件存在不安全的默认连接地址

    当环境变量缺失时,程序会自动使用代码中的默认公网地址、端口和账号。这会使配置错误被静默掩盖,而不是在启动阶段立即报错。

  3. 修改环境变量后未确认运行时配置

    只修改配置文件但未重新创建容器,可能导致应用继续使用旧值。

  4. 启动日志未输出实际数据库目标

    服务启动时没有明确记录当前数据库的 hostportdatabaseuser,导致无法快速确认接口实际连接了哪个数据库。

  5. 最初排查范围过宽

    排查过程先后涉及监听端口、账号权限、数据库性能、连接池、SQL执行和业务代码,直到读取应用运行时配置后才锁定真正原因。


8. 解决措施

8.1 修改数据库连接地址

将后端服务中的数据库地址由:

1
2
MYSQL_HOST=172.17.0.1
MYSQL_PORT=3306

修改为:

1
2
MYSQL_HOST=baota
MYSQL_PORT=3306

数据库密码通过 Docker Secret 或环境变量安全注入,不在报告中记录明文。

8.2 确认容器处于同一网络

确认以下容器均已加入 app_net

1
2
3
sca-backend
fds-backend
baota

检查命令:

1
2
3
docker inspect sca-backend
docker inspect fds-backend
docker inspect baota

8.3 重新创建后端容器

修改 Docker Compose 配置后,重新创建相关服务:

1
docker compose up -d --force-recreate sca-backend fds-backend

如服务分别位于不同 Compose 项目中,则分别执行重新创建命令。

仅在确认环境变量已经写入现有容器的情况下,才可使用:

1
2
docker restart sca-backend
docker restart fds-backend

通常修改 Compose 环境变量后,应优先使用 --force-recreate,而不是只执行普通重启。

8.4 验证应用实际配置

重新创建容器后执行:

1
2
3
4
5
6
7
8
docker exec -it sca-backend python3 - <<'PY'
from app.config import settings

print("host:", settings.mysql_host)
print("port:", settings.mysql_port)
print("db:", settings.mysql_database)
print("user:", settings.mysql_user)
PY

预期结果:

1
2
3
4
host: baota
port: 3306
db: glue_sca
user: bandlinksca

fds-backend 使用同样方式验证。


9. 修复后验证结果

9.1 sca-backend 数据库连接

1
2
MySQL Host: baota
连接耗时: 0.044s

9.2 fds-backend 数据库连接

1
2
MySQL Host: baota
连接耗时: 0.045s

9.3 SQL 查询验证

1
2
3
execute: 0.013s
fetch: 0.0s
count: 85861

9.4 应用验证

修复并重新加载配置后:

  • 两个后端容器均可正常连接 MySQL;
  • MySQL 初始通信阶段异常消失;
  • 应用接口恢复正常;
  • 未再出现原有的长时间等待和接口超时问题。

10. 前因后果总结

前因

后端服务和 MySQL 均运行在 Docker 容器中,且已经接入同一个用户自定义网络。按照正确架构,后端应通过 baota:3306 访问数据库。

但后端环境变量仍保留了旧配置:

1
MYSQL_HOST=172.17.0.1

该地址是 Docker 默认网关,不是 MySQL 容器服务地址。

经过

应用请求通过错误的网关路径访问 MySQL,导致连接需要经过宿主机网络和端口转发,最终出现握手等待、连接中断和接口超时。

排查过程中依次验证了:

  1. MySQL 监听状态;
  2. 3306端口连通性;
  3. 数据库账号权限;
  4. Docker 容器网络;
  5. MySQL 连接速度;
  6. SQL 查询速度;
  7. 应用运行时数据库配置。

前六项均未发现足以解释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
2
mysql_host = os.getenv("MYSQL_HOST", "某个公网IP")
mysql_port = int(os.getenv("MYSQL_PORT", "13306"))

建议在缺少必要配置时直接终止启动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os


def required_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value


mysql_host = required_env("MYSQL_HOST")
mysql_port = int(required_env("MYSQL_PORT"))
mysql_database = required_env("MYSQL_DATABASE")
mysql_user = required_env("MYSQL_USER")
mysql_password = required_env("MYSQL_PASSWORD")

12.2 启动时输出脱敏配置

1
2
3
4
5
6
7
print(
"MySQL target:",
settings.mysql_host,
settings.mysql_port,
settings.mysql_database,
settings.mysql_user,
)

严禁输出:

1
2
3
MYSQL_PASSWORD
Token
Secret Key

12.3 增加 Docker 健康检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services:
baota:
healthcheck:
test:
[
"CMD-SHELL",
"mysqladmin ping -h 127.0.0.1 -P 3306 --silent"
]
interval: 10s
timeout: 5s
retries: 6

sca-backend:
depends_on:
baota:
condition: service_healthy

实际配置应根据当前 Docker Compose 版本和数据库容器环境进行调整。


13. 标准排查方法

今后遇到“数据库直接查询快,但接口超时”的问题,应按以下顺序排查:

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 查看应用运行时实际配置

2. 使用相同配置测试数据库连接

3. 检查 Docker 网络和服务名解析

4. 检查 MySQL 监听及账号权限

5. 测试真实 SQL 的 execute 和 fetch 耗时

6. 检查连接池、业务逻辑和序列化耗时

7. 验证修改后进程是否真正重载配置

关键原则:

测试命令必须使用与应用进程完全相同的主机、端口、账号、数据库和网络环境。否则测试成功不能代表应用链路正常。


14. 最终结论

本次事故不是 MySQL 性能问题,也不是 SQL 查询效率问题。

根本原因是:

1
应用数据库连接地址配置错误

应用使用了 Docker 默认网关:

1
172.17.0.1:3306

而不是通过 Docker 用户自定义网络直接访问:

1
baota:3306

修复数据库环境变量并重新创建后端容器后,MySQL 连接耗时恢复至约0.045秒,应用接口恢复正常。

事故已解决并关闭。

-------------    本文结束  感谢阅读    -------------

欢迎关注我的其它发布渠道