28
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

株式会社ピーアールオー(あったらいいな!を作ります) Advent Calendar 2020の19日目になります。

前振り

昨年末、会社の忘年会のビンゴでiPadをもらったり、今年初頭に投げ売りされていたノートPCを購入したりと、我が家の端末が増えました。
それら端末で作業していると、デスクトップPCに保存している音楽が聴きたくなります。
単純に再生するだけならば、sambaなどを使用してファイルサーバを構築すればよいですが、
ファイルの検索やプレイリストの作成並びに各端末での共有などを考えると、使い勝手があまりよくなさそうです。
かといって、今更iTunesなどの使い方を覚えるのもピンと来ませんでした。
そこで、自分用のメディアサーバを構築し、音楽ファイルやプレイリストを一元管理することにしました。
いろいろ調べているうちによさそうだな、と思ったのがJellyfinです。

Jellyfinとは

Jellyfinとは、音楽や動画といった各種メディアの管理と、ストリーミング再生を行えるフリー(GPLv2)のメディアサーバです。

著作権

まず気になるのは著作権についてです。

日本オーディオ協会

  • ご自身で購入したディスクからリッピングしてください。
  • リッピングしたデータは個人使用しかできません。無断で、売る、貸す、ホームページやSNSに公開する、などを行ってはいけません。

JASRAC

個人的または家庭内その他これに準ずる限られた範囲内において使用する目的で、使用する本人がコピーする場合、著作権者の許諾を得ずにコピーすることができます。
ただし、違法にインターネット上にアップロードされたものと知りながら著作物をダウンロードする場合など、私的使用のための複製であっても違法となる場合があります。

素人判断ですが、以下の行為は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を使用してイメージ管理をしているので、ビルドしてプッシュしています。

Dockerfile
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. 永続ボリューム作成

公式のドキュメントを参考に、mediaconfigcacheの3種を設定します。
私の環境では、この3ディレクトリをあらかじめNFSの設定をしておき、mediaには再生したいメディアファイルを格納しておきます。
全て自炊のファイルとしています!

2.1 media用PV & PVC

media-pv.yml
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
media-pvc.yml
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

config-pv.yml
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
config-pvc.yml
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

cache-pv.yml
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
cache-pvc.yml
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定義を作成します。

jellyfin-deployment.yml
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を作成します。

kustomization.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の設定をデプロイします。

ingress.yaml
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.100https://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

あとは、podが上がれば起動完了です!
image.png

動かしてみる

https://jellyfin.localでアクセスします。
なお、管理者ユーザの作成等が発生するので、この時点での外部公開は避けたほうがよいでしょう。

初期設定

1. 表示する優先言語を設定

今のところ日本語はありません。
今回は英語で進めます。
image.png

2. 管理者ユーザ作成

ユーザ名とパスワードを入力し、管理者ユーザを作成します。
image.png

3. メディアライブラリの設定

まずはAdd Media Libraryを選択します。
image.png

ダイアログが表示されます。
今回はContent typeMusicを選択します。
image.png

Folders+を押し、/mediaを選択します。
このディレクトリがPVで設定したメディア用ディレクトリです。
image.png

正しくPVが設定されていれば、格納した音楽が表示されるはずです。
image.png

OKを押すと/mediaがライブラリとして追加されます。
image.png

メディアのmetadataの優先言語の設定です。
ひとまず英語で進めます。
image.png

リモート接続の可否を設定します。
デフォルトはチェックONです。
image.png

以上で初期設定完了です!
image.png

4. ログインする

ログイン画面が表示されるので、作成したアカウント/パスワードでログインします。
image.png

ログインすると、メディアが表示されます。(これでメディアの再生が可能です)
初回はライブラリのスキャンが発生するため、全てのメディアが表示されるまで少し時間がかかります。
image.png

使ってみて

Jellyfinはまだまだ発展途上です。
以下に使ってみてよかったところ、今後に期待なところを挙げてみます。

いいところ

  • クライアントのプラットフォームを問わない
  • 専用のモバイルアプリが存在
    • まだまだ発展途上のようだが、必要最低限の操作は可能
  • 内部のSQLiteでデータ管理するので、別途DBを用意する必要がない
  • 公式にDockerfileが用意されているので、構築が容易
  • 見た目がしゃれている

今後に期待なところ

  • SQLiteにしか対応していない
    • ライブラリが巨大化するとパフォーマンスが懸念される
  • HA構成ができない
    • 試したところ、sqliteのファイルロックでぶつかった
  • OIDC/SAMLに対応していない。サポートされているのは以下の二つ
    • ID/パスワード認証
    • LDAP認証
  • モバイルアプリはストリーミング再生のみ対応
    • 事前にダウンロードしておくことは不可(非公式クライアントは対応しているのでそちらを推奨)

クライアントアプリ

非公式アプリですが、どちらも優秀なアプリです。
(事前に楽曲をダウンロードすることでオフライン再生が可能)

最後に

CDは買いましょう。(置き場所が。。。)
IMG_20201221_134225011.jpg

28
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?