用K8s部署一个Jellyfin


发布于

|

分类

死长死长的配置文件啊!越写越打退堂鼓:人为什么要这么折磨自己啊!

反正基本上是跟着这篇文章走的吧。

起因

买了个NUC,想学学K8s。想着之前用Jellyfin很爽,就从它下手吧。

先看看原始需求

就,部署一个jellyfin,正常挂载一些东西,完事儿。

如果用docker-compose写的话,大概长这样:

version: '3.5'
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    network_mode: 'host'
    devices:
      - /dev/dri:/dev/dri
    volumes:
      - /host/path/to/config:/config
      - /host/path/to/media:/media

定义一下使用的镜像,定义两个本地volume,一个用于存配置,一个用于存媒体文件。最后还需要挂一下/dev/dri 这个东西,我也不知道是干啥的,反正挂上去之后字幕和解码啥啥的就莫名能用了。

这个配置在我的NAS上一直工作很好。

Namespace

一般情况下,K8s有个默认的Namespace叫「default」。为了整洁干净,所以我们可以给Jellyfin分配个单独的Namespace,就叫jellyfin好了。

这步不是必须的。如果不做的话,后面所有配置里面的 namespace 都改成 default 即可。

apiVersion: v1
kind: Namespace
metadata:
  name: jellyfin

Persistent Volume Claim

这玩意儿是说,「我需要一些存储空间,K8s你帮忙准备一下啊~」。

原文里是先声明PersistentVolume,后声明PersistentVolumeClaim。但是我不太清楚 Persistent Volume 和 Persistent Volume Claim 有啥区别。因为我看到的是,只写Persistent Volume Claim似乎也能正常部署。所以就懒了一下,只写了Persistent Volume Claim。

按照最前面的docker-compose,我们需要俩存储空间,一个用来存放Jellyfin的配置,另一个用来放媒体文件。但其实,我自己的规划是「NFS共享里面只放配置」,所以只需要一个PersistentVolumeClaim就好了。那么数据怎么办呢?按照这篇文章的介绍,我们可以直接在部署那里指定NFS就好。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jellyfin
  namespace: jellyfin
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: datapool
  resources:
    requests:
      storage: 2Gi

上面的配置中有个叫 storageClassName 的东西,那是我提前配置好的NFS远程存储,具体可见这篇文章。反正这样写会比原文中的写法简洁那么一点点。

Jellyfin本体

我们要创建一个deployment,这样万一一个节点挂了,K8s能自动给我们在其他节点上再启一个实例。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jellyfin
  namespace: jellyfin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jellyfin
  template:
    metadata:
      labels:
        app: jellyfin
    spec:
      containers:
        - name: jellyfin
          image: jellyfin/jellyfin
          imagePullPolicy: IfNotPresent
          ports:
            - name: http-tcp
              containerPort: 8096
              protocol: TCP
          env:
            - name: TZ
              value: Asia/Shanghai
          securityContext:        # Container must run as privileged inside of the pod
            privileged: true 
          volumeMounts:
            - mountPath: /config
              name: jellyfin-config
            - mountPath: /dev/dri
              name: device-dri
            - mountPath: /media
              name: jellyfin-media
      volumes:
        - name: jellyfin-config
          persistentVolumeClaim:
            claimName: jellyfin
        - name: jellyfin-media # Use raw NFS directly
          nfs:
            path: /Media          # NFS of Multimedia
            server: 192.168.1.123 # IP of my NAS
        - name: device-dri        # For hardware accrlerate
          hostPath:
            path: /dev/dri
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        supplementalGroups:     # Change this to match your "render" host group id and remove this comment
          - 44 # video
          - 103 # input
          - 106 # render

在这个配置里,我们先定义了一堆K8s内部「如何滚动升级、挂了怎么办」的东西,然后就像docker-compose一样,声明了一些用来存储的卷,我把媒体文件的声明单独拆出来了。最后是配置Jellyfin本体:使用啥镜像、开放哪些端口、Volume挂载到哪儿、用啥UID和GID(可忽略,默认用root好像)

Service设置

这里没太看懂,所以就差不多直接搬上来了。

kind: Service
apiVersion: v1
metadata:
  name: jellyfin
  namespace: jellyfin
spec:
  selector:
    app: jellyfin
  ports:
    - port: 8096
      targetPort: 8096
      name: http-tcp
      protocol: TCP
  type: LoadBalancer
  sessionAffinity: ClientIP

大概是说,这个Jellyfin在集群里面是如何映射的。反正经过这个配置后,集群上的每个节点都可以将流量转发到Jellyfin的工作Pod(似乎是LoadBalancer的作用)。

