EP17:从零部署:15 分钟跑起你自己的 CDN¶
| 上集回顾 | EP16《如何应对突发性高并发场景》,Module 3 结束 |
|---|---|
| 下集预告 | EP18《录像与回放:给直播加上 VOD》 |
0. 本集目标¶
看完这一集,观众应该能做到三件事:
- 把仓库里的
docker compose当作当前部署模板来审查和运行,并说清每个组件在拓扑里的位置、端口与前置修复。 - 看懂这份本地编排里,哪些配置项刚好对应前面几集讲过的机制(节点池 / 容量 / 按需回源 / ABR / 节点鉴权)。
- 分清"环境起来了"和"端到端验证过了"——冒烟测试验证的是控制面健康,不是一条真实的推流到播放链路。
1. 开场钩子(逐字稿要点)¶
前面十六集讲了通用的媒体系统机制,这一集用 PPCDN 仓库里的 compose 作为部署案例。先明确状态:它是当前部署模板,不是开箱即用的发布包;原样运行前还缺数据库配置、mmx 镜像和 TLS 文件一致性修复。下面给出的命令会始终使用同一组 compose 文件,但只有完成这些前置项后才是可运行流程。“容器起来”和“媒体端到端跑通”仍是两回事。
2. 先看清要起哪几个组件¶
这套系统不是"一个进程",本地编排要同时拉起四样东西,正好是前面几集反复出现的那张拓扑图:
| 组件 | 容器名 | 端口 | 角色 |
|---|---|---|---|
| nginx | ppcdn-nginx |
80 / 443 | 前门,统一入口:/origin/、/edge/、/v1/、/ws/ 的反向代理 |
| ppcenter | ppcdn-ppcenter |
8090 | 控制面:节点注册、调度、鉴权、告警、管理后台 |
| mmx-origin | ppcdn-mmx-origin |
1935(RTMP 入)、8889(WebRTC)、8189/udp | 媒体面:唯一推流入口 |
| mmx-edge | ppcdn-mmx-edge |
8890(WebRTC)、8190/udp | 媒体面:从 Origin 回源、向观众分发 |
外加两个依赖:MySQL(ppcenter 的持久化,必需)和 Redis(默认关闭;控制面在无 Redis 时以只读模式运行)。
三个必须提前知道的依赖,别被"15 分钟"误导:
- mmx 镜像不来自本仓库。ppmmx 是独立仓库(本 monorepo 里只有它的配置文件);compose 里用的是
ppcdn/mmx:latest镜像,你需要先从 ppmmx 仓库构建或获取这个镜像。 - ppcenter 硬依赖 MySQL。
deploy/config/ppcenter-local.json当前数据库字段为空;必须至少填host=mysql、用户、密码和dbName=philcdn,并保证与 MySQL override 一致。 - nginx 的 TLS 挂载当前不完整。
deploy/nginx.conf引用/etc/nginx/tls.crt和/etc/nginx/tls.key,但 compose 没有挂载它们。运行前必须修复模板:开发环境可移除 443 SSL listener;需要 WebRTC/公网访问时应生成证书并把证书、私钥挂载到这两个路径。
因此「15 分钟」只适用于镜像已准备、数据库配置已填、TLS 模板已修复的环境。未满足前置条件时,本文不承诺原样命令可运行。
这不是唯一的自建路径
本集讲的是把整套控制面(ppcenter + mmx-origin + mmx-edge + nginx)自己跑起来。如果你的目标只是往别人已经在运营的 PPCDN 控制面上加一个自己花钱买的节点(比如只想贡献一台 VPS、按授权付费,不想自己运维调度/鉴权/计费),有一条更短的路:购买 ppmmx 授权(license code),在独立的 ppmmxDocker 仓库里跑一条 deploy.sh --license-code <code>,节点会以 NODE_ROLE_STANDALONE 角色(单实例同时接收推流并直接服务 WHEP 观众,不与其它节点组网)自动探测角色、注册、按授权计费,不需要本集的任何一步。这条路径已经上线且有真实自建客户在用(见 docs/deployment/ppcdn生产部署总结.md 2026-10-06 及以后多条记录)。它在 2026-10-08 暴露并修复过一个组合缺陷:licenseCode 注册时 ppcenter 从没把内部解析出的 nodeSecret 回传给节点,节点拿不到凭据,白名单永久同步失败、任何 appId 都推不进流;修复后改由节点自行上报密钥,console 也不再展示 nodeSecret。两条路径解决的是不同问题,不要混着看。
3. 15 分钟的步骤¶
第 1 步:拿到代码¶
git clone <ppcdn 仓库>
cd ppcdn
第 2 步:起依赖(MySQL / Redis)¶
默认 compose 不含数据库,用 override 叠上:
先定义本节所有命令使用的同一组文件:
COMPOSE="docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.mysql.yml"
$COMPOSE up -d mysql redis
它会起一个 MySQL 8.0(库名 philcdn)和一个 Redis 7,端口分别是 3306、6379。
第 3 步:填本地配置¶
编辑 deploy/config/ppcenter-local.json——它会被挂载成容器里的 /app/conf/ppcenter.json。重点三项:
mysql.host/user/password/dbName:指向刚起的 MySQL(主机名mysql)。当前 override 只设置 root 密码,因此模板至少应填user=root、password=ppcdn_local、dbName=philcdn;生产环境应改为专用用户和密钥。nodeAuth.bearerToken:平台级节点接入 token,本地默认值是local-dev-token-change-in-prod,生产环境必须换掉(EP06 讲过的边界)。redis.enabled:想启用 Redis 就设为true并填地址;不启用时控制面以只读模式跑。
第 4 步:准备镜像并启动¶
先从独立 ppmmx 仓库准备 ppcdn/mmx:latest,并完成上面的 TLS 修复,然后在本仓库执行:
docker build -t ppcdn/ppcenter:latest -f deploy/ppcenter.Dockerfile ppcenter
$COMPOSE up -d --build
这里不使用 make docker-up,因为它只加载基础 compose,会漏掉本节所需的 MySQL/Redis override。
第 5 步:等健康检查通过¶
ppcenter 容器定义了 healthcheck:每 10s 打一次 http://localhost:8090/health。等它 healthy:
$COMPOSE ps
curl -f http://localhost:8090/health
第 6 步:访问¶
- ppcenter 管理后台:
http://localhost:8090(超管 SPA,构建时已打进去)。 - 统一入口:
http://localhost/(nginx),/origin/和/edge/分别反代到两台 mmx。 - Swagger:仓库里的
ppcenter/bin/docs/swagger.yaml。
第 7 步:收拾¶
$COMPOSE down -v
4. 当前冒烟模板与可运行替代命令¶
仓库有 make docker-smoke,但当前版本不能作为上述模板的可运行一键入口。deploy/smoke-test.sh 的 COMPOSE_FILE 从 deploy/ 目录又拼接了 deploy/docker-compose.yml,路径不一致;脚本也没有加载 MySQL override,且 nginx TLS 挂载仍未解决。修复这些问题前,请使用上一节同一组 $COMPOSE 命令,并手工执行以下控制面检查:
$COMPOSE up -d --build
curl -f http://localhost:8090/health
curl -i http://localhost:8090/admin/nodes
curl -i -X POST http://localhost:8090/v1/play/requests -H 'Content-Type: application/json' -d '{"streamName":"test","clientId":"smoke","requestRegion":"local","capabilities":["whep"]}'
$COMPOSE down -v
这些命令验证 ppcenter 能启动并访问控制面端点。它们不推流、不播放、不验证 P2P,也不证明 mmx 媒体链路可用。HTTP 状态码还会受鉴权和节点注册状态影响,检查时应结合响应体判断,而不是把多个状态码一概视为成功。
5. 三个配置项,对着前面几集读¶
deploy/config/mmx-origin.yml 和 mmx-edge.yml 里有几个键,正好把前面讲过的机制落到具体数值上:
① 节点身份与容量(对应 EP14 节点池 / EP15 容量 / EP16 告警)
mmxControl:
nodeRole: origin # 或 edge
nodeRegion: local
heartbeatIntervalSeconds: 10
nodeCapacity: 30 # origin 配 30,edge 配 50
节点靠这套配置向 ppcenter 注册自己的角色、区域和容量(EP14 讲的 poolId 同理,也属于这种"部署时静态声明"的属性)。nodeCapacity 就是 EP15 里那个"软准入阈值"的分母,也是 EP16 里 capacity_high 告警的触发基准。
② ABR 自适应码率(对应 EP11)
# mmx-edge.yml
webrtcABREnable: true
webrtcABRWSPath: /ws/control
webrtcABRSwitchCooldown: 3000
Edge 开着 ABR,通过 /ws/control 这条 WebSocket 做分层切换——EP11 讲的"无缝切换"就落在这个开关和冷却时间上。
③ 回源方式:Publisher vs 按需回源(对应 EP15)
# mmx-origin.yml # mmx-edge.yml
paths: paths:
all: all:
source: publisher source: redirect
sourceOnDemand: false sourceOnDemand: true
Origin 是 publisher(等推流端推上来),Edge 是 redirect + sourceOnDemand: true——正是 EP15 讲的"没有观众的 Edge 不占 Origin 带宽"在配置里的样子。
6. 从本地到生产:同一套组件,三种形态¶
本地这份 compose 只是最小形态。同一套四组件,仓库里还给了另外两种形态:
- K8s 清单:
deploy/k8s-*.yaml(ppcenter、mmx-origin、mmx-edge、nginx、infra),用 Service DNS 代替硬编码 IP,配置通过 ConfigMap 注入,探针用/health。适合要上集群的场景。 - 生产部署:一台台机器上用 systemd 拉起 mmx,前门是 nginx + 正式 TLS 证书,MySQL/Redis 拆到独立实例;配套的
deploy/remote-deploy-*.sh脚本负责停服务、备份旧二进制、换新版、重启、看状态、清旧备份。扩容仍是 EP15 讲的人工 7 步流程——加机器后,还要去 Origin 的forwardMmxTargets追加新 Edge 的私有 IP 并 reload。
local compose → K8s → systemd 生产可以复用组件和协议,但差别绝不只在编排和密钥。网络暴露、负载均衡、持久卷、升级策略、故障域、观测和证书自动化都会改变生产行为。
Kubernetes 尤其要补三类前置设计:
- 公网 UDP:WebRTC 媒体端口不能只靠集群内 Service DNS;需要
LoadBalancer、NodePort、host networking 或支持 UDP 的入口,并开放云安全组和节点防火墙。 - ICE 可达性:候选地址必须是客户端可达的公网 IP/端口,处理 Service NAT、external traffic policy 与多网卡地址通告;复杂 NAT 场景还要评估 STUN/TURN,而不是假定 Pod IP 可达(这类问题不是 K8s 专属——第 7 节有一个已确认的、同类型的 docker bridge 网络真实案例)。
- TLS 与域名:浏览器 WebRTC/鉴权入口需要可信证书、正确 SNI 和 WebSocket/HTTP 升级配置;应使用 Ingress/Gateway 加 cert-manager 或等价方案,并明确 UDP 媒体不等同于 HTTPS Ingress。
6.1 PPCDN 自己的生产形态¶
上面是通用要求;PPCDN 实际的生产机队(完整方案见 docs/deployment/ppcdn生产部署方案.md,实际发生的事见同目录 ppcdn生产部署总结.md)把它落成了几个具体取舍,正好是这一集"从本地到生产"的落地版:
- 媒体面同 Region 同可用区:Origin / Record / Edge 统一部署在媒体 Region(如 AWS 新加坡)的同一个可用区,Origin→Record/Edge 走私有 IP 免媒体流量费;一旦跨 AZ 就要按约 $0.01/GB 双向计费。这一步必须在创建实例时显式指定并逐台核实,不是默认保证。
- 控制面与数据面可以分离、甚至跨 Region:ppcenter 与 ppcenter-db(MySQL8 + Redis)可放在与媒体面不同的 Region——因为节点都是向外拨号,ppcenter 从不主动回连媒体节点,两者之间不需要媒体数据通道,只要求控制面到数据层可私网/安全网络可达(跨 Region 要单独核算流量与延迟)。把数据层拆成独立实例,是用 +$12/mo 换内存隔离、独立扩容与独立快照。
- 容量是两条互不相干的线:一台
$12/mo(2vCPU/2GB)Lightsail Edge 有两个上限——瞬时并发(mmxNodeCapacity,生产取 12)和 Lightsail 月度出站配额(3TB ≈ 平均 5.5 并发)。必须分开设置、分开监控:只看并发告警、不看月度流量,会在月底收到远超实例本身的超额账单。这也是 EP15/EP16 反复强调"按实测校准分母"的同一个坑。 - 按 Region 的 vCPU 配额推算节点数:媒体 Region 申请到多少 vCPU 直接决定 Edge 台数——目前口径是 64 vCPU,减去 Origin/Record 各 2 vCPU 后可放 30 台
$12Edge,对应约 360 峰值并发 / 约 165 平均并发;若要 500 峰值并发,按每台 12 并发反推约需 88 vCPU,实际申请 96~100 留余量。这些是用量上限的估算,不是 SLA,真实可用容量还受 CPU/内存、公网出口、P2P 命中率、TLS/DNS 与 Origin 转发目标规模影响。 - 扩容仍是纯人工 7 步:容量告警能触发 webhook,但扩容执行器尚未实现;每加一台 Edge,都要同步往 Origin 的
forwardMmxTargets追加它的私有 IP 并 reload。
7. 诚实的边界¶
- 冒烟 ≠ 端到端。修复后的
make docker-smoke或本文手工命令都只验证控制面。真正验证一条流,需要 ppobs 推流 + ppplayer 播放,并且要处理真实的 NAT/网络条件。 - 本仓库的集成测试只覆盖 record 角色的录像面(
integrationTest/record,编译真实 mmx 二进制、真 ffmpeg 推流验证分片落盘)。origin/edge 的媒体链路目前没有自动化端到端测试。 - 本地默认值全是要换的:
nodeAuth.bearerToken是 dev 值,TLS 是自签,MySQL 密码是本地口令。生产上线前这些一个都不能原样带过去。 - 把这份 compose 原样搬到公网云主机上,WHEP 播放大概率连不上(已确认、尚未修复的真实 bug)。本地配置里的
webrtcAdditionalHosts: [localhost, 127.0.0.1, mmx-origin/mmx-edge]只解决「同机通过 localhost 访问」这一种场景。一旦把同一份模板部署到公网云主机(仍是 docker bridge 网络 + 端口映射),默认的webrtcIPsFromInterfaces: true会采集到 docker 内部网桥 IP(例如172.18.0.2)而不是公网 IP,WebRTC ICE 候选地址指向一个外部永远到不了的地址——推流(RTMP/SRT)不受影响,但播放端会卡在deadline exceeded while waiting connection。这是 2026-10-08 给自建节点做压测时在生产环境确认的真实缺陷(详见docs/deployment/ppcdn生产部署总结.md对应条目),截至目前仍未修复。公网部署前必须手动把公网 IP 加进webrtcAdditionalHosts,或把网络模式换成host。 - mmx 镜像不在本仓库构建。本地 compose 用的是预构建的
ppcdn/mmx:latest,要自己从独立的 ppmmx 仓库产出。 - compose/smoke 是当前模板,不是已验证的一键发行物。先修数据库配置、smoke 路径/override 和 TLS 挂载,再谈耗时;「15 分钟」是准备完备环境下的目标,不是保证。
- 这台机器大概能撑多少路,已有一个实测基准可参考:2026-10-08 对一台 1 vCPU / 2GB 的自建节点(
ppmmx-standalone-2,走的是独立ppmmxDocker仓库的 standalone 部署,不是本文这份 origin+edge 两容器的 compose)做过阶梯加压测试——纯 H.264 视频、1280×720@30、约 2.5 Mbps、SRT 推流,加压端与被测机近同机房(RTT ~4ms)。结果:18 路 WHEP 观众以内全程稳定(CPU 峰值 53%),第 19 路在约 15 秒内把 CPU 从 ~40% 冲到 88~103% 并开始丢帧,18→19 路之间几乎没有过渡带——单核机器接近饱和时是悬崖式拥塞,不是线性退化。这不是对本文 compose 拓扑的直接测试(这里 origin/edge 分在两个容器里分担负载,standalone 是单进程全扛),只能当作「同等级小机器、同一套 mmx 媒体内核」的容量参考起点,自己的硬件和码率仍需单独实测校准。完整方法与数据见docs/test/ppmmx-standalone-1vcpu-stress-test.zh-CN.md。
8. 框图¶
8.1 本地 compose 的组件与端口¶
┌───────────────────────────┐
浏览器 / 客户端 ───▶│ nginx :80/:443 │
│ /origin/ /edge/ /v1/ /ws/│
└───────┬───────────┬────────┘
│ │
┌────────────────┘ └────────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ mmx-origin │◀── 回源(仅观众到来后)─────│ mmx-edge │
│ :1935 RTMP入 │ │ :8890 WebRTC │
│ :8889 WebRTC │ │ :8190/udp │
│ role=origin │ │ role=edge │
│ cap=30 │ │ cap=50, ABR │
└───────┬───────┘ └───────┬───────┘
│ 节点注册 / 心跳 / 指令 │
└───────────────┬─────────────────────────────┘
▼
┌─────────────────┐ ┌──────────────┐
│ ppcenter │───────▶│ MySQL(必需) │
│ :8090 控制面 │ └──────────────┘
│ /health /admin │ ┌──────────────┐
└─────────────────┘───────▶│ Redis(可选) │
└──────────────┘
8.2 一条命令的旅程¶
当前手工 smoke(同一组 compose 文件)
│
▼
$COMPOSE up -d --build
│
├─ /health 控制面健康
├─ 检查 /admin/nodes、/v1/play/requests
└─ $COMPOSE down -v 清场
验证的是"编排 + 控制面"通;不是"推流 → 播放"通
9. 结尾与下集预告(逐字稿要点)¶
这一集把当前 compose 当作部署模板审查:先补齐 mmx 镜像、数据库配置和 TLS 挂载,再用同一组 compose 文件启动、检查和清理;现有
make docker-smoke在路径与依赖上仍需修复。控制面冒烟不等于媒体端到端。到了 Kubernetes,还必须单独解决公网 UDP、ICE 可达地址和可信 TLS,不能把生产差异缩减成“只是换了编排”。下一集,我们给这套跑起来的直播系统加上一块新能力:录像与回放——把直播流落成可按场次回放的 VOD。