# 灾备操作手册 (DR Runbook)

> **文档版本**: 1.0
> **最后更新**: 2026-06-17
> **适用场景**: 生产环境故障处理

---

## 概述

本手册提供常见故障场景的诊断和处理步骤。每个场景包含:症状、诊断、处理步骤、验证方法。

**紧急联系**: 参见 `docs/dr/dr-plan.md` 第 6 节联系人列表

---

## 场景 1: 数据库故障

### 1.1 数据库不可达

#### 症状
- 应用报错: `ECONNREFUSED` 或 `Connection refused`
- 健康检查 `database` 状态为 `fail`
- 用户无法登录、查询数据

#### 诊断
```bash
# 1. 检查数据库连接
mysql -h <DB_HOST> -P <DB_PORT> -u <DB_USER> -p -e "SELECT 1;"

# 2. 检查数据库进程
systemctl status mysql
# 或 Docker 环境
docker ps | grep mysql

# 3. 检查端口
telnet <DB_HOST> <DB_PORT>
# 或
nc -zv <DB_HOST> <DB_PORT>

# 4. 查看数据库日志
tail -100 /var/log/mysql/error.log
# 或 Docker
docker logs <mysql_container> --tail 100
```

#### 处理步骤

**情况 A: 数据库服务停止**
```bash
# 重启数据库服务
sudo systemctl restart mysql
# 或 Docker
docker restart <mysql_container>

# 等待启动完成
sleep 10
mysql -h <DB_HOST> -P <DB_PORT> -u <DB_USER> -p -e "SELECT 1;"
```

**情况 B: 数据库无法启动**
```bash
# 1. 检查磁盘空间
df -h

# 2. 检查配置文件
mysql --verbose --help | head -20

# 3. 如果磁盘满,清理空间
sudo find /var/log -type f -name "*.log" -mtime +7 -delete

# 4. 如果配置错误,恢复备份配置
sudo cp /etc/mysql/my.cnf.bak /etc/mysql/my.cnf
sudo systemctl restart mysql
```

**情况 C: 数据库损坏,需要从备份恢复**
```bash
# 1. 获取最新备份
LATEST_BACKUP=$(ls -t backups/db_backup_*.sql.gz | head -1)
echo "Using backup: $LATEST_BACKUP"

# 2. 校验备份
./scripts/backup-verify.sh "$LATEST_BACKUP"

# 3. 恢复数据库
./scripts/restore-db.sh "$LATEST_BACKUP"

# 4. 重启应用
docker restart nextjs-app
```

**情况 D: 主库故障,需要切换到备库**
```bash
# 1. 执行故障切换(手动模式)
./scripts/failover.sh

# 2. 或半自动模式
./scripts/failover.sh --auto

# 3. 验证切换结果
./scripts/health-check.sh
```

#### 验证
```bash
# 1. 运行健康检查
./scripts/health-check.sh

# 2. 验证应用功能
curl -f http://localhost:8015

# 3. 验证数据库查询
mysql -h <DB_HOST> -P <DB_PORT> -u <DB_USER> -p -e "SELECT COUNT(*) FROM users;"

# 4. 运行灾备演练验证数据完整性
./scripts/dr-drill.sh
```

---

### 1.2 数据库性能问题

#### 症状
- 应用响应缓慢
- 查询超时
- CPU/内存使用率高

#### 诊断
```bash
# 1. 查看当前连接
mysql -e "SHOW PROCESSLIST;"

# 2. 查看慢查询
mysql -e "SHOW VARIABLES LIKE 'slow_query%';"
tail -100 /var/log/mysql/slow.log

# 3. 查看系统资源
top
iostat -x 1
```

#### 处理步骤
```bash
# 1. 终止长时间运行的查询
mysql -e "KILL <process_id>;"

# 2. 优化表
mysql -e "OPTIMIZE TABLE <table_name>;"

# 3. 重启数据库(如果必要)
sudo systemctl restart mysql
```

#### 验证
```bash
# 监控性能指标
mysql -e "SHOW STATUS LIKE 'Threads%';"
mysql -e "SHOW STATUS LIKE 'Slow_queries';"
```

---

## 场景 2: 应用故障

### 2.1 应用不可达

#### 症状
- HTTP 502/503 错误
- 页面无法访问
- 健康检查 `app` 状态为 `fail`

