2026-05-14 09:57:01 +08:00

308 lines
14 KiB
Markdown
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.

# 腾讯云 TAT 滚动部署方案
本方案只部署本仓库 Go 微服务,不接管同机已有 Java 项目。远端连接使用腾讯云 TAT发布时只操作 `hyapp-*` systemd unit、`hyapp-*` Docker 容器和 `/etc/hyapp/<service>/docker.env`
MySQL、Redis、MQ 都按腾讯云托管资源处理。本方案不会在这些 CVM 上部署、重启或迁移本地 MySQL/Redis/MQ服务配置只需要指向腾讯云托管实例的内网地址。
## 机器拓扑
`deploy` 机器是发布控制面,不承载业务流量:
| 机器 | 实例 ID | 公网 IP | 私网 IP | 角色 |
| --- | --- | --- | --- | --- |
| `deploy` | `ns-ntmpcd3a` | `43.164.75.199` | `10.2.1.3` | 构建镜像、推送镜像、调用 TAT/CLB API 调度发布 |
当前机器按截图落到以下服务:
| 机器 | 实例 ID | 私网 IP | 本仓库服务 |
| --- | --- | --- | --- |
| `gateway-1` | `ins-aeyaoj40` | `10.2.1.8` | `gateway-service` |
| `gateway-2` | `ins-irvi5b7u` | `10.2.2.17` | `gateway-service` |
| `new-app-1` | `ins-fi5zufpk` | `10.2.1.4` | `room-service`, `user-service` |
| `new-app-2` | `ins-hwhaoe8c` | `10.2.1.13` | `room-service`, `user-service` |
| `new-game-1` | `ins-ir229jtw` | `10.2.1.10` | `game-service` |
| `new-game-2` | `ins-460rw7gu` | `10.2.1.6` | `game-service` |
| `new-app-activity-1` | `ins-n8wut6n8` | `10.2.1.11` | `activity-service`, `notice-service` |
| `new-app-activity-2` | `ins-d1n303sa` | `10.2.1.7` | `activity-service`, `notice-service` |
| `pay-1` | `ins-ceqfcxd2` | `10.2.11.14` | `wallet-service` |
| `pay-2` | `ins-awhb74q6` | `10.2.12.5` | `wallet-service` |
如果 `new-app-*` 只部署 `room-service` 或只部署 `user-service`,直接删掉 `inventory` 中对应服务的 host 绑定,不要让脚本猜。
## 部署模式
采用“deploy 服务器调度 + 业务机只运行镜像”的模式:
1. `deploy` 服务器拉取本仓库代码,构建 Docker 镜像,推送到 CCR/Harbor。
2. `deploy` 服务器调用腾讯云 CLB API 对目标实例摘流。
3. `deploy` 服务器通过 TAT 在目标业务机执行 `docker pull``systemctl restart hyapp-<service>`
4. 目标业务机只保存 `/etc/hyapp/<service>/config.yaml``docker.env`,不拉代码、不编译、不保存 Git 凭据。
不要让每台业务服务器自己 `git pull && build`。那会把代码权限、构建环境、版本一致性和回滚都扩散到业务机,且多服务发布时无法统一做 CLB 摘流。
## 前置条件
1. 每台目标机已安装腾讯云 TAT agent控制台能执行命令。
2. 每个 Go 服务已按 `deploy/standalone/systemd` 安装对应 systemd unit。
3. 每个 Go 服务已有 `/etc/hyapp/<service>/config.yaml``/etc/hyapp/<service>/docker.env`
4. 每个服务至少有两台实例CLB 后端都挂实例端口。
5. `room-service` 每台机器必须有唯一 `node_id``advertise_addr` 必须是当前机器私网地址,例如 `10.2.1.4:13001`,不能写内网 CLB。
6. 每个 `config.yaml``mysql_dsn``redis_addr`、MQ endpoint/topic/group 指向腾讯云托管资源的私网地址。
7. 发布机安装腾讯云 Python SDK 和 YAML 解析依赖:
```bash
python3 -m pip install tencentcloud-sdk-python PyYAML
```
凭据通过环境变量提供:
```bash
export TENCENTCLOUD_SECRET_ID="..."
export TENCENTCLOUD_SECRET_KEY="..."
export TENCENTCLOUD_REGION="me-saudi-arabia"
```
如果继续沿用 `hy-app-monitor`,可以把这些变量放在 deploy 机器的 `/opt/hy-app-monitor/.env`,运行脚本时传 `--env-file /opt/hy-app-monitor/.env`。本地开发机路径 `/Users/hy/Documents/hy/hy-app-monitor/.env` 只用于本机调试。
## Deploy 服务器初始化
`deploy / 10.2.1.3` 上安装基础工具:
```bash
sudo mkdir -p /opt/deploy/sources /opt/deploy/inventory
sudo yum install -y git docker python3 python3-pip || sudo apt-get update && sudo apt-get install -y git docker.io python3 python3-pip
sudo systemctl enable --now docker
python3 -m pip install --user tencentcloud-sdk-python PyYAML
```
拉取本仓库:
```bash
cd /opt/deploy/sources
git clone <YOUR_GIT_REPO_URL> hyapp-server
cd hyapp-server
```
准备 inventory
```bash
cp deploy/tencent-tat/inventory.prod.example.json /opt/deploy/inventory/hyapp.prod.json
vi /opt/deploy/inventory/hyapp.prod.json
```
`inventory` 里的 `registry` 必须和实际镜像仓库一致。如果使用 `hy-app-monitor` 里的 Harbor 配置,通常是:
```text
registry = ${HARBOR_REGISTRY}/${HARBOR_PROJECT}
```
业务配置放到各目标机 `/etc/hyapp/<service>/config.yaml`。腾讯云 MySQL、Redis、MQ、JWT、IM/RTC/COS 等密钥不要提交到 Git。
配置字段契约以 `services/<service>/configs/config.tencent.example.yaml` 为准。开发时如果在本地 `config.yaml``config.docker.yaml` 新增字段,必须同时补到 `config.tencent.example.yaml`;生产渲染前运行校验,避免线上漏字段:
```bash
python3 deploy/tencent-tat/validate_rendered_configs.py \
--inventory deploy/tencent-tat/inventory.prod.example.json
```
如果已经渲染了生产最终配置,还要校验每台机器的最终 YAML
```bash
python3 deploy/tencent-tat/validate_rendered_configs.py \
--inventory deploy/tencent-tat/inventory.prod.example.json \
--rendered-dir /tmp/hyapp-rendered-prod
```
该脚本只比较 YAML key不打印配置值避免把生产密钥带到日志里。
## 构建并推送镜像
`deploy` 服务器上执行。2 核 2GiB 机器可以作为调度器;如果全量构建 OOM 或太慢,把构建放到 CIdeploy 服务器只负责调度发布。
```bash
cd /opt/deploy/sources/hyapp-server
TAG="$(date -u +%Y%m%d%H%M%S)-$(git rev-parse --short HEAD)"
REGISTRY="10.2.1.3:18082/hyapp"
docker build -t "$REGISTRY/gateway-service:$TAG" -f services/gateway-service/Dockerfile .
docker build -t "$REGISTRY/room-service:$TAG" -f services/room-service/Dockerfile .
docker build -t "$REGISTRY/user-service:$TAG" -f services/user-service/Dockerfile .
docker build -t "$REGISTRY/wallet-service:$TAG" -f services/wallet-service/Dockerfile .
docker build -t "$REGISTRY/activity-service:$TAG" -f services/activity-service/Dockerfile .
docker build -t "$REGISTRY/game-service:$TAG" -f services/game-service/Dockerfile .
docker build -t "$REGISTRY/notice-service:$TAG" -f services/notice-service/Dockerfile .
docker push "$REGISTRY/gateway-service:$TAG"
docker push "$REGISTRY/room-service:$TAG"
docker push "$REGISTRY/user-service:$TAG"
docker push "$REGISTRY/wallet-service:$TAG"
docker push "$REGISTRY/activity-service:$TAG"
docker push "$REGISTRY/game-service:$TAG"
docker push "$REGISTRY/notice-service:$TAG"
```
如果实际用 Harbor`REGISTRY` 换成 `${HARBOR_REGISTRY}/${HARBOR_PROJECT}`,并先 `docker login`
## 配置 inventory
复制模板后填真实 CLB
```bash
cp deploy/tencent-tat/inventory.prod.example.json deploy/tencent-tat/inventory.prod.json
vi deploy/tencent-tat/inventory.prod.json
```
必须替换:
- `gateway-service.clb.load_balancer_id`:公网 gateway CLB。
- `gateway-service.clb.listener_ids`:公网 HTTP/HTTPS 监听器。
- 内部服务各自的 `load_balancer_id``listener_ids`room/user/wallet/activity/game 内网 CLB。
脚本通过实例 ID、私网 IP、端口三者匹配 CLB target。发布前会把当前实例在相关 listener 下的权重改成 `0`,服务健康后恢复原权重。
`managed_dependencies` 只记录部署边界:腾讯云 MySQL/Redis/MQ 是外部托管依赖,不纳入 TAT 发布动作。生产变更这些资源时单独走云数据库、Redis 或 MQ 的变更流程。
当前生产 inventory 已写入真实绑定:
| 服务 | CLB | 监听器 | 对外/内网入口 |
| --- | --- | --- | --- |
| `gateway-service` | `lb-cbiabr1q` | `lbl-ktvz8m6k` / rule `loc-pwedmsmm` | `https://api.global-interaction.com` -> `13000` |
| `room-service` | `lb-epvnr4o0` | `lbl-d2urlzba` | `10.2.1.16:13001` |
| `user-service` | `lb-epvnr4o0` | `lbl-honi8z3a` | `10.2.1.16:13005` |
| `activity-service` | `lb-epvnr4o0` | `lbl-j7oqtfhq` | `10.2.1.16:13006` |
| `game-service` | `lb-epvnr4o0` | `lbl-ku138b4y` | `10.2.1.16:13008` |
| `notice-service` | `lb-epvnr4o0` | `lbl-aux9xsjq` | `10.2.1.16:13009` |
| `wallet-service` | `lb-4f5xi6p0` | `lbl-9wi5mvu4` | `10.2.1.15:13004` |
Harbor 项目使用 `hyapp`,当前生产镜像前缀是 `10.2.1.3:18082/hyapp`。不要使用其他业务项目名前缀。
## 线上服务配置边界
这些机器上只部署 Go 服务进程:
- `gateway-service``room_service_addr/user_service_addr/wallet_service_addr/activity_service_addr/game_service_addr` 指向各服务内网 CLB/DNS。
- `room-service``mysql_dsn` 指向腾讯云 MySQL`redis_addr` 指向腾讯云 Redis`advertise_addr` 指向本机私网 IP。
- `user-service``mysql_dsn`、worker 里引用的 wallet/room MySQL DSN、登录风控 Redis 都指向腾讯云托管实例。
- `wallet-service``activity-service``game-service`MySQL 和下游 gRPC 地址都使用腾讯云托管资源或内网 CLB/DNS。
- `notice-service``mysql_dsn` 指向 `hyapp_notice``wallet_database` 指向 `hyapp_wallet`;它只读取 wallet outbox 并写自己的投递位点,不修改钱包事实表。
- MQ 当前只作为 outbox/event bus 方向的外部托管依赖;本脚本不会创建 topic、consumer group 或本地 broker。
腾讯云 MySQL DSN 必须保留 `loc=UTC`。Redis 使用同一托管实例时,用 `redis_db` 或 key prefix 区分业务域,不要把 Redis 当恢复事实来源。
## 常用命令
先看计划,不执行远端动作:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py plan \
--inventory deploy/tencent-tat/inventory.prod.json \
--services gateway-service,room-service
```
用 TAT 连接目标机并自动发现真实部署状态和 CLB 绑定:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py discover \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services gateway-service,room-service,user-service,wallet-service,activity-service,game-service,notice-service
```
`discover` 会读取每台目标机的 `hyapp-*` systemd unit、`/etc/hyapp/*/docker.env``hyapp-*` 容器和监听端口,并通过 CLB API 按后端私网 IP/端口反查负载均衡绑定。
单服务重启:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py restart \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services wallet-service \
--yes
```
多服务重启:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py restart \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services room-service,user-service,wallet-service,activity-service,game-service,notice-service \
--yes
```
单服务更新部署:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py deploy \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services gateway-service \
--tag 20260513-1349 \
--yes
```
多服务更新部署:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py deploy \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services gateway-service,room-service,user-service,wallet-service,activity-service,game-service,notice-service \
--tag 20260513-1349 \
--yes
```
如果某个服务镜像 tag 不同,可以显式传镜像:
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py deploy \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services gateway-service,wallet-service \
--image gateway-service=10.2.1.3:18082/hyapp/gateway-service:gw-20260513 \
--image wallet-service=10.2.1.3:18082/hyapp/wallet-service:pay-20260513 \
--yes
```
只维护某一台机器时使用 `--hosts`,例如只重启 `gateway-1`
```bash
python3 deploy/tencent-tat/hyapp_tat_deploy.py restart \
--env-file /opt/hy-app-monitor/.env \
--inventory /opt/deploy/inventory/hyapp.prod.json \
--services gateway-service \
--hosts gateway-1 \
--yes
```
## 无感更新顺序
对每个服务实例,脚本固定执行:
1. 读取该实例当前 CLB 绑定和原权重。
2. 将该实例在所有匹配 listener/rule 下的权重改为 `0`
3. 等待 `drain_seconds`,让 CLB 停止分配新请求。
4. 通过 TAT 在目标机更新 `docker.env``IMAGE`,执行 `docker pull`
5. `systemctl restart hyapp-<service>`Docker 发送 `SIGTERM`,服务进入 graceful shutdown。
6. 等待 systemd active 和 Docker health 恢复。
7. 恢复该实例原 CLB 权重。
8. 等待 `stabilize_seconds` 后进入下一台实例。
如果重启或健康检查失败,脚本会停止后续步骤;已经摘流的故障实例保持权重 `0`,避免坏实例重新接流,需要人工确认后再恢复。
## 用户无感边界
- 客户端长连接、房间公屏、单聊仍由腾讯云 IM 承接gateway 重启不会断开 IM。
- gateway 是 HTTP 入口,摘流后只影响当前实例的新 HTTP 请求;另一台 gateway 继续接流。
- 内部服务通过各自内网 CLB 摘流滚动gateway 的 gRPC client 已配置短重试,只覆盖瞬时 `UNAVAILABLE`
- `room-service` 已有 owner route/node registry。旧 owner 停机时会释放 lease异常退出时最多等 `lease_ttl` 到期后其他节点接管。
- 正在执行的单个 HTTP/gRPC 请求可能返回失败,客户端必须按现有接口幂等键重试;“无感”指发布期间整体入口不断流、房间 IM 不断连。
## 禁止项
- 不要使用 `--no-clb` 做正常发布;它只适合孤立排障。
- 不要多个 `room-service` 共用同一个 `node_id`
- 不要把 `room-service.advertise_addr` 写成 CLB 地址。
- 不要先 `systemctl restart` 再摘 CLB。
- 不要在同机 Java compose 目录里执行本仓库部署命令。