死长死长的配置文件啊!越写越打退堂鼓:人为什么要这么折磨自己啊!
反正基本上是跟着这篇文章走的吧。
起因
买了个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可以直接配置,万一出错了也不知道是哪里错了,只能没头苍蝇乱改一通。
不管怎么说,用上了。
发表回复