株式会社ピーアールオー(あったらいいな!を作ります) Advent Calendar 2020の19日目になります。
前振り
昨年末、会社の忘年会のビンゴでiPadをもらったり、今年初頭に投げ売りされていたノートPCを購入したりと、我が家の端末が増えました。
それら端末で作業していると、デスクトップPCに保存している音楽が聴きたくなります。
単純に再生するだけならば、sambaなどを使用してファイルサーバを構築すればよいですが、
ファイルの検索やプレイリストの作成並びに各端末での共有などを考えると、使い勝手があまりよくなさそうです。
かといって、今更iTunesなどの使い方を覚えるのもピンと来ませんでした。
そこで、自分用のメディアサーバを構築し、音楽ファイルやプレイリストを一元管理することにしました。
いろいろ調べているうちによさそうだな、と思ったのがJellyfin
です。
Jellyfinとは
Jellyfinとは、音楽や動画といった各種メディアの管理と、ストリーミング再生を行えるフリー(GPLv2)のメディアサーバです。
著作権
まず気になるのは著作権についてです。
- ご自身で購入したディスクからリッピングしてください。
- リッピングしたデータは個人使用しかできません。無断で、売る、貸す、ホームページやSNSに公開する、などを行ってはいけません。
個人的または家庭内その他これに準ずる限られた範囲内において使用する目的で、使用する本人がコピーする場合、著作権者の許諾を得ずにコピーすることができます。
ただし、違法にインターネット上にアップロードされたものと知りながら著作物をダウンロードする場合など、私的使用のための複製であっても違法となる場合があります。
素人判断ですが、以下の行為はNGでしょう。
くれぐれも個人の範囲で使用しなければなりません!
- 他者(関係問わず)とのアカウントの共有
- そもそも他者にサービスに触れさせることがよくない
- 自ら購入していないメディアの使用
- 不特定多数を相手にしたメディアの再生
構築環境
ちょうど我が家にはラズパイ4で作ったKubernetesクラスタ(k3s)があるので、そこにJellyfinを構築します。
(Jellyfinは現状HA構成が不可能なので、クラスタに乗せるうまみはあまりないです)
構成は以下になります。
役割 | 機器名 | OS | 備考 |
---|---|---|---|
マスター | Raspberry pi4 8GB | Ubuntu 20.04 LTS | |
ワーカーA | Raspberry pi4 8GB | Ubuntu 20.04 LTS | |
ワーカーB | Raspberry pi4 8GB | Ubuntu 20.04 LTS | |
NAS | Raspberry pi4 4GB | Raspbian10 (buster) | 独立したNFSファイルサーバー。外付けのHDDを接続済み。 |
ロードバランサにはレイヤー2なMetalLBを使用していて、IngressはNginxです。
まさしくこのドキュメントの構成です。
また、ストレージは外付けHDDを接続したNFSサーバを別途用意して、永続ボリュームを作成します。
なお、公式ドキュメントにはdocker-compose.yamlのサンプルが置かれています。
試しに起動してみたいという場合には、こちらを使うのがよいでしょう。
構築
1. ビルド、プッシュ
とてもありがたいことに、aarch64(ARM64)用のイメージが用意されているのでそのまま使用します。
私はGitHub Container Registryを使用してイメージ管理をしているので、ビルドしてプッシュしています。
FROM jellyfin/jellyfin
# docker build . -t ghcr.io/<github-user>/jellyfin:x.y.z
# docker push ghcr.io/<github-user>/jellyfin:x.y.z
2. 永続ボリューム作成
公式のドキュメントを参考に、media
、config
、cache
の3種を設定します。
私の環境では、この3ディレクトリをあらかじめNFSの設定をしておき、media
には再生したいメディアファイルを格納しておきます。
全て自炊のファイルとしています!
2.1 media用PV & PVC
apiVersion: v1
kind: PersistentVolume
metadata:
name: jf-media-pv
annotations:
volume.beta.kubernetes.io/storage-class: "slow"
namespace: app
spec:
capacity:
storage: 2Ti
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.0.10
path: /mnt/path/jellyfin/media/
claimRef:
name: jf-media-pv-claim
namespace: app
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jf-media-pv-claim
labels:
app: jellyfin
namespace: app
annotations:
"volume.beta.kubernetes.io/storage-class": "slow"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Ti
2.2 config用PV & PVC
apiVersion: v1
kind: PersistentVolume
metadata:
name: jf-config-pv
annotations:
volume.beta.kubernetes.io/storage-class: "slow"
namespace: app
spec:
capacity:
storage: 2Ti
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.0.10
path: /mnt/path/jellyfin/config/
claimRef:
name: jf-config-pv-claim
namespace: app
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jf-config-pv-claim
labels:
app: jellyfin
namespace: app
annotations:
"volume.beta.kubernetes.io/storage-class": "slow"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Ti
2.3 cache用PV & PVC
apiVersion: v1
kind: PersistentVolume
metadata:
name: jf-cache-pv
annotations:
volume.beta.kubernetes.io/storage-class: "slow"
namespace: app
spec:
capacity:
storage: 2Ti
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.0.10
path: /mnt/path/jellyfin/cache/
claimRef:
name: jf-cache-pv-claim
namespace: app
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jf-cache-pv-claim
labels:
app: jellyfin
namespace: app
annotations:
"volume.beta.kubernetes.io/storage-class": "slow"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Ti
3. Deployment定義作成
Deployment定義を作成します。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: jellyfin
name: jellyfin
namespace: app
spec:
replicas: 1
selector:
matchLabels:
app: jellyfin
strategy:
type: Recreate
template:
metadata:
labels:
app: jellyfin
spec:
containers:
- image: ghcr.io/<github-user>/jellyfin:x.y.z
imagePullPolicy: ""
name: jellyfin
ports:
- containerPort: 8096
volumeMounts:
- mountPath: /cache
name: jellyfin-cache-persistent-storage
- mountPath: /config
name: jellyfin-config-persistent-storage
- mountPath: /media
name: jellyfin-data-persistent-storage
restartPolicy: Always
imagePullSecrets:
- name: github
volumes:
- name: jellyfin-cache-persistent-storage
persistentVolumeClaim:
claimName: jf-cache-pv-claim
- name: jellyfin-config-persistent-storage
persistentVolumeClaim:
claimName: jf-config-pv-claim
- name: jellyfin-data-persistent-storage
persistentVolumeClaim:
claimName: jf-media-pv-claim
4. Service定義作成
今回はIngressをhttps終端とし、コンテナにはhttpでアクセスします。
8096でhttpリクエストを受け付けています。
apiVersion: v1
kind: Service
metadata:
annotations:
name: jellyfin
labels:
app: jellyfin
name: jellyfin
namespace: app
spec:
ports:
- name: jellyfin
port: 8096
targetPort: 8096
type: NodePort
selector:
app: jellyfin
5. kustomization作成
作成したyamlを一つにまとめます。
例えば、以下のようなディレクトリ構造の場合。
# tree
.
├── deployment
│ ├── jellyfin-deployment.yaml
│ └── jellyfin-service.yaml
└── pv
├── media-pv.yaml
├── media-pvc.yaml
├── config-pv.yaml
├── config-pvc.yaml
├── cache-pv.yaml
└── cache-pvc.yaml
このようなyamlを作成します。
resources:
- pv/media-pv.yaml
- pv/media-pvc.yaml
- pv/config-pv.yaml
- pv/config-pvc.yaml
- pv/cache-pv.yaml
- pv/cache-pvc.yaml
- deployment/jellyfin-deployment.yaml
- deployment/jellyfin-service.yaml
最終的に以下のようなファイル構成となります。
# tree
.
├── deployment
│ ├── jellyfin-deployment.yaml
│ └── jellyfin-service.yaml
├── kustomization.yaml
└── pv
├── media-pv.yaml
├── media-pvc.yaml
├── config-pv.yaml
├── config-pvc.yaml
├── cache-pv.yaml
└── cache-pvc.yaml
6. Jellyfin(pod)をデプロイ
以下のコマンドでデプロイします。
# kubectl apply -k ./
7. Ingressをデプロイ
Ingressの設定をデプロイします。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app-proxy
namespace: app
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- secretName: servercert
rules:
- host: jellyfin.local
http:
paths:
- path:
backend:
serviceName: jellyfin
servicePort: 8096
# kubectl apply -f ingress.yaml
これは、例えば以下のような出力の場合、
192.168.0.100
にhttps://jellyfin.local
でアクセスすると、jellyfinにアクセスすることができます。
# kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx LoadBalancer 10.43.142.98 192.168.0.100 80:31614/TCP,443:30633/TCP 177d
動かしてみる
https://jellyfin.local
でアクセスします。
なお、管理者ユーザの作成等が発生するので、この時点での外部公開は避けたほうがよいでしょう。
初期設定
1. 表示する優先言語を設定
2. 管理者ユーザ作成
3. メディアライブラリの設定
ダイアログが表示されます。
今回はContent type
にMusic
を選択します。
Folders
の+
を押し、/media
を選択します。
このディレクトリがPVで設定したメディア用ディレクトリです。
正しくPVが設定されていれば、格納した音楽が表示されるはずです。
メディアのmetadataの優先言語の設定です。
ひとまず英語で進めます。
リモート接続の可否を設定します。
デフォルトはチェックONです。
4. ログインする
ログイン画面が表示されるので、作成したアカウント/パスワードでログインします。
ログインすると、メディアが表示されます。(これでメディアの再生が可能です)
初回はライブラリのスキャンが発生するため、全てのメディアが表示されるまで少し時間がかかります。
使ってみて
Jellyfinはまだまだ発展途上です。
以下に使ってみてよかったところ、今後に期待なところを挙げてみます。
いいところ
- クライアントのプラットフォームを問わない
- 専用のモバイルアプリが存在
- まだまだ発展途上のようだが、必要最低限の操作は可能
- 内部のSQLiteでデータ管理するので、別途DBを用意する必要がない
- 公式にDockerfileが用意されているので、構築が容易
- 見た目がしゃれている
今後に期待なところ
- SQLiteにしか対応していない
- ライブラリが巨大化するとパフォーマンスが懸念される
- HA構成ができない
- 試したところ、sqliteのファイルロックでぶつかった
- OIDC/SAMLに対応していない。サポートされているのは以下の二つ
- ID/パスワード認証
- LDAP認証
- モバイルアプリはストリーミング再生のみ対応
- 事前にダウンロードしておくことは不可(非公式クライアントは対応しているのでそちらを推奨)
クライアントアプリ
非公式アプリですが、どちらも優秀なアプリです。
(事前に楽曲をダウンロードすることでオフライン再生が可能)
- Finamp
- Fintunes