跳到主要内容

第 13 课:让 FastAPI 连接宝塔 MySQL

第四阶段 · 真实 student-management-api · 面向 Docker 初学者

本课完成标准​

你能够:

  • 理解容器内 127.0.0.1 与 ECS 主机的区别;
  • 选择 host.docker.internal 或 ECS 内网 IP;
  • 从容器内部测试主机名、TCP 3306 和 MySQL 登录;
  • 使用 DB_HOST、DB_PORT、DB_USER、DB_PASSWORD、DB_NAME;
  • 保持 3306 不开放公网;
  • 关闭生产环境 SQL 详细日志。

1. 先画清网络关系​

FastAPI 容器
│ TCP 3306
▼
ECS 主机 MySQL(宝塔安装和管理)

在容器中:

地址代表什么
127.0.0.1当前 API 容器自己,不是 ECS 主机
host.docker.internalDocker 提供的主机名;Linux 需要映射到 host-gateway
ECS 内网 IP服务器在阿里云 VPC 中的地址
公网 IP不应作为同机数据库连接的首选

**推荐顺序:**同一台 ECS 上先尝试 host.docker.internal + host-gateway;如果环境不支持,再使用 ECS 内网 IP,并确认 MySQL 监听和授权。

2. 检查宝塔 MySQL 状态​

在宝塔面板确认 MySQL 正在运行,并记录数据库名、应用用户和端口。通过 ECS 终端查看监听:

sudo ss -lntp | grep 3306
sudo systemctl status mysqld || sudo systemctl status mysql

可能看到 MySQL 监听 127.0.0.1:3306、ECS 内网地址或 0.0.0.0:3306。不要为了“先跑通”直接开放安全组 3306 到公网。

监听结果含义下一步
127.0.0.1:3306只接受主机回环连接检查是否允许 Docker 网桥/主机网关访问
ECS 内网 IP:3306接受该内网接口连接使用 ECS 内网 IP 测试
0.0.0.0:3306多个接口监听仍需用安全组和用户来源限制访问

3. 给 Compose 加主机网关别名​

在 compose.yaml 的 api 服务中增加:

services:
api:
extra_hosts:
- "host.docker.internal:host-gateway"

如果已有 api 配置,只添加这些行。然后把 ECS 上的 .env 设置为:

DB_HOST=host.docker.internal
DB_PORT=3306
DB_USER=student_app
DB_NAME=student_db
DB_PASSWORD=请填写宝塔中创建的密码

不要在课程文件、GitHub 或聊天中填写真实密码。如果密码曾经暴露,先在宝塔 MySQL 中修改。

4. 重新创建容器并解析主机名​

docker compose up -d --build
docker compose ps
docker compose exec api python -c "import socket,os; h=os.environ['DB_HOST']; print(h, socket.gethostbyname(h))"

如果输出一个 IP,说明容器能解析主机名;这还不代表 3306 一定可连。继续测试 TCP:

docker compose exec api python -c "import socket,os; s=socket.create_connection((os.environ['DB_HOST'], int(os.environ.get('DB_PORT','3306'))), 5); print('tcp ok'); s.close()"
结果判断
能解析、TCP ok网络路径基本正常,继续测试 MySQL 账号
getaddrinfo 错误主机名或 extra_hosts 配置错误
Connection refusedMySQL 没监听该地址/端口
Timeout防火墙、监听地址或网络路径阻断

5. 从容器内部测试 MySQL 登录​

镜像已经安装 PyMySQL,可以让 Python 从环境变量读取密码:

docker compose exec api python -c "import os,pymysql; c=pymysql.connect(host=os.environ['DB_HOST'], port=int(os.environ['DB_PORT']), user=os.environ['DB_USER'], password=os.environ['DB_PASSWORD'], database=os.environ['DB_NAME'], connect_timeout=5); print(c.get_server_info()); c.close(); print('mysql ok')"

不要把完整命令输出中的环境变量或连接串复制到公开聊天。

登录失败时重点看:

  • 用户名是否是宝塔创建的 student_app;
  • 用户是否授权了 student_db;
  • MySQL 用户的 Host 来源是否允许 Docker 网桥或主机网关;
  • 密码是否修改过但 ECS 的 .env 仍是旧值。

6. 让 FastAPI 使用同一套变量​

项目的 config.py 会读取 DB_HOST、DB_PORT、DB_USER、DB_PASSWORD、DB_NAME,生成 mysql+pymysql 连接 URL。Alembic 的 migrations/env.py 也读取同名变量。

修改 .env 后需要重新创建容器:

docker compose up -d
docker compose exec api env | grep '^DB_' | sed 's/DB_PASSWORD=.*/DB_PASSWORD=******/'
docker compose logs --tail=100 api

验证页面:

curl -i http://127.0.0.1:8000/login
curl -i http://127.0.0.1:8000/students

/students 需要登录和数据库,具体返回可能是未授权响应;重点观察日志中不再出现连接拒绝、认证失败或 Unknown database。

7. 生产日志不要输出 SQL 和密码​

当前项目的 database.py 使用了 echo=True,生产环境应改为:

engine = create_engine(
settings.database_url,
echo=False
)

不要通过日志打印完整连接 URL。SQL 日志可能包含用户输入或敏感数据。

8. 常见错误排查​

错误优先检查不要做什么
Unknown MySQL server hostextra_hosts、DB_HOST不要改成公网 IP 试运气
Can't connect / refusedss -lntp、MySQL 监听地址不要开放 3306 到 0.0.0.0/0
Access denied用户名、密码、用户 Host 和授权不要改用 root
Unknown databasestudent_db 是否存在不要在应用启动时偷偷创建生产库
页面 500docker compose logs api不要只看浏览器提示

9. 一分钟自测​

问题 1:容器里的 127.0.0.1 指向哪里?

  • A. 当前 FastAPI 容器自己
  • B. ECS 主机 MySQL
  • C. GitHub 服务器

问题 2:连接数据库失败时,为什么先测 DNS 和 TCP?

  • A. 区分地址解析、网络连通和账号认证问题
  • B. 自动修改 MySQL 密码
  • C. 自动执行 Alembic 迁移

答案:1A,2A。

本课通关证据​

docker compose exec api python -c "import socket,os; print(socket.gethostbyname(os.environ['DB_HOST']))"
docker compose exec api python -c "import socket,os; s=socket.create_connection((os.environ['DB_HOST'],3306),5); print('tcp ok'); s.close()"
docker compose logs --tail=100 api

完成标准:容器能解析主机地址、TCP 3306 可达、专用用户能登录 student_db,需要数据库的页面不再出现连接错误,且 3306 没有公网规则。

如果遇到错误,请贴完整命令和输出,但隐藏密码、Token 与私钥。


← 第 12 课:真实项目 Dockerfile 与 Compose · 第 14 课:SQLModel + Alembic →

课程推荐

想系统学习这门课程?

你已阅读完本文。需要更完整的知识体系,可以继续了解「Vibe Coding 实战课程」。

查看课程详情