DEPLOYMENT.md 17 KB

部署文档

文本转视频生成流水线的部署与运行说明。项目以容器化、服务化为重心:Docker Compose 一键起服务,CLI / HTTP / WebUI 共享同一套配置与统一输出目录。


1. 架构概览

                         ┌──────────────────────────────┐
   浏览器 (WebUI) ──────► │  Next.js Web 服务 (端口 3000)  │
   HTTP /api/render ────► │  apps/web                     │
                         └───────────────┬──────────────┘
                                         │ spawn 子进程
                                         ▼
                         ┌──────────────────────────────┐
                         │  CLI  apps/cli/dist/index.js   │
                         │  runPipeline (7 阶段)          │
                         │   parse → tts → assets →       │
                         │   compose → render → export    │
                         │   → publish(可选)              │
                         └───────────────┬──────────────┘
                                         │
                ┌────────────────────────┼────────────────────────┐
                ▼                        ▼                        ▼
        统一输出目录                 阿里云 OSS              飞书 Webhook
  {output}/{模板}/{日期}/{文件}      (上传视频)            (推送结果/失败)
  • Web 服务(Next.js 15)提供 UI 与 HTTP API;渲染时 spawn 调用 CLI 子进程执行 runPipeline
  • CLIapps/cli)是流水线唯一入口;WebUI 与 HTTP 都复用它,因此 OSS 上传 / 飞书推送只在 CLI 进程内发生一次。
  • 统一输出目录:CLI / HTTP / WebUI 写入同一目录,按 {模板}/{ISO日期}/{文件名}.mp4 自动建子目录。
  • monorepo:pnpm workspaces + Turborepo(packages/shared|core|tts|templates|collectapps/cli|web)。

2. 前置要求

Docker 方式(推荐)

  • Docker 20+ 与 Docker Compose v2。

本地开发方式

  • Node.js 22、pnpm 10、ffmpeg。
  • 渲染依赖 headless Chrome:首次运行 Remotion 会自动下载(@remotion/renderer),或设置 PUPPETEER_EXECUTABLE_PATH 指向系统 Chrome。

3. 快速开始:Docker Compose(推荐)

# 1. 准备配置(密钥 / 端点)
cp .env.example .env
#   编辑 .env,至少填入 LLM、TTS 的 key;按需开启 OSS / 飞书

# 2. 构建并启动
docker compose up --build -d

# 3. 访问
#    WebUI:   http://localhost:13000
#    (PORT 可在 .env 中覆盖,默认 13000 → 容器 3000)

docker compose up 会自动读取仓库根目录的 .env,把其中的变量替换进容器的 environment:(见 docker-compose.yml)。因此密钥放在宿主机 .env,不需要把 .env 打进镜像

停止 / 查看日志 / 重建:

docker compose logs -f pipeline     # 跟随日志
docker compose down                 # 停止
docker compose up --build -d        # 改完代码后重新构建

容器内:

  • Web 服务运行于 /app/apps/web,监听 3000
  • 输出目录 /app/output 挂载到宿主机 ./output(视频落盘可见、可清理)。
  • 任务元数据(jobs.json)保存在 docker volume pipeline-jobs/tmp/pipeline-jobs)。

4. 配置说明

配置分两层:.env(密钥 / 端点) + config/default.yaml(业务参数)。CLI 读取顺序:--config 参数 → pipeline.config.yamlconfig/default.yaml;密钥统一走环境变量。

