@@ -0,0 +1,598 @@
|
||||
# Ubuntu + 宝塔 + MySQL 8.0.35 从零部署
|
||||
|
||||
本方案允许宝塔原生 MySQL 5.7 与 Docker MySQL 8.0.35 并存:
|
||||
|
||||
```text
|
||||
旧应用(如仍需保留) → MySQL 5.7 :3306
|
||||
葫芦数学 Django → MySQL 8.0.35 :3307
|
||||
```
|
||||
|
||||
生产拓扑:
|
||||
|
||||
```text
|
||||
用户
|
||||
↓ 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。
|
||||
|
||||
防火墙只放行:
|
||||
|
||||
```text
|
||||
22 / 80 / 443
|
||||
```
|
||||
|
||||
不要向公网开放:
|
||||
|
||||
```text
|
||||
3306 / 3307 / 6379 / 8000
|
||||
```
|
||||
|
||||
## 2. 备份旧站
|
||||
|
||||
```bash
|
||||
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 执行:
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```bash
|
||||
apt install -y default-mysql-client
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
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`。
|
||||
|
||||
## 4. 配置 Docker MySQL 8.0.35
|
||||
|
||||
宝塔 `MySQL 多版本管理(Docker 应用)` 中设置:
|
||||
|
||||
```text
|
||||
版本:mysql:8.0.35
|
||||
数据库名称:mysql_hulumath
|
||||
MySQL 端口:3307
|
||||
MySQL root 密码:生成强密码并离线保存
|
||||
数据目录:保持宝塔默认持久化目录
|
||||
内存限制:1024 MB
|
||||
```
|
||||
|
||||
在 `compose 文件` 中确认或补充:
|
||||
|
||||
```yaml
|
||||
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。
|
||||
|
||||
查看容器:
|
||||
|
||||
```bash
|
||||
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
|
||||
```
|
||||
|
||||
记录 MySQL 8 容器名:
|
||||
|
||||
```bash
|
||||
MYSQL8_CONTAINER=实际容器名
|
||||
```
|
||||
|
||||
## 5. 创建应用数据库与用户
|
||||
|
||||
进入 MySQL 8 容器:
|
||||
|
||||
```bash
|
||||
docker exec -it "$MYSQL8_CONTAINER" mysql -uroot -p
|
||||
```
|
||||
|
||||
执行:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE hulumath
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE USER 'hulumath'@'%' IDENTIFIED 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`。
|
||||
|
||||
从宿主机验证:
|
||||
|
||||
```bash
|
||||
mysql -h 127.0.0.1 -P 3307 \
|
||||
-u hulumath -p hulumath \
|
||||
-e 'SELECT VERSION(), DATABASE();'
|
||||
```
|
||||
|
||||
数据库密码若含有 `@`、`#`、`:`、`/` 等字符,写入 `DATABASE_URL` 前必须 URL 编码。
|
||||
|
||||
## 6. 验证服务器可以读取 Gitea 仓库
|
||||
|
||||
```bash
|
||||
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. 重新创建项目目录
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
确认:
|
||||
|
||||
```bash
|
||||
test -f backend/manage.py
|
||||
test -f scripts/deploy_production.sh
|
||||
test -f deploy/hulumath-web.service
|
||||
```
|
||||
|
||||
## 8. 创建生产环境变量
|
||||
|
||||
```bash
|
||||
cd /www/wwwroot/Hulumath-Web
|
||||
cp .env.production.example .env.production
|
||||
openssl rand -hex 48
|
||||
nano .env.production
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```dotenv
|
||||
DJANGO_SETTINGS_MODULE=config.settings
|
||||
DJANGO_SECRET_KEY='替换为 openssl 生成的密钥'
|
||||
DJANGO_DEBUG=false
|
||||
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
|
||||
MYSQLDUMP_BIN=/usr/bin/mysqldump
|
||||
```
|
||||
|
||||
如果 `command -v mysqldump` 返回其他路径,修改 `MYSQLDUMP_BIN`。
|
||||
|
||||
锁定权限:
|
||||
|
||||
```bash
|
||||
chown root:root .env.production
|
||||
chmod 600 .env.production
|
||||
```
|
||||
|
||||
## 9. 在宝塔创建网站
|
||||
|
||||
宝塔 `网站` → `添加站点`:
|
||||
|
||||
```text
|
||||
域名:math.example.com
|
||||
根目录:/www/wwwroot/Hulumath-Web
|
||||
PHP:纯静态
|
||||
数据库:不创建
|
||||
FTP:不创建
|
||||
```
|
||||
|
||||
站点配置加入:
|
||||
|
||||
```nginx
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
检查并重载:
|
||||
|
||||
```bash
|
||||
nginx -t
|
||||
/etc/init.d/nginx reload
|
||||
```
|
||||
|
||||
## 10. 配置 SSL
|
||||
|
||||
在宝塔站点 `SSL` 页面:
|
||||
|
||||
1. 申请 Let's Encrypt。
|
||||
2. 开启强制 HTTPS。
|
||||
3. 确认证书域名与 `DJANGO_ALLOWED_HOSTS` 一致。
|
||||
|
||||
## 11. 停止旧 Flask
|
||||
|
||||
在宝塔 Python 项目管理器停止旧 Flask。
|
||||
|
||||
```bash
|
||||
ss -ltnp | grep ':8000' || true
|
||||
```
|
||||
|
||||
首次 Django 部署前,8000 不应被旧进程占用。
|
||||
|
||||
## 12. 首次手动部署
|
||||
|
||||
确保系统安装了 mysqlclient 编译依赖:
|
||||
|
||||
```bash
|
||||
apt install -y build-essential pkg-config default-libmysqlclient-dev
|
||||
```
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
脚本顺序:
|
||||
|
||||
```text
|
||||
创建虚拟环境
|
||||
→ 安装 mysqlclient 等依赖
|
||||
→ mysqldump --single-transaction
|
||||
→ Django MySQL 检查
|
||||
→ migrate
|
||||
→ collectstatic
|
||||
→ systemd restart
|
||||
→ HTTP health
|
||||
→ WebSocket/Redis health
|
||||
```
|
||||
|
||||
查看:
|
||||
|
||||
```bash
|
||||
systemctl status hulumath-web --no-pager
|
||||
journalctl -u hulumath-web -n 200 --no-pager
|
||||
```
|
||||
|
||||
## 13. 首次初始化业务数据
|
||||
|
||||
只在空数据库执行:
|
||||
|
||||
```bash
|
||||
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 与网站
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
检查表引擎与字符集:
|
||||
|
||||
```bash
|
||||
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`。
|
||||
|
||||
健康检查:
|
||||
|
||||
```bash
|
||||
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 密钥:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C 'gitea-actions-hulumath' \
|
||||
-f ./hulumath_actions_deploy -N ''
|
||||
```
|
||||
|
||||
把公钥加入生产服务器:
|
||||
|
||||
```bash
|
||||
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。
|
||||
|
||||
PR 合并到 `main` 后,workflow 会:
|
||||
|
||||
```text
|
||||
SQLite 快速测试
|
||||
→ 启动 MySQL 8.0.35 临时容器
|
||||
→ MySQL 全量迁移和测试
|
||||
→ SSH 生产服务器
|
||||
→ MySQL 备份
|
||||
→ migrate
|
||||
→ 重启和健康检查
|
||||
```
|
||||
|
||||
## 16. 备份与恢复
|
||||
|
||||
自动备份目录:
|
||||
|
||||
```text
|
||||
/www/backup/hulumath/
|
||||
```
|
||||
|
||||
文件格式:
|
||||
|
||||
```text
|
||||
hulumath_YYYYMMDD_HHMMSS.sql.gz
|
||||
```
|
||||
|
||||
保留 14 天。
|
||||
|
||||
人工备份:
|
||||
|
||||
```bash
|
||||
mysqldump \
|
||||
-h 127.0.0.1 -P 3307 \
|
||||
-u hulumath -p \
|
||||
--single-transaction --quick \
|
||||
--routines --triggers --events \
|
||||
--hex-blob --set-gtid-purged=OFF \
|
||||
hulumath | gzip > /www/backup/hulumath/manual.sql.gz
|
||||
```
|
||||
|
||||
恢复前必须停止应用并先备份当前库:
|
||||
|
||||
```bash
|
||||
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. 旧数据迁移边界
|
||||
|
||||
不能直接执行:
|
||||
|
||||
```text
|
||||
复制 SQLite 文件到 MySQL
|
||||
复制 MySQL 5.7 数据目录给 MySQL 8
|
||||
把 PostgreSQL dump 导入 MySQL
|
||||
```
|
||||
|
||||
如果新 MySQL 8 还是空库,直接迁移 Django schema 并导入种子数据即可。
|
||||
|
||||
如果需要保留旧 SQLite 或其他数据库中的业务数据,应通过 Django `dumpdata/loaddata` 或专用 ETL 脚本迁移,并在 Staging 核对:
|
||||
|
||||
- 用户数
|
||||
- MathBTI 结果数
|
||||
- 剧情存档数
|
||||
- 比赛记录数
|
||||
- 卡牌和成长记录数
|
||||
|
||||
## 18. 常见问题
|
||||
|
||||
### mysqlclient 安装失败
|
||||
|
||||
```bash
|
||||
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 外部宿主机连接:
|
||||
|
||||
```sql
|
||||
SELECT user, host FROM mysql.user WHERE user='hulumath';
|
||||
SHOW GRANTS FOR 'hulumath'@'%';
|
||||
```
|
||||
|
||||
### Can't connect to MySQL server
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
ss -ltnp | grep 3307
|
||||
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
|
||||
```
|
||||
|
||||
### 部署找不到 mysqldump
|
||||
|
||||
```bash
|
||||
command -v mysqldump
|
||||
find /www/server/mysql /usr -type f -name mysqldump 2>/dev/null
|
||||
```
|
||||
|
||||
然后设置:
|
||||
|
||||
```dotenv
|
||||
MYSQLDUMP_BIN=/实际路径/mysqldump
|
||||
```
|
||||
|
||||
### 字符集或 Emoji 写入失败
|
||||
|
||||
确认:
|
||||
|
||||
```sql
|
||||
SHOW VARIABLES LIKE 'character_set_server';
|
||||
SHOW VARIABLES LIKE 'collation_server';
|
||||
```
|
||||
|
||||
应为 `utf8mb4` 和 `utf8mb4_0900_ai_ci`。
|
||||
|
||||
### 实时匹配出现锁等待
|
||||
|
||||
确认使用 MySQL 8.0.35 而不是 5.7,并检查索引:
|
||||
|
||||
```sql
|
||||
SHOW INDEX FROM contest_realtimematch;
|
||||
```
|
||||
|
||||
应存在 `matchmaking_lookup_idx`。
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
```bash
|
||||
systemctl status hulumath-web
|
||||
journalctl -u hulumath-web -n 200 --no-pager
|
||||
ss -ltnp | grep 8000
|
||||
```
|
||||
|
||||
### WebSocket 失败
|
||||
|
||||
检查 Nginx `/ws/` 的 `Upgrade`/`Connection` 头,并确认:
|
||||
|
||||
```bash
|
||||
redis-cli ping
|
||||
journalctl -u hulumath-web -n 200 --no-pager
|
||||
```
|
||||
Reference in New Issue
Block a user