LAN上のMinIOからRcloneで曲データを引っ張りながらNavidromeを題材に、Docker Compose風運用をPodmanで動かす

Posted by 雅楽斎 on Wednesday, August 30, 2023

TOC

概要図

今回は登場人物が多いので図にしてみました。

実現すること

  • 曲データはNASに格納し、MinIOをNASで動作させてS3として見せる
  • Orange Pi 5でRcloneを動かして、曲データは特定のマウントポイントでS3互換ストレージをマウントする。Rcloneの管理はSystemdでやる
  • NavidromeでRcloneのマウントポイントの曲をLAN上のSubsonicクライアントに配信する

この構成になった理由

  • 曲データは基本的に時間が経てば経つほど増えるので、Orange Pi 5に乗せるといつかストレージがあふれる
  • かと言ってRaspberry Pi 4の時の様に外付けHDDに曲を入れるのはNASもあるので無駄感がある(USBポートを潰すし、外付けHDDの電源の面倒を見ないといけない)
  • NASでMinIOを動かすのは以前テスト的にやったので、曲データ置き場としてはこれを使って外付けHDDを使った場合のデメリットを解消する
  • 基本的にDockerではなくPodmanを使いたいので、NavidromeをPodmanで管理する。OSの起動時にコンテナを動かしたいのでSystemdでPodmanを制御する
  • ストリーミング再生をするにあたって曲データはFLACを再生する前提だが、Orange Pi 5とNASは有線LAN(1000BASE-T)で接続しているので帯域的には余裕があり、NavidromeとLAN上のSubsonicクライアントの間を無線LANで接続して問題なく再生することを目標とする(実際は大抵のSubsonicクライアントは数曲分の曲を予めダウンロードするので問題になりにくい)

今まで全く使っていなかったのはRcloneのみで、NAS上のMinIOやNavidromeは以前弊blogで取り上げています。

【非推奨】NETGEARのReadyNAS RN104にMinIOを入れてオブジェクトストレージとしても使う話

Subsonic互換サーバーとクライアントで始める音楽ライブラリ

また、Podmanシリーズとしては前回記事の続きです。

Nginxを題材に、Docker Compose風運用をPodmanでする

MinIOとは

AWSのS3互換ストレージを構築するGo製のソリューションで、ARM用のバイナリも公開されています。Debian/Ubuntuで考えた場合、AMD64とARM64はdebパッケージで公開されているので、インストール・管理はそれなりに簡単です(ReadyNAS 104は32bitARMなのでバイナリを直接配置する形になります)

Rcloneとは

クラウドストレージをローカルに仮想的にマウントするGo製のソリューションで、S3やS3互換ストレージもローカルに仮想的にマウントすることができます。

Navidromeとは

Subsonicというクライアント-サーバー型のネットワークオーディオの互換サーバーです。特徴はGo製、メンテナンスされている、Dockerhubでコンテナが公開されている、ARM用コンテナもある。ということで、使っていてかなりお気に入りのものです。

今回サンプルとしてNavidromeを選んだのは、実際にRPi4で使っているコンテナであること、docker-composeで運用していること、Podmanへの移行ができるかどうかの検証にふさわしい。というのが理由です。

MinIOの設定

基本的には以前の内容と変わっていないので要所のみ記載します。

MinIOの更新

現在のMinIOのバージョンを確認。

# /opt/minio/minio --version
minio version RELEASE.2021-10-13T00-23-17Z

更新するので落とす。

# systemctl disable minio
Removed /etc/systemd/system/multi-user.target.wants/minio.service.
# systemctl stop minio

今回使うバケットを以下の内容で作成。

項目設定値
NAS上のユーザーadmin
MinIOのバケットを作るNAS上のHDDTOSHIBA
MinIOのバケットを作るNAS上のボリュームMINIO
MinIOのバケットを作成するディレクトリのフルパス/TOSHIBA/MINIO/navidrome
MinIOの実行ファイルのパス(前述の通り)/opt/minio/minio
MinIOの作業ディレクトリ/opt/minio/
MinIOのユーザー名navidromeminio
MinIOのパスワード(説明用)navidrome
MinIOのAPI待受ポート番号9199

minioの更新

