用 K8S 搭建自动追剧工作流

别人都是用 Docker,咱有了 K8S,就想啥啥啥都用 K8S 来解决。

想象一下:你想明天抽时间看部电影。打开一个网页,输入电影名称,点击一个按钮。过一会儿(几小时?),这部电影就会出现在你的影音库里,高清、有海报、有字幕、有剧情介绍、有演职员表,没有广告。——是不是很爽?

对于电影和剧,我还是比较传统地喜欢下载下来看:不卡顿、没广告,就这两点已经秒杀无数流媒体和各种小电影网站了。我是真心不喜欢一边看电影,一边飘着各种博彩和色情的小广告。

之前买了 Nas,装了 Jellyfin,每次都是从威联通的 DownloadStation 里搜索、下载,甚至需要手动改下文件名,然后 Jellyfin 负责后续信息匹配、海报墙抓取、字幕下载。稍微有一点点点麻烦。这个春节看到有一些比较成熟的自动工作流,就硬搞了一番。本来各种教程都是 Docker 的,但,咱不是搞了个 K8S 集群嘛,闲着也是闲着,就真的用 K8S 硬搞了一番(虽说和 Docker 差别不大,就当 K8S 的练手了)

基本概念

好多文章一上来就开始说刮削(影片信息和海报抓取)的事儿,看得我云里雾里……反正最终是找到个文章给梳理清楚了。

  • BT/PT
    • 额,似乎现在普通人都只知道「百度网盘」、「阿里云盘」了……不像我们这些老人,都是从 FlashGet 、网络蚂蚁、 BT 、电驴啥的一路走过来的……下面只是一些简单的科普。
    • 在古早的时候,大家下载资源都是从原始服务器上下载的。比如服务器带宽只有 1M,那么 N 个人下载的话,就是大家在平分这 1M 带宽,人越多下载越慢。
    • 在 200X 年的时候,有一项特别牛 X 的技术出现了:每个人在下载的时候,也当作了一个临时的「服务器」,下载程序会自动搜索所有可用的「服务器」来下载。所以下载人越多,下载速度越快。这就大概就是 BT 。
    • 这里面就会出现一大堆新名词了,比如「种子」(对于完全没有接触过 BT 的人来说,你可以理解为一种特殊的下载链接)、「分享率」(你下载了多少数据?又给别人传了多少数据?不能只下载不上传,那样太自私了)啥啥啥的。
    • BT 是公开的,拿到「种子」之后就能下载;PT 是私有的,一般需要种子+对应网站的用户信息才能下载,并且下载完成后还要强制上传一段时间以保持更高的分享率和赚积分。
    • 一般来说,我们会用 Transmission 、 qBittorrent 甚至 Aria2 来下载 BT 资源。你说啥?迅雷?求求放过这些吸血的国产软件吧……
  • 索引器
    • 上面 BT/PT 解决了「下载」本身的问题。那么,下载链接从哪里拿到?
    • 比如,我们经常去逛一些 BT 站、下载站。对,就是从这里。但是,世界上有成千上万的 BT 站和下载站,我还要一个一个网站去搜索,看看这个网站里面有没有我们需要的资源么?
    • 于是有了「索引器」。它的作用就是将各个网站的数据,整理成一个统一的格式,方便后续进行统一的搜索。
    • 一般来说,Prowlarr 和 Jackett 这俩软件用得比较多。但我实际体验下来,可能 Prowlarr 会更方便一点。后续再说。
    • 你说啥?Google?那个太宽泛了。所谓「术业有专攻」。
  • 整套自动流程工作的核心
    • 这个「核心」的作用是,你告诉它「我想看某某某」,它就会去索引器里面进行搜索。如果搜索到了,那么自动获取种子,调用下载器进行下载,下载完成后对文件进行重新整理(比如将千奇百怪的文件名重命名为某种易于识别的统一格式);如果没搜索到,那么将持续监控上述网站,直到有对应资源的时候自动执行上面那一大堆流程。如果你想看的是个剧,那么它还会持续监控更新,一有新集放出,就会第一时间进行下载。
    • 很不幸的是,常用的有俩东西,Radarr 负责电影,Sonarr 负责电视剧。这俩软件几乎是一个模子刻出来的,但是功能有所区别,就很让人郁闷。
  • 整合前端
    • 来,跟我一起念:Radarr 负责电影,Sonarr 负责电视剧,Radarr 负责电影,Sonarr 负责电视剧,Radarr 负责电影,Sonarr 负责电视剧。记住了么?——那么我想看《老友记》,应该用啥?
    • 那好,Jellyseerr 就是屏蔽了这些东西。你只需要在这里面搜索,它会自动区分是电影还是电视剧,然后提交给对应的核心。
  • 影音库
    • 你下载了的电影和剧,就以文件的形式放在硬盘上啊?想看啥自己双击打开?那这体验太差了。影音库的作用就是把所有文件 “展示” 出来,比如展示成你常见的海报墙,点进去后有各种剧情介绍、有演职人员表和介绍,还能顺带搞下字幕。
    • 对我来说,更大的作用其实是个 “播放器” 吧,能用网页播放,还能有播放进度同步,在网页上看了一半,在手机上还能接着看,明天在投影仪上依然能接着看。
    • 如果你下载的是 4K 甚至更高画质,但你出门在外,传输个几十 G 的文件不方便,那么影音库还可以实时给你转码成 1080P,便于手机上播放。
  • 太麻烦了,有点简单点的东西没有?
    • 有,但我没咋搞懂。似乎都是 “索引+核心+前端” 集成到了一起,你还需要装下载器、影音库
    • NasTools 不更新了,我自己没体验
    • MoviePilot,装上后发现我完全不会添加网站。
    • MovieRobot 目前收费了,就没再体验

