podman unshareコマンドによるボリュームマウントのパーミッション問題の対応

概要・前提

Podmanを使ったコンテナ環境でホスト側のファイルやディレクトリをコンテナにボリュームマウントしたときに、コンテナ側で所有者やパーミッションの問題にあたることがありました。

その際に、 podman unshare コマンドを使うことで問題が解消出来たのでその記録です。

事象

コンテナホストにlogsというログディレクトリを作成し、ボリュームマウントした状態でそのログディレクトリにNginxのアクセスログを出力し、コンテナホスト側でそのログを取得したいと考えていました。

Nginxのログ出力設定には以下のようにコンテナホストからボリュームマウントしたlogsというログディレクトリにアクセスログを出力させる設定をおこないます。

access_log  /logs/host.access.log  main;

以下のようにlogsというログディレクトリをボリュームマウントするよう設定した上でコンテナを起動させます。

podman run \
-p 8080:8080 \
-v "$(pwd)/conf.d/default.conf":/etc/nginx/conf.d/default.conf \
-v "$(pwd)/logs":/logs \
nginxinc/nginx-unprivileged

ところがコンテナは起動せず権限エラーが出ました。

nginx: [emerg] open() "/logs/host.access.log" failed (13: Permission denied)



原因

コンテナ内に入り、ボリュームマウントしたlogsというログディレクトリを確認すると所有者とグループがrootとなっていました。

drwxr-xr-x.   2 root   root       6 Mar 20 09:55 logs

コンテナの起動ユーザは以下のようにnginxユーザ、nginxグループとなっており、所有者とグループがrootとなっていたボリュームマウントしたlogsというログディレクトリにアクセスログが書き込み出来ない状態となっていました。

$ id
uid=101(nginx) gid=101(nginx) groups=101(nginx)



UIDとGIDの確認

コンテナ内とコンテナホストそれぞれのUID、GIDを確認します。

コンテナ内

$ id
uid=101(nginx) gid=101(nginx) groups=101(nginx)
$

コンテナホスト

$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant)
$

コンテナホストにあるボリュームマウント対象のファイルとディレクトリのオーナー

次にコンテナホストのボリュームマウントするディレクトリのオーナーとグループを確認します。

drwxr-xr-x. 2 vagrant vagrant    6 Mar 20 18:55 logs

コンテナ内のファイルとディレクトリのオーナー

コンテナ内のボリュームマウントされたディレクトリのオーナーとグループを確認します。

drwxr-xr-x.   2 root   root       6 Mar 20 09:55 logs



対応

ルートレスモードの Podman では、ユーザ名前空間という仕組みによって実行ユーザーのUIDがコンテナ内の root としてマッピングされます。 これにより、ホスト側のディレクトリをコンテナにボリュームマウントしたときに、コンテナ側でファイルを作成してもパーミッションの問題に悩まされることなく利用できます。 ただ、今回コンテナ内の起動ユーザが一般ユーザのため、ボリュームマウントされたディレクトリにアクセス出来ないという問題が起きました。

UIDとGIDのマッピング

podman unshare コマンドを利用してPodmanの実行ユーザの名前空間内のUIDとGIDのマッピング情報を確認します。

[vagrant@localhost ~]$ podman unshare cat /proc/self/uid_map
         0       1000          1
         1     100000      65536
[vagrant@localhost ~]$

上記の意味はコンテナ内のUID 0がコンテナホストの1000とマッピングされ、 UID 1がコンテナホストの100000にマッピングされて以降UID 2が100001にマッピングされます。 つまり、コンテナ内のUID 101はコンテナホストの100100にマッピングされることになります。

[vagrant@localhost ~]$ podman unshare cat /proc/self/gid_map
         0       1000          1
         1     100000      65536
[vagrant@localhost ~]$

UIDと同様、上記の意味はコンテナ内のGID 0がコンテナホストの1000とマッピングされ、 GID 1がコンテナホストの100000にマッピングされて以降GID 2が100001にマッピングされます。

上記のUID、GIDのマッピング設定なので、設定せずにボリュームマウントしてコンテナを起動すると

