@@ -18,5 +18,8 @@ SECURE_HSTS_SECONDS=31536000
|
|||||||
# 仅供部署脚本直接访问 127.0.0.1:8000 时设置 Host 头。
|
# 仅供部署脚本直接访问 127.0.0.1:8000 时设置 Host 头。
|
||||||
DEPLOY_HEALTH_HOST=math.example.com
|
DEPLOY_HEALTH_HOST=math.example.com
|
||||||
|
|
||||||
# 宝塔/Docker MySQL 的 mysqldump 不在 PATH 时取消注释并填写实际路径。
|
# Docker MySQL 推荐直接使用容器内的 MySQL 8 客户端。
|
||||||
|
MYSQL_CONTAINER=mysql_o0fbdmmf8q
|
||||||
|
|
||||||
|
# 仅非 Docker MySQL 使用。客户端必须是 MySQL 8。
|
||||||
# MYSQLDUMP_BIN=/www/server/mysql/bin/mysqldump
|
# MYSQLDUMP_BIN=/www/server/mysql/bin/mysqldump
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ ls -l /www/server/pyporject_evn/versions/3.12.13/bin/python*
|
|||||||
|
|
||||||
Redis 应返回 `PONG`。
|
Redis 应返回 `PONG`。
|
||||||
|
|
||||||
|
如果这里显示宿主机 `mysql/mysqldump` 是 5.7,而 Docker 容器是
|
||||||
|
MySQL 8.0.35,不需要卸载旧客户端。宿主机 5.7 客户端只用于简单连通性测试;
|
||||||
|
生产自动备份必须配置 `MYSQL_CONTAINER`,使用容器内部的 MySQL 8
|
||||||
|
`mysqldump`,避免跨版本备份兼容问题。
|
||||||
|
|
||||||
## 4. 配置 Docker MySQL 8.0.35
|
## 4. 配置 Docker MySQL 8.0.35
|
||||||
|
|
||||||
宝塔 `MySQL 多版本管理(Docker 应用)` 中设置:
|
宝塔 `MySQL 多版本管理(Docker 应用)` 中设置:
|
||||||
@@ -170,7 +175,8 @@ CREATE DATABASE hulumath
|
|||||||
CHARACTER SET utf8mb4
|
CHARACTER SET utf8mb4
|
||||||
COLLATE utf8mb4_0900_ai_ci;
|
COLLATE utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
CREATE USER 'hulumath'@'%' IDENTIFIED BY 'ReplaceWithStrongPassword';
|
CREATE USER 'hulumath'@'%'
|
||||||
|
IDENTIFIED WITH mysql_native_password BY 'ReplaceWithStrongPassword';
|
||||||
GRANT ALL PRIVILEGES ON hulumath.* TO 'hulumath'@'%';
|
GRANT ALL PRIVILEGES ON hulumath.* TO 'hulumath'@'%';
|
||||||
FLUSH PRIVILEGES;
|
FLUSH PRIVILEGES;
|
||||||
|
|
||||||
@@ -182,6 +188,10 @@ SHOW VARIABLES LIKE 'sql_mode';
|
|||||||
|
|
||||||
版本必须是 `8.0.35`,字符集应为 `utf8mb4`。
|
版本必须是 `8.0.35`,字符集应为 `utf8mb4`。
|
||||||
|
|
||||||
|
这里使用 `mysql_native_password` 是为了兼容服务器现有 MySQL 5.7 命令行客户端
|
||||||
|
和系统 `mysqlclient` 动态库。数据库端口只绑定 `127.0.0.1:3307`,该账号不会
|
||||||
|
暴露公网。
|
||||||
|
|
||||||
从宿主机验证:
|
从宿主机验证:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -254,10 +264,12 @@ API_USER_RATE=600/minute
|
|||||||
SECURE_HSTS_SECONDS=31536000
|
SECURE_HSTS_SECONDS=31536000
|
||||||
|
|
||||||
DEPLOY_HEALTH_HOST=math.example.com
|
DEPLOY_HEALTH_HOST=math.example.com
|
||||||
MYSQLDUMP_BIN=/usr/bin/mysqldump
|
MYSQL_CONTAINER=mysql_o0fbdmmf8q
|
||||||
```
|
```
|
||||||
|
|
||||||
如果 `command -v mysqldump` 返回其他路径,修改 `MYSQLDUMP_BIN`。
|
`MYSQL_CONTAINER` 必须填写 `docker ps` 显示的 MySQL 8 容器名。你的服务器
|
||||||
|
当前应填写 `mysql_o0fbdmmf8q`。部署脚本会把临时凭据复制进容器,调用容器
|
||||||
|
内部的 MySQL 8 `mysqldump`,完成后立即删除。
|
||||||
|
|
||||||
锁定权限:
|
锁定权限:
|
||||||
|
|
||||||
@@ -478,14 +490,13 @@ hulumath_YYYYMMDD_HHMMSS.sql.gz
|
|||||||
|
|
||||||
保留 14 天。
|
保留 14 天。
|
||||||
|
|
||||||
人工备份:
|
人工备份建议同样使用容器内的 MySQL 8 客户端。以下命令会提示输入应用数据库密码:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mysqldump \
|
docker exec -i mysql_o0fbdmmf8q mysqldump \
|
||||||
-h 127.0.0.1 -P 3307 \
|
|
||||||
-u hulumath -p \
|
-u hulumath -p \
|
||||||
--single-transaction --quick \
|
--single-transaction --quick \
|
||||||
--routines --triggers --events \
|
--triggers --no-tablespaces \
|
||||||
--hex-blob --set-gtid-purged=OFF \
|
--hex-blob --set-gtid-purged=OFF \
|
||||||
hulumath | gzip > /www/backup/hulumath/manual.sql.gz
|
hulumath | gzip > /www/backup/hulumath/manual.sql.gz
|
||||||
```
|
```
|
||||||
@@ -546,17 +557,16 @@ ss -ltnp | grep 3307
|
|||||||
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
|
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
|
||||||
```
|
```
|
||||||
|
|
||||||
### 部署找不到 mysqldump
|
### 部署提示宿主机 mysqldump 版本过低
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
command -v mysqldump
|
docker exec mysql_o0fbdmmf8q mysqldump --version
|
||||||
find /www/server/mysql /usr -type f -name mysqldump 2>/dev/null
|
|
||||||
```
|
```
|
||||||
|
|
||||||
然后设置:
|
容器内应显示 MySQL 8。然后在 `.env.production` 设置:
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
MYSQLDUMP_BIN=/实际路径/mysqldump
|
MYSQL_CONTAINER=mysql_o0fbdmmf8q
|
||||||
```
|
```
|
||||||
|
|
||||||
### 字符集或 Emoji 写入失败
|
### 字符集或 Emoji 写入失败
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ ENV_FILE="${ENV_FILE:-$PROJECT_DIR/.env.production}"
|
|||||||
SERVICE_NAME="${SERVICE_NAME:-hulumath-web}"
|
SERVICE_NAME="${SERVICE_NAME:-hulumath-web}"
|
||||||
BACKUP_DIR="${BACKUP_DIR:-/www/backup/hulumath}"
|
BACKUP_DIR="${BACKUP_DIR:-/www/backup/hulumath}"
|
||||||
MYSQLDUMP_BIN="${MYSQLDUMP_BIN:-}"
|
MYSQLDUMP_BIN="${MYSQLDUMP_BIN:-}"
|
||||||
|
MYSQL_CONTAINER="${MYSQL_CONTAINER:-}"
|
||||||
PREVIOUS_REVISION="${PREVIOUS_REVISION:-}"
|
PREVIOUS_REVISION="${PREVIOUS_REVISION:-}"
|
||||||
HEALTH_HOST="${DEPLOY_HEALTH_HOST:-}"
|
HEALTH_HOST="${DEPLOY_HEALTH_HOST:-}"
|
||||||
ROLLBACK_ENABLED=0
|
ROLLBACK_ENABLED=0
|
||||||
MYSQL_CREDENTIALS_FILE=""
|
MYSQL_CREDENTIALS_FILE=""
|
||||||
|
MYSQL_CONTAINER_CREDENTIALS_PATH=""
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||||
@@ -39,6 +41,10 @@ DEPLOY_OWNER="$(id -un):$(id -gn)"
|
|||||||
rollback_application() {
|
rollback_application() {
|
||||||
local exit_code="${1:-$?}"
|
local exit_code="${1:-$?}"
|
||||||
trap - ERR
|
trap - ERR
|
||||||
|
if [ -n "$MYSQL_CONTAINER_CREDENTIALS_PATH" ] && [ -n "$MYSQL_CONTAINER" ]; then
|
||||||
|
docker exec "$MYSQL_CONTAINER" rm -f "$MYSQL_CONTAINER_CREDENTIALS_PATH" \
|
||||||
|
>/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
if [ -n "$MYSQL_CREDENTIALS_FILE" ]; then
|
if [ -n "$MYSQL_CREDENTIALS_FILE" ]; then
|
||||||
rm -f "$MYSQL_CREDENTIALS_FILE"
|
rm -f "$MYSQL_CREDENTIALS_FILE"
|
||||||
fi
|
fi
|
||||||
@@ -124,20 +130,32 @@ $SUDO chown -R "$DEPLOY_OWNER" "$VENV_DIR"
|
|||||||
"$VENV_DIR/bin/pip" install --disable-pip-version-check -r requirements.txt
|
"$VENV_DIR/bin/pip" install --disable-pip-version-check -r requirements.txt
|
||||||
|
|
||||||
log "备份 MySQL"
|
log "备份 MySQL"
|
||||||
if [ -z "$MYSQLDUMP_BIN" ]; then
|
if [ -n "$MYSQL_CONTAINER" ]; then
|
||||||
MYSQLDUMP_BIN="$(command -v mysqldump || true)"
|
command -v docker >/dev/null 2>&1 || fail "配置了 MYSQL_CONTAINER,但服务器没有 docker 命令"
|
||||||
|
docker inspect "$MYSQL_CONTAINER" >/dev/null 2>&1 \
|
||||||
|
|| fail "MySQL 容器不存在: $MYSQL_CONTAINER"
|
||||||
|
docker exec "$MYSQL_CONTAINER" mysqldump --version \
|
||||||
|
| grep -Eq 'Distrib 8\.' \
|
||||||
|
|| fail "容器内 mysqldump 不是 MySQL 8: $MYSQL_CONTAINER"
|
||||||
|
else
|
||||||
|
if [ -z "$MYSQLDUMP_BIN" ]; then
|
||||||
|
MYSQLDUMP_BIN="$(command -v mysqldump || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "$MYSQLDUMP_BIN" ]; then
|
||||||
|
for candidate in \
|
||||||
|
/www/server/mysql/bin/mysqldump \
|
||||||
|
/usr/bin/mysqldump; do
|
||||||
|
if [ -x "$candidate" ]; then
|
||||||
|
MYSQLDUMP_BIN="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
[ -x "$MYSQLDUMP_BIN" ] \
|
||||||
|
|| fail "未找到 mysqldump;Docker MySQL 请设置 MYSQL_CONTAINER"
|
||||||
|
"$MYSQLDUMP_BIN" --version | grep -Eq 'Distrib 8\.' \
|
||||||
|
|| fail "宿主机 mysqldump 不是 MySQL 8;请设置 MYSQL_CONTAINER 使用容器内客户端"
|
||||||
fi
|
fi
|
||||||
if [ -z "$MYSQLDUMP_BIN" ]; then
|
|
||||||
for candidate in \
|
|
||||||
/www/server/mysql/bin/mysqldump \
|
|
||||||
/usr/bin/mysqldump; do
|
|
||||||
if [ -x "$candidate" ]; then
|
|
||||||
MYSQLDUMP_BIN="$candidate"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
[ -x "$MYSQLDUMP_BIN" ] || fail "未找到 mysqldump;可通过 MYSQLDUMP_BIN 指定客户端路径"
|
|
||||||
$SUDO mkdir -p "$BACKUP_DIR"
|
$SUDO mkdir -p "$BACKUP_DIR"
|
||||||
$SUDO chown "$(id -un):$(id -gn)" "$BACKUP_DIR"
|
$SUDO chown "$(id -un):$(id -gn)" "$BACKUP_DIR"
|
||||||
BACKUP_FILE="$BACKUP_DIR/hulumath_$(date '+%Y%m%d_%H%M%S').sql.gz"
|
BACKUP_FILE="$BACKUP_DIR/hulumath_$(date '+%Y%m%d_%H%M%S').sql.gz"
|
||||||
@@ -167,24 +185,41 @@ def quote(value):
|
|||||||
|
|
||||||
with open(sys.argv[1], "w", encoding="utf-8") as target:
|
with open(sys.argv[1], "w", encoding="utf-8") as target:
|
||||||
target.write("[client]\n")
|
target.write("[client]\n")
|
||||||
target.write(f"host={quote(values['host'])}\n")
|
if not os.environ.get("MYSQL_CONTAINER"):
|
||||||
target.write(f"port={values['port']}\n")
|
target.write(f"host={quote(values['host'])}\n")
|
||||||
|
target.write(f"port={values['port']}\n")
|
||||||
target.write(f"user={quote(values['user'])}\n")
|
target.write(f"user={quote(values['user'])}\n")
|
||||||
target.write(f"password={quote(values['password'])}\n")
|
target.write(f"password={quote(values['password'])}\n")
|
||||||
target.write("default-character-set=utf8mb4\n")
|
target.write("default-character-set=utf8mb4\n")
|
||||||
print(database)
|
print(database)
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
"$MYSQLDUMP_BIN" \
|
if [ -n "$MYSQL_CONTAINER" ]; then
|
||||||
--defaults-extra-file="$MYSQL_CREDENTIALS_FILE" \
|
MYSQL_CONTAINER_CREDENTIALS_PATH="/tmp/hulumath-dump-$$.cnf"
|
||||||
--single-transaction \
|
docker cp "$MYSQL_CREDENTIALS_FILE" \
|
||||||
--quick \
|
"$MYSQL_CONTAINER:$MYSQL_CONTAINER_CREDENTIALS_PATH"
|
||||||
--routines \
|
docker exec "$MYSQL_CONTAINER" mysqldump \
|
||||||
--triggers \
|
--defaults-extra-file="$MYSQL_CONTAINER_CREDENTIALS_PATH" \
|
||||||
--events \
|
--single-transaction \
|
||||||
--hex-blob \
|
--quick \
|
||||||
--set-gtid-purged=OFF \
|
--triggers \
|
||||||
"$MYSQL_DATABASE_NAME" | gzip -c > "$BACKUP_FILE"
|
--hex-blob \
|
||||||
|
--no-tablespaces \
|
||||||
|
--set-gtid-purged=OFF \
|
||||||
|
"$MYSQL_DATABASE_NAME" | gzip -c > "$BACKUP_FILE"
|
||||||
|
docker exec "$MYSQL_CONTAINER" rm -f "$MYSQL_CONTAINER_CREDENTIALS_PATH"
|
||||||
|
MYSQL_CONTAINER_CREDENTIALS_PATH=""
|
||||||
|
else
|
||||||
|
"$MYSQLDUMP_BIN" \
|
||||||
|
--defaults-extra-file="$MYSQL_CREDENTIALS_FILE" \
|
||||||
|
--single-transaction \
|
||||||
|
--quick \
|
||||||
|
--triggers \
|
||||||
|
--hex-blob \
|
||||||
|
--no-tablespaces \
|
||||||
|
--set-gtid-purged=OFF \
|
||||||
|
"$MYSQL_DATABASE_NAME" | gzip -c > "$BACKUP_FILE"
|
||||||
|
fi
|
||||||
rm -f "$MYSQL_CREDENTIALS_FILE"
|
rm -f "$MYSQL_CREDENTIALS_FILE"
|
||||||
MYSQL_CREDENTIALS_FILE=""
|
MYSQL_CREDENTIALS_FILE=""
|
||||||
test -s "$BACKUP_FILE" || fail "数据库备份为空: $BACKUP_FILE"
|
test -s "$BACKUP_FILE" || fail "数据库备份为空: $BACKUP_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user