#### 诊断
```bash
# 1. 检查应用容器
docker ps | grep nextjs-app

# 2. 查看应用日志
docker logs nextjs-app --tail 100

# 3. 检查端口
netstat -tlnp | grep 8015

# 4. 检查健康端点
curl -v http://localhost:8015
```

#### 处理步骤

**情况 A: 容器停止**
```bash
# 启动容器
docker start nextjs-app

# 等待启动
sleep 10
curl -f http://localhost:8015
```

**情况 B: 容器崩溃,需要重启**
```bash
# 重启容器
docker restart nextjs-app

# 如果重启失败,重新部署
docker stop nextjs-app || true
docker rm nextjs-app || true
# 重新运行部署流程(参见 CI/CD)
```

**情况 C: 应用配置错误**
```bash
# 1. 检查环境变量
docker exec nextjs-app env | grep DATABASE_URL

# 2. 检查 .env.local
cat .env.local

# 3. 修正配置后重启
docker restart nextjs-app
```

#### 验证
```bash
# 1. 健康检查
./scripts/health-check.sh

# 2. 功能测试
curl -f http://localhost:8015
curl -f http://localhost:8015/api/auth/providers

# 3. 查看日志确认无错误
docker logs nextjs-app --tail 20
```

---

### 2.2 应用 OOM(内存不足)

#### 症状
- 容器被 OOM Killer 终止
- 日志中出现 `JavaScript heap out of memory`

#### 诊断
```bash
# 1. 查看容器状态
docker inspect nextjs-app | grep -A 5 "State"

# 2. 查看内存使用
docker stats nextjs-app

# 3. 查看系统日志
dmesg | grep -i "oom"
```

#### 处理步骤
```bash
# 1. 增加 Node.js 内存限制
docker stop nextjs-app
docker rm nextjs-app
docker run -d \
  --init \
  -p 8015:3000 \
  --restart unless-stopped \
  --name nextjs-app \
  --network 1panel-network \
  -e NODE_OPTIONS="--max-old-space-size=2048" \
  -e NODE_ENV=production \
  -e DATABASE_URL=$DATABASE_URL \
  -e NEXTAUTH_SECRET=$NEXTAUTH_SECRET \
  -e NEXTAUTH_URL=$NEXTAUTH_URL \
  nextjs-app

# 2. 或增加容器内存限制
docker run -d --memory=2g ...
```

#### 验证
```bash
# 监控内存使用
docker stats nextjs-app

# 确认应用正常
curl -f http://localhost:8015
```

---

## 场景 3: 备份失败

### 3.1 定时备份未执行

#### 症状
- 健康检查 `backup` 状态为 `fail`(备份超过 24 小时)
- `./backups/` 目录无新文件
- CI 中 `scheduled-backup` job 失败

#### 诊断
```bash
# 1. 检查最新备份
ls -lt backups/db_backup_*.sql.gz | head -5

# 2. 检查 CI 运行记录
# 访问 Gitea Actions 页面查看 scheduled-backup job

# 3. 手动运行备份测试
./scripts/backup-db.sh

# 4. 检查磁盘空间
df -h
```

#### 处理步骤

**情况 A: 磁盘空间不足**
```bash
# 1. 清理旧备份
find backups/ -name "db_backup_*.sql.gz" -mtime +30 -delete

# 2. 清理其他临时文件
find /tmp -type f -mtime +7 -delete

# 3. 重新运行备份
./scripts/backup-db.sh
```

**情况 B: 数据库连接问题**
```bash
# 1. 验证数据库连接
mysql -h <DB_HOST> -P <DB_PORT> -u <DB_USER> -p -e "SELECT 1;"

# 2. 检查 DATABASE_URL 环境变量
echo $DATABASE_URL

# 3. 修正配置后重新备份
export DATABASE_URL="mysql://correct_url"
./scripts/backup-db.sh
```

**情况 C: mysqldump 权限问题**
```bash
# 1. 检查用户权限
mysql -u <DB_USER> -p -e "SHOW GRANTS;"

# 2. 授予必要权限
mysql -u root -p -e "GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO '<DB_USER>'@'%';"
FLUSH PRIVILEGES;

# 3. 重新备份
./scripts/backup-db.sh
```

#### 验证
```bash
# 1. 确认新备份存在
ls -lt backups/db_backup_*.sql.gz | head -1

# 2. 校验备份完整性
./scripts/backup-verify.sh

# 3. 运行健康检查
./scripts/health-check.sh
```

