前回の記事では、 Podman が Docker とほぼ同様に利用できることを紹介しました。今回は Podman を使ってみてつまづいたことを書いていきます。

今回の記事で利用した環境は、 Fedora 37 および Podman 4.4.2 です。

準備: Jenkinsを実行する

前回の記事と同様に、今回も Jenkins を題材にします。まずは Jenkins をポッドとして実行するため、前回と同じ jenkins-pod.yml を用意します。

apiVersion: v1
kind: Pod
metadata:
  name: jenkins-pod
spec:
  containers:
  - name: jenkins
    image: docker.io/jenkins/jenkins:lts
    ports:
    - hostPort: 8080
      containerPort: 8080
    volumeMounts:
    - name: jenkins_home-pvc
      mountPath: /var/jenkins_home
  - name: ssh-agent
    image: docker.io/jenkins/ssh-agent
  volumes:
  - name: jenkins_home-pvc
    persistentVolumeClaim:
      claimName: jenkins_home
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins_home
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

この YAML ファイルを利用して Jenkins のポッドを起動します。

$ podman kube play jenkins-pod.yml
Volumes:
jenkins_home
Pod:
71ff664e0cd82a864e4193c4b17c27cee6a5d3d577f578af5009aded1401bdb1
Containers:
1610ce719463c87ea43a895aaa204bfef53925dda15dd281a4da8044fb90ccb1
a242e344e37f3ab41011589e6aedcd412e5d8b3789469db5874875870ad83ccd

コマンドの実行後にウェブブラウザで http://localhost:8080/ にアクセスすると、しばらくして Unlock Jenkins の画面が表示されます。

つまづき1: コンテナが勝手に停止する

Jenkins が無事起動したので、しばらくの間 Jenkins を実行させたままにしたいと考えます。しかし Linux マシンからログアウトすると、その数秒後に Jenkins にアクセスできなくなってしまいました。

もう一度 Linux マシンにログインしてコンテナを確認すると、コンテナが停止していました。

$ podman container ps -a
CONTAINER ID  IMAGE                                    COMMAND     CREATED             STATUS                      PORTS                   NAMES
4b07e210db44  localhost/podman-pause:4.4.2-1677669779              About a minute ago  Exited (0) 4 seconds ago    0.0.0.0:8080->8080/tcp  71ff664e0cd8-infra
1610ce719463  docker.io/jenkins/jenkins:lts                        About a minute ago  Exited (143) 3 seconds ago  0.0.0.0:8080->8080/tcp  jenkins-pod-jenkins
a242e344e37f  docker.io/jenkins/ssh-agent:latest                   About a minute ago  Exited (0) 4 seconds ago    0.0.0.0:8080->8080/tcp  jenkins-pod-ssh-agent

Linux では通常、ユーザーセッションが完了するとユーザーのプロセスが強制終了されます。コンテナもユーザープロセスであるため、ログアウトとともにコンテナも強制終了されてしまいます。これを防止するには、コンテナの実行中はログインしっぱなしにするか、あるい、 loginctl コマンドを使用してユーザーの「残留モード」を設定する必要があります。

$ loginctl enable-linger $UID   # 残留モードを設定する
$ podman pod start jenkins-pod  # ポッドを開始する
71ff664e0cd82a864e4193c4b17c27cee6a5d3d577f578af5009aded1401bdb1

残留モードを設定してコンテナを実行したところ、Linux からログアウトしても Jenkins は実行されたままになりました。なお残留モードを解除するには、 loginctl disable-linger $UID を実行します。

つまづき2: ボリュームにファイルを書き込めなくなる

Jenkins の設定を変更したり、ポッドにコンテナを追加したりするために、 YAML ファイルを変更してポッドを再作成することがあると思います。そこでポッドの再作成を試してみます。