用 K8S 搭建

我搭建的是 prowlarr+radarr+sonarr+jellyseerr+jellyfin 的组合,大致是这个效果

我自己是把 Transmission 直接装到了 nas 上,然后其他所有东西都放在我的 K8S 专用小主机上。

存储

我是将存储分成了两部分:nas 存储,和 config 存储。 nas 用于存视频文件本身,用 nfs 的方式挂载给其他应用;config 存储直接使用 k8s 的 pv/pvc,反正都那些 config 也占不了多大地儿。

PV/PVC 很好说,随便搞一搞就是了

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: movie-stack
spec:
  resources:
    requests:
      storage: 1Gi
  accessModes:
    - ReadWriteMany

那么之后写 Deployment 的时候,里面的 volumes 段我是这么写的:

volumes:
  - name: movie-stack # 用于存储各种配置
    persistentVolumeClaim:
      claimName: movie-stack
  - name: nfs-media # NAS 的 NFS 共享,用于存放影视数据
    nfs:
      path: /Media # NFS 共享目录
      server: 192.168.1.250 # IP of my NAS

各种组件

先来写个架子:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: movie-stack
spec:
  replicas: 1
  selector:
    matchLabels:
      app: movie-stack
  template:
    metadata:
      labels:
        app: movie-stack
    spec:
      containers:

看到最后的 containers 了么?下面各种 yaml 都是从这里接上的。比如最后可能的形态是:

apiVersion: apps/v1
... # 省略一大堆
spec:
  ... # 省略一大堆
  template:
    ... # 省略一些
    containers:
      - name: flaresolverr
        ... # flaresolverr 的各种配置
      - name: prowlarr
        ... # prowlarr 的各种配置
      - ...
    volumes:
      - name: movie-stack
        ...
      - name: nfs-media
        ...

下载器

我用的是 transmission 。目前主流可以选 transmission 、 qBittorrent,甚至可以选 Aria2 。如果你有群晖,那么甚至可以直接用群晖自带的 download station(威联通的 download station 似乎不支持)。你还可以直接使用群晖或者威联通软件中心里面安装的 Transmission 。

