Files
pangolin/deploy/docker-compose.yml
T
wangjia 20dbb5f363 feat(backup): 备份与灾备演练系统 [tsk_bYv-CoKSwgAC]
实现 xtrabackup + binlog 增量备份、age 加密异地存储与月度 DR 演练。

## 交付内容

### deploy/backup/ —— 备份容器
- Dockerfile:Ubuntu 22.04 + xtrabackup 8.0 + age v1.2.0 + AWS CLI v2 + pg_client
- entrypoint.sh:初始化 AWS 凭据 / age 公钥,启动 crond
- crontab.txt:全量 02:00 / 增量每 15min / DR 演练每月 1 日
- scripts/lib.sh:日志 / age 加密 / S3 上传 / 连接测试等共用函数
- scripts/backup-full.sh:MySQL xtrabackup 全量 + pg_dump 全量 + SQLite 备份
- scripts/backup-inc.sh:MySQL xtrabackup 增量 + binlog flush & 归档 + pg_dump 快照
- scripts/restore.sh:从 S3 下载、解密、prepare / pg_restore 完整恢复
- scripts/dr-drill.sh:月度演练 —— RPO 验证 / 备份完整性 / MySQL 临时容器恢复 / RTO 度量
- scripts/verify-rpo-rto.sh:实时 RPO/RTO 状态检查(S3 最新备份时间戳)
- backup.env.example:配置模板(MySQL / PG / S3 / age 公钥)

### 修改已有文件
- deploy/docker-compose.yml:新增 pangolin-backup 服务
- deploy/scripts/gen-secrets.sh:幂等生成 age 密钥对 + backup.env 模板
- .gitignore:排除 age 私钥 / backup.env
- deploy/README.md:容器一览 + 备份快速命令

### docs/备份与灾备.md
S3 初始化、IAM 权限设计、age 密钥管理、MySQL 权限、DR 恢复步骤、RTO 测量标准

## 设计要点
- RPO ≤ 15min:每 15min xtrabackup 增量 + binlog flush + pg_dump 快照
- RTO ≤ 4h:月度演练度量,估算恢复时长约 2h
- 独立身份账号:备份 IAM 仅有 s3:PutObject,与生产账号完全隔离
- age 加密:公钥存服务器,私钥离线保存,S3 中无明文
- 无 crontab:cron 在容器内运行,绕过 ec2-user crontab 限制
- 幂等部署:backup.env 存在则跳过,age 密钥对存在则跳过

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 01:09:18 +08:00

100 lines
4.7 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pangolin 数据/分发面(方案 C)。
# 与现网 marzban/jiu/billing/blog 并存:edge 反代到它们,其余容器不动。
# 镜像固定版本(EC2 实测:nginx 1.27 / sing-box 1.13.12 / xray 26.3.27)。
# project 名由命令行 `-p pangolin` 指定(兼容宿主 docker-compose 版本)。
services:
# 分发层:接管 80/443,移植现网全部路由(host 网络才能绑端口并回连 127.0.0.1 后端)。
# 自定义镜像 = 官方 nginx + certbot,容器自己续签自己用的证书(无需宿主 cron / docker socket)。
edge:
# 镜像由脚本用 `docker build -t pangolin-edge:local ./edge` 预先构建
# (宿主 docker-compose 版本不支持 compose build)。
image: pangolin-edge:local
container_name: pangolin-edge
restart: unless-stopped
network_mode: host
volumes:
- ./edge/nginx.conf:/etc/nginx/nginx.conf:ro
- ./edge/stream.conf:/etc/nginx/stream.conf:ro
- ./edge/conf.d:/etc/nginx/conf.d:ro
- /etc/letsencrypt:/etc/letsencrypt # rw:certbot 续期写入,nginx 读取
- ./secrets/cloudflare.ini:/etc/cloudflare.ini:ro # DNS-01 续期凭据
- /opt/jiu:/opt/jiu:ro # jiu 静态(images/web/marketing)
- /var/www/pay:/var/www/pay:ro # 收款页静态
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 1m
timeout: 10s
retries: 3
# Hysteria2 加速线(UDP 443)
singbox:
image: ghcr.io/sagernet/sing-box:v1.13.12
container_name: pangolin-singbox
restart: unless-stopped
network_mode: host
volumes:
- ./singbox/config.json:/etc/sing-box/config.json:ro
- ./secrets/hy2:/etc/sing-box/cert:ro
command: ["run", "-c", "/etc/sing-box/config.json"]
# 证书工具(DNS-01 / Cloudflare)—— 仅手动用于首次签发/重配:
# docker-compose -p pangolin run --rm certbot certonly --dns-cloudflare ...
# 日常【自动续期由 edge 容器自身完成】(见 edge/pangolin-entrypoint.sh),无需本服务。
certbot:
image: certbot/dns-cloudflare:latest
container_name: pangolin-certbot
profiles: ["certbot"]
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- ./secrets/cloudflare.ini:/etc/cloudflare.ini:ro
entrypoint: ["certbot"]
# ──────────────────────────────────────────────────────────────────────────
# 备份与灾备(xtrabackup 全量 + binlog 增量 + age 加密 + S3 异地)
# 使用方法:
# 1. cp backup/backup.env.example secrets/backup.env && vi secrets/backup.env
# 2. docker build -t pangolin-backup:local ./backup
# 3. docker-compose -p pangolin up -d backup
# 注意:
# - 使用 network_mode: host,可访问 127.0.0.1 上的 MySQL/PostgreSQL
# - MySQL 数据目录挂载为只读(xtrabackup 需要);若 MySQL 在 Docker 卷中,
# 修改 MYSQL_DATADIR_HOST 指向实际卷路径(如 /var/lib/docker/volumes/jiu_jiu_mysql_data/_data
# - /opt/backup 是宿主备份暂存目录,重建容器后数据不丢失
# - age 私钥不存服务器;如需 DR 演练解密恢复,临时注入 AGE_PRIVATE_KEY 环境变量
# ──────────────────────────────────────────────────────────────────────────
backup:
# 镜像由脚本预先构建:docker build -t pangolin-backup:local ./backup
image: pangolin-backup:local
container_name: pangolin-backup
restart: unless-stopped
network_mode: host
env_file:
- ./secrets/backup.env
volumes:
# 宿主备份暂存目录(全量 / 增量 / binlog 解密临时文件)
- /opt/backup:/backup
# MySQL 数据目录(只读挂载供 xtrabackup)
# 若 MySQL 在 Docker 卷内,替换为实际路径:
# - /var/lib/docker/volumes/<volume_name>/_data:/var/lib/mysql:ro
- ${MYSQL_DATADIR_HOST:-/var/lib/mysql}:/var/lib/mysql:ro
# Marzban SQLite 数据目录(只读)
- ${MARZBAN_DATA_HOST:-/var/lib/marzban}:/var/lib/marzban:ro
healthcheck:
test: ["CMD", "pgrep", "cron"]
interval: 5m
timeout: 10s
retries: 3
# 新节点用的 REALITY —— 本台不启用(REALITY 仍由 Marzban 提供)
xray:
image: ghcr.io/xtls/xray-core:26.3.27
container_name: pangolin-xray
profiles: ["newnode"]
restart: unless-stopped
ports:
- "127.0.0.1:11443:11443"
volumes:
- ./xray/config.json:/etc/xray/config.json:ro
command: ["run", "-c", "/etc/xray/config.json"]