drwxr-xr-x. 2 vagrant vagrant    6 Mar 20 18:55 logs

というオーナーの場合、vagrantユーザのUIDは1000でマッピング設定からコンテナ内ではUID 0つまりrootユーザに変換されます。 GIDも同様にvagrantグループのGIDは1000でマッピング設定でコンテナ内ではGID 0のrootグループに変換されることになります。

UIDのマッピングをまとめると以下のようになります。

GIDのマッピングをまとめると以下のようになります。

上述のことからなにも設定せずにボリュームマウントをおこなうとファイルとディレクトリのオーナーがrootになってしまい、コンテナ内で一般ユーザ権限でディレクトリ内へファイル書き込みなどが出来ない問題が発生します。 そこで、以下のように podman unshare コマンドを実行してコンテナホスト上のファイルとディレクトリのオーナーをコンテナ内のUIDとGIDを指定してchownコマンドを実行します。 すると、マッピングされたUIDとGIDでコンテナホスト上のファイルとディレクトリのオーナーに設定されることになります。

podman unshare chown 101:101 logs

実行後コンテナホストで再度オーナーを確認すると以下のようになります。

drwxr-xr-x. 2  100100  100100    6 Mar 20 18:55 logs

UIDがコンテナ内のUID101にマッピングされている100100になり、GIDに関してもコンテナ内のGID 101にマッピングされている100100になります。

コンテナ内のボリュームマウントされたディレクトリを確認すると以下のようにオーナーがnginx、グループもnginxとなっていることが確認出来ます。

drwxr-xr-x.   2 nginx  nginx      6 Mar 20 09:55 logs

これでパーミッションのエラーが出ることなく、アクセスログがボリュームマウントしたディレクトリに出力できるようになりました。

参考

Openterface Mini-KVMを購入した

Openterface Mini-KVM

OpenterfaceのMini-KVMの情報をXで見かけ、注文してみたものが先日到着しました。

Mini-KVMはノートPCにターゲットデバイスを接続して操作出来るものです。

上記写真ではMini-KVMにターゲットデバイスのRaspberry Piを繋いでPCから操作出来るように配線しているものです。

構成

PCに付属品のオレンジのケーブルをMini-KVMに接続します。

Mini-KVMはPCから給電するため、電源ケーブルは必要なく配線がかさばらなく済みます。

Mini-KVMからRaspberry PiにはHDMIとUSBケーブルを接続しています。

操作

Mini-KVMに接続したターゲットデバイス(本記事ではRaspberry Pi)を操作するにはホストアプリをインストールします。

ホストアプリは こちら からインストール可能です。

私の環境ではPCにMac Book Air(M3 2024)を使用しましたのでApp Storeからインストールしました。

ホストアプリを使用すると上記のようにコンソール画面が表示されます。

感想

本体も小型で配線も少なく、すぐに使い始められるためものすごく使い勝手が良いと感じました。

今回は自宅にあるRaspberry PIで試しただけですが、BIOSやOS起動メッセージも表示可能であるため普段使いからデータセンターでのトラブル対応時にも便利に利用可能と思います。

データセンターなどではコンソール接続する際にはデータセンターの方にディスプレイやキーボードをお借りして接続することがありますが、 スペースが狭いことが多く作業しづらいことがあります。 一方、MIni-KVMを使えばノートPCさえあればMini-KVM本体も小型でケーブルも最小限のものだけで済むためとても作業しやすいと思います。

データセンターにあるサーバーはKVM接続ではなく、IPMIのみ設定していることが多々かなと思います。 リモートからの操作の場合はIPMIでも問題ないと思いますが、現地にいる場合はこういったKVM接続のほうが操作しやすいことが多かったので大変便利だなと思いました。

Podmanのauto-update機能でコンテナの自動更新を試す

概要

Podmanのauto-update機能を利用するとレジストリ上のコンテナイメージが更新された場合に自動でコンテナを新しいコンテナイメージに差し替える自動更新が可能になる。 また、auto-update機能は更新後のコンテナイメージでコンテナ起動が失敗すると自動で以前起動していたコンテナイメージで起動し直すロールバック機能も備えているのでそれらの機能を試す。