# cd /opt/minio
# rm minio
# rm minio.shasum
# wget https://dl.min.io/server/minio/release/linux-arm/minio
# wget https://dl.min.io/server/minio/release/linux-arm/minio.shasum
# sha1sum minio
4b08a1297227f97871f9bed960c2ae2beea85d7f  minio
# cat minio.shasum 
4b08a1297227f97871f9bed960c2ae2beea85d7f minio.RELEASE.2023-08-23T10-07-06Z
# chmod +x minio
# chown -R admin:admin /opt/minio/
# /opt/minio/minio --version
minio version RELEASE.2023-08-23T10-07-06Z (commit-id=af564b8ba07a7805a578b5f6f2b3827490f74ccd)
Runtime: go1.19.12 linux/arm
License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
Copyright: 2015-2023 MinIO, Inc.

NASの管理画面から共有を作成

  • TOSHIBAの下にMINIOを作成、smbとftpのみ有効
  • FTPクライアントからログインしてMINIOの下にnavidromeを作成(コマンドで作る場合はchownでownerとgroupをadminに変更)

Systemdまわりを設定

変数定義ファイルを/etc/default/minioとして作成

# Volume to be used for MinIO server.
MINIO_VOLUMES="/TOSHIBA/MINIO/navidrome"
# Use if you want to run MinIO on a custom port.
MINIO_OPTS="--address :9199"
# Root user for the server.
MINIO_ROOT_USER=navidromeminio
# Root secret for the server.
MINIO_ROOT_PASSWORD=navidrome

systemdのユニットファイルを作成(/etc/systemd/system/minio.service)

前回から変わっているところは2箇所

  • 実行ファイルのパス(AssertFileIsExecutable ExecStart)は/opt/minio/minio
  • WorkingDirectory=/opt/minio/

    [Unit]
    Description=MinIO
    Documentation=https://docs.min.io
    Wants=network-online.target
    After=network-online.target
    AssertFileIsExecutable=/opt/minio/minio
    AssertFileNotEmpty=/etc/default/minio
    
    [Service]
    Type=notify
    
    WorkingDirectory=/opt/minio/
    
    User=admin
    Group=admin
    ProtectProc=invisible
    
    EnvironmentFile=/etc/default/minio
    ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi"
    ExecStart=/opt/minio/minio server $MINIO_OPTS $MINIO_VOLUMES
    
    # Let systemd restart this service always
    Restart=always
    
    # Specifies the maximum file descriptor number that can be opened by this process
    LimitNOFILE=1048576
    
    # Specifies the maximum number of threads this process can create
    TasksMax=infinity
    
    # Disable timeout logic and wait until process is stopped
    TimeoutStopSec=infinity
    SendSIGKILL=no
    
    [Install]
    WantedBy=multi-user.target
    
    # Built for ${project.name}-${project.version} (${project.name})
    

Systemdで反映

# systemctl daemon-reload 
# systemctl enable minio
Created symlink /etc/systemd/system/multi-user.target.wants/minio.service → /etc/systemd/system/minio.service.
# systemctl start minio
# systemctl status minio
● minio.service - MinIO
   Loaded: loaded (/etc/systemd/system/minio.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2023-08-28 22:45:07 JST; 9s ago
     Docs: https://docs.min.io
  Process: 6983 ExecStartPre=/bin/bash -c if [ -z "${MINIO_VOLUMES}" ]; then echo "Variable MINIO_VOLUMES not set in /etc/default/minio"; exit 1; fi (code=exited, status=0/SUCCESS)
 Main PID: 6985 (minio)
   CGroup: /system.slice/minio.service
           └─6985 /opt/minio/minio server --address :9199 /TOSHIBA/MINIO/navidrome

Aug 28 22:45:07 nas-E6-03-60 systemd[1]: Started MinIO.
Aug 28 22:45:08 nas-E6-03-60 minio[6985]: MinIO Object Storage Server
Aug 28 22:45:08 nas-E6-03-60 minio[6985]: Copyright: 2015-2023 MinIO, Inc.
Aug 28 22:45:08 nas-E6-03-60 minio[6985]: License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
Aug 28 22:45:08 nas-E6-03-60 minio[6985]: Version: RELEASE.2023-08-23T10-07-06Z (go1.19.12 linux/arm)
Aug 28 22:45:08 nas-E6-03-60 minio[6985]: Status:         1 Online, 0 Offline.
Aug 28 22:45:08 nas-E6-03-60 minio[6985]: S3-API: http://192.168.1.126:9199  http://127.0.0.1:9199

MinIOのバケット作成

Web画面からログインしてmusicという名前のバケットを作成。

Rcloneの設定

Rcloneのインストール

今回はUbuntuに収録されているものを使いました。

# apt-get install rclone

RcloneのMinIO用設定

Rcloneの設定は一問一答形式で設定ファイルを吐き出すrclone configコマンドがありますが、想定問答の量が無駄に多く今回使うMinIOと合わないので、1回流した後に必要に応じて手で書き換えるのがいいと思います。

試しに流してみると

$ rclone config
2023/08/28 18:10:17 NOTICE: Config file "/home/hogehoge/.config/rclone/rclone.conf" not found - using defaults
No remotes found - make a new one
n) New remote
s) Set configuration password
q) Quit config
n/s/q> n
name> miniotest
Type of storage to configure.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
 1 / 1Fichier
   \ "fichier"
 2 / Alias for an existing remote
   \ "alias"
 3 / Amazon Drive
   \ "amazon cloud drive"
 4 / Amazon S3 Compliant Storage Provider (AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, Tencent COS, etc)
   \ "s3"
 5 / Backblaze B2
   \ "b2"
 6 / Box
   \ "box"
 7 / Cache a remote
   \ "cache"
 8 / Citrix Sharefile
   \ "sharefile"
 9 / Dropbox
   \ "dropbox"
