Files
EchoChat/docs/deployment/meeting-mvp.md
bujinyuan a01e973335 feat(deploy): Task 14 完成 docker-compose 扩展 + 环境变量双态部署
- deploy/docker-compose.dev.yml 新增 media-server 服务 + coturn(profiles:public),
  全部服务参数改为 env 变量注入,go-service 依赖 media-server
- deploy/.env.example / .env.local.example / .env.public.example 三份模板,
  公网模板用 _REPLACE_WITH_*_ 占位符防"示例值上生产"
- scripts/start.sh / stop.sh / status.sh 新增 full 子命令对接 docker compose --profile public
- scripts/deploy-public.sh 新建:env 校验 + 端口 checklist + Docker 自检 +
  --profile public up -d --build + 健康检查闭环
- docs/deployment/meeting-mvp.md 新建:双态部署全流程 + 强密码生成 + 防火墙 + FAQ
- 双模式 docker compose config 校验通过;deploy-public.sh 三种错误场景按预期退出

同步更新 CURRENT_STATUS / implementation.plan / project-context
Task 0-14  / 剩余 Task 15-16

Made-with: Cursor
2026-04-22 17:59:48 +08:00

259 lines
9.3 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.

# EchoChat 会议 MVP 部署指南
> **适用阶段**Phase 2e-2会议 MVP
> **覆盖形态**:本机 Demo零配置+ 公网部署(含 TURN
> **最后更新**2026-04-23Task 14 双态部署脚本 + docker-compose 扩展完成)
## 一、双态部署总览
EchoChat 会议 MVP 按 Phase 2e-2 设计决策 D01 采用**"一套代码 + 环境变量切换"** 的双态策略,不做代码分支。
| 对比项 | 本机 Demolocal | 公网部署public |
|---|---|---|
| 适用场景 | 开发调试、同局域网演示 | 上线、跨公网用户 |
| `MEDIASOUP_ANNOUNCED_IP` | 留空(自动探测) | **必填**服务器公网 IP 或域名解析 A 记录 IP |
| `TURN_ENABLED` | `false`(不起 coturn | `true`coturn 走 `profiles: [public]` |
| 防火墙/安全组 | 无特殊要求 | 需放通 UDP:40000-40199 + TURN 端口 |
| 启动方式 | `./scripts/start.sh full` | `./scripts/deploy-public.sh` |
| 关键文件 | `deploy/.env.local.example` | `deploy/.env.public.example` |
服务拓扑对比见设计文档 [docs/plans/2026-04-21-phase2e-2-design.md §4.3 双态部署拓扑](../plans/2026-04-21-phase2e-2-design.md)。
---
## 二、本机 Demo 部署
**前置要求**Docker Desktop含 Compose V2、Node 20+、Go 1.22+
### Step 1复制环境变量文件
```bash
cp deploy/.env.local.example deploy/.env
```
本机模式下无需修改任何字段即可跑通(`MEDIASOUP_ANNOUNCED_IP` 留空,`TURN_ENABLED=false`)。
### Step 2一键启动全栈
```bash
./scripts/start.sh full
```
这会做以下事情:
1. `docker compose up -d --build` 拉起 5 个容器:`postgres / redis / minio / go-service / media-server`
2. 等待 `media-server` healthcheck 通过(最多 180 秒)
3. 用本地进程启动 `frontend`5173+ `admin`3100—— 热更体验更好
启动完成后会打印访问地址:
```
前台用户端 (H5): http://localhost:5173
后台管理端: http://localhost:3100
Go 后端 API: http://localhost:8085
媒体服务器 (SFU): http://localhost:3300 (healthz: /healthz)
MinIO 控制台: http://localhost:9001 (echochat / echochat123456)
```
### Step 3验证
```bash
./scripts/status.sh
# 预期全部 ● RUNNING
curl http://localhost:8085/healthz # Go backend
curl http://localhost:3300/healthz # media-server
curl http://localhost:3300/readyz # mediasoup worker 已就绪
```
### 停止
```bash
./scripts/stop.sh full # 停止 docker compose 全栈(保留数据卷)
./scripts/stop.sh # 仅停应用层(保留所有容器)
./scripts/stop.sh --all # 含数据库中间件
```
---
## 三、公网部署
**前置要求**
- 服务器有**公网 IP** 或已解析 A 记录的域名
- Docker 已安装
- 云安全组/iptables 可由你控制(不能仅有 80/443 的 PaaS
### Step 1复制模板并替换所有占位符
```bash
cp deploy/.env.public.example deploy/.env
vim deploy/.env
```
**必须替换**:所有 `_REPLACE_WITH_*_` 占位符(否则 `deploy-public.sh` 会拒绝启动)。
生成强密码的示例:
```bash
openssl rand -hex 32 # JWT_SECRET / MEDIA_INTERNAL_TOKEN
openssl rand -base64 24 # 数据库密码 / TURN 密码
```
**核心字段**
| 字段 | 示例 | 说明 |
|---|---|---|
| `MEDIASOUP_ANNOUNCED_IP` | `203.0.113.42` | **必填**服务器公网 IP不能写 `127.0.0.1` |
| `TURN_ENABLED` | `true` | 对称 NAT fallback 必备 |
| `TURN_USERNAME / TURN_PASSWORD` | 自设 | 避免 TURN 账号被互联网滥用 |
| `JWT_SECRET` | 64 hex | 生产必改 |
| `MEDIA_INTERNAL_TOKEN` | 32 hex | Go ↔ Node 内部鉴权,必须与 `media-server/.env` 一致 |
### Step 2放通云安全组 / iptables
本脚本**不会**自动改防火墙(不同云厂商命令不同),请你提前在云控制台/iptables 放通:
| 协议 | 端口 | 用途 |
|---|---|---|
| TCP | 8085 | Go backend API |
| TCP | 5173 | 前台用户端 H5如单独暴露 |
| UDP | 40000-40199 | mediasoup RTC 流量 |
| TCP | 40000-40199 | mediasoup ICE-TCP fallback |
| UDP | 3478 | coturn STUN/TURN |
| TCP | 3478 | coturn |
| TCP | 5349 | coturn TLS |
| UDP | 49160-49200 | coturn 中继流量 |
`iptables` 示例(仅参考,请结合实际 zone 调整):
```bash
# UDP 范围
sudo iptables -I INPUT -p udp --dport 40000:40199 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 49160:49200 -j ACCEPT
# STUN/TURN
sudo iptables -I INPUT -p udp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 5349 -j ACCEPT
# API
sudo iptables -I INPUT -p tcp --dport 8085 -j ACCEPT
```
### Step 3运行部署脚本
```bash
./scripts/deploy-public.sh
```
脚本会按 4 步依次执行:
1. **校验 `.env`**:占位符、长度、`ANNOUNCED_IP` 必填、`DEPLOY_MODE=public`
2. **本机端口检查**TCP:8085 / TCP:3300 / 潜在冲突
3. **Docker 环境**docker daemon + compose v2 可访问
4. **启动**`TURN_ENABLED=true` 时用 `--profile public`(含 coturn否则跳过
启动完成后会打印访问地址。此时用浏览器访问 `http://<ANNOUNCED_IP>:5173`(或你放通的端口)即可。
### 验证
```bash
./scripts/status.sh
# 容器级检查
docker compose -f deploy/docker-compose.dev.yml ps
docker logs echochat-media-server | tail -40
docker logs echochat-coturn | tail -20
```
公网验证要点:
- 用**两台不同网络**的设备(比如手机 4G + 家里 WiFi分别入会双向能看到对端画面
- Chrome 调 `chrome://webrtc-internals``Remote candidate type` —— 对称 NAT 场景能看到 `relay` 类型,说明 TURN 生效
- `curl http://<ANNOUNCED_IP>:8085/healthz` 返回 200
### 停止
```bash
./scripts/stop.sh full
# 等价于docker compose -f deploy/docker-compose.dev.yml --profile public stop
```
---
## 四、常见问题 FAQ
### Q1`docker compose up` 卡在 media-server 构建,报 `python3 not found` 或 `g++ not found`
**原因**mediasoup 的 C++ worker 需要在构建阶段编译原生代码。`media-server/Dockerfile` 的 builder 阶段已装 python3 + build-essential若你绕过 Dockerfile 本地构建,需要手动装这些。
```bash
# Debian/Ubuntu
sudo apt install python3 build-essential pkg-config
# macOS
brew install python3
xcode-select --install
```
### Q2本机 Demo 下两个浏览器 Tab 都在 127.0.0.1:5173但视频流对端一片黑
**检查 1**`MEDIASOUP_ANNOUNCED_IP` 一定不要写 `127.0.0.1`,留空让 mediasoup 自动探测到局域网 IP 即可。
**检查 2**浏览器摄像头权限是否授予Chrome 左上角锁图标)。
**检查 3**`docker logs echochat-media-server | grep announcedIp`,确认探测到的 IP 在你本机 `ifconfig` 能看到。
### Q3公网部署后用手机4G 网络)加入会议,能看到自己但看不到别人?
**典型症状**:单向 ICE 候选协商不成功。通常是以下之一:
1. **TURN 没打开** —— 确认 `TURN_ENABLED=true``docker ps` 里有 `echochat-coturn`。对称 NAT 用户必走 TURN。
2. **UDP:40000-40199 没放通** —— mediasoup 要在这个范围分配动态 RTC 端口。很多云默认只放 80/443/22。
3. **`MEDIASOUP_ANNOUNCED_IP` 写错** —— 如果是 NAT 机器且写了内网 IP公网用户拿到的候选地址不可达。
### Q4为什么 coturn 用 `network_mode: host`
TURN 服务器为每个会话动态分配中继端口(`--min-port=49160 --max-port=49200`Docker 默认 bridge 网络的端口映射会把回包的源端口改写导致客户端收不到流量。host 网络能绕过这个问题,是 coturn 官方推荐做法。
代价coturn 会占用宿主机端口命名空间,请确保 `3478 / 5349 / 49160-49200` 宿主机无占用。
### Q5我有 HTTPS 证书,想把前端端口改成 443
MVP 阶段脚本没封装 HTTPS 反代,建议在 compose 外层加 Nginx或 Caddy作为 TLS 终结。mediasoup 的 RTC 流量本身就走 DTLS 加密,不需要你额外处理。
Caddy 最小示例(独立容器或宿主进程):
```
yourdomain.com {
reverse_proxy /api/* localhost:8085
reverse_proxy /ws localhost:8085
reverse_proxy * localhost:5173
}
```
### Q6public profile 下 `stop.sh full` 能停掉 coturn 吗?
能。`scripts/stop.sh full` 里写了 `docker compose --profile public stop`profile 参数确保 coturn 这种默认不暴露的服务也被覆盖。
### Q7数据库要备份怎么操作
```bash
# 快速备份当前数据库
docker exec echochat-postgres pg_dump -U echochat echochat | gzip > backup-$(date +%F).sql.gz
# 从备份恢复(容器停机时)
gunzip -c backup-2026-04-23.sql.gz | docker exec -i echochat-postgres psql -U echochat -d echochat
```
`pgdata / redisdata / minio_data` 三个 volume 的物理位置由 Docker 管理:
```bash
docker volume inspect deploy_pgdata
```
---
## 五、相关文档
- 设计文档:[docs/plans/2026-04-21-phase2e-2-design.md](../plans/2026-04-21-phase2e-2-design.md)
- 实施计划:[docs/plans/2026-04-21-phase2e-2-implementation.plan.md](../plans/2026-04-21-phase2e-2-implementation.plan.md)
- 会议 API[docs/api/frontend/meeting.md](../api/frontend/meeting.md)
- WebSocket 信令:[docs/api/frontend/meeting.md#websocket-信令协议](../api/frontend/meeting.md)
- media-server 子项目:[media-server/README.md](../../media-server/README.md)(如有)