4.1 环境变量(.env

变量 必填 说明
OPENAI_API_KEY / OPENAI_BASE_URL 是* LLM(OpenAI 兼容端点:DeepSeek / Qwen / GLM 等)。*用结构化 JSON + --skip-llm 时可省
OPENAI_TTS_API_KEY / OPENAI_TTS_BASE_URL 是* 默认 TTS provider。*--no-tts 时可省
OPENAI_TTS_MODEL TTS 模型,默认 seed-tts-1.1
TTS_MAX_RETRIES TTS 请求失败重试次数(网络错误 / 429 / 5xx,指数退避),默认 3;0 关闭
FISH_AUDIO_API_KEY Fish Audio provider
MINIMAX_API_KEY / MINIMAX_GROUP_ID MiniMax provider
ELEVENLABS_API_KEY ElevenLabs provider
OUTPUT_DIR 统一输出目录;空则用 config.output.dir(默认 ./output)。Docker 内固定 /app/output
OUTPUT_RETENTION_DAYS 缓存保留天数,默认 30;0 关闭自动清理
OSS_REGION OSS_BUCKET OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET 阿里云 OSS;四项齐全才启用上传
OSS_ENDPOINT OSS_PATH OSS_PUBLIC_DOMAIN OSS_SECURE OSS 自定义端点 / key 前缀 / 资源链接域名(如 CDN) / 是否 HTTPS
FEISHU_WEBHOOK_URL FEISHU_WEBHOOK_SECRET 飞书自定义机器人 webhook 及签名密钥
LOG_LEVEL 日志级别 debug\|info\|warn\|error,默认 info
PORT 宿主机映射端口,默认 13000
SCHEDULES 定时任务(JSON 数组),整体覆盖 config/default.yamlschedules,便于部署期改而无需重建镜像
SCHEDULER_ENABLED 调度器总开关,false 关闭(默认开启)

也可在 WebUI Settings 页面填写并保存到 .env(敏感值自动脱敏显示)。

4.2 config/default.yaml

TTS provider 选择、模型、对齐(whisper)、模板配色、采集源(collect)、OSS/飞书的非敏感默认值(region/bucket/path 等)在此配置。详见文件内注释。

4.3 定时生成(容器内调度)

schedules 段定义定时任务,Next.js 服务启动时由进程内调度器instrumentation 钩子 + node-cron)注册,到点 spawn CLI 渲染——复用 WebUI/HTTP 同一链路,任务进 jobs 列表、走 OSS/飞书发布。cron 按容器时区(TZ,默认 Asia/Shanghai=北京时间)解释。

schedules:
  - cron: "50 7 * * *"          # 北京时间每天 07:50
    template: "github-trending"
    platform: "bilibili"
    source: "github-trending"    # 先采集当日 trending,再解析→渲染→发布
    publish: true
    enabled: true
  • 改配置后需重启容器(docker compose up -d)生效;或用 SCHEDULES 环境变量(JSON 数组,同结构)整体覆盖,部署期改无需重建镜像。
  • SCHEDULER_ENABLED=false 关闭整个调度器。
  • 单副本下进程内调度天然不会重复执行;渲染较重,已内置单飞(上一次未结束则跳过本次)。

5. 输出目录与缓存清理

  • 布局{OUTPUT_DIR}/{模板名}/{YYYY-MM-DD(UTC)}/{模板}-{平台}-{jobId前8位}.mp4
    • 例:output/github-trending/2026-07-16/github-trending-bilibili-701832d4.mp4
  • 统一性:相对路径相对 monorepo 根解析,CLI 与 apps/web 落到同一物理目录;容器内为 /app/output
  • 自动清理:每次渲染启动时按 OUTPUT_RETENTION_DAYS(默认 30)扫描,删除 mtime 超过阈值的文件及随之变空的日期目录(best-effort,不影响渲染)。设 0 关闭。

6. 发布:OSS 与飞书

渲染完成后自动执行(可被 --no-publish / config.skipPublish 跳过):

场景 行为
上传 OSS 成功 推送飞书:✅ 视频生成成功 + OSS 资源链接
渲染成功但上传失败 推送飞书:⚠️ 已生成但上传失败 + 本地路径 + 错误(本地视频仍保留)
渲染本身失败 推送飞书:❌ 生成失败 + 错误信息
  • OSS 用 ali-oss 分片上传;资源链接优先用 OSS_PUBLIC_DOMAIN(CDN),否则用 bucket 默认域名。假定对象为公开读;若为私读需另行接签名 URL。
  • 飞书支持自定义机器人的签名校验FEISHU_WEBHOOK_SECRET),算法为 base64(HMAC-SHA256(key=timestamp+"\n"+secret, message=""))
  • 上传后的 OSS URL 会回填到导出文件(CLI 打印 oss: <url>;WebUI 任务详情页展示「OSS Links」)。

7. 日志与可观测性

排查问题看三处:

  1. 容器日志(实时)docker compose logs -f pipeline。CLI 子进程的每阶段进度、TTS 重试、publish(OSS 上传 / 飞书发送)日志都会镜像到这里,每行带 [job <id前8位>] 前缀与时间戳,便于区分并发任务。
  2. WebUI 任务详情页「日志」面板:任务结束后展示完整捕获的 CLI 输出(上限 ~200KB,超出截断头部)。失败任务同样可看。
  3. 失败任务的 error / publishError:任务卡片与详情页直接显示。