10 / Encrypt/Decrypt a remote
   \ "crypt"
11 / FTP Connection
   \ "ftp"
12 / Google Cloud Storage (this is not Google Drive)
   \ "google cloud storage"
13 / Google Drive
   \ "drive"
14 / Google Photos
   \ "google photos"
15 / Hubic
   \ "hubic"
16 / In memory object storage system.
   \ "memory"
17 / Jottacloud
   \ "jottacloud"
18 / Koofr
   \ "koofr"
19 / Local Disk
   \ "local"
20 / Mail.ru Cloud
   \ "mailru"
21 / Microsoft Azure Blob Storage
   \ "azureblob"
22 / Microsoft OneDrive
   \ "onedrive"
23 / OpenDrive
   \ "opendrive"
24 / OpenStack Swift (Rackspace Cloud Files, Memset Memstore, OVH)
   \ "swift"
25 / Pcloud
   \ "pcloud"
26 / Put.io
   \ "putio"
27 / SSH/SFTP Connection
   \ "sftp"
28 / Sugarsync
   \ "sugarsync"
29 / Transparently chunk/split large files
   \ "chunker"
30 / Union merges the contents of several upstream fs
   \ "union"
31 / Webdav
   \ "webdav"
32 / Yandex Disk
   \ "yandex"
33 / http Connection
   \ "http"
34 / premiumize.me
   \ "premiumizeme"
35 / seafile
   \ "seafile"
Storage> 4
** See help for s3 backend at: https://rclone.org/s3/ **

Choose your S3 provider.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
 1 / Amazon Web Services (AWS) S3
   \ "AWS"
 2 / Alibaba Cloud Object Storage System (OSS) formerly Aliyun
   \ "Alibaba"
 3 / Ceph Object Storage
   \ "Ceph"
 4 / Digital Ocean Spaces
   \ "DigitalOcean"
 5 / Dreamhost DreamObjects
   \ "Dreamhost"
 6 / IBM COS S3
   \ "IBMCOS"
 7 / Minio Object Storage
   \ "Minio"
 8 / Netease Object Storage (NOS)
   \ "Netease"
 9 / Scaleway Object Storage
   \ "Scaleway"
10 / StackPath Object Storage
   \ "StackPath"
11 / Tencent Cloud Object Storage (COS)
   \ "TencentCOS"
12 / Wasabi Object Storage
   \ "Wasabi"
13 / Any other S3 compatible provider
   \ "Other"
provider> 7
Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
Only applies if access_key_id and secret_access_key is blank.
Enter a boolean value (true or false). Press Enter for the default ("false").
Choose a number from below, or type in your own value
 1 / Enter AWS credentials in the next step
   \ "false"
 2 / Get AWS credentials from the environment (env vars or IAM)
   \ "true"