auto-updateに対応したコンテナの起動手順

auto-update用のラベルを付けてコンテナを作成

io.containers.autoupdate=registry というラベルを付けてコンテナを作成する。

podman create --label "io.containers.autoupdate=registry" -p 8080:80 --name nginx docker.io/tsunokawa/nginx:release

作成したコンテナは起動状態ではないため、 podman ps コマンドでは表示されず podman ps -a コマンドで以下のように STATUSCreated となる。

$ podman ps -a
CONTAINER ID  IMAGE                              COMMAND               CREATED        STATUS      PORTS                 NAMES
c37a4190f51d  docker.io/tsunokawa/nginx:release  nginx -g daemon o...  8 seconds ago  Created     0.0.0.0:8080->80/tcp  nginx
$

起動するコンテナは以下のContainerfileでビルドしたNginxのコンテナを使用する。

Containerfile

FROM nginx:latest

COPY default.conf /etc/nginx/conf.d/
COPY index.html /usr/share/nginx/html/

Nginxの設定ファイルは以下を読み込ませるようにして SSI を有効化する。

default.conf

server {
    listen       80;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    ssi on;
}

HTMLファイルは以下のようにしてコンテナにアクセスするとホスト名(コンテナ名)が表示されるようにする。

index.html

<!--#echo var="HOSTNAME" --> / Version: blue

表示例

xxxxx / Version: blue

systemdユニットファイルを作成

以下コマンドでsystemdユニットファイルを作成する。

podman generate systemd nginx --new > ~/.config/systemd/user/nginx.service

ディレクトリが存在しない場合は以下で事前に作成をおこなう。

mkdir -p ~/.config/systemd/user

systemdユニットファイルをsystemdへ反映

systemctl --user daemon-reload

systemdサービスを起動

systemctl --user start nginx

コンテナの起動状態を確認

podman ps では以下のように起動状態が確認出来る。

$ podman ps
CONTAINER ID  IMAGE                              COMMAND               CREATED        STATUS        PORTS                 NAMES
25beec1b9ef4  docker.io/tsunokawa/nginx:release  nginx -g daemon o...  6 seconds ago  Up 6 seconds  0.0.0.0:8080->80/tcp  nginx
$

curlでアクセスをおこなうと以下のような表示になることを確認する。

$ curl http://127.0.0.1:8080/
25beec1b9ef4 / Version: blue
$

ここまででauto-updateに対応したコンテナが起動した状態になる。

コンテナイメージとコンテナのイメージID一覧を確認すると以下のように出力される。

$ podman images
REPOSITORY                 TAG         IMAGE ID      CREATED             SIZE
docker.io/tsunokawa/nginx  release     c88b7e82b4fc  About a minute ago  192 MB
docker.io/library/nginx    latest      a72860cb95fd  6 weeks ago         192 MB

起動しているコンテナのコンテナイメージIDを確認すると c88b7e82b4fc で起動していることが分かる。

$ podman inspect --format='{{.Id}} {{.ImageName}} {{.Image}} {{.Name}}' 25beec1b9ef4
25beec1b9ef4ce7b071bc16887412b9e999c813e5ee9bd21602cfe8299ae0214 docker.io/tsunokawa/nginx:release c88b7e82b4fc5c24ae5133576c1d40d735f6a4bc732debe628e9cd5a1a4f0f21 nginx
$



自動更新のテスト

コンテナイメージを更新

curlでアクセスした際に、 Version: green となるようにしてコンテナイメージを更新する。 コンテナをビルドし、レジストリにpushする。※イメージ名とタグ名は同じものを指定する。

自動更新のdry-run

イメージの更新前は --dry-run オプションを付けてコマンドでauto-updateを実行した場合は以下の表示で UPDATED の箇所が false になっている。

$ podman auto-update --dry-run
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  25beec1b9ef4 (nginx)  docker.io/tsunokawa/nginx:release  registry    false
$

更新したイメージをレジストリにpushした後は、以下のように --dry-run オプションを付けてコマンドでauto-updateを実行すると UPDATED の箇所が pending となっており更新待ちの状態となっている。