---

### 3.2 备份文件损坏

#### 症状
- `backup-verify.sh` 校验失败
- gzip 解压失败
- SQL 文件内容异常

#### 诊断
```bash
# 1. 运行校验脚本
./scripts/backup-verify.sh backups/db_backup_YYYYMMDD_HHMMSS.sql.gz

# 2. 手动检查 gzip
gunzip -t backups/db_backup_YYYYMMDD_HHMMSS.sql.gz

# 3. 检查文件大小
ls -lh backups/db_backup_*.sql.gz
```

#### 处理步骤
```bash
# 1. 删除损坏的备份
rm backups/db_backup_YYYYMMDD_HHMMSS.sql.gz

# 2. 重新执行备份
./scripts/backup-db.sh

# 3. 校验新备份
./scripts/backup-verify.sh

# 4. 如果新备份也损坏,检查数据库完整性
mysql -e "CHECK TABLE users; CHECK TABLE schools;"
```

#### 验证
```bash
# 1. 校验新备份
./scripts/backup-verify.sh

# 2. 运行灾备演练
./scripts/dr-drill.sh
```

---

## 场景 4: 异地同步失败

### 4.1 S3/OSS 同步失败

#### 症状
- `backup-offsite-sync.sh` 失败
- CI 中 "Sync backup to offsite storage" 步骤失败
- 异地存储缺少最新备份

#### 诊断
```bash
# 1. 检查后端配置
echo $BACKUP_OFFSITE_BACKEND
echo $BACKUP_OFFSITE_REMOTE
echo $BACKUP_OFFSITE_BUCKET

# 2. 检查凭证
echo $BACKUP_OFFSITE_ACCESS_KEY
echo $BACKUP_OFFSITE_SECRET_KEY

# 3. 测试连接
aws s3 ls s3://$BACKUP_OFFSITE_BUCKET/  # S3
# 或
ossutil ls oss://$BACKUP_OFFSITE_BUCKET/  # OSS

# 4. 手动运行同步
./scripts/backup-offsite-sync.sh
```

#### 处理步骤

**情况 A: 凭证错误**
```bash
# 1. 更新凭证
export BACKUP_OFFSITE_ACCESS_KEY="new_access_key"
export BACKUP_OFFSITE_SECRET_KEY="new_secret_key"

# 2. 更新 Gitea Secrets
# 访问仓库 Settings > Secrets 更新对应 secret

# 3. 重新同步
./scripts/backup-offsite-sync.sh
```

**情况 B: 网络问题**
```bash
# 1. 测试网络连通性
ping s3.amazonaws.com  # S3
ping oss-cn-beijing.aliyuncs.com  # OSS

# 2. 检查代理设置
echo $http_proxy
echo $https_proxy

# 3. 配置代理后重试
export http_proxy=http://proxy:port
export https_proxy=http://proxy:port
./scripts/backup-offsite-sync.sh
```

**情况 C: 工具未安装**
```bash
# 1. 安装 aws-cli
pip install awscli
# 或
apt-get install -y awscli

# 2. 安装 rclone
curl https://rclone.org/install.sh | sudo bash

# 3. 重新同步
./scripts/backup-offsite-sync.sh
```

#### 验证
```bash
# 1. 列出远程文件
aws s3 ls s3://$BACKUP_OFFSITE_BUCKET/backups/
# 或
rclone lsf $BACKUP_OFFSITE_REMOTE

# 2. 对比本地和远程文件数量
LOCAL_COUNT=$(ls backups/db_backup_*.sql.gz | wc -l)
REMOTE_COUNT=$(aws s3 ls s3://$BACKUP_OFFSITE_BUCKET/backups/ | grep -c "db_backup")
echo "Local: $LOCAL_COUNT, Remote: $REMOTE_COUNT"
```

---

### 4.2 NFS 同步失败

#### 症状
- `backup-offsite-sync.sh` NFS 后端失败
- NFS 目录不可写

#### 诊断
```bash
# 1. 检查 NFS 挂载
mount | grep nfs

# 2. 检查目录权限
ls -la $BACKUP_OFFSITE_REMOTE

# 3. 测试写入
touch $BACKUP_OFFSITE_REMOTE/test && rm $BACKUP_OFFSITE_REMOTE/test
```

