Files
lisglosips/docs/PHASE2_BUSINESS_PREFIX_GATEWAY_MIGRATION_RUNBOOK.md
T

7.7 KiB
Raw Blame History

业务前缀重构第 8 步迁移与发布前验收 Runbook

范围

本文用于业务前缀、客户网关匹配和落地号码改写重构的发布前准备。当前仍以本地 KVM/Tailscale 环境为准,不进入阿里云生产迁移。

本步骤覆盖:

  • B 执行新增 Prisma migration 前后的检查。
  • 旧客户网关单 IP 和旧 customer_gateway_policies 到二期模型的安全迁移。
  • Redis 配置发布检查。
  • A OpenSIPS/Lua 替换前语法检查和回滚点。
  • A/B/T 端到端验收清单。

影响、风险与回滚点

影响范围:

  • MySQL:新增二期表/列后,customer_gateways.line_group_idcustomer_gateway_ipscustomer_gateway_caller_prefixes 会被迁移脚本写入。
  • Redis:迁移脚本成功应用后会写 customer_gateway_config outboxconfig-publisher 会发布新版本配置。
  • OpenSIPS:后续发布 A 侧 opensips.cfglisglosips_hotpath.lua 后,INVITE 鉴权、路由、主被叫改写进入新热路径。
  • CDR:第 7 步已让 Worker 可写入原始被叫、业务前缀、落地主被叫字段。

主要风险:

  • 多条旧策略无法自动合并到“一个客户网关一个线路组”,必须人工拆分或确认保留规则。
  • calleeMode=EQUALS/PREFIX 不能安全推断为业务前缀,必须先在“业务前缀管理”中建立前缀并重新绑定客户网关。
  • callerMode=EQUALS 在新模型下没有严格等值语义,不能自动迁移为前缀,否则可能扩大匹配范围。
  • A 侧 OpenSIPS 配置替换会影响现有通话,必须维护窗口或确认可接受。

回滚点:

  • B 数据库:执行 migration 前必须按 S30 Runbook 备份 MySQL;迁移脚本 apply 前需保存 dry-run 报告。
  • B release:保留当前 /opt/lisglosips/current 指向,可通过 S30 rollback 脚本切回旧 release。
  • Redis:记录 cfg:active_versioncfg:previous_version,必要时切回上一版本。
  • A OpenSIPS:替换前备份 /etc/opensips/opensips.cfg/etc/opensips/lisglosips_hotpath.lua;语法检查失败不得重启;重启后失败则恢复备份并再次 opensips -C

迁移脚本

本仓库新增:

scripts/phase2-gateway-migration.mjs

默认 dry-run,不写库:

node scripts/phase2-gateway-migration.mjs --json

显式 apply 才写库:

node scripts/phase2-gateway-migration.mjs --apply --json

脚本只自动迁移满足以下条件的客户网关:

  • 每个客户网关恰好一条启用旧策略。
  • 旧策略 calleeMode=ANY
  • 旧策略 callerMode=ANY,或 callerMode=PREFIXcallerValue 非空。

脚本会拒绝自动迁移以下情况:

  • 多条启用旧策略。
  • 没有启用旧策略。
  • 旧被叫等值或前缀匹配。
  • 旧主叫等值匹配。
  • 新旧线路组字段冲突。

apply 成功后,脚本会写入一个 customer_gateway_config outbox,用于触发配置发布。

B 侧发布前步骤

  1. 记录状态:
readlink -f /opt/lisglosips/current
mysql -NBe "SELECT migration_name, finished_at FROM _prisma_migrations ORDER BY finished_at DESC LIMIT 5;"
redis-cli --no-auth-warning GET cfg:active_version
  1. 执行 S30 备份:
sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-preflight.sh
sudo /usr/local/sbin/lisglosips-mysql-backup
sudo /usr/local/sbin/lisglosips-redis-backup
  1. 部署新 release 到独立目录,不在线修改 current。

  2. 执行数据库 migration

export DATABASE_URL='mysql://<MIGRATE_USER>@127.0.0.1:3306/lisglosips'
pnpm exec prisma migrate deploy
pnpm exec prisma migrate status
  1. 迁移脚本 dry-run
export DATABASE_URL='mysql://<MIGRATE_USER>@127.0.0.1:3306/lisglosips'
node scripts/phase2-gateway-migration.mjs --json > /tmp/phase2-gateway-migration.dry-run.json
  1. blockers 非空,先人工处理,不执行 apply。

  2. 确认 dry-run 后执行 apply