我是这样想的:我的 k8s 集群和 nas 是完全分开的设备,但是下载器要大量读写存储,所以要离存储近一点。所以我直接把 transmission 安装在了 nas 上,用挂载 volume 的方式直接读写磁盘。比较逆天的是,我用了一块很老的 ssd 作为临时盘,下载完成后选择直接复制到机械硬盘上,不知道能不能减少一点对机械硬盘的损害。

version: "3"
services:
  transmission:
    image: lscr.io/linuxserver/transmission:latest
    container_name: transmission
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - TRANSMISSION_WEB_HOME= #optional
      - USER=admin #optional
      - PASS=password #optional
      - WHITELIST= #optional
      - PEERPORT= #optional
      - HOST_WHITELIST= #optional
    volumes:
      - /share/Media/transmission/seeds:/watch
      - /share/Media/transmission/config:/config
      - /share/Media:/downloads
    ports:
      - 9091:9091
      - 51413:51413
      - 51413:51413/udp
    restart: unless-stopped

然后新建俩文件夹,一个叫 movie 用于放电影,一个叫 series 用于放剧集。

索引器

比较知名的有两个,一个是 jackett,另一个是 prowlarr 。我刚刚开始用,只是觉得 prowlarr 可以把添加进来的网站自动推送给后面的 radarr 和 sonarr,并且它和后续几个应用几乎是同一个模子刻出来的,体验比较统一一些,更何况它还有中文。那,就,它,了。

据说 jackett 对 PT 支持会更好一些。但是它没中文,还得把各种站点信息手动复制给 radarr 和 sonarr,不适合我这种懒人,所以我就没管它

containers:
  # 资源索引器 (BT)
  - name: prowlarr
    image: linuxserver/prowlarr
    imagePullPolicy: IfNotPresent
    ports:
      - name: prowlarr-web
        containerPort: 9696
        protocol: TCP
    volumeMounts:
      - name: movie-stack
        subPath: prowlarr/config
        mountPath: /config

顺带,有些资源站点设置了 CloudFlare 质询(5 秒盾)来防御 DDOS 或者爬虫。那么为了能让这些站点也能工作,我们还需要再部署个 flaresolverr

containers:
  # FlareSolverr 解决 CloudFlare 5 秒盾
  - name: flaresolverr
    image: flaresolverr/flaresolverr
    imagePullPolicy: IfNotPresent
    ports:
      - name: flaresolver-web # 不能超过 15 个字
        containerPort: 8191
        protocol: TCP

当然,你要是想部署个 jackett,也很简单:

containers:
  - name: jackett
    image: linuxserver/jackett
    imagePullPolicy: IfNotPresent
    ports:
      - name: jackett-web
        containerPort: 9117
        protocol: TCP
    volumeMounts:
      - name: movie-stack
        subPath: jackett/config
        mountPath: /config

核心组件

我们需要同时部署 radarr 和 sonarr

containers:
  # 资源刮削器(电影)
  - name: radarr
    image: linuxserver/radarr
    imagePullPolicy: IfNotPresent
    ports:
      - name: radarr-web
        containerPort: 7878
        protocol: TCP
    volumeMounts:
      - name: movie-stack
        subPath: radarr/config
        mountPath: /config
      - name: nfs-media
        mountPath: /media

  # 资源刮削器(电视剧&动漫)
  - name: sonarr
    image: linuxserver/sonarr
    imagePullPolicy: IfNotPresent
    ports:
      - name: sonarr-web
        containerPort: 8989
        protocol: TCP
    volumeMounts:
      - name: movie-stack
        subPath: sonarr/config
        mountPath: /config
      - name: nfs-media
        mountPath: /media

聚合搜索前端

我用的是 jellyseerr,它可以和 jellyfin 配合。

containers:
  # 资源聚合搜索
  - name: jellyseerr
    image: fallenbagel/jellyseerr
    imagePullPolicy: IfNotPresent
    ports:
      - name: jellyseerr-web
        containerPort: 5055
        protocol: TCP
    volumeMounts:
      - name: movie-stack
        subPath: jellyseerr/config
        mountPath: /app/config