#### 处理步骤
```bash
# 1. 重新挂载 NFS
sudo umount /mnt/nfs
sudo mount -t nfs <nfs_server>:/path /mnt/nfs

# 2. 检查权限
sudo chown -R $USER:$USER /mnt/nfs/backups

# 3. 重新同步
./scripts/backup-offsite-sync.sh
```

---

## 场景 5: 灾备演练失败

### 5.1 演练恢复失败

#### 症状
- `dr-drill.sh` 步骤 3(恢复)失败
- 测试数据库创建成功但恢复失败

#### 诊断
```bash
# 1. 查看演练报告
cat docs/dr/reports/dr_drill_*.md

# 2. 手动测试恢复
mysql -h <DB_HOST> -u <DB_USER> -p -e "CREATE DATABASE test_manual;"
gunzip -c backups/db_backup_*.sql.gz | mysql -h <DB_HOST> -u <DB_USER> -p test_manual

# 3. 检查备份文件
./scripts/backup-verify.sh
```

#### 处理步骤
```bash
# 1. 清理失败的测试数据库
mysql -h <DB_HOST> -u <DB_USER> -p -e "DROP DATABASE IF EXISTS next_edu_dr_drill;"

# 2. 校验备份
./scripts/backup-verify.sh

# 3. 如果备份损坏,重新备份
./scripts/backup-db.sh

# 4. 重新运行演练
./scripts/dr-drill.sh
```

---

### 5.2 演练后测试数据库未清理

#### 症状
- `next_edu_dr_drill` 数据库残留
- 磁盘空间异常增长

#### 诊断
```bash
# 1. 检查测试数据库
mysql -e "SHOW DATABASES LIKE 'next_edu_dr_drill';"

# 2. 检查数据库大小
mysql -e "SELECT table_schema, SUM(data_length + index_length) / 1024 / 1024 AS size_mb FROM information_schema.tables WHERE table_schema = 'next_edu_dr_drill' GROUP BY table_schema;"
```

#### 处理步骤
```bash
# 1. 手动删除测试数据库
mysql -h <DB_HOST> -u <DB_USER> -p -e "DROP DATABASE IF EXISTS next_edu_dr_drill;"

# 2. 验证已删除
mysql -e "SHOW DATABASES LIKE 'next_edu_dr_drill';"
```

---

## 场景 6: 磁盘空间不足

#### 症状
- 健康检查 `disk` 状态为 `fail`
- 应用或数据库写入失败
- 系统响应缓慢

#### 诊断
```bash
# 1. 检查磁盘使用
df -h

# 2. 查找大文件
du -sh /* 2>/dev/null | sort -rh | head -10
du -sh /var/* 2>/dev/null | sort -rh | head -10

# 3. 查找大日志文件
find /var/log -type f -size +100M -exec ls -lh {} \;
```

#### 处理步骤
```bash
# 1. 清理旧备份
find backups/ -name "db_backup_*.sql.gz" -mtime +30 -delete

# 2. 清理日志
sudo find /var/log -type f -name "*.log" -mtime +7 -delete
sudo journalctl --vacuum-time=7d

# 3. 清理 Docker 资源
docker system prune -a --volumes
# 注意: 这会删除未使用的镜像和卷,谨慎使用

# 4. 清理 npm 缓存
npm cache clean --force

# 5. 清理临时文件
find /tmp -type f -mtime +7 -delete
```

#### 验证
```bash
# 1. 检查磁盘空间
df -h

# 2. 运行健康检查
./scripts/health-check.sh
```

---

## 附录: 快速参考命令

### 备份相关
```bash
# 执行备份
npm run backup

# 校验备份
npm run dr:backup-verify

# 异地同步
npm run dr:offsite-sync

# 灾备演练
npm run dr:drill

# 健康检查
npm run dr:health-check
```

### 恢复相关
```bash
# 从备份恢复
./scripts/restore-db.sh backups/db_backup_YYYYMMDD_HHMMSS.sql.gz

# 故障切换
./scripts/failover.sh --auto
```

### 诊断相关
```bash
# 完整健康检查
./scripts/health-check.sh

# 检查数据库
mysql -h <DB_HOST> -P <DB_PORT> -u <DB_USER> -p -e "SHOW PROCESSLIST;"

# 检查应用日志
docker logs nextjs-app --tail 100

# 检查磁盘空间
df -h
```

---

## 变更记录

| 日期 | 版本 | 变更内容 | 变更人 |
|------|------|---------|--------|
| 2026-06-17 | 1.0 | 初始版本 | - |