node scripts/phase2-gateway-migration.mjs --apply --json > /tmp/phase2-gateway-migration.apply.json
  1. 等待或触发 config-publisher,检查新配置发布:
systemctl status lisglosips@config-publisher --no-pager
redis-cli --no-auth-warning GET cfg:active_version

A 侧 OpenSIPS 发布前步骤

  1. 备份:
sudo install -d -m 0750 /var/backups/lisglosips-phase2
sudo cp -a /etc/opensips/opensips.cfg /var/backups/lisglosips-phase2/opensips.cfg.$(date +%Y%m%d%H%M%S)
sudo cp -a /etc/opensips/lisglosips_hotpath.lua /var/backups/lisglosips-phase2/lisglosips_hotpath.lua.$(date +%Y%m%d%H%M%S)
  1. 替换候选文件后,加载 Lua 并确认 SHA 与 opensips.cfgEVALSHA 一致。

  2. 必须先执行语法检查:

sudo opensips -C -f /etc/opensips/opensips.cfg
  1. 语法检查通过后才可重启:
sudo systemctl restart opensips
sudo systemctl is-active opensips

Release Artifact 规范

发布包必须由固定脚本生成,禁止临时手工复制目录作为 release:

pnpm release:artifact -- --release-id sXX-name-YYYYMMDDHHmmss

脚本会执行构建、校验发布必需目录、生成 RELEASE_MANIFEST.json,并输出 dist/releases/<release-id>.tar.gz.sha256。正式给 B 机使用的 artifact 必须在 Linux 环境生成;在本地 Windows 仅允许使用 --check 或带 --allow-non-linux 生成检查包,不能作为服务器最终发布包。

A/B/T 验收清单

至少验证以下呼叫:

  • 命中业务前缀:例如客户送 67118822190000,话单 callee=18822190000raw_callee=67118822190000business_prefix=671
  • 任意被叫规则:客户网关 calleeMatchMode=ANY 时,旧无业务前缀呼叫仍可路由。
  • 匹配优先级:业务前缀优先于主叫前缀,精确规则优先于空业务前缀兜底;命中业务前缀但主叫不通过时,不应退回裸号兜底网关。
  • 唯一性约束:同一 IP + 同一业务前缀组合只能归属一个客户网关;同一 IP 下主叫前缀不能互相覆盖;IP 可跨客户复用但最终匹配必须唯一。
  • 计费口径:客户网关决定客户侧计费,落地网关决定供应商侧成本,业务前缀不参与计费产品、报表维度或权限隔离。
  • 主叫前缀命中:配置 callerPrefixes 后,命中前缀允许呼叫。
  • 主叫前缀不命中:返回 CALLER_PREFIX_NOT_MATCHED
  • 业务前缀不命中:返回 BUSINESS_PREFIX_NOT_MATCHED
  • 落地被叫前缀:落地侧实际 Request-URI 被叫包含 landingCalleePrefix
  • 落地指定主叫:落地侧 From 主叫按权重池选择,话单 landing_caller 有值。
  • 屏蔽地区跳过:命中落地网关屏蔽地区时尝试同线路组下一网关。
  • 当前通话页:呼叫过程中可看到当前通话,强制挂断可用。
  • 话单详情:可看到原始被叫、业务前缀、落地主叫、落地被叫、地级市、运营商。

回滚

B 应用回滚:

sudo infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/<previous-release>

Redis 配置回滚:

# 使用当前系统已验证的 Redis 运维方式切回 cfg:previous_version

A OpenSIPS 回滚:

sudo cp -a /var/backups/lisglosips-phase2/<opensips.cfg.backup> /etc/opensips/opensips.cfg
sudo cp -a /var/backups/lisglosips-phase2/<lisglosips_hotpath.lua.backup> /etc/opensips/lisglosips_hotpath.lua
sudo opensips -C -f /etc/opensips/opensips.cfg
sudo systemctl restart opensips

数据库回滚原则:

  • 本次 migration 为向前兼容新增表/列,不做破坏性 down migration。
  • 若 apply 后发现业务规则错误,优先通过客户网关页面/API 修正 lineGroupId、主叫规则和业务前缀绑定,再重新发布 Redis 配置。
  • 若必须恢复数据,使用执行前 MySQL 备份在隔离环境验证后再恢复。