# S30 备份、Runbook、灰度与上线 ## 1. 范围 当前 S30 面向本地 KVM 开发环境的 V2 闭环冻结与上线演练,不执行真实阿里云切流。阿里云迁移必须在新 VPC、正式 DNS/TLS、独立数据盘和安全组完成后,按本文重新演练。 ## 2. 冻结清单 | 项 | 当前值 | | --- | --- | | B release | `/opt/lisglosips/releases/s28-v2-20260621220924` | | B current | `/opt/lisglosips/current` 指向当前 release | | A OpenSIPS | `/etc/opensips/opensips.cfg` | | T 测试脚本 | `/opt/lisglosips-s28/lisglosips-s28-sip.py` | | 数据库迁移 | 以 `prisma/migrations/` 和 B 当前库为准 | | Redis 配置版本 | 以 `cfg:active_version` 为准 | 冻结后只允许以下变更进入上线窗口: - 修复已确认阻塞缺陷; - 更新正式域名/TLS/安全组等环境参数; - 文档化灰度和回滚记录。 ## 3. 上线前备份 在任何 release 切换、数据库迁移、OpenSIPS 配置替换、Nginx/TLS 调整前执行: ```bash sudo systemctl start lisglosips-backup.service sudo find /data/backups/mysql -mindepth 1 -maxdepth 1 -type d | sort | tail -1 sudo find /data/backups/redis -mindepth 1 -maxdepth 1 -type d | sort | tail -1 sudo sha256sum -c /data/backups/mysql//SHA256SUMS sudo sha256sum -c /data/backups/redis//SHA256SUMS ``` 另外备份: ```bash sudo install -d -m 0700 /var/backups/lisglosips-s30/ sudo cp -a /etc/lisglosips /etc/nginx /etc/systemd/system/lisglosips@.service /var/backups/lisglosips-s30// sudo cp -a /etc/opensips/opensips.cfg /var/backups/lisglosips-s30//opensips.cfg ``` 备份目录只允许 root 读取,不复制明文 secret 到仓库或聊天记录。 ## 4. 恢复演练 MySQL 只恢复到隔离临时库: ```bash sudo mysql -e 'DROP DATABASE IF EXISTS lisglosips_s30_restore; CREATE DATABASE lisglosips_s30_restore CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;' gzip -dc /data/backups/mysql//lisglosips.sql.gz | sudo mysql lisglosips_s30_restore sudo mysql -e 'SELECT COUNT(*) FROM lisglosips_s30_restore.raw_cdrs;' sudo mysql -e 'DROP DATABASE lisglosips_s30_restore;' ``` Redis 只启动隔离临时实例: ```bash sudo install -d -m 0700 /tmp/lisglosips-s30-redis-restore sudo cp /data/backups/redis//dump.rdb /tmp/lisglosips-s30-redis-restore/dump.rdb sudo redis-server --dir /tmp/lisglosips-s30-redis-restore --dbfilename dump.rdb --port 0 --unixsocket /tmp/lisglosips-s30-redis.sock --daemonize yes sudo redis-cli -s /tmp/lisglosips-s30-redis.sock DBSIZE sudo redis-cli -s /tmp/lisglosips-s30-redis.sock SHUTDOWN NOSAVE ``` 禁止把备份直接覆盖运行中 `/var/lib/mysql`、`/var/lib/redis`。 ## 5. 发布顺序 1. 记录当前 release、数据库迁移版本、Redis `cfg:active_version`、A OpenSIPS 配置 SHA-256。 2. 执行 S30 备份与恢复演练。 3. 上传新 release 到 `/opt/lisglosips/releases/`,校验 `package.json`、`pnpm-lock.yaml` 和构建产物。 4. 执行向前兼容数据库迁移,禁止同窗口不可逆删列。 5. 原子切换 `/opt/lisglosips/current`。 6. 重启 `lisglosips@api`、`lisglosips@cdr-worker`、`lisglosips@recording-worker`。 7. `nginx -t` 后 reload Nginx。 8. 执行 `infra/server-b/s30/lisglosips-release-preflight.sh`。 9. 发布 Redis 配置快照并确认 A 使用新版本。 10. T 发起测试呼叫,按 Call-ID 核对 SIP、CDR、计费、录音、HOMER。 11. 开始灰度。 ## 6. 灰度策略 本地环境使用 `cus_s28_t` 和 T 专用 UAS 作为灰度对象;生产环境选择一个低风险客户或一条低风险线路。 灰度观察项: - SIP 2xx/4xx/5xx 分布; - Redis Stream lag 和 pending; - CDR 入库与重复扣费; - 余额变化和充值幂等; - 录音 READY 比例、A tmpfs 使用率; - API 5xx、P95/P99; - HOMER 是否可按 Call-ID 查询。 扩大条件:连续观察 24-72 小时无 P1/P2 缺陷、无 CDR/余额异常、录音搬运无持续积压。 ## 7. 回滚 ### B 应用回滚 ```bash sudo infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/ ``` 脚本会切换 `current`、重启 API/CDR/Recording Worker、校验 Nginx 并检查 API ready。 ### Redis 配置回滚 切回上一个 `cfg:active_version`,不删除 Stream 或 deadletter。回滚后用 T 呼叫验证 A 已读取旧版本。 ### OpenSIPS 回滚 ```bash sudo infra/server-a/s30/lisglosips-opensips-config-restore.sh /var/backups//opensips.cfg ``` 脚本会先备份当前配置、执行 `opensips -C`,通过后替换并重启 OpenSIPS。重启会影响现有通话,生产必须放在维护窗口或确认可接受。 ### 数据库回滚 优先发布向前修复迁移;只有在确认不可用且有完整备份时,才进入维护窗口做数据恢复。充值、CDR、审计、录音记录不直接物理删除。 ### S29 Redis 恢复遗留风险 若 B Redis 故障恢复后 A 继续返回 `503 Redis Unavailable`: 1. 确认 B `redis-server` active,A 到 B Redis 端口可达。 2. 重启 A `lisglosips-redis-auth-proxy`。 3. 若仍失败,维护窗口内重启 A `opensips`。 4. 记录 Call-ID、A 日志、Redis proxy 日志,作为后续根因修复输入。 ## 8. 阿里云迁移前阻断项 - 正式 DNS/TLS 未完成; - A/B 未位于同 VPC 低时延私网; - B 未使用独立数据盘承载 MySQL、Redis、录音和监控; - 安全组未限制 SSH 管理来源,或禁用了 SSH 密钥登录; - 未完成新环境备份恢复演练; - 未完成 T 或外部测试端到端呼叫; - Redis 恢复后 OpenSIPS 自动恢复风险未纳入告警/Runbook。