$ podman auto-update --dry-run
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  25beec1b9ef4 (nginx)  docker.io/tsunokawa/nginx:release  registry    pending
$

自動更新を実行

$ podman auto-update
Trying to pull docker.io/tsunokawa/nginx:release...
Getting image source signatures
Copying blob 9e891cdb453b skipped: already exists
Copying blob 3dfc528a4df9 skipped: already exists
Copying blob 045037a63be8 skipped: already exists
Copying blob 8fe9a55eb80f skipped: already exists
Copying blob ba59045628c1 skipped: already exists
Copying blob efc2b5ad9eec skipped: already exists
Copying blob 0f11e17345c5 skipped: already exists
Copying blob be56a8a98726 skipped: already exists
Copying blob 7111b42b4bfa skipped: already exists
Copying config 54d7ad174e done
Writing manifest to image destination
Storing signatures
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  25beec1b9ef4 (nginx)  docker.io/tsunokawa/nginx:release  registry    true
$

再度 --dry-run オプションを付けてコマンドでauto-updateを実行すると UPDATED の箇所が false となっており更新がないことが分かる。

$ podman auto-update --dry-run
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  6973c850a56f (nginx)  docker.io/tsunokawa/nginx:release  registry    false
$

再度コンテナの起動状態を確認

再度 podman ps でコンテナの起動状態を確認するとコンテナIDが変更となりコンテナが起動し直されていることが分かる。

$ podman ps
CONTAINER ID  IMAGE                              COMMAND               CREATED         STATUS         PORTS                 NAMES
6973c850a56f  docker.io/tsunokawa/nginx:release  nginx -g daemon o...  31 seconds ago  Up 31 seconds  0.0.0.0:8080->80/tcp  nginx
$

curlでアクセスした際に、 Version: green となって最新のコンテナイメージが利用された状態でコンテナが起動していることが分かる。

$ curl http://127.0.0.1:8080/
6973c850a56f / Version: green
$

更新後のコンテナイメージとコンテナイメージID一覧を確認する。

$ podman images
REPOSITORY                 TAG         IMAGE ID      CREATED             SIZE
docker.io/tsunokawa/nginx  release     54d7ad174ea8  About a minute ago  192 MB
<none>                     <none>      c88b7e82b4fc  3 minutes ago       192 MB
docker.io/library/nginx    latest      a72860cb95fd  6 weeks ago         192 MB
$

コンテナイメージ更新後のコンテナイメージIDを確認すると 54d7ad174ea8 で起動していることが分かる。

$ podman inspect --format='{{.Id}} {{.ImageName}} {{.Image}} {{.Name}}' 6973c850a56f
6973c850a56fd2a760ce1ed5b82714b3cf4d34c7605cdbcaa3bf43bdf83954ff docker.io/tsunokawa/nginx:release 54d7ad174ea8f6b07fbb6250efcb13512d0c0d0416eeb1121154d80e5b15fe98 nginx
$



ロールバック機能

ロールバック機能を試すため、わざと起動に失敗するコンテナイメージに更新してロールバックして正常に起動するコンテナで起動し直すのか確認する。

まずは、正常に動作しているコンテナが起動中の状態を確認する。

$ podman ps
CONTAINER ID  IMAGE                              COMMAND               CREATED        STATUS        PORTS                 NAMES
6f33577d282b  docker.io/tsunokawa/nginx:release  nginx -g daemon o...  8 seconds ago  Up 8 seconds  0.0.0.0:8080->80/tcp  nginx
$

curlでアクセスすると blue と応答が返ってくることが確認出来る。

$ curl http://127.0.0.1:8080/
6f33577d282b / Version: blue
$

コンテナイメージの更新がないことを確認する。

$ podman auto-update --dry-run
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  6f33577d282b (nginx)  docker.io/tsunokawa/nginx:release  registry    false
$

コンテナイメージ、イメージID一覧と起動中のコンテナイメージIDを確認する。

$ podman images
REPOSITORY                 TAG         IMAGE ID      CREATED        SIZE
docker.io/tsunokawa/nginx  release     2cc2e3d8fb09  2 minutes ago  192 MB
docker.io/library/nginx    latest      a72860cb95fd  6 weeks ago    192 MB