$ podman kube play --replace jenkins-pod.yml
Pods stopped:
71ff664e0cd82a864e4193c4b17c27cee6a5d3d577f578af5009aded1401bdb1
Pods removed:
71ff664e0cd82a864e4193c4b17c27cee6a5d3d577f578af5009aded1401bdb1
Volumes removed:
Volumes:
jenkins_home
Pod:
468dd0a6802590f969d0b608bba1165ad12cdff0f674879e665774eb7541abfd
Containers:
6197a46e6e3c98dda52111d5201c354962558ed46cf695756d5a287b780482ea
f1e1a3cf1ddce3118b6473862abc52468e327a3533ba1c35e2df8d180e6237df

--replace オプションを利用すると、 以前の podman kube play コマンドで作成されたポッドが存在すれば削除して、 YAML ファイルで定義されたポッドを再作成します。ポッドの再作成後に Jenkins にアクセスしてみると、なんと Permission denied というエラーが発生してしまいました。

Permission denied エラーが発生

表示されたエラーの5行目に、 Jenkins のホームディレクトリである /var/jenkins_home ディレクトリにファイルを作成できないというメッセージが見つかります。先程まで問題なく実行できていたのに、 Jenkins ホームディレクトリにファイルを作成するという基本的なところでエラーが発生するのは衝撃です。

気を取り直してエラーを解決していきます。まずはホスト上でボリュームの状態を調べます。ボリュームのホスト上のパスは podman volume inspect コマンドで確認できます。このパスはあとで使うため mntPoint というシェル変数に代入しておきます。

$ mntPoint=$(podman volume inspect jenkins_home --format {{.Mountpoint}})
$ echo $mntPoint
/home/username/.local/share/containers/storage/volumes/jenkins_home/_data
$ ls -lna $mntPoint
合計 36
drwxr-xr-x 12   1000   1000 4096  3月 24 10:28 .
drwx------  3   1000   1000   19  3月 24 10:14 ..
drwxr-xr-x  3 100999 100999   24  3月 24 10:14 .cache
drwxr-xr-x  3 100999 100999   19  3月 24 10:14 .java
-rw-r--r--  1 100999 100999    0  3月 24 10:28 .lastStarted
-rw-r--r--  1 100999 100999 1663  3月 24 10:28 config.xml
-rw-r--r--  1 100999 100999  150  3月 24 10:30 copy_reference_file.log
(以下略)

1000 はホストのログインユーザーのIDです。しかし 100999 とはどのユーザーでしょうか。この答えは podman top コマンドで得られました。

$ podman top jenkins-pod-jenkins user uid huser
USER        UID         HUSER
jenkins     1000        100999
jenkins     1000        100999
jenkins     1000        ?

コンテナ内のプロセスは、コンテナの中から見ると jenkins というユーザー(ユーザー ID は 1000 )で実行されていますが、ホスト上では 100999 という ID のユーザーにマッピングされていることが分かります。このような振舞いはセキュリティ上の理由でコンテナをホストから隔離するのに有用と思いますが、ここでは深追いしません。

一方、 podman unshare コマンドを使用すると、ユーザーがマッピングされた状態でホスト上のコマンドを実行できます。これはコマンドをあたかもコンテナの中で実行したかのような結果が得られます。例えば先程と同じようにボリュームの状態を確認してみます。

$ podman unshare ls -lna $mntPoint
合計 36
drwxr-xr-x 12    0    0 4096  3月 24 10:28 .
drwx------  3    0    0   19  3月 24 10:14 ..
drwxr-xr-x  3 1000 1000   24  3月 24 10:14 .cache
drwxr-xr-x  3 1000 1000   19  3月 24 10:14 .java
-rw-r--r--  1 1000 1000    0  3月 24 10:28 .lastStarted
-rw-r--r--  1 1000 1000 1663  3月 24 10:28 config.xml
-rw-r--r--  1 1000 1000  150  3月 24 10:30 copy_reference_file.log
(以下略)