env_auth> false
AWS Access Key ID.
Leave blank for anonymous access or runtime credentials.
Enter a string value. Press Enter for the default ("").
access_key_id> 
AWS Secret Access Key (password)
Leave blank for anonymous access or runtime credentials.
Enter a string value. Press Enter for the default ("").
secret_access_key> minpass1
Region to connect to.
Leave blank if you are using an S3 clone and you don't have a region.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
 1 / Use this if unsure. Will use v4 signatures and an empty region.
   \ ""
 2 / Use this only if v4 signatures don't work, eg pre Jewel/v10 CEPH.
   \ "other-v2-signature"
region> 1
Endpoint for S3 API.
Required when using an S3 clone.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
endpoint> 
Location constraint - must be set to match the Region.
Leave blank if not sure. Used when creating buckets only.
Enter a string value. Press Enter for the default ("").
location_constraint> 
Canned ACL used when creating buckets and storing or copying objects.

This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.

For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl

Note that this ACL is applied when server side copying objects as S3
doesn't copy the ACL from the source but rather writes a fresh one.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
 1 / Owner gets FULL_CONTROL. No one else has access rights (default).
   \ "private"
 2 / Owner gets FULL_CONTROL. The AllUsers group gets READ access.
   \ "public-read"
   / Owner gets FULL_CONTROL. The AllUsers group gets READ and WRITE access.
 3 | Granting this on a bucket is generally not recommended.
   \ "public-read-write"
 4 / Owner gets FULL_CONTROL. The AuthenticatedUsers group gets READ access.
   \ "authenticated-read"
   / Object owner gets FULL_CONTROL. Bucket owner gets READ access.
 5 | If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
   \ "bucket-owner-read"
   / Both the object owner and the bucket owner get FULL_CONTROL over the object.
 6 | If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
   \ "bucket-owner-full-control"
acl> 1
The server-side encryption algorithm used when storing this object in S3.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
 1 / None
   \ ""
 2 / AES256
   \ "AES256"
 3 / aws:kms
   \ "aws:kms"
server_side_encryption> 
If using KMS ID you must provide the ARN of Key.
Enter a string value. Press Enter for the default ("").
Choose a number from below, or type in your own value
 1 / None
   \ ""
 2 / arn:aws:kms:*
   \ "arn:aws:kms:us-east-1:*"
sse_kms_key_id> 
Edit advanced config? (y/n)
y) Yes
n) No (default)
y/n> n
Remote config
--------------------
[miniotest]
provider = Minio
env_auth = false
secret_access_key = minpass1
acl = private
--------------------
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> 
Current remotes:

Name                 Type
====                 ====
miniotest            s3

e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q> q

それっぽい選択肢を選んだつもりですが、実行結果(~/.config/rclone/rclone.conf)はこんな感じです。

[miniotest]
type = s3
provider = Minio
env_auth = false
secret_access_key = minpass1
acl = private

で、手でこんな感じに書き換えます。

[navidrome]
type = s3
provider = Minio
env_auth = false
secret_access_key = navidrome
acl = private
access_key_id = navidromeminio
endpoint = http://192.168.1.126:9199

これでとりあえず実行します。

$ rclone lsd navidrome:
2023/08/28 16:00:37 NOTICE: Time may be set wrong - time from "192.168.1.126:9199" is 2h4m47.310008039s different from this computer
2023/08/28 16:00:37 ERROR : : error listing: RequestTimeTooSkewed: The difference between the request time and the server's time is too large.
	status code: 403, request id: 17746637FC3F5D1F, host id: 37b62f02de360c6949659e1d2fb72852efb5d74e5dcbc7d4e665920a1c72a29e
2023/08/28 16:00:37 Failed to lsd with 2 errors: last error was: RequestTimeTooSkewed: The difference between the request time and the server's time is too large.
	status code: 403, request id: 17746637FC3F5D1F, host id: 37b62f02de360c6949659e1d2fb72852efb5d74e5dcbc7d4e665920a1c72a29e

時刻がズレすぎているということで、NASの時刻を設定します。

# date
Sun Aug 28 13:57:02 JST 2023
# date -s "Sun Aug 28 04:01:41 PM JST 2023"
Sun Aug 28 16:01:41 JST 2023

もう一回実行します。

$ rclone lsd navidrome:
          -1 2023-08-28 08:44:47        -1 music

miniotestの下にmusicというバケットが見つかります。適当にファイルを1つ置いてあるので見えることを確認。

$ rclone ls navidrome:music
 43769712 適当ディレクトリ/01Singles/tekitou.flac

ということで、musicバケットの中身が見えるようになりました。

Rcloneでバケットをマウント

