跳到主要内容

RSSHub 生产影子部署

这一步完成了什么

RSSHub 是这台 VPS 上第一个真实应用负载。它已经有公开 HTTPS 域名、真实可用路由、访问控制和监控,但仍然叫“影子部署”,因为生产消费者尚未切换:

公网测试者
`-- HTTPS -> Caddy -> infra-edge -> VPS RSSHub

既有 feed consumer
`-- 继续使用本地 RSSHub -> 仍是生产路径和回退

这样可以先证明服务器端链路,再把消费者迁移留给单独的 Phase 4。出现问题时,不需要赶着修复一个已经接管生产的服务。

当前工具分别做什么

组件职责不负责什么
权威 DNS把 RSS 子域名解析到 VPS不代理应用流量,也不管理容器
Caddy接收公网 80/443、管理 TLS、把请求转给 RSSHub不保存 RSSHub 原始 ACCESS_KEY
infra-edge为 Caddy 与 RSSHub 提供 Docker 内部通道和名称解析不向主机或公网发布 1200
RSSHub抓取来源并生成 RSS;用 ACCESS_KEY 保护真实路由不保存 consumer 数据
Uptime Kuma无密钥访问公开 /healthz,记录可用性不含 ACCESS_KEY,管理 UI 不公开
本地 RSSHub继续服务既有 feed consumerPhase 3 不停止、不移除

完整公网路径是:

rss.example.com
|
v
Caddy :80/:443
|
v
infra-edge
|
v
rsshub-prod:1200

只有 Caddy 发布主机 80/443。RSSHub 的 1200/tcp 只存在于容器网络;公网 IP 和 Tailscale IP 都不能直接访问它。

为什么只有 RSSHub,没有一整套配套服务

代表性 Bilibili 路由已经在现有本地 RSSHub 上成功返回有效 RSS 和非零条目。该环境没有 Redis、Browserless 或 Chromium,因此 VPS 从同样的最小条件开始:

  • RSSHub 官方镜像,生产 Compose 固定不可变 digest;
  • CACHE_TYPE=memory
  • 不部署 Redis;
  • 不部署 Browserless 或 Chromium;
  • 不创建数据库和应用数据卷。

“上游示例里出现了某个组件”并不代表个人单节点必须安装它。只有真实必需路由证明缺少依赖时,才重新做架构评审。

文件与秘密边界

/srv/rsshub/
├── compose.yaml # 可审查的服务、健康检查、网络与重启策略
└── .env # mode 600,不进 Git,不在报告中展开

.env 只包含实际需要的变量:新生成的 ACCESS_KEY,以及代表性 Bilibili 路由所需的 UID 与对应 Cookie。变量名可以记录,值不能进入聊天、Git、网站或完整 Compose 输出。

Compose 的关键不是“写了多少”,而是以下边界:

services:
rsshub:
image: diygod/rsshub@sha256:<immutable-digest>
restart: unless-stopped
env_file: [.env]
environment:
NODE_ENV: production
CACHE_TYPE: memory
DISALLOW_ROBOT: "1"
networks:
infra-edge:
aliases: [rsshub-prod]

networks:
infra-edge:
external: true

这里故意没有 ports:、host networking、privileged、Docker socket 或数据卷。

健康检查与访问控制为什么不同

真实 RSS 路由必须带有效 ACCESS_KEY;不带 key 会返回 403。监控 URL 则必须保持无密钥,否则 Kuma 需要保存高价值凭据。

当前 RSSHub 版本在设置 ACCESS_KEY 后也会保护 /healthz。本次部署没有把原始 key 复制给 Caddy,而是使用 RSSHub 支持的、只绑定 /healthz 路径的派生 code:容器健康检查在运行时计算,Caddy 仅给公开健康路径注入对应 code。结果是:

GET /healthz -> 200,不需要调用者携带 key
GET /真实/rss/route -> 403
GET /真实/rss/route?key=<valid-key> -> 200

公开健康端点仍经过 Caddy、infra-edge 和 RSSHub;它不是 Caddy 自己伪造的一句 ok。派生 code 的值同样不进入公开笔记。

日常检查

在 VPS 上:

[REMOTE: VPS]
cd /srv/rsshub
docker compose ps
docker compose logs --tail 100 rsshub

RSSHUB_ID=$(docker compose ps -q rsshub)
docker inspect "$RSSHUB_ID" \
--format '{{.State.Status}} {{.State.Health.Status}} {{json .HostConfig.PortBindings}}'

docker network inspect infra-edge

合格结果是 running healthy、端口绑定 {},并且 infra-edge 同时包含 Caddy 与 RSSHub。

在 WSL2 上:

[LOCAL: WSL2]
curl --fail https://rss.example.com/healthz
curl -I http://rss.example.com/healthz

# 都应超时或拒绝,不能返回 RSSHub 页面
curl --connect-timeout 5 --max-time 7 \
"http://$VPS_PUBLIC_IPV4:1200/"
curl --connect-timeout 5 --max-time 7 \
"http://$TAILSCALE_IPV4:1200/"

真实路由检查只报告状态、Feed 格式、频道身份是否匹配和条目数;不要把包含 key、UID 或 Cookie 的完整 URL 粘贴到日志或笔记。

受控重启

Phase 3 不需要重启整台 VPS。只重启新应用:

[REMOTE: VPS]
cd /srv/rsshub
docker compose restart rsshub
docker compose ps

等待 healthy 后,重新检查公开健康、无 key 的 403、有 key 的真实 feed、Caddy、Kuma、Tailscale SSH 和公网 SSH 阻断。只重启 RSSHub 时,Caddy 与 Kuma 的容器 ID 和启动时间不应变化。

回滚

健康状态下不需要为了“证明”而真的回滚。机械步骤是:

  1. /srv/caddy/Caddyfile.pre-rsshub-p3 恢复 Caddyfile;
  2. 验证完整 Caddy 配置;
  3. reload Caddy,不重启容器;
  4. 执行 docker compose -f /srv/rsshub/compose.yaml down
  5. 保留 /srv/rsshub/compose.yaml 与受保护的 .env
  6. 保持本地 RSSHub 运行,DNS 记录可稍后单独移除。

不要执行 docker system prune -a、volume prune 或 network prune。

尚未发生的事情

Phase 3 没有修改 feed consumer 的生产端点、持久数据、调度器或来源配置,也没有停止本地 RSSHub。下一阶段必须把消费者切换、持久数据备份与恢复、调度迁移和回退退役决定当成一个受控整体。