跳转到内容

赛博堡垒的私人影院:基于 GitOps 部署 Servarr 自动化全家桶

🌱 创建: 2026/05/02 ⏱️ 更新: 2026/05/22

📖 架构解析:什么是真正的“赛博流水线”?

Section titled “📖 架构解析:什么是真正的“赛博流水线”?”

在完成了底层数据库(CloudNativePG)和存储引擎(Longhorn + NFS)的铺设后,我们终于迎来了赛博堡垒中最令人激动的上层建筑——全自动私人影音流水线

我们将要部署一套由多个微服务协同工作的完美闭环,先来看看它们是如何协同作战的:

⚙️ 流水线运转逻辑简述:

  1. 提出需求:你在 Seerr 极美的界面中点选了一部电影,它立即将任务交给大管家 Radarr
  2. 检索与下载:Radarr 通过 Prowlarr 在全网 PT 站搜寻到最佳种子的磁力链接,并将其发送给 qBittorrent
  3. 入库与刮削:qB 下载完成后,Radarr 利用 硬链接 (Hardlink) 将视频瞬间放入电影库,并从网上刮削好高清海报。
  4. 通知观影:最后,Radarr 拍了拍 Jellyfin 的肩膀:“新货到了”。Jellyfin 瞬间点亮海报墙,等待你的观看。

💡 首席包工头的架构巧思:

  1. 全面拥抱 PostgreSQL:抛弃极易卡死和损坏的 SQLite,将所有 Arr 应用的后端数据库直接接入我们上一期搭建的 CNPG 高可用集群。
  2. 存储“动静分离”:频繁读写的海报缓存与转码分片(/config, /cache)放置在高性能的 Longhorn 块存储;体积庞大的视频原片(/media)放置在大容量的 NFS 网络存储
  3. 零损耗硬链接 (Hardlinks):通过给所有容器统一挂载底层 /media 目录,实现下载目录与电影库同盘流转,下载完成瞬间“秒入库”,彻底告别双倍硬盘空间浪费。

👉 情报中心:besthomelab/k3s-homelab-gitops 图纸库


🛠️ 施工前置:点亮赛博堡垒的科技树

Section titled “🛠️ 施工前置:点亮赛博堡垒的科技树”

罗马不是一天建成的,全自动赛博流水线也需要坚实的基建支撑。在正式开工前,请确保你的环境已经具备以下核心条件:

  1. Kubernetes 赛博集群 与 ArgoCD 你需要一个运行良好的 K8s 集群作为总指挥部,并已安装 ArgoCD 来执行 GitOps 自动化部署。 (注:本文的所有 YAML 图纸均基于 Kubernetes 架构编写。纯 Docker 玩家可借鉴其核心的“动静分离”和“硬链接”逻辑,自行翻译成 docker-compose 配置。)

  2. 外接海量大硬盘 (NFS 存储) 作为赛博堡垒的“大容量集装箱”,专职收容 TB 级的视频原片与下载缓存。本教程示例中,我们使用的 NFS 服务器 IP 为 10.0.10.10,物理挂载路径规划为 /mnt/nfs/media

    ⚠️ 包工头的网络架构警告: 如果你的 K8s 集群节点和底层存储(NAS)不在同一台物理主机上,千兆/2.5G 内网极易成为性能瓶颈!qBittorrent 的高频读写和 Jellyfin 的视频推流会产生海量的跨设备流量。为了减少内网传输损耗,强烈推荐将 qB 和 Jellyfin 直接部署在 NAS 宿主机上(例如使用 Docker),而让 K8s 集群专职运行 Arr 系列,充当纯粹的“控制大脑”。

  3. PostgreSQL 高可用数据库 为了彻底解决 SQLite 容易锁死和损坏的顽疾,本博客采用 CloudNativePG 驱动的 PostgreSQL 集群,为所有 Arr 应用提供坚不可摧的共享数据心脏。(注:如果你不想在 K8s 里折腾 CNPG,完全可以自己用 Docker 单独建一个普通的 PostgreSQL 实例,只要能在局域网内连通即可。)

  4. Longhorn 分布式块存储 空间宝贵的高性能“保险箱”,专职承载各应用的 /config 目录。极速的块存储读写能力,是保障 Jellyfin 海报墙“秒开”的关键。

  5. Traefik 边缘网关Sealed Secrets Traefik 负责接管所有外部访问的域名路由 (IngressRoute);而 Sealed Secrets 则作为非对称加密穹顶,确保我们的数据库密码可以安全地推送到公开的 Git 仓库中。

  6. 神秘的“数字矿区”入场券 (私有资源社区账号) 工具再锋利,也得有矿可挖。赛博流水线(Arr 全家桶)只是一支执行力极强的“自动化物流舰队”,它本身并不生产数据。最终你的海报墙能有多震撼,完全取决于你给 Prowlarr 雷达输入了多少优质的“矿区坐标”。 由于高质量的私有影视社区(如各大 PT 站点)普遍遵循“低调潜行、闭门发展”的圈内共识,获取这些“深水区”的邀请码需要各位架构师各凭本事。本文仅提供物流基建的图纸,请大家保持极客精神,低调跑满你的带宽。

地基打牢,才能万丈高楼平地起。如果你对上述配置已经轻车熟路,那我们直接进入施工第一步!


🗺️ 空间映射:物理存储与赛博容器的“传送门”

Section titled “🗺️ 空间映射:物理存储与赛博容器的“传送门””

在动工之前,我们必须在物理 NAS 和 K8s 容器之间建立一套统一的“空间坐标系”。

为了实现全自动入库且零拷贝(Hardlink),我们必须保证:在所有应用看来,所有的影视资源都处于同一个根目录下。 无论是负责下载的 qBittorrent,负责刮削的 Radarr/Sonarr,还是负责播放的 Jellyfin,它们的视野必须完全对齐。

以下是本次工程的“空间映射地图”,请在后续配置各大管家时严格遵照此坐标系:

cyberbuilder@k3s-master-01:~# tree -L 2 /mnt/nfs/media —dirsfirst
存储区域物理 NAS 路径 (10.0.10.10)容器内挂载路径关联的核心应用核心职能 / 备注
📁 数据总根目录/mnt/nfs/media/media全家桶通用所有的“传送门”都从这里开启,是跨容器硬链接成立的物理基石。
🎬 电影库/mnt/nfs/media/movies/media/moviesRadarr, JellyfinRadarr 负责入库重命名,Jellyfin 负责读取、刮削与最终播放。
📺 剧集库/mnt/nfs/media/tv/media/tvSonarr, JellyfinSonarr 负责追剧入库管理,Jellyfin 负责读取与播放。
🌸 动漫库/mnt/nfs/media/anime/media/animeSonarr, Jellyfin隔离二次元资源,通常在 Sonarr 中会为其单独建立一套刮削与重命名规则。
📥 下载卸货区/mnt/nfs/media/downloads/media/downloadsqBittorrent, Radarr, SonarrqB 的默认下载路径。Radarr/Sonarr 监控此目录,完成下载后瞬间触发硬链接。

💡 包工头的“硬链接”避坑指南: 如果你在 Radarr 里把下载挂载目录设为 /downloads,而电影库挂载为 /movies,虽然在面板里看得很直观,但对于容器来说,跨越了不同的挂载点就等于不同的文件系统。这时候入库,容器只能通过极其缓慢的“复制+删除”来操作,不仅浪费 IO 性能,还会瞬间吃掉你双倍的硬盘空间!

按照我们这套坐标系设计,下载区和电影库都在 /media 统一大盘下。入库操作只是在底层修改了一个 inode 指向坐标,秒级完成且物理空间零增加。 这就是真正工业级赛博流水线的优雅之处。


🎬 施工演示录像 (Video Walk-through)

Section titled “🎬 施工演示录像 (Video Walk-through)”
正在嗅探浏览器语言并加载赛博录像...

🔐 阶段一:凭证整备与数据库开荒

Section titled “🔐 阶段一:凭证整备与数据库开荒”

由于整套应用都将连接到底层数据库,我们首先要为它们生成统一的通信凭证并开辟专属空间。

1. 锻造数据库加密钥匙 (SealedSecret)

Section titled “1. 锻造数据库加密钥匙 (SealedSecret)”

与上期 pgAdmin 类似,我们绝不推送明文密码到 GitHub。 参考本地的 secret.template.yaml,填入你的数据库密码,并使用 kubeseal 加密封壳:

Terminal window
kubeseal --format yaml < apps/media/shared/secret.template.yaml > apps/media/shared/media-db-secret.yaml

(加密完成后,记得销毁或忽略本地的模版文件)

打开我们的数据总控台 (pgAdmin),使用 Query Tool 运行以下自动化施工脚本。这段 SQL 将为四个核心应用创建独立的专属账号与数据库,实现严格的数据隔离:

-- ==========================================
-- 第一步:强制踢掉所有连接(拔掉所有钉子户的网线)
-- ==========================================
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'prowlarr-main' AND pid <> pg_backend_pid();
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'prowlarr-log' AND pid <> pg_backend_pid();
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'radarr-main' AND pid <> pg_backend_pid();
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'radarr-log' AND pid <> pg_backend_pid();
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'sonarr-main' AND pid <> pg_backend_pid();
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'sonarr-log' AND pid <> pg_backend_pid();
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'seerr' AND pid <> pg_backend_pid();
-- ==========================================
-- 第二步:大楼清空,直接引爆 (Database)
-- ==========================================
DROP DATABASE IF EXISTS "prowlarr-main";
DROP DATABASE IF EXISTS "prowlarr-log";
DROP DATABASE IF EXISTS "radarr-main";
DROP DATABASE IF EXISTS "radarr-log";
DROP DATABASE IF EXISTS "sonarr-main";
DROP DATABASE IF EXISTS "sonarr-log";
DROP DATABASE IF EXISTS seerr;
-- ==========================================
-- 第三步:开除打工人 (Role)
-- ==========================================
DROP USER IF EXISTS prowlarr_user;
DROP USER IF EXISTS radarr_user;
DROP USER IF EXISTS sonarr_user;
DROP USER IF EXISTS seerr_user;
-- 创建独立打工人 (Role)
CREATE USER prowlarr_user WITH PASSWORD 'YOUR_SUPER_STRONG_PASSWORD';
CREATE USER radarr_user WITH PASSWORD 'YOUR_SUPER_STRONG_PASSWORD';
CREATE USER sonarr_user WITH PASSWORD 'YOUR_SUPER_STRONG_PASSWORD';
CREATE USER seerr_user WITH PASSWORD 'YOUR_SUPER_STRONG_PASSWORD';
-- 划分专属数据仓库 (Database)
-- ⚠️ 防坑:带有连字符的库名必须使用双引号包裹
CREATE DATABASE "prowlarr-main" OWNER prowlarr_user;
CREATE DATABASE "prowlarr-log" OWNER prowlarr_user;
CREATE DATABASE "radarr-main" OWNER radarr_user;
CREATE DATABASE "radarr-log" OWNER radarr_user;
CREATE DATABASE "sonarr-main" OWNER sonarr_user;
CREATE DATABASE "sonarr-log" OWNER sonarr_user;
CREATE DATABASE seerr OWNER seerr_user;

📁 阶段二:存储地基与权限规范

Section titled “📁 阶段二:存储地基与权限规范”

LinuxServer.io 镜像引发的大多数玄学 Bug 都源自权限不匹配。为了让流水线畅通无阻,我们需要在 NFS 服务器(例如 10.0.10.10)上提前规划好标准化的英文目录,并将所有权移交给容器的默认执行用户(UID 1000)。

登录你的 NFS 服务器执行:

Terminal window
# 1. 创建英文目录结构
sudo mkdir -p /mnt/nfs/media/{movies,tv,anime,downloads/{movies,tv}}
# 2. 统一权限(确保容器内的 UID 1000 有权读写)
sudo chown -R 1000:1000 /mnt/nfs/media
sudo chmod -R 775 /mnt/nfs/media

🔨 阶段三:核心组件配置拆解 (Core Manifests)

Section titled “🔨 阶段三:核心组件配置拆解 (Core Manifests)”

所有的 YAML 图纸均存放在仓库的 apps/media/ 目录下。作为参考,以下是我们在这个庞大工程中注入的核心排雷设计

1. 统一视图,解锁硬链接 我们刻意将 NFS 的根目录挂载到所有容器的 /media 路径。在下载器 (qB) 和管家 (Radarr) 看来,文件是在同一个硬盘分区内移动的。这满足了硬链接(Hardlink)的触发条件,实现了瞬间转移且不占额外空间。

# 所有容器的通用挂载配置
volumeMounts:
- name: data
mountPath: /media

2. 防死锁更新策略 为配合 Longhorn 块存储的特性,所有的 Deployment 强制加入了 strategy: type: Recreate。这保证了在应用升级时,旧 Pod 必须先彻底死亡释放硬盘锁,新 Pod 才能挂载并启动。

3. Jellyfin 的极限性能优化 对于重型应用 Jellyfin,我们配置了 K8s 原生的 emptyDir 作为 /cache 转码缓存。它直接利用宿主机本地 SSD,性能极佳且重启自动清空;同时挂载了 /dev/dri 配合 privileged: true,彻底激活核显硬件加速。

👉 前往 GitHub 查阅 Prowlarr/Radarr/Sonarr/qB/Jellyfin 的全套详细图纸

⚠️ 抄作业必看:图纸本土化修改指南 直接 Copy 赛博图纸前,请务必在你的代码编辑器中全局确认并替换以下核心参数,否则你的流水线将全部建在别人的工地上:

  1. IngressRoute 域名替换:将图纸中的 *.k3s.besthomelab.tech 全部替换为你自己规划的泛域名。

  2. NFS 存储锚点替换(绝对重点!):全量排查所有的 deployment.yaml,把示例中的 NFS 服务器 IP 10.0.10.10 和物理路径 /mnt/nfs/media,精准替换为你局域网内真实 NAS 的地址与路径。

  3. PVC 存储配额按需定夺:对于存放在 Longhorn 上的 /config 卷容量(如 storage: 3Gi),请根据你实际的追剧规模来填。高阶建议:初始给最小,后续按需扩容。 依托 Kubernetes + Longhorn 的云原生特性,以后空间红了,只需改大这个值并 Push,集群会在底层无缝热扩容,连 Pod 都不用重启!

  4. ⚠️ 跨环境挂载避坑(Docker 独立部署必看):如果你没有将 qBittorrent 和 Jellyfin 部署在 K3s 集群内,而是直接在 NAS 上使用 Docker 独立部署,必须保证容器内的路径映射与 K3s 内部完全一致。请务必将宿主机的物理路径(如 /mnt/nfs/media)映射到容器内部的 /media 目录。

    Docker Compose 磁盘映射示例:

    volumes:
    # 宿主机实际物理路径 : 容器内部标准路径
    - /mnt/nfs/media:/media

    原理解析:只有保持内外路径绝对一致,当 K3s 里的 Radarr 告诉外面的 qB “去 /media/downloads/movies 下载”时,qB 才能找对地方,后续的跨容器硬链接也才能完美触发。


万事俱备,最后我们需要向 ArgoCD 下达总攻指令,接管 apps/media 目录下的所有资源。

关键技巧:在 ArgoCD Application 的 directory 配置中,依然保留 exclude: '*template*' 过滤网,防止明文配置意外泄漏到集群。

👉 获取 ArgoCD 触发器指令:bootstrap/media-stack-app.yaml


完整设置请看上面的视频

提交代码并在 ArgoCD 中点击 Sync 后,稍等片刻,你的所有服务将通过 Traefik 自动获取 HTTPS 证书并开放访问:

  1. Jellyfin (jellyfin.k3s.besthomelab.tech):创建管理员账号,并添加 /media/movies/media/tv/media/anime 为媒体库。
  2. qBittorrent (qb.k3s.besthomelab.tech):进入设置,将默认下载路径修改为 /media/downloads
  3. Radarr/Sonarr (radarr.k3s...):
    • Settings -> Media Management 中,务必勾选 Use Hardlinks instead of Copy
    • 添加根目录(Root Folders):分别为 /media/movies/media/tv/media/anime

💡 核心避坑:集群内部通信地址簿 在配置 Prowlarr 索引器、Seerr 门户或 Radarr 下载客户端进行应用联动时,由于这些 Pod 处于同一个命名空间内,强烈建议直接使用 K8s 内部 Service 名称进行相互通信。这比走外部域名路由延迟更低、也更稳定。具体配置请直接参考或复制下表:

cyberbuilder@k3s-master-01:~# get-services.sh —core-infra
应用名称内部 Service 访问地址备注说明
Prowlarrhttp://prowlarr-service:9696索引聚合中心
Radarrhttp://radarr-service:7878电影大管家
Sonarrhttp://sonarr-service:8989剧集大管家
Seerrhttp://seerr-service:5055需求总门户
qBittorrenthttp://qb-service:8080⚠️ 若未部署在集群中,请使用你自行部署的 NAS 实际 IP/域名和端口号
Jellyfinhttp://jellyfin-service:8096⚠️ 若未部署在集群中,请使用你自行部署的 NAS 实际 IP/域名和端口号

如果你想将qBittorrent部署在集群中,又存在openclash代理,可以参考这篇文章:在 K8s/K3s 中为特定 Pod 分配独立物理 IP (Multus + Macvlan + VLAN) 慎用

到这里,整座赛博堡垒最华丽的全自动影音流水线已全面贯通。点开 Seerr,搜索一部你想看的电影,点击“请求”。接下来,请倒杯咖啡,看着海报墙自动亮起吧!

最后欢迎进群讨论