死长死长的配置文件啊!越写越打退堂鼓:人为什么要这么折磨自己啊!
反正基本上是跟着这篇文章走的吧。
起因
买了个 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:
labels:
app: jellyfin
name: jellyfin
namespace: jellyfin
spec:
progressDeadlineSeconds: 600
replicas: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app: jellyfin
template:
metadata:
labels:
app: jellyfin
spec:
volumes:
- name: jellyfin-config
persistentVolumeClaim:
claimName: jellyfin
- name: jellyfin-media # Use raw NFS directly
nfs:
path: /Media # NFS of Multimedia
server: 192.168.1.10 # IP of my NAS
- name: device-dri
hostPath:
path: /dev/dri
containers:
- env:
- name: TZ
value: Asia/Shanghai
securityContext:
privileged: true # Container must run as privileged inside of the pod, required for hardware acceleration
image: jellyfin/jellyfin
imagePullPolicy: Always
name: jellyfin
ports:
- containerPort: 8096
name: http-tcp
protocol: TCP
- containerPort: 8920
name: https-tcp
protocol: TCP
- containerPort: 1900
name: dlna-udp
protocol: UDP
- containerPort: 7359
name: discovery-udp
protocol: UDP
resources: {}
stdin: true
tty: true
volumeMounts:
- mountPath: /config
name: jellyfin-config
- mountPath: /media
name: jellyfin-media
- mountPath: /dev/dri
name: device-dri
dnsPolicy: ClusterFirst
restartPolicy: Always
securityContext: # UID and GID, can ignore. Default is root.
runAsUser: 1000
runAsGroup: 100
在这个配置里,我们先定义了一堆 K8s 内部「如何滚动升级、挂了怎么办」的东西,然后就像 docker-compose 一样,声明了一些用来存储的卷,我把媒体文件的声明单独拆出来了。最后是配置 Jellyfin 本体:使用啥镜像、开放哪些端口、Volume 挂载到哪儿、用啥 UID 和 GID(可忽略,默认用 root 好像)
Service 设置
这里没太看懂,所以就差不多直接搬上来了。
kind: Service
apiVersion: v1
metadata:
name: jellyfin # < name of the service
namespace: jellyfin # < namespace where to place service
spec:
selector:
app: jellyfin # < reference to the deployment (connects the service with the deployment)
ports:
- port: 1900 # < port to open on the outside on the server
targetPort: 1900 # < targetport. port on the pod to passthrough
name: dlna-udp # < reference name for the port in the deployment yaml
protocol: UDP
- port: 7359
targetPort: 7359
name: discovery-udp
protocol: UDP
- port: 8096
targetPort: 8096
name: http-tcp
protocol: TCP
- port: 8920
targetPort: 8920
name: https-tcp
type: LoadBalancer
sessionAffinity: ClientIP # This is necessary for multi-replica deployments
大概是说,这个 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 # < name of ingress entry
namespace: jellyfin # < namespace where place the ingress enty
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……
硬件解码
没搞好。直接看这个官方文档吧。
总结
K8s 的概念真的是太 tm 多了……本来用 docker-compose,12 行就能搞完的东西,K8s 硬生生搞了 150 行。yaml 工程师真的是名不虚传。更可恶的是,还没有 UI 可以直接配置,万一出错了也不知道是哪里错了,只能没头苍蝇乱改一通。
不管怎么说,用上了。
发表回复