原文里说TCP端口和UDP端口需要分别用一个单独的配置文件来配置,我也查到官方文档里面说「所有端口必须具有相同的协议」。但我尝试可以合在一起来搞,所以,就合在一起了。

Ingress配置

如果没有Ingress,那么几乎所有东西都是在集群内互通的(访问方式是 http://<service-name>.<namespace>),集群外访问不到(也不绝对,比如可以ssh port forward啥的,总之访问会很麻烦)。Ingress,gress我看不懂,前面的In 就是开了一个从外部访问到集群内部的口子。

这里用的是域名的方式来搞的。所以配置比较爽,后面在其他平台上设置比较麻烦。由于也看不太懂,所以差不多也是直接抄下来的:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jellyfin
  namespace: jellyfin
spec:
  rules:
    - host: jellyfin.myprovatedomain
      http:
        paths:
          - path: /
            pathType: Prefix # pathType no longer has a default value in v1; "Exact", "Prefix", or "ImplementationSpecific" must be specified
            backend:
              service:
                name: jellyfin
                port:
                  name: http-tcp # < same label as the port in the service tcp file

在这里,我告诉Ingress说,我的域名是jellyfin.myprivatedomain,然后从根路径开始都路由到jellyfin的http-tcp口上。

K3s有个基于traefik的Ingress,不需要我们自己再去安装基于nginx的Ingress。

域名解析

额,都说了是在内网,解析个毛线啊!

但成也内网,败也内网。因为jellyfin.myprivatedimain这个域名是不存在的,所以我们需要想办法Hack一下,让自己路由器下能正常解析这个域名。本质上是改DNS,将这个不存在的域名解析到集群里面的任意一个节点上(因为用了LoadBalancer)。

第一种方法是改本机的hosts文件。如果用了Surge之类的代理软件,在配置里有个DNS,加上对应的解析即可。

第二种方法是改路由器的DNS。如果是OpenWRT,在DHCP/DNS设置里面,有个地方可以自定义解析,加上去就好了。如果你用了AdGuard,那么在AdGuard里面加也是极好的。不过需要注意的是,如果你电脑上有代理软件,那么你的代理软件需要放过这个域名,用OpenWRT或者AdGuard发来的解析结果。

好了,访问一下试试

在浏览器里面输入 http://jellyfin.myprivatedomain ,应该就能看到Jellyfin的配置界面了。注意一定要输全,不然可能会跳Google搜索。

配置代理

额,为啥会有这么个奇怪的茬……

就是说,tvdb啥的在国内无法访问,所以刮削基本上都会失败。那么为了正常刮削,我们需要配置个代理。

在Jellyfin的「设置-控制面-网络-防火墙和代理」里面可以配置代理,不过似乎不咋生效。

另一个方法是在Docker里面配置HTTP_PROXY 这个环境变量。配置文件大概这样改:

apiVersion: apps/v1
kind: Deployment
...
spec:
  ...
  template:
    spec:
      ...
      containers:
        - env:
            - name: HTTP_PROXY
              value: http://192.168.1.1:1111
            - name: HTTPS_PROXY
              value: http://192.168.1.1:1111
            ...

话说Jellyfin刮削好慢啊,八季美剧刮削了接近一个小时……怪不得那么多人用TMM 或者NasTool……

硬件解码

按照这个官方文档走吧。其实上面的配置已经把所有问题都解决了,可以直接开启QSV。

如果你是在LXC容器里面跑的K8s,需要将母鸡上的/dev/dri挂载给容器。在母鸡里,找到PVE的配置文件/etc/pve/lxc/${VMID}.conf,加入这几行

lxc.cgroup2.devices.allow: a
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir

第一句话是说允许使用所有设备(这样就不必为每个设备单独写一条规则了)。

第二句话是说将/dev/dri挂载到LXC里面。

验证方法是,进到pod里面,执行下面两句话,如果没有报错,就是OK的

/usr/lib/jellyfin-ffmpeg/vainfo
/usr/lib/jellyfin-ffmpeg/ffmpeg -v verbose -init_hw_device vaapi=va -init_hw_device opencl@va

然后去后台,选QSV,「启用硬件编码」那里全部钩上。

(额,折腾一大通,似乎也没省下太多CPU资源。但是呢,我都花了集成显卡的钱了,总归是得让它干点活的吧)

总结

K8s的概念真的是太tm多了……本来用docker-compose,12行就能搞完的东西,K8s硬生生搞了100行(网上其他yaml大概在150行左右)。yaml工程师真的是名不虚传。更可恶的是,还没有UI可以直接配置,万一出错了也不知道是哪里错了,只能没头苍蝇乱改一通。

不管怎么说,用上了。

参考资料


评论

  1. […] 我用的是Jellyfin,更详细的步骤可以参考之前的教程 […]

发表回复

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