影音库

我用的是 Jellyfin,更详细的步骤可以参考之前的教程

containers:
  - name: jellyfin
    image: jellyfin/jellyfin
    imagePullPolicy: IfNotPresent
    ports:
      - name: jellyfin-web
        containerPort: 8096
        protocol: TCP
    env:
      - name: TZ
        value: Asia/Shanghai
    securityContext:
      privileged: true 
    volumeMounts:
      - name: movie-stack
        mountPath: /config
        subPath: jellyfin/config
      - name: device-dri
        mountPath: /dev/dri
      - name: nfs-media
        mountPath: /media
volumes:
  - name: device-dri
    hostPath:
      path: /dev/dri
securityContext:
  supplementalGroups:
    - 44 # video
    - 103 # input
    - 106 # render

UID/GID 权限

上面几个应用都说,最好不要用默认用户和组来运行。

LinuxServer 的应用,需要设置 PUID 和 PGID 这两个环境变量。最好再顺手设置一下时区,大概搞成这样:

containers:
  - name: xxx
    image: xxx
    imagePullPolicy: IfNotPresent
    ports:
      ...
    env:
      - name: PUID
        value: "1000"
      - name: PGID
        value: "100"
      - name: TZ
        value: Asia/Shanghai
    volumeMounts:
      ...

jellyfin,需要在 container 那里设置成这个样子:

containers:
  - name: jellyfin
    image: jellyfin/jellyfin
    imagePullPolicy: IfNotPresent
    ports:
      ...
    env:
      - name: TZ
        value: Asia/Shanghai
      - name: HTTP_PROXY
        value: http://192.168.1.1:7890
      - name: HTTPS_PROXY
        value: http://192.168.1.1:7890
      - name: NO_PROXY
        value: "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,100.64.0.0/10,localhost,*.local,::ffff:0:0:0:0/1,::ffff:128:0:0:0/1,*.local,*.lan,*.homelab"
    securityContext:
      privileged: true
      runAsUser: 1000
      runAsGroup: 100
    volumeMounts:
      - ...

所以,能不能统一在 spec 里面设置 securityContext?不行。这样的话,LinuxServer 的各种应用会报无法启动。看起来好像是 LinuxServer 里都有一个类似于 Loader 的东西,它必须是 root 启动。

Service

都是一些端口啥的。如果你用了 jackett,自己补一下就是了。

kind: Service
apiVersion: v1
metadata:
  name: movie-stack
  namespace: docker
spec:
  selector:
    app: movie-stack
  ports:
    - name: jackett-web
      port: 9117
      targetPort: 9117
      protocol: TCP
    - name: prowlarr-web
      port: 9696
      targetPort: 9696
      protocol: TCP
    - name: radarr-web
      port: 7878
      targetPort: 7878
      protocol: TCP
    - name: sonarr-web
      port: 8989
      targetPort: 8989
      protocol: TCP
    - name: jellyseerr-web
      port: 5055
      targetPort: 5055
      protocol: TCP
    - name: jellyfin-web
      port: 8096
      targetPort: 8096
      protocol: TCP
  type: LoadBalancer
  sessionAffinity: ClientIP

Ingress

负责配置域名啊啥的,这样就不用记 IP 和端口了。如果你用了 jackett,同样的照猫画虎自己补一下就是了。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: movie-stack
spec:
  rules:
    - host: prowlarr.homelab
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: movie-stack
                port:
                  name: prowlarr-web
    - host: radarr.homelab
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: movie-stack
                port:
                  name: radarr-web
    - host: sonarr.homelab
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: movie-stack
                port:
                  name: sonarr-web
    - host: jellyseerr.homelab
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: movie-stack
                port:
                  name: jellyseerr-web
    - host: jellyfin.home
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: movie-stack
                port:
                  name: jellyfin-web

