はじめに
このドキュメントはNFSで共有されているホームディレクトリを利用してpodmanを起動したい方を対象としています。Docker CEでも基本的には同様で、方法は最後に追記しました。
これまでKubernetes環境を利用するために、Docker環境を利用してきましたが、数名がログインするクライアント端末上で、podmanを利用してみた時の顛末を残しておきます。
この作業は、LDAPで認証を行ないホームディレクトリをNFSで共有する利用者が、podmanを非root権限で利用できるか、確認した時のメモです。
一般的なCentOS/RHEL単体でpodmanを利用する方は、他のドキュメントが参考になると思います。
参考資料
- RedHat - Running rootless Podman as a non-root user
- https://www.si1230.com/?p=40757
- RedHat RHEL8 Manual - 3.2. LDAP を使用し、TLS 認証を必要とする SSSD の設定
- https://serverfault.com/questions/1018828/editing-authselect-files
- RedHat Blog - New features for running containers on NFS with rootless Podman
- RedHat Blog - Why can’t rootless Podman pull my image?
環境
- VMware Workstation Pro 16 (2-core, 4GB-memory)
- CentOS 8 Stream (4.18.0-338.el8.x86_64)
- 認証: LDAP
- ホームディレクトリ: autofsによるNFS領域 (with root_squash)
想定される課題
- NFS領域からのコンテナの起動に失敗する
準備作業
最小構成でCentOS8 Streamをセットアップしてから、必要なモジュールを導入しています。
$ sudo yum update
$ sudo yum install podman python3
non-root対応していないpodmanを一般ユーザーで実行してみる
確認のため、以下のようなコマンドを実行してみます。
$ podman pull docker.io/library/nginx:latest
$ podman run -it -d --name nginx nginx:latest
$ podman stop nginx
$ podman run -it -d -p 8080:80 --name nginx nginx:latest
$ podman stop nginx
問題なく、これらのコマンドが実行できた場合には、useraddコマンドなどでユーザーを作成した際に、/etc/subuid, /etc/subgid ファイルが設定済みと思われます。おそらくホームディレクトリもローカルディスク上にあると推測されます。
今回の動作確認のためには、これらのファイルを空にしておくか、該当ユーザーのエントリを削除しておいてください。
$ sudo cp -ip /etc/subuid /etc/subuid.$(date +%Y%m%d.%H%M%S)
$ sudo cp -ip /etc/subgid /etc/subgid.$(date +%Y%m%d.%H%M%S)
$ cat /dev/null | sudo tee /etc/subuid
$ cat /dev/null | sudo tee /etc/subgid
## /etc/subuid, /etc/subgidを編集した後は反映させるために再起動してください
ホームディレクトリについては、NFS経由でマウントしている事を確認してください。
$ cd
$ df .
成功すれば次のような出力結果が得られているはずです。
## 成功例: NFSを利用している場合、NFSサーバーのIPアドレス/ホスト名から始まります
Filesystem 1K-blocks Used Available Use% Mounted on
192.168.1.10:/export/home 2883091456 17161216 2719407104 1% /nfs/home
次のように出力されていれば、ホームディレクトリはローカルデバイス上に存在していることになります。
## 失敗例: ローカルディスクを利用している場合は、/devで始まるデバイス名から始まります
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/mapper/cs_s10tp08-home 398374860 2810800 395564060 1% /home
/etc/subuid,/etc/subgidファイルの該当エントリを空にした後には、エラーが表示されるようになります。
$ podman pull docker.io/library/nginx:1.17
Trying to pull docker.io/library/nginx:1.17...
Getting image source signatures
Copying blob 11fa52a0fdc0 done
Copying blob afb6ec6fdc1c done
Copying blob b90c53a0b692 done
Error: writing blob: adding layer with blob "sha256:afb6ec6fdc1c3ba04f7a56db32c5ff5ff38962dc4cd0ffdef5beaa0ce2eb77e2": \
Error processing tar file(exit status 1): potentially insufficient UIDs or GIDs available in user \
namespace (requested 0:42 for /etc/gshadow): Check /etc/subuid and /etc/subgid: lchown /etc/gshadow: \
invalid argument
ここでエラーが出力されることは意図した動作です。
反映は即時されるはずですが、既に稼動しているpodmanプロセスが存在していると正しく反映されないかもしれません。
podmanプロセスがある場合には停止してから、再度、動作を確認してください。
$ sudo killall /usr/bin/podman
$ podman pull docker.io/library/nginx:1.17
...
podman以外の追加の操作について
ansibleを利用して、LDAP認証の準備を行なう
まっさらなCentOS8 Streamを準備したので、LDAP認証などを行なっていきます。
podmanと一緒にpython3を導入しているので、他のサーバーからansible経由で操作をしていきます。
.PHONY: setup
setup:
ansible all -m yum -a "name={{ pkglist }}" --extra-vars='{"pkglist":[autofs,podman,openldap-clients,ss
sd,sssd-ldap,sssd-tools,authselect,oddjob-mkhomedir]}'
ansible all -m command -b -a "authselect select sssd --force"
ansible all -m command -b -a "authselect select sssd with-mkhomedir"
ansible all -m lineinfile -b -a "dest=/etc/openldap/ldap.conf regexp='^URI' line='URI ldaps://ldap.example.com'"
ansible all -m lineinfile -b -a "dest=/etc/openldap/ldap.conf regexp='^BASE' line='BASE dc=example,dc=com'"
ansible all -m copy -b -a "src=files/sssd.ldap.conf dest=/etc/sssd/conf.d/ldap.conf owner=root group=root mode=600"
Makefileの中で指定している files/sssd.ldap.conf ファイルの内容は、参考にしたRedHatのドキュメントどおりで、次のようになっています。
[domain/default]
id_provider = ldap
autofs_provider = ldap
auth_provider = ldap
chpass_provider = ldap
ldap_uri = ldaps://ldap.example.com/
ldap_search_base = dc=example,dc=com
ldap_id_use_start_tls = True
cache_credentials = True
ldap_tls_cacertdir = /etc/openldap/certs
ldap_tls_reqcert = allow
[sssd]
services = nss, pam, autofs
domains = default
[nss]
homedir_substring = /home
実際にはroleにして操作をまとめたり、yumモジュールにリストで必要なパッケージを指定するなどして、ansible-playbookを利用するのがお勧めです。
設定はmakeコマンドで反映させています。
$ make setup
$ ansible all -m command -b -a "shutdown -r now"
この再起動後に、LDAPに登録されたユーザーが認識されます。
NFSによるホームディレクトリの共有
既にファイルサーバー側で、特定の領域をユーザーのホームディレクトリとしてexportしているので、これを認識できるようにクライアントの設定だけ行なっていきます。
autofs:
ansible all -m yum -a "name={{ pkglist }}" --extra-vars='{"pkglist":[nfs-utils]}'
ansible all -m copy -b -a "src=files/etc.auto.master.d.nfs.autofs dest=/etc/auto.master.d/nfs.autofs owner=root group=root mode=644"
ansible all -m copy -b -a "src=files/etc.auto.nfs dest=/etc/auto.nfs owner=root group=root mode=644"
ansible all -m systemd -b -a "name=autofs state=restarted"
この中で指定している files/etc.auto.master.d.nfs.autofs, files/etc.auto.nfs の2つのファイルの内容は以下のとおりです。
/nfs /etc/auto.nfs
home -rw,soft,intr 192.168.1.5:/export/home
files/etc.auto.nfs(/etc/auto.nfs) の内容は適宜変更してください。
サーバー側の設定については、下記の記事に記述しています。
LDAP認証したユーザーでのpodmanの利用について
LDAP認証したユーザーでログインし、先ほどと同じコマンドを実行します。
この段階ではLDAP上に存在するユーザー名に対応するエントリは、/etc/subuid,/etc/subgidファイルには存在していません。
$ podman pull docker.io/library/nginx:latest
Trying to pull docker.io/library/nginx:latest...
Getting image source signatures
Copying blob a4723e260b6f done
Copying blob fca7e12d1754 done
Copying blob 1c84ebdff681 done
Copying blob 745ab57616cb done
Copying blob 858292fd2e56 done
Copying blob b380bbd43752 done
Error: writing blob: adding layer with blob "sha256:b380bbd43752f83945df8b5d1074fef8dd044820e7d3aef33b655a2483e030c7": \
Error processing tar file(exit status 1): Error setting up pivot dir: \
mkdir /home/nfs/user01/.local/share/containers/storage/overlay/e81bff2725dbc0bf2003db10272fef362e882eb96353055778a66cda430cf81b/diff/.pivot_root392550752: \
permission denied
このようにNFS上のホームディレクトリ以下にコンテナイメージを保存しようとしてエラーになっています。
これを回避するために、環境変数 XDG_DATA_HOME
、あるいは、.config/containers/storage.confファイル
を使用して、ローカルディスク上の書き込み可能なディレクトリを指定しています。
参考文献に挙げたRedHat Blogの中で、/var/tmpなどを利用する場合には、定期的に古いファイルを削除するdaemonプロセスが必要なファイルを稼動中に削除する可能性があることが指摘されています。
[storage]
driver = "overlay"
rootless_storage_path = "/var/tmp/$USER"
なお、podmanが認識するstorage.confの正確なファイルパスは、podman info
コマンドで確認してください。
$ podman info
...
store:
configFile: /nfs/home/user01/.config/containers/storage.conf
...
graphRoot: /var/tmp/user01
...
環境変数を使用した場合は、どこにpullしたcontainerのイメージファイルを保存したか忘れる可能性があります。
このためstorage.confの利用をお勧めします。
/etc/{subuid,subgid}ファイルの編集について
最後にrootless起動に必要なことは、/etc/subuid, /etc/subgidファイルを正しく編集することです。
このファイルについては多くのサイトで説明が行なわれています。
/etc/passwdのようにコロン(:)で区切られた形式になっています。
<uid or username>:<起点となるUIDを示す数字>:<利用できるIDの数>
実システム上でpodmanはnewuidmap, newgidmapコマンドを利用して、コンテナプロセスのroot(uid:0)のユーザーのUIDは、実行ユーザーのIDと同一ですが、1番から始まるUIDは、<起点となるUIDを示す数字> + (UID - 1)
に割り当てられます。
このコンテナ上で1000番のUIDでプロセスを動作させる(uid:1000)場合は、システム上の<起点となるUIDを示す数字> + (1000 - 1)
番のUIDが割り当てられます。
例えば、postgres:14 を稼動します。これは、Dockerfileの定義上で、uid:999 を割り当てるpostgresユーザーが実行するようになっています。
# explicitly set user/group IDs
RUN set -eux; \
...
useradd -r -g postgres --uid=999 --home-dir=/var/lib/postgresql --shell=/bin/bash postgres; \
...
このコンテナを実行するために、以下のように、/etc/{subuid,subgid} を設定します。
user01:100000:1000
この状態でコンテナを実行し、実システム上のUIDを調べてみます。
$ podman run -it -d --rm --name postgres -e POSTGRES_PASSWORD=password postgres:14
$ ps aux|grep postgres
...
100998 33687 0.0 0.0 213280 10032 ? Ss 10:49 0:00 postgres: walwriter
999番を利用しますが、1番が100000に割り当てられるので、999番は、100998番に割り当てられます。
このように利用するコンテナの設計によって、/etc/{subuid,subgid} ファイルの設定を調整する必要があります。
Alpineコンテナにおける/etc/passwdのデフォルト設定
様々なコンテナをベースとすると思いますが、ここではalpineコンテナの/etc/passwdの設定を確認しておきます。
## $ podman run -it --rm alpine:3.14 cat /etc/passwd
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
考察: コンテナイメージ利用上の考慮点
一般的なUID,GIDとして利用される 0 〜 65534 の範囲を自由に使えるように、/etc/{subuid,subgid} ファイル を設定できれば問題ありませんが、多数のユーザーにサービスを提供したい場合には、もう少しレンジを狭めたい運用を希望する場合も考えられます。
ただ、そのような設定は、あまりうまくいかないと思われます。
Officialコンテナを利用する場合
利用したいコンテナイメージが特定のUID,GIDを利用するように設定されている場合に、問題が発生する可能性があります。
例に挙げたpostgresのように、999番を利用するものもあれば、ネットワークサービスを提供する場合、1024番以上のサービスが利用するポート番号と同じUID,GIDを割り当てるような作成方法も一般的に思われます。
どのような番号を利用するか想定することは難しいですし、nobody:nogroup を利用する場合には、UID,GIDとして65534番を確保しておく必要があります。
/etc/subuidファイルなどの説明では、3つ目の値がサイズを示すとされていますが、コンテナの稼動を前提とすると、実際には65535を指定することが必須のようになってしまいます。
コンテナを自作し、利用する場合
自作する場合にはUID,GIDをコントロールすることが可能なので、それほど問題はないと思われます。
少ないUID,GIDレンジの利用を前提としてシステムを運用することが可能となります。
現実的な/etc/{subuid,subgid} ファイルの利用
ここではrootless環境でコンテナを稼動させることだけを目的として整理しておきます。
各ユーザーがコンテナで利用できる 0 〜 65534 番をUID,GIDとして準備するという前提では、利用できるUID, GIDの数は 100000 〜 4294900000 のレンジになります。
古典的なUNIXが利用する16ビットの範囲を実システムで利用することを前提として、そこから十分に大きなIDレンジを各ユーザーに確保することを考えます。
一般的には、4桁のUID,GIDレンジをユーザーに開放していることが多いと思います。あるいはシステムIDのUID,GIDはwell-knownポート番号と一致させているかもしれません。
4桁のUID,GIDを持つユーザーを対象にする場合
4桁のUID,GIDを持つユーザーが利用できる仮想UID,GIDは、uid:1000のユーザーであれば、1000をprefixとして、0 〜 65534 のIDを確保すると、100000000 〜 100065534、uid:1001であれば 100100000 〜 100165534、のように1億番以上の番号をコンテナで利用するUID,GIDとして各ユーザーに確保することができるようになります。
5桁のUID,GIDを持つユーザーが存在する場合
5桁のUID,GIDを持つユーザーを対象として、同様に考えると、全体のUID,GIDで利用できるのは32bitの範囲l(4294967295)だけなので、この方法で利用できるレンジは少し狭くなります。5桁でもuid:10000〜42949の範囲を越えてしまうと、この方法ではうまくアサインできなくなってしまいます。もちろん使われていない xxxxx65535〜xxxxx99999 の範囲をうまく扱えば、効率を高めることはできそうです。
現実的な対応
ただ、0〜65534までのUID,GIDを準備する必要は必ずしもありません。せいぜい4桁の9999番くらいまでの範囲で大抵のイメージをUIDを割り当てると思われます。
手元のDockerfileを調べた限りでは、Rocket.Chatが65535を指定していた以外は、Apache Solrの8983が一番大きな値でした。
大勢のユーザーに学習環境を提供するという意味では、0 〜 9999 番程度の範囲で、UID,GIDが自由に使えれば現実的な利用は可能になりそうです。
Docker CEを利用している場合
基本的な考え方はPodmanと同様ですが、変更するファイルは~/.config/docker/daemon.json
です。
$ mkdir -p ~/.config/docker
$ echo "{ \"data-root\":\"/var/tmp/$(id -un)/\" }" | tee ~/.config/docker/daemon.json
~/.config/docker/daemon.json が次のような内容になれば完了です。
{ "data-root":"/var/tmp/yasu.docker" }
Docker Desktopが主流になってからは環境全体が重くなっているので、コンテナの学習だけを行いたいのであればpodmanがお勧めです。
以上