podman unshare コマンドを使用しない場合と比較して、ファイルおよびディレクトリの所有者・所有グループの表示が変わりました。ボリューム内のファイルの所有者が 1000 (jenkins) となっている一方、ボリュームのルートディレクトリの所有者が 0 (root) となっています。そしてディレクトリのパーミッションは rwxr-xr-x です。 Jenkins のプロセスは jenkins ユーザーで実行されますが、 jenkins ユーザーにはこのディレクトリへの書き込み権限がありません。これで Permission denied のエラーを発生させている原因が分かりました。

暫定的な対策として、ボリュームのルートディレクトリの所有者と所有グループを、 Jenkins のコンテナの中から見て Jenkins のプロセスと同一のものに変更して、 Jenkins を再起動します。

$ podman unshare chown 1000:1000 $mntPoint
$ podman pod restart jenkins-pod
468dd0a6802590f969d0b608bba1165ad12cdff0f674879e665774eb7541abfd

これでディレクトリにファイルを作成でき、 Jenkins が正常に起動するようになりました。

しかしながら、 YAML ファイルを変更するたびにボリュームの所有者を変更するのは気が滅入ります。そのためというわけではありませんが、 YAML ファイルでボリュームのルートディレクトリの所有者を指定することができます。所有者を指定するには、 PersistentVolumeClaimvolume.podman.io/uid および volume.podman.io/gid アノテーションを追加します。

--- a/jenkins-pod.yml   2023-03-24 11:33:33.078894478 +0900
+++ b/jenkins-pod.yml   2023-03-24 11:33:40.398939054 +0900
@@ -23,6 +23,9 @@
 kind: PersistentVolumeClaim
 metadata:
   name: jenkins_home
+  annotations:
+    volume.podman.io/uid: 1000
+    volume.podman.io/gid: 1000
 spec:
   accessModes:
   - ReadWriteOnce

変更した YAML ファイルを利用してポッドを再作成します。

$ podman kube play --replace jenkins-pod.yml
Pods stopped:
468dd0a6802590f969d0b608bba1165ad12cdff0f674879e665774eb7541abfd
Pods removed:
468dd0a6802590f969d0b608bba1165ad12cdff0f674879e665774eb7541abfd
Volumes removed:
Volumes:
jenkins_home
Pod:
e39f112fa66e3c0d84f071dfb4eb75c3425feff5fb8766840f09d040473819cc
Containers:
aac35e8eb9b0b352eb6663e1ccc2444e9f389ed5ceeec5eb46a5b032325e4257
fb295a5e5dec791747e799f6d0194a04d718d90f818e66533563ae6925aeab46

ウェブブラウザで http://localhost:8080/ にアクセスして、 Unlock Jenkins の画面が表示されることが確認できました。最後にボリュームのディレクトリも確認します。

$ podman unshare ls -lna $mntPoint
合計 36
drwxr-xr-x 12 1000 1000 4096  3月 24 11:05 .
drwx------  3 1000 1000   19  3月 24 10:14 ..
drwxr-xr-x  3 1000 1000   24  3月 24 10:14 .cache
drwxr-xr-x  3 1000 1000   19  3月 24 10:14 .java
-rw-r--r--  1 1000 1000    0  3月 24 11:05 .lastStarted
-rw-r--r--  1 1000 1000 1663  3月 24 11:05 config.xml
-rw-r--r--  1 1000 1000  250  3月 24 11:05 copy_reference_file.log
(以下略)

コンテナの中から見てボリュームのルートディレクトリの所有者が jenkins となりました。これにより、ポッドを何度再作成しても Jenkins が正常に起動するようになりました。

まとめ

Podman を使用してポッドを実行する際にはまったことを2点ご紹介しました。ホストからログアウトすることでコンテナが停止してしまうことを防止するには、 loginctl enable-linger $UID コマンドを実行します。ボリュームのルートディレクトリの所有者を指定するには、 Kubernetes YAML ファイルの PersistentVolumeClaim にアノテーション volume.podman.io/uid および volume.podman.io/gid を追加します。 Podman と Docker で異なることも多いようです。

By tsakai

Jenkins関連のサービスやCloudBees製品を主に担当しています。 Certified CloudBees Jenkins Engineer (CCJE) および CloudBees CI DevOps Associate です。