感觉写好长……其实有些东西是想用子域名来搞的,但是我不会😂或者是,部署出来后跳转啥啥的处理不好。那就一个东西一个域名吧。

部署

好了,这一大堆东西,呼呼啦啦用 kubectl apply -f xxxx.yaml 一顿运行,应该就能跑起来了。

连调

prowlarr

  1. 先配置用户名和密码。
  2. 「设置-索引器」那里,添加一个类型为「FlareSolverr」的搜刮器代理,标签随便写,例如叫 cloudflare 。其他都可以保持默认。
  3. 点击「索引器」,添加索引器。先从 BT 开始吧就……Privacy 选「public」即可。如果遇到提示说需要 FlareSolver,那么在「标签」那里填上你刚才创建的 FlareSolver 标签(比如,我设置的是 cloudflare)就好了。

radarr & sonarr

这俩的设置方法一模一样。

  1. 先设置用户名和密码。
  2. 去「设置-媒体管理」那里把自动重命名打开。
  3. 去「下载客户端」那里,把单独部署的 Transmission 的地址给写上。端口是 9091,用户名密码都是你自己设置的。
  4. 这个页面下面有个「远程路径映射」。这里的意思是,你的 Transmission 和 radarr/sonarr 不在一起对吧,我现在在 Transmission 里面可以看到一个/path/to/some/file,那么在 radarr/sonarr 看来,这个文件的路径会是什么?可能是/some/other/path/file 。哎,这俩文件,是同一个么?——有了路径映射,我就可以说,现在有这么一个文件夹,在 transmission 里面看来路径是/path/to/some,在 radarr/sonarr 里面看来路径是 some/other/path 。那么我们点加号,选择对应的下载器,填上路径映射,就好了。
  5. 去「通用」那里,找到「API Key」,复制下来

回到 prowlarr 继续设置

  1. 去「设置-应用程序」,点击那个大加号,选择 radarr 或 sonarr 。
  2. 下面有俩地址,一个是 prowlarr 地址,一个是 radarr 或 sonarr 的地址。如果你是照着我的 yaml 来走的,那么这一步就都是 localhost,端口号也都是默认的。
  3. 最后只需要把 API Key 填上去,就好了。

jellyfin

它啊,没啥好说的。按照上面的设置,核显解码啥啥啥的应该都能用。

挂上代理之后,建议安装下那些字幕插件。这样可以自动下载字幕。

我看有好多人是单独又部署了个 ChineseSubtitle 啥啥啥的,我觉得……没啥必要😂Jellyfin+插件就能解决这个事儿了。咱要求没那么高。

jellyseerr

  1. 登录的时候会让你填 jellyfin 的地址,填 http://localhost:8096 即可。
  2. 去「设定-jellyfin」,外网地址可以填 http://jellyfin.homelab 。或者留空也成。
  3. 去「设定-服务器」,分别添加 Radarr 和 Sonarr 。主机名和 IP 地址都直接填 localhost 。记得勾选上「默认服务器」。

好了,到这里,一切都设置完毕了。在 jellyfin 里面搜索个还算热门的东西(比如《沙丘》),点击「提交请求」,然后去 Radarr 或 Sonarr 里面看看有没有「监控中」,去 Transmission 里面看看有没有在下载,等下载完毕后看看有没有自动出现在 Jellyfin 中(可能需要等 Jellyfin 自动刷新。或者去 Jellyfin 的后台手动更新一下媒体库试试)


哦对了,我发现用这套东西下载下来的电影都贼大。我自己喜欢 1080P 的,一部电影大概也就 1G,但是这套东西下载下来的 1080P 要接近 40G,恐怖。还没找到怎么减小电影体积。

看着上面各种设置,其实大概 15 分钟就能搭建起来。真的,和搭建个 WordPress 差不多。

另外再次声明,我用 K8S 部署完全是春节期间吃饱了撑的。其实用 Docker 部署足以。

参考资料

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注