さっきのRCloneで操作できるようになったMinioをマウントします。まずはマウントポイントの作成。

# mkdir /mnt/navidromeminio

で、実際にマウントするのはFUSE経由で使うユーザーはnavidromeコンテナを動かすユーザーですが、とりあえず一般ユーザーで触れるパーミッションにします。

# chown hogehoge:hogehoge /mnt/navidromeminio/

マウントします。引数はエンドポイント名:バケット名、マウントポイント、バックグラウンドで動かすので--daemonも指定します。

$ rclone mount navidrome:music /mnt/navidromeminio --daemon
$ mount
(snip)
navidrome:music on /mnt/navidromeminio type fuse.rclone (rw,nosuid,nodev,relatime,user_id=1001,group_id=1002)

マウントできているはずなのでファイルを覗きます。

$ cd /mnt/navidromeminio/
$ tree .
.
├── 適当ディレクトリ
│   └── 01Singles
│       └── tekitou.flac
└── 適当ディレクトリ2

問題ないのでアンマウントします。

$ cd
$ umount /mnt/navidromeminio
$ ls /mnt/navidromeminio/
(表示されない)

mountを実行して、マウントポイントがなくなっていることを確認します。 ということで、問題なくRCloneでMinioをマウントできることを確認しました。

Podmanの実行ユーザーでもマウントできるようにする

今回NavidromeはPodmanで動かすので、RcloneでマウントするディレクトリはPodmanの実行ユーザーで読み取れるようにする必要がありますが、Rcloneが使用しているFUSEの制約で、デフォルトではマウントした一般ユーザーでしかマウントしたパーティションにアクセスできません。

マウントしたユーザーとは別の一般ユーザーにアクセスできるようにするために、/etc/fuse.confuser_allow_otherを追加します。(コメント行になっているのでアンコメント)

# The file /etc/fuse.conf allows for the following parameters:
#
# user_allow_other - Using the allow_other mount option works fine as root, in
# order to have it work as user you need user_allow_other in /etc/fuse.conf as
# well. (This option allows users to use the allow_other option.) You need
# allow_other if you want users other than the owner to access a mounted fuse.
# This option must appear on a line by itself. There is no value, just the
# presence of the option.

user_allow_other


# mount_max = n - this option sets the maximum number of mounts.
# Currently (2014) it must be typed exactly as shown
# (with a single space before and after the equals sign).

#mount_max = 1000

一般ユーザーで--alow-otherでマウントできることを確かめます。また、今回の用途ではNavidrome側から曲データファイルを更新することがないので、--read-onlyも付与します。

$ rclone mount navidrome:music /mnt/navidromeminio --daemon --allow-other --read-only
$ ls /mnt/navidromeminio/
適当ディレクトリ 適当ディレクトリ2

問題なく見えることを確認したら、アンマウントします。

$ umount /mnt/navidromeminio

RcloneのSystemd設定

基本的には電源ONでRcloneでNASをマウントしたいので、Systemdで電源投入時にマウントします。どのタイミングでマウントしに行くかについてはネットワーク系の初期処理がすべて終わった後とするのが無難なので、依存ユニットも考慮します。

~/.config/systemd/user/rclonenavidromeminio@.serviceを以下の内容で作成します。


[Unit]
Description=rclone: Remote FUSE filesystem for cloud storage config %i
Documentation=man:rclone(1)
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
ExecStart= \
  /usr/bin/rclone mount \
    --config=%h/.config/rclone/rclone.conf \
    --read-only \
    --allow-other \
    %i: /mnt/navidromeminio
ExecStop=/bin/fusermount -uz /mnt/navidromeminio

[Install]
WantedBy=default.target

systemdに登録します。

