14 KiB
Ubuntu + 宝塔 + MySQL 8.0.35 从零部署
本方案允许宝塔原生 MySQL 5.7 与 Docker MySQL 8.0.35 并存:
旧应用(如仍需保留) → MySQL 5.7 :3306
葫芦数学 Django → MySQL 8.0.35 :3307
生产拓扑:
用户
↓ HTTPS / WSS
宝塔 Nginx
↓ 127.0.0.1:8000
systemd: Gunicorn + UvicornWorker
├── Django HTTP API
└── Django Channels WebSocket
├── Docker MySQL 8.0.35 :3307
└── Redis :6379
约定:
- Ubuntu 22.04 或 24.04。
- Gitea 仓库:
http://117.72.28.96:8765/Jacky/Hulumath-Web.git。 - 项目目录:
/www/wwwroot/Hulumath-Web。 - 应用监听:
127.0.0.1:8000。 - MySQL 8 宿主机端口:
3307。 - systemd 服务:
hulumath-web。 - 示例域名:
math.example.com,操作时替换为真实域名。
1. 服务器资源
8GB 内存服务器建议:
| 服务 | 建议限制 |
|---|---|
| MySQL 8.0.35 | 1024 MB |
| Django 2 workers | 约 200-500 MB |
| Redis | 50-200 MB |
| Nginx | 通常小于 100 MB |
首发负载下压力不大。不要同时为 MySQL 5.7 和 8.0 分配数 GB Buffer Pool。
防火墙只放行:
22 / 80 / 443
不要向公网开放:
3306 / 3307 / 6379 / 8000
2. 备份旧站
mkdir -p /www/backup/hulumath-legacy
cp -a /www/wwwroot/Hulumath-Web \
"/www/backup/hulumath-legacy/Hulumath-Web.$(date +%Y%m%d_%H%M%S)"
旧 SQLite 数据不会自动导入 MySQL。若旧站已有真实用户,需要另做一次性迁移。
3. 安装系统依赖
宝塔软件商店保留或安装:
- Nginx
- Redis 7
- Docker 管理器
- Python 项目管理器及 Python 3.12.13
SSH 执行:
apt update
apt install -y git curl openssl build-essential pkg-config \
default-libmysqlclient-dev mysql-client-8.0 redis-tools
若 Ubuntu 软件源没有 mysql-client-8.0:
apt install -y default-mysql-client
验证:
redis-cli -h 127.0.0.1 ping
mysql --version
mysqldump --version
ls -l /www/server/pyporject_evn/versions/3.12.13/bin/python*
Redis 应返回 PONG。
如果这里显示宿主机 mysql/mysqldump 是 5.7,而 Docker 容器是
MySQL 8.0.35,不需要卸载旧客户端。宿主机 5.7 客户端只用于简单连通性测试;
生产自动备份必须配置 MYSQL_CONTAINER,使用容器内部的 MySQL 8
mysqldump,避免跨版本备份兼容问题。
4. 配置 Docker MySQL 8.0.35
宝塔 MySQL 多版本管理(Docker 应用) 中设置:
版本:mysql:8.0.35
数据库名称:mysql_hulumath
MySQL 端口:3307
MySQL root 密码:生成强密码并离线保存
数据目录:保持宝塔默认持久化目录
内存限制:1024 MB
在 compose 文件 中确认或补充:
services:
mysql:
image: mysql:8.0.35
ports:
- "127.0.0.1:3307:3306"
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_0900_ai_ci
- --default-time-zone=+08:00
- --sql-mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
environment:
MYSQL_ROOT_PASSWORD: 替换为强密码
volumes:
- /www/dk_project/dk_mm_datas:/var/lib/mysql
deploy:
resources:
limits:
memory: 1024M
关键点:
- 最好绑定
127.0.0.1:3307,而不是0.0.0.0:3307。 - 数据目录必须是持久化目录。
- 不要复用旧 MySQL 5.7 的数据目录。
- 不要直接把 5.7 数据目录挂载给 8.0。
查看容器:
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
记录 MySQL 8 容器名:
MYSQL8_CONTAINER=实际容器名
5. 创建应用数据库与用户
进入 MySQL 8 容器:
docker exec -it "$MYSQL8_CONTAINER" mysql -uroot -p
执行:
CREATE DATABASE hulumath
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'hulumath'@'%'
IDENTIFIED WITH mysql_native_password BY 'ReplaceWithStrongPassword';
GRANT ALL PRIVILEGES ON hulumath.* TO 'hulumath'@'%';
FLUSH PRIVILEGES;
SELECT VERSION();
SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'collation_server';
SHOW VARIABLES LIKE 'sql_mode';
版本必须是 8.0.35,字符集应为 utf8mb4。
这里使用 mysql_native_password 是为了兼容服务器现有 MySQL 5.7 命令行客户端
和系统 mysqlclient 动态库。数据库端口只绑定 127.0.0.1:3307,该账号不会
暴露公网。
从宿主机验证:
mysql -h 127.0.0.1 -P 3307 \
-u hulumath -p hulumath \
-e 'SELECT VERSION(), DATABASE();'
数据库密码若含有 @、#、:、/ 等字符,写入 DATABASE_URL 前必须 URL 编码。
6. 验证服务器可以读取 Gitea 仓库
git ls-remote \
http://117.72.28.96:8765/Jacky/Hulumath-Web.git \
refs/heads/main
该仓库当前允许匿名读取,因此生产服务器无需保存 Gitea 账号密码。若以后改为私有仓库,应使用只读 Deploy Token,并通过 Git credential helper 配置,不要把 Token 写进 workflow 或仓库 URL。
7. 重新创建项目目录
cd /www/wwwroot
if [ -d Hulumath-Web ]; then
mv Hulumath-Web \
"Hulumath-Web.flask.$(date +%Y%m%d_%H%M%S)"
fi
git clone \
http://117.72.28.96:8765/Jacky/Hulumath-Web.git \
Hulumath-Web
cd Hulumath-Web
git checkout main
确认:
test -f backend/manage.py
test -f scripts/deploy_production.sh
test -f deploy/hulumath-web.service
8. 创建生产环境变量
cd /www/wwwroot/Hulumath-Web
cp .env.production.example .env.production
openssl rand -hex 48
nano .env.production
示例:
DJANGO_SETTINGS_MODULE=config.settings
DJANGO_SECRET_KEY='替换为 openssl 生成的密钥'
DJANGO_DEBUG=false
DJANGO_USE_HTTPS=true
DJANGO_ALLOWED_HOSTS=math.example.com
DATABASE_URL='mysql://hulumath:ReplaceWithStrongPassword@127.0.0.1:3307/hulumath'
REDIS_URL='redis://127.0.0.1:6379/0'
CORS_ALLOWED_ORIGINS=https://math.example.com
CSRF_TRUSTED_ORIGINS=https://math.example.com
API_ANON_RATE=120/minute
API_USER_RATE=600/minute
SECURE_HSTS_SECONDS=31536000
DEPLOY_HEALTH_HOST=math.example.com
MYSQL_CONTAINER=mysql_o0fbdmmf8q
MYSQL_CONTAINER 必须填写 docker ps 显示的 MySQL 8 容器名。你的服务器
当前应填写 mysql_o0fbdmmf8q。部署脚本会把临时凭据复制进容器,调用容器
内部的 MySQL 8 mysqldump,完成后立即删除。
锁定权限:
chown root:root .env.production
chmod 600 .env.production
仅使用 IP、尚未配置域名和证书时,可临时改为:
DJANGO_USE_HTTPS=false
DJANGO_ALLOWED_HOSTS=117.72.28.96,127.0.0.1,localhost
CORS_ALLOWED_ORIGINS=http://117.72.28.96
CSRF_TRUSTED_ORIGINS=http://117.72.28.96
SECURE_HSTS_SECONDS=0
DEPLOY_HEALTH_HOST=117.72.28.96
绑定域名并申请 SSL 后,必须把 DJANGO_USE_HTTPS 改回 true,并将
Allowed Hosts、CORS、CSRF 和健康检查 Host 全部替换为正式域名。
9. 在宝塔创建网站
宝塔 网站 → 添加站点:
域名:math.example.com
根目录:/www/wwwroot/Hulumath-Web
PHP:纯静态
数据库:不创建
FTP:不创建
站点配置加入:
client_max_body_size 10m;
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
}
检查并重载:
nginx -t
/etc/init.d/nginx reload
10. 配置 SSL
在宝塔站点 SSL 页面:
- 申请 Let's Encrypt。
- 开启强制 HTTPS。
- 确认证书域名与
DJANGO_ALLOWED_HOSTS一致。
11. 停止旧 Flask
在宝塔 Python 项目管理器停止旧 Flask。
ss -ltnp | grep ':8000' || true
首次 Django 部署前,8000 不应被旧进程占用。
12. 首次手动部署
确保系统安装了 mysqlclient 编译依赖:
apt install -y build-essential pkg-config default-libmysqlclient-dev
执行:
cd /www/wwwroot/Hulumath-Web
chmod +x scripts/deploy_production.sh
PROJECT_DIR=/www/wwwroot/Hulumath-Web \
PYTHON_BIN=/www/server/pyporject_evn/versions/3.12.13/bin/python3 \
bash scripts/deploy_production.sh
脚本顺序:
创建虚拟环境
→ 安装 mysqlclient 等依赖
→ mysqldump --single-transaction
→ Django MySQL 检查
→ migrate
→ collectstatic
→ systemd restart
→ HTTP health
→ WebSocket/Redis health
查看:
systemctl status hulumath-web --no-pager
journalctl -u hulumath-web -n 200 --no-pager
13. 首次初始化业务数据
只在空数据库执行:
cd /www/wwwroot/Hulumath-Web
set -a
source .env.production
set +a
.venv-production/bin/python backend/manage.py seed_initial_content
.venv-production/bin/python backend/manage.py seed_contests
.venv-production/bin/python backend/manage.py createsuperuser
不要把种子命令加入每次自动部署。
14. 验证 MySQL 与网站
cd /www/wwwroot/Hulumath-Web
set -a
source .env.production
set +a
.venv-production/bin/python backend/manage.py check --database default
.venv-production/bin/python backend/manage.py showmigrations
检查表引擎与字符集:
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath -e "
SELECT TABLE_NAME, ENGINE, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='hulumath'
ORDER BY TABLE_NAME;"
所有业务表应使用 InnoDB 和 utf8mb4_0900_ai_ci。
健康检查:
curl -H 'Host: math.example.com' \
-H 'X-Forwarded-Proto: https' \
http://127.0.0.1:8000/health/
curl https://math.example.com/health/
15. 配置 Gitea Actions
生成 Actions 到服务器的 SSH 密钥:
ssh-keygen -t ed25519 -C 'gitea-actions-hulumath' \
-f ./hulumath_actions_deploy -N ''
把公钥加入生产服务器:
cat hulumath_actions_deploy.pub >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
在 Gitea 仓库 Settings → Actions → Secrets 添加:
| Secret | 值 |
|---|---|
DEPLOY_HOST |
服务器公网 IP 或 SSH 域名 |
DEPLOY_USER |
推荐首次使用 root |
DEPLOY_SSH_KEY |
私钥全文 |
确保有能匹配 ubuntu-latest 的在线 Runner。
如果 Runner 和生产环境在同一 Docker 宿主机,且公网 IP 不支持 NAT 回环,
DEPLOY_HOST 可使用 Docker 网桥宿主机地址 172.17.0.1。
先在 Gitea 保护 main 分支,禁止直接推送,并要求 CI / test 通过后才能合并。
PR 阶段会执行:
Ruff 静态检查
→ SQLite 测试和 75% 覆盖率门槛
→ MySQL 8.0.35 全量迁移和测试
PR 合并到 main 后会执行:
MySQL 8.0.35 发布迁移检查
→ SSH 生产服务器
→ MySQL 备份
→ migrate
→ 重启
→ 应用与 Nginx HTTP/WebSocket 冒烟检查
16. 备份与恢复
自动备份目录:
/www/backup/hulumath/
文件格式:
hulumath_YYYYMMDD_HHMMSS.sql.gz
保留 14 天。
人工备份建议同样使用容器内的 MySQL 8 客户端。以下命令会提示输入应用数据库密码:
docker exec -i mysql_o0fbdmmf8q mysqldump \
-u hulumath -p \
--single-transaction --quick \
--triggers --no-tablespaces \
--hex-blob --set-gtid-purged=OFF \
hulumath | gzip > /www/backup/hulumath/manual.sql.gz
恢复前必须停止应用并先备份当前库:
systemctl stop hulumath-web
gunzip -c /www/backup/hulumath/具体备份.sql.gz | \
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
systemctl start hulumath-web
17. 旧数据迁移边界
不能直接执行:
复制 SQLite 文件到 MySQL
复制 MySQL 5.7 数据目录给 MySQL 8
把 PostgreSQL dump 导入 MySQL
如果新 MySQL 8 还是空库,直接迁移 Django schema 并导入种子数据即可。
如果需要保留旧 SQLite 或其他数据库中的业务数据,应通过 Django dumpdata/loaddata 或专用 ETL 脚本迁移,并在 Staging 核对:
- 用户数
- MathBTI 结果数
- 剧情存档数
- 比赛记录数
- 卡牌和成长记录数
18. 常见问题
mysqlclient 安装失败
apt install -y build-essential pkg-config default-libmysqlclient-dev
rm -rf /www/wwwroot/Hulumath-Web/.venv-production
bash scripts/deploy_production.sh
Access denied for user
确认用户允许 Docker 外部宿主机连接:
SELECT user, host FROM mysql.user WHERE user='hulumath';
SHOW GRANTS FOR 'hulumath'@'%';
Can't connect to MySQL server
docker ps
ss -ltnp | grep 3307
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
部署提示宿主机 mysqldump 版本过低
docker exec mysql_o0fbdmmf8q mysqldump --version
容器内应显示 MySQL 8。然后在 .env.production 设置:
MYSQL_CONTAINER=mysql_o0fbdmmf8q
字符集或 Emoji 写入失败
确认:
SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'collation_server';
应为 utf8mb4 和 utf8mb4_0900_ai_ci。
实时匹配出现锁等待
确认使用 MySQL 8.0.35 而不是 5.7,并检查索引:
SHOW INDEX FROM contest_realtimematch;
应存在 matchmaking_lookup_idx。
502 Bad Gateway
systemctl status hulumath-web
journalctl -u hulumath-web -n 200 --no-pager
ss -ltnp | grep 8000
WebSocket 失败
检查 Nginx /ws/ 的 Upgrade/Connection 头,并确认:
redis-cli ping
journalctl -u hulumath-web -n 200 --no-pager