現在のコンテナは 2cc2e3d8fb09 のコンテナイメージIDで起動していることが分かる。

$ podman inspect --format='{{.Id}} {{.ImageName}} {{.Image}} {{.Name}}' 6f33577d282b
6f33577d282b7d5e3d95ccf3c5fa25425bef050adb6e824964902a3732464d8d docker.io/tsunokawa/nginx:release 2cc2e3d8fb098002d9349e0d0c2e27c15a0ccd126fe5f9fb7d1e45d85f000833 nginx
$

curlでアクセスすると green と応答が返ってくるよう変更をおこない、コンテナ起動が失敗するように以下のようにContainerfileの最終行に終了コードの 1 を返すよう変更してビルドしてレジストリにpushする。

FROM nginx:latest

COPY default.conf /etc/nginx/conf.d/
COPY index.html /usr/share/nginx/html/

ENTRYPOINT ["exit", "1"]

ビルドしてレジストリにpush後、 podman auto-update --dry-run コマンドを実行すると pending状態に変わっていることが分かる。

$ podman auto-update --dry-run
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  6f33577d282b (nginx)  docker.io/tsunokawa/nginx:release  registry    pending
$

コンテナ起動が失敗するコンテナイメージで更新しようと podman auto-update を実行すると以下のように UPDATEDrolled back になっていることが分かる。

$ podman auto-update
Trying to pull docker.io/tsunokawa/nginx:release...
Getting image source signatures
Copying blob 9e891cdb453b skipped: already exists
Copying blob 8fe9a55eb80f skipped: already exists
Copying blob 3dfc528a4df9 skipped: already exists
Copying blob 7111b42b4bfa skipped: already exists
Copying blob 045037a63be8 skipped: already exists
Copying blob efc2b5ad9eec skipped: already exists
Copying blob 0f11e17345c5 skipped: already exists
Copying blob 8b3954e4d45e skipped: already exists
Copying blob cd5399ba9716 skipped: already exists
Copying config e99e6a0fbb done
Writing manifest to image destination
Storing signatures
            UNIT           CONTAINER             IMAGE                              POLICY      UPDATED
            nginx.service  6f33577d282b (nginx)  docker.io/tsunokawa/nginx:release  registry    rolled back
$

コンテナIDが変わりコンテナが起動し直していることが分かる。

$ podman ps
CONTAINER ID  IMAGE                              COMMAND               CREATED         STATUS         PORTS                 NAMES
23639d9bcb0f  docker.io/tsunokawa/nginx:release  nginx -g daemon o...  19 seconds ago  Up 20 seconds  0.0.0.0:8080->80/tcp  nginx
$

curlでアクセスをおこなうと bluepodman auto-update 前のイメージでコンテナが起動していることが確認出来る。

$ curl http://127.0.0.1:8080/
23639d9bcb0f / Version: blue

再度コンテナイメージとコンテナイメージID一覧を確認する。

$ podman images
REPOSITORY                 TAG         IMAGE ID      CREATED             SIZE
<none>                     <none>      e99e6a0fbbd2  About a minute ago  192 MB
docker.io/tsunokawa/nginx  release     2cc2e3d8fb09  4 minutes ago       192 MB
docker.io/library/nginx    latest      a72860cb95fd  6 weeks ago         192 MB

起動し直されたコンテナイメージIDを確認すると以前のコンテナイメージ( 2cc2e3d8fb09 )を使ってコンテナが起動していることが分かる。

$ podman inspect --format='{{.Id}} {{.ImageName}} {{.Image}} {{.Name}}' 23639d9bcb0f
23639d9bcb0f4bcd167f26ea4300924bc46cc796c68b74450ff1e334b8250bef docker.io/tsunokawa/nginx:release 2cc2e3d8fb098002d9349e0d0c2e27c15a0ccd126fe5f9fb7d1e45d85f000833 nginx
$

更新後のコンテナイメージでコンテナ起動が失敗すると自動で以前起動していたコンテナイメージで起動し直すロールバック機能の確認が出来た。

参考