$ systemctl --user enable rclonenavidromeminio@navidrome
Created symlink /home/hogehoge/.config/systemd/user/default.target.wants/rclonenavidromeminio@navidrome.service → /home/hogehoge/.config/systemd/user/rclonenavidromeminio@.service.
$ systemctl --user start rclonenavidromeminio@navidrome
$ systemctl --user status rclonenavidromeminio@navidrome.service 
● rclonenavidromeminio@navidrome.service - rclone: Remote FUSE filesystem for cloud storage config navidrome
     Loaded: loaded (/home/hogehoge/.config/systemd/user/rclonenavidromeminio@.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2023-08-29 17:16:43 JST; 45s ago
       Docs: man:rclone(1)
   Main PID: 2420 (rclone)
     CGroup: /user.slice/user-1001.slice/user@1001.service/app.slice/app-rclonenavidromeminio.slice/rclonenavidromeminio@navidrome.service
             └─2420 /usr/bin/rclone mount --config=/home/hogehoge/.config/rclone/rclone.conf --read-only --allow-other navidrome: /mnt/na>

Aug 29 17:16:43 ubuntu systemd[993]: Starting rclone: Remote FUSE filesystem for cloud storage config navidrome...
Aug 29 17:16:43 ubuntu systemd[993]: Started rclone: Remote FUSE filesystem for cloud storage config navidrome.

OS再起動後も自動的にマウントすることを確認して終了。

Navidromeの設定

Navidromeの設定については曲データをRcloneから取ってくる以外は以前Docker composeでやったまんまなのでさらっと書きます。

podman-composeを実行するディレクトリを作成します。

$ mkdir -p ~/podmancompose/navidrome

設定値はこんな感じにします。

項目設定値
dockerイメージdocker.io/deluan/navidrome:latest
使用ポート番号4533
ボリューム(管理データ)/var/containers/navidrome_data:/data
ボリューム(曲データ)/mnt/navidromeminio:/music:ro
ユーザー(podman実行ユーザーのUID、GID)1001:1002

docker-compose.ymlをこんな内容で作成します。

version: "3"
services:
  navidrome:
    image: docker.io/deluan/navidrome:latest
    user: 1001:1002 # should be owner of volumes
    ports:
      - "4533:4533"
    restart: unless-stopped
    environment:
      # Optional: put your config options customization here. Examples:
      ND_SCANSCHEDULE: 1h
      ND_LOGLEVEL: info  
      ND_SESSIONTIMEOUT: 24h
      ND_BASEURL: ""
    volumes:
      - "/var/containers/navidrome_data:/data"
      - "/mnt/navidromeminio:/music:ro"

podman-composeで一式の動作確認

podman実行ユーザーを事前に確認できるのかと思ったができず(kernelのバージョン依存?)

rootless dockerと同じようにpodmanを一般ユーザーで実行するとコンテナの実行ユーザーはpodmanコマンドを実行したユーザーとは別の一般ユーザーになりますが、コンテナ内でファイルの書き込みを行うなら必然的にそのファイルはコンテナ内のユーザーから読み書きが可能である必要があります。

podmanではここも多少は融通が効いており、podman topを使ってコンテナ内ユーザーを確認しやすくなっていると思い込んでいましたがそんなことはありませんでした。

本来想定していたpodmanの動作を確認

大前提として、podmanのサブコマンドであるpodman top

  • 引数として-l/--latestを実行すると実行中のコンテナのうち最後に起動したコンテナにたいしてpodman topコマンドを実行する
  • 引数としてhuserを指定すると「ホスト上のコンテナプロセスの対応する実効ユーザー(The corresponding effective user of a container process on the host.)」を表示する

という動作をします。

podman-top — Podman documentation

この説明から、Podmanコンテナの中で動くユーザーのホストから見たユーザーIDをpodman topでわかるんだろうなと理解していたので、rootless dockerと違って随分簡単にコンテナ操作ユーザーIDわかるんだなと思っていました。

ではここでUbuntu 22.04(Joshua Riek氏ビルド)でpodman topコマンドを実行した結果を見てみましょう。

$ podman run -d docker.io/library/alpine sleep 40 #40秒以内にpodman topコマンドを実行する
$ podman top -l user huser
USER        HUSER
root        1001

いや1001て。それはpodmanコマンドを実行している一般ユーザーのユーザーIDですよ。ということでrootless dockerと同様泥臭くコンテナ内ユーザーのIDを当てることに。

コンテナ内実行ユーザーのユーザーID特定作業

docker-compose.ymlに書いたvolumesのうち、/dataに割り当てる方はディレクトリのパーミッションを777にして、コンテナ実行後に管理データを書き込んだ時のUIDを確認することにします。

$ mkdir -p /var/containers/navidrome_data
# chmod 777 /var/containers/navidrome_data

podman-composeでコンテナを起動し、Web画面まで上がったことを確認したらそのまま落とします。

$ podman-compose up -d
$ podman-compose logs
(snip)
time="2023-08-29T08:46:32Z" level=info msg="Finished processing Music Folder" added=0 deleted=0 elapsed=14.1ms folder=/music playlistsImported=0 updated=0
exit code: 0
$ podman-compose down

肝心の結果は。

$ ls -l /var/containers/navidrome_data/
total 2016
drwxr-xr-x 4 166536 166537    4096 Aug 29 18:46 cache
-rw-r--r-- 1 166536 166537    4096 Aug 29 18:46 navidrome.db
-rw-r--r-- 1 166536 166537   32768 Aug 29 18:46 navidrome.db-shm
-rw-r--r-- 1 166536 166537 2022952 Aug 29 18:46 navidrome.db-wal

ということで、UIDは166536、GIDは166537が正解でした。なので、Navidromeの管理データのディレクトリのオーナーを変更します。

# chown 166536:166537 /var/containers/navidrome_data/ -R
# chmod 755 /var/containers/navidrome_data/

podman-composeはOSの再起動を跨いで起動状態を保持できないので、前回に引き続きsystemdからpodmanコンテナを実行するようにします。

また、今回はRcloneによるMinIOのマウントができていないと曲データが空になって起動する意味がないので、起動前にRcloneを待ち合わせるように指定します。

docker-compose.ymlのあるパスに移動して、podman-composeでコンテナを起動、

$ podman generate systemd --new --files --name navidrome_navidrome_1
/home/hogehoge/podmancompose/navidrome/container-navidrome_navidrome_1.service

※ 実はAfterに指定するユニットはPodman 4.0.0で引数--afterが追加されているので、Podmanのバージョンがこれ以降であればpodman generate systemdコマンドの引数で指定できるはずです。

# container-navidrome_navidrome_1.service
# autogenerated by Podman 3.4.4
# Tue Aug 29 19:07:06 JST 2023

[Unit]
Description=Podman container-navidrome_navidrome_1.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
TimeoutStopSec=70
ExecStartPre=/bin/rm -f %t/%n.ctr-id
ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon --replace --name=navidrome_navidrome_1 -d --label io.podman.compose.config-hash=cfc729e3a0834f5d5b477bc6218c79389cf2ef9010df32e85f549ce0d5e6881d --label io.podman.compose.project=navidrome --label io.podman.compose.version=1.0.6 --label PODMAN_SYSTEMD_UNIT=podman-compose@navidrome.service --label com.docker.compose.project=navidrome --label com.docker.compose.project.working_dir=/home/hogehoge/podmancompose/navidrome --label com.docker.compose.project.config_files=docker-compose.yml --label com.docker.compose.container-number=1 --label com.docker.compose.service=navidrome -e ND_SCANSCHEDULE=1h -e ND_LOGLEVEL=info -e ND_SESSIONTIMEOUT=24h -e ND_BASEURL= -v /var/containers/navidrome_data:/data -v /mnt/navidromeminio:/music:ro --net navidrome_default --network-alias navidrome -p 4533:4533 -u 1001:1002 docker.io/deluan/navidrome:latest
ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all

[Install]
WantedBy=default.target

Rcloneの起動を待ち合わせるため、Afterにrclonenavidromeminio@.serviceを追加

$ diff -u container-navidrome_navidrome_1.service{.org,}
--- container-navidrome_navidrome_1.service.org	2023-08-29 19:07:06.103784090 +0900
+++ container-navidrome_navidrome_1.service	2023-08-29 19:09:59.357941863 +0900
@@ -6,7 +6,7 @@
 Description=Podman container-navidrome_navidrome_1.service
 Documentation=man:podman-generate-systemd(1)
 Wants=network-online.target
-After=network-online.target
+After=network-online.target rclonenavidromeminio@.service
 RequiresMountsFor=%t/containers
 
 [Service]

ユニットファイルをSystemdに登録します。

$ mv container-navidrome_navidrome_1.service ~/.config/systemd/user/
$ systemctl --user enable container-navidrome_navidrome_1.service 
Created symlink /home/hogehoge/.config/systemd/user/default.target.wants/container-navidrome_navidrome_1.service → /home/hogehoge/.config/systemd/user/container-navidrome_navidrome_1.service.
$ systemctl --user start container-navidrome_navidrome_1.service

問題がなければOSを再起動してRcloneのMinIOマウントが終わってNavidromeコンテナが起動してSubsonicクライアントから接続できて全て完了です。今回はやることが多かったですね。

スポンサーリンク


comments powered by Disqus