日志格式:<ISO 时间> [LEVEL] [scope] message,scope 含 pipeline / tts / publish。用 LOG_LEVELdebug|info|warn|error,默认 info)调节详细程度。

说明:渲染由 Web 服务 spawn CLI 子进程执行;CLI 的 stdout/stderr 默认被管道捕获。现在这些输出既会镜像到容器 stdout/stderr(供 docker compose logs),也会存入任务(供 UI)。TTS 的瞬时 502/网络重试会以 [WARN] [tts] HTTP 502, retrying … 形式出现;OSS/飞书失败会以 [ERROR] [publish] … 出现,不再被静默吞掉。

8. 本地开发(非 Docker)

pnpm install
pnpm build            # 构建所有包(含 apps/cli/dist、apps/web/.next)

# CLI 直接渲染
node apps/cli/dist/index.js render test/fixtures/sample-knowledge.json \
  -t knowledge -p bilibili --skip-llm --no-tts --no-publish

# WebUI 开发服务器(热更新)
cd apps/web && pnpm dev    # http://localhost:3000

常用命令:

pnpm typecheck       # 全包类型检查
pnpm lint            # 全包 lint
pnpm build           # 全量构建

9. CLI 用法

node apps/cli/dist/index.js render <input> -t <template> [options]
  • <input>:JSON / Markdown / 纯文本文件路径;或用 --source <name> 从采集源取数(如 github-trendinggithub-daily)。
  • -t/--templatenews|knowledge|opinion|marketing|github-trending(必填)。
  • -p/--platformbilibili|douyin-long|douyin-short|universal-16x9|universal-9x16,逗号分隔多平台。
  • -o/--output:输出目录(默认走 config.output.dir / ./output)。
  • --no-tts:跳过 TTS,生成静音视频(调试布局)。
  • --skip-llm:跳过 LLM,要求输入是合法 VideoInputSchema JSON。
  • --no-publish:跳过 OSS 上传与飞书推送。
  • --config:指定配置文件路径。

其它子命令:collectpreviewtemplatesvoicesinit--help 查看)。


10. HTTP API(服务化调用)

发起渲染

POST /api/render
Content-Type: application/json

{
  "input": {
    "template": "knowledge",
    "platforms": ["bilibili"],
    "ttsProvider": "openai-tts",
    "text": "<VideoInput JSON / Markdown / 纯文本>"
  },
  "options": { "noTts": false }
}
  • input.template(必填)、input.ttsProvider(必填)。
  • input.textinput.source 二选一;platforms 缺省为 ["bilibili"]
  • 采集源:input.source + input.sourceArgs(如 { "owner": "x", "repo": "y" })。

响应:{ "jobId": "<uuid>", "status": "started" }

轮询任务 / 下载

GET /api/jobs/<jobId>            # 返回任务状态、outputFiles、ossUrls、publishError
GET /api/jobs                    # 任务列表
GET /api/download?file=<绝对路径>&download=1   # 下载视频(仅允许统一输出目录或任务目录内文件)

任务状态:pending | running | completed | failedcompletedoutputFiles 给出本地路径,ossUrls 给出 OSS 链接(若已上传)。

cURL 示例

curl -X POST http://localhost:13000/api/render \
  -H 'Content-Type: application/json' \
  -d '{"input":{"template":"knowledge","platforms":["bilibili"],"ttsProvider":"openai-tts","text":"<JSON 字符串>"}}'

# 轮询
curl http://localhost:13000/api/jobs/<jobId>

11. 数据持久化(卷)

挂载 容器路径 用途
./output /app/output 视频产物 + 渲染临时目录(按模板/日期归档,自动清理)
docker volume pipeline-jobs /tmp/pipeline-jobs 任务元数据 jobs.json(重启保留)

注意:任务元数据存在内存卷中,删除 volume 会丢失任务历史(但不影响已生成的视频文件)。


12. 排错

现象 排查
渲染卡在 [5/7] Rendering headless Chrome 未就绪。本地确认 Chrome / Chromium 可用,或设 PUPPETEER_EXECUTABLE_PATH;Docker 镜像已内置所需系统库
TTS 报 API_KEY 错误 检查 .env 的 TTS key;或临时用 --no-tts 调布局
TTS 报 502 / 503 / 网络错误 TTS 网关上游瞬时故障(如 DNS、滚动重启);已内置重试(TTS_MAX_RETRIES 默认 3)。若持续失败,检查 OPENAI_TTS_BASE_URL 网关健康度或临时换 provider
日志里出现 Note: … @remotion/media-parser … license 这是 Remotion 的许可证提醒parseMedia() 探测音频时长时打印),不是错误、不影响渲染。若要消除,需在 packages/tts/src/duration.tsparseMedia() 传入 acknowledgeRemotionLicense: true——注意这是对许可证的法律确认,公司用途请先确认是否需要 Remotion 许可
OSS 上传失败 检查 OSS_REGION/BUCKET/ACCESS_KEY_*;任务详情会展示 Publish warning,本地视频仍生成
飞书收不到消息 确认 FEISHU_WEBHOOK_URL;若启用了签名校验需同时填 FEISHU_WEBHOOK_SECRET
容器内找不到输出 确认挂载到 /app/outputOUTPUT_DIR),docker compose 已默认如此
改了 .env 不生效 重启服务:docker compose up -d(环境变量在容器启动时注入)

13. 目录速查

apps/cli/            CLI 入口(Commander)
apps/web/            Next.js WebUI + API 路由 + 进程内定时调度器(instrumentation/node-cron)
packages/shared/     Zod schema、类型、常量、LLM 客户端、路径/发布配置解析
packages/core/       流水线编排、7 阶段、cleanup、publish(OSS+飞书)
packages/tts/        TTS provider 注册表
packages/templates/  Remotion 场景组件
packages/collect/    数据采集器(github-trending 等)
config/default.yaml  业务配置(provider / 模板配色 / 采集源 / OSS·飞书默认值)
.env(.example)       密钥与端点
docker-compose.yml   容器编排(Docker Compose 单机)
Dockerfile           多阶段构建(非 root、内置 HEALTHCHECK)
deploy/k8s/          Kubernetes 清单 + 部署说明

14. Kubernetes 部署(Harbor 推送)

镜像本身已为 k8s 做好兼容:内置 /api/health/live(liveness)与 /api/health/ready(readiness)探针端点、以非 root(node,uid 1000)运行、输出与任务目录可挂载 PVC、进程重启后自动清理中断的孤儿任务。清单见 deploy/k8s/(含单副本 Deployment、Service、Ingress、ConfigMap、PVC 及部署 README)。

14.1 构建并推送镜像到 Harbor

# 1. 设定你的 Harbor 地址与项目(替换为实际值)
HARBOR=harbor.example.com          # Harbor 注册地址
PROJECT=library                     # Harbor 上已创建的项目名
IMAGE=$HARBOR/$PROJECT/pipeline
TAG=$(git rev-parse --short HEAD)   # 也可用日期或 latest

# 2. 登录 Harbor
docker login $HARBOR -u <用户名>     # 回车后输入密码 / Harbor CLI secret

# 3. 用 Harbor 地址构建并打 tag(Dockerfile 在仓库根目录,多阶段构建)
docker build -t $IMAGE:$TAG -t $IMAGE:latest .

# 4. 推送
docker push $IMAGE:$TAG
docker push $IMAGE:latest

# 5. 把 deploy/k8s/03-deployment.yaml 里的 image: 改成 $IMAGE:$TAG(或 :latest)

要点:

  • Harbor 走 HTTP(非 HTTPS) 时,需在 docker daemon 的 /etc/docker/daemon.json 加入 "insecure-registries": ["harbor.example.com"] 后重启 dockerd;或在 k8s 各节点同理配置 containerd。
  • 私有项目:k8s 拉镜像需要 imagePullSecret,创建方法见 deploy/k8s/README.md
  • 镜像约 1.5–2GB(含 ffmpeg、Python/faster-whisper、Chrome 依赖、字体)。首次推送较慢。

14.2 部署到集群

完整步骤(生成 Secret、应用清单、验证、扩缩容与持久化说明)见 deploy/k8s/README.md。最简流程:

# 用根目录 .env 生成密钥 Secret
kubectl create secret generic pipeline-env-secret --from-env-file=.env -n pipeline || \
  kubectl create namespace pipeline && \
  kubectl create secret generic pipeline-env-secret --from-env-file=.env -n pipeline

# 替换 03-deployment.yaml 中的镜像地址后:
kubectl apply -f deploy/k8s/
kubectl -n pipeline get pods -w

单副本:应用以本地 jobs.json + spawn CLI 渲染,无共享状态/分布式锁,因此 Deployment 固定 replicas: 1(PVC ReadWriteOnce + strategy: Recreate)。水平扩展需先外置任务存储,详见 k8s README。