赛博堡垒的私人影院:基于 GitOps 部署 Servarr 自动化全家桶
This content is not available in your language yet.
📖 架构解析:什么是真正的“赛博流水线”?
Section titled “📖 架构解析:什么是真正的“赛博流水线”?”在完成了底层数据库(CloudNativePG)和存储引擎(Longhorn + NFS)的铺设后,我们终于迎来了赛博堡垒中最令人激动的上层建筑——全自动私人影音流水线。
我们将要部署一套由多个微服务协同工作的完美闭环,先来看看它们是如何协同作战的:
⚙️ 流水线运转逻辑简述:
- 提出需求:你在 Seerr 极美的界面中点选了一部电影,它立即将任务交给大管家 Radarr。
- 检索与下载:Radarr 通过 Prowlarr 在全网 PT 站搜寻到最佳种子的磁力链接,并将其发送给 qBittorrent。
- 入库与刮削:qB 下载完成后,Radarr 利用 硬链接 (Hardlink) 将视频瞬间放入电影库,并从网上刮削好高清海报。
- 通知观影:最后,Radarr 拍了拍 Jellyfin 的肩膀:“新货到了”。Jellyfin 瞬间点亮海报墙,等待你的观看。
💡 首席包工头的架构巧思:
- 全面拥抱 PostgreSQL:抛弃极易卡死和损坏的 SQLite,将所有 Arr 应用的后端数据库直接接入我们上一期搭建的 CNPG 高可用集群。
- 存储“动静分离”:频繁读写的海报缓存与转码分片(
/config,/cache)放置在高性能的 Longhorn 块存储;体积庞大的视频原片(/media)放置在大容量的 NFS 网络存储。 - 零损耗硬链接 (Hardlinks):通过给所有容器统一挂载底层
/media目录,实现下载目录与电影库同盘流转,下载完成瞬间“秒入库”,彻底告别双倍硬盘空间浪费。
👉 情报中心:besthomelab/k3s-homelab-gitops 图纸库
🛠️ 施工前置:点亮赛博堡垒的科技树
Section titled “🛠️ 施工前置:点亮赛博堡垒的科技树”罗马不是一天建成的,全自动赛博流水线也需要坚实的基建支撑。在正式开工前,请确保你的环境已经具备以下核心条件:
-
Kubernetes 赛博集群 与 ArgoCD 你需要一个运行良好的 K8s 集群作为总指挥部,并已安装 ArgoCD 来执行 GitOps 自动化部署。 (注:本文的所有 YAML 图纸均基于 Kubernetes 架构编写。纯 Docker 玩家可借鉴其核心的“动静分离”和“硬链接”逻辑,自行翻译成
docker-compose配置。) -
外接海量大硬盘 (NFS 存储) 作为赛博堡垒的“大容量集装箱”,专职收容 TB 级的视频原片与下载缓存。本教程示例中,我们使用的 NFS 服务器 IP 为
10.0.10.10,物理挂载路径规划为/mnt/nfs/media。⚠️ 包工头的网络架构警告: 如果你的 K8s 集群节点和底层存储(NAS)不在同一台物理主机上,千兆/2.5G 内网极易成为性能瓶颈!qBittorrent 的高频读写和 Jellyfin 的视频推流会产生海量的跨设备流量。为了减少内网传输损耗,强烈推荐将 qB 和 Jellyfin 直接部署在 NAS 宿主机上(例如使用 Docker),而让 K8s 集群专职运行 Arr 系列,充当纯粹的“控制大脑”。
-
PostgreSQL 高可用数据库 为了彻底解决 SQLite 容易锁死和损坏的顽疾,本博客采用 CloudNativePG 驱动的 PostgreSQL 集群,为所有 Arr 应用提供坚不可摧的共享数据心脏。(注:如果你不想在 K8s 里折腾 CNPG,完全可以自己用 Docker 单独建一个普通的 PostgreSQL 实例,只要能在局域网内连通即可。)
-
Longhorn 分布式块存储 空间宝贵的高性能“保险箱”,专职承载各应用的
/config目录。极速的块存储读写能力,是保障 Jellyfin 海报墙“秒开”的关键。 -
Traefik 边缘网关 与 Sealed Secrets Traefik 负责接管所有外部访问的域名路由 (IngressRoute);而 Sealed Secrets 则作为非对称加密穹顶,确保我们的数据库密码可以安全地推送到公开的 Git 仓库中。
-
神秘的“数字矿区”入场券 (私有资源社区账号) 工具再锋利,也得有矿可挖。赛博流水线(Arr 全家桶)只是一支执行力极强的“自动化物流舰队”,它本身并不生产数据。最终你的海报墙能有多震撼,完全取决于你给 Prowlarr 雷达输入了多少优质的“矿区坐标”。 由于高质量的私有影视社区(如各大 PT 站点)普遍遵循“低调潜行、闭门发展”的圈内共识,获取这些“深水区”的邀请码需要各位架构师各凭本事。本文仅提供物流基建的图纸,请大家保持极客精神,低调跑满你的带宽。
地基打牢,才能万丈高楼平地起。如果你对上述配置已经轻车熟路,那我们直接进入施工第一步!
🗺️ 空间映射:物理存储与赛博容器的“传送门”
Section titled “🗺️ 空间映射:物理存储与赛博容器的“传送门””在动工之前,我们必须在物理 NAS 和 K8s 容器之间建立一套统一的“空间坐标系”。
为了实现全自动入库且零拷贝(Hardlink),我们必须保证:在所有应用看来,所有的影视资源都处于同一个根目录下。 无论是负责下载的 qBittorrent,负责刮削的 Radarr/Sonarr,还是负责播放的 Jellyfin,它们的视野必须完全对齐。
以下是本次工程的“空间映射地图”,请在后续配置各大管家时严格遵照此坐标系:
| 存储区域 | 物理 NAS 路径 (10.0.10.10) | 容器内挂载路径 | 关联的核心应用 | 核心职能 / 备注 |
|---|---|---|---|---|
| 📁 数据总根目录 | /mnt/nfs/media | /media | 全家桶通用 | 所有的“传送门”都从这里开启,是跨容器硬链接成立的物理基石。 |
| 🎬 电影库 | /mnt/nfs/media/movies | /media/movies | Radarr, Jellyfin | Radarr 负责入库重命名,Jellyfin 负责读取、刮削与最终播放。 |
| 📺 剧集库 | /mnt/nfs/media/tv | /media/tv | Sonarr, Jellyfin | Sonarr 负责追剧入库管理,Jellyfin 负责读取与播放。 |
| 🌸 动漫库 | /mnt/nfs/media/anime | /media/anime | Sonarr, Jellyfin | 隔离二次元资源,通常在 Sonarr 中会为其单独建立一套刮削与重命名规则。 |
| 📥 下载卸货区 | /mnt/nfs/media/downloads | /media/downloads | qBittorrent, Radarr, Sonarr | qB 的默认下载路径。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 加密封壳:
kubeseal --format yaml < apps/media/shared/secret.template.yaml > apps/media/shared/media-db-secret.yaml(加密完成后,记得销毁或忽略本地的模版文件)
2. 在 pgAdmin 中批量建库建号
Section titled “2. 在 pgAdmin 中批量建库建号”打开我们的数据总控台 (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 服务器执行:
# 1. 创建英文目录结构sudo mkdir -p /mnt/nfs/media/{movies,tv,anime,downloads/{movies,tv}}
# 2. 统一权限(确保容器内的 UID 1000 有权读写)sudo chown -R 1000:1000 /mnt/nfs/mediasudo 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: /media2. 防死锁更新策略
为配合 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 赛博图纸前,请务必在你的代码编辑器中全局确认并替换以下核心参数,否则你的流水线将全部建在别人的工地上:
IngressRoute域名替换:将图纸中的*.k3s.besthomelab.tech全部替换为你自己规划的泛域名。
NFS存储锚点替换(绝对重点!):全量排查所有的deployment.yaml,把示例中的 NFS 服务器 IP10.0.10.10和物理路径/mnt/nfs/media,精准替换为你局域网内真实 NAS 的地址与路径。
PVC存储配额按需定夺:对于存放在 Longhorn 上的/config卷容量(如storage: 3Gi),请根据你实际的追剧规模来填。高阶建议:初始给最小,后续按需扩容。 依托 Kubernetes + Longhorn 的云原生特性,以后空间红了,只需改大这个值并 Push,集群会在底层无缝热扩容,连 Pod 都不用重启!⚠️ 跨环境挂载避坑(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 才能找对地方,后续的跨容器硬链接也才能完美触发。
🚀 启动全自动装配线
Section titled “🚀 启动全自动装配线”万事俱备,最后我们需要向 ArgoCD 下达总攻指令,接管 apps/media 目录下的所有资源。
关键技巧:在 ArgoCD Application 的 directory 配置中,依然保留 exclude: '*template*' 过滤网,防止明文配置意外泄漏到集群。
👉 获取 ArgoCD 触发器指令:bootstrap/media-stack-app.yaml
🎉 竣工验收与流水线调试
Section titled “🎉 竣工验收与流水线调试”提交代码并在 ArgoCD 中点击 Sync 后,稍等片刻,你的所有服务将通过 Traefik 自动获取 HTTPS 证书并开放访问:
- Jellyfin (
jellyfin.k3s.besthomelab.tech):创建管理员账号,并添加/media/movies和/media/tv、/media/anime为媒体库。 - qBittorrent (
qb.k3s.besthomelab.tech):进入设置,将默认下载路径修改为/media/downloads。 - Radarr/Sonarr (
radarr.k3s...):- 在 Settings -> Media Management 中,务必勾选
Use Hardlinks instead of Copy。 - 添加根目录(Root Folders):分别为
/media/movies和/media/tv、/media/anime。
- 在 Settings -> Media Management 中,务必勾选
💡 核心避坑:集群内部通信地址簿 在配置 Prowlarr 索引器、Seerr 门户或 Radarr 下载客户端进行应用联动时,由于这些 Pod 处于同一个命名空间内,强烈建议直接使用 K8s 内部 Service 名称进行相互通信。这比走外部域名路由延迟更低、也更稳定。具体配置请直接参考或复制下表:
| 应用名称 | 内部 Service 访问地址 | 备注说明 |
|---|---|---|
| Prowlarr | http://prowlarr-service:9696 | 索引聚合中心 |
| Radarr | http://radarr-service:7878 | 电影大管家 |
| Sonarr | http://sonarr-service:8989 | 剧集大管家 |
| Seerr | http://seerr-service:5055 | 需求总门户 |
| qBittorrent | http://qb-service:8080 | ⚠️ 若未部署在集群中,请使用你自行部署的 NAS 实际 IP/域名和端口号 |
| Jellyfin | http://jellyfin-service:8096 | ⚠️ 若未部署在集群中,请使用你自行部署的 NAS 实际 IP/域名和端口号 |
如果你想将qBittorrent部署在集群中,又存在openclash代理,可以参考这篇文章:在 K8s/K3s 中为特定 Pod 分配独立物理 IP (Multus + Macvlan + VLAN) 慎用
到这里,整座赛博堡垒最华丽的全自动影音流水线已全面贯通。点开 Seerr,搜索一部你想看的电影,点击“请求”。接下来,请倒杯咖啡,看着海报墙自动亮起吧!