🗂

Docker開発環境(2): コンテナのユーザーとホストのユーザーをマップする

2021/08/04に公開

前回の続きです。

https://zenn.dev/anyakichi/articles/0fb5865cc5e1e3

以下のような環境を構築することを考えます。

  • コンテナ内でホストの自分の uid/gid でビルドができるようにする。
  • コンテナ内には当該 uid/gid を持つ正しいユーザーが存在する。
  • コンテナ内には予め用意されたホームディレクトリが存在する。
  • ホスト環境から不必要に多くのボリュームをマップしない(コンテナの独立性のため)。

まず、コンテナ内にユーザーを予め作っておくことを考えてみましょう。これは以下のような Docker ファイルで可能です(ここでは builder というユーザーを作ってみます)。

Dockerfile
FROM ubuntu:20.04

RUN useradd -m builder

実際に使ってみましょう。

$ docker build -t builder .
$ docker run --rm -it -u $(id -u):$(id -g) -v $PWD:/build -w /build builder
builder@d61e7be9264f:/build$ id
uid=1000(builder) gid=1000(builder) groups=1000(builder)
builder@d61e7be9264f:/build$ touch a
builder@d61e7be9264f:/build$ ls -l
total 4
-rw-r--r-- 1 builder builder 42 Aug  3 04:16 Dockerfile
-rw-r--r-- 1 builder builder  0 Aug  3 04:18 a
builder@d61e7be9264f:/build$ exit
exit
$ ls -l
total 4
-rw-r--r-- 1 <user>  <user>  42 Aug  3 13:16 Dockerfile
-rw-r--r-- 1 <user>  <user>   0 Aug  3 13:18 a

ホストの(あなたの) uid/gid が 1000/1000 になっている場合、上記のように違和感なく動作すると思います。

また、Dockerfile を以下のように直すと、コンテナに入ったときに "hello, world" と表示されるはずです。

Dockerfile
FROM ubuntu:20.04

RUN useradd -m builder && echo 'echo "hello, world"' >> /home/builder/.bashrc

ユーザーのホームディレクトリについても意図通りの環境が作れています。

骨格としてはこれで良さそうなのですが、uid/gid の数値が 1000/1000 ではないユーザーで実行した場合は、前回ユーザーが存在しない状態で docker run の -u オプションを指定した場合と同じように、ユーザー名がなく、ホームディレクトリもないという動作になると思います。

次にこれを解決したいのですが、どのようにするのが良いでしょうか。ホスト環境の uid/gid は予め決まっていますし、コンテナ環境の builder の uid/gid も予め(uesradd した時点で)決まっています。これが同じならうまく動きますが、違うとうまく動きません。

ただ、実は builder の uid/gid は動的に変更することができます。これには Docker の entrypoint を使用します。

以下のような entrypoint.sh ファイルを用意します。

entrypoint.sh
#!/bin/bash

export USER=builder
export HOME=/home/$USER

# カレントディレクトリの uid と gid を調べる
uid=$(stat -c "%u" .)
gid=$(stat -c "%g" .)

if [ "$uid" -ne 0 ]; then
    if [ "$(id -g $USER)" -ne $gid ]; then
        # builder ユーザーの gid とカレントディレクトリの gid が異なる場合、
	# builder の gid をカレントディレクトリの gid に変更し、ホームディレクトリの
	# gid も正常化する。
        getent group $gid >/dev/null 2>&1 || groupmod -g $gid $USER
        chgrp -R $gid $HOME
    fi
    if [ "$(id -u $USER)" -ne $uid ]; then
        # builder ユーザーの uid とカレントディレクトリの uid が異なる場合、
	# builder の uid をカレントディレクトリの uid に変更する。
	# ホームディレクトリは usermod によって正常化される。
        usermod -u $uid $USER
    fi
fi

# このスクリプト自体は root で実行されているので、uid/gid 調整済みの builder ユーザー
# として指定されたコマンドを実行する。
exec setpriv --reuid=$USER --regid=$USER --init-groups "$@"

やっていること自体はあまり複雑ではありません。基本的には usermod/groupmod で builder ユーザーの uid/gid をホストユーザーの uid/gid で置き換えるラッパースクリプトです。

次にこの entrypoint.sh を実環境に組み込みます。Dockerfile は以下のようになります。

Dockerfile
FROM ubuntu:20.04

RUN useradd -m builder && echo 'echo "hello, world"' >> /home/builder/.bashrc

COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/bin/bash"]

今度は -u オプション無しでコンテナを起動します。

$ docker build -t builder .
$ docker run --rm -it -v $PWD:/build -w /build builder
hello, world
builder@ea67bbbdcbbe:/build$ id
uid=1000(builder) gid=1000(builder) groups=1000(builder)

builder ユーザーとして動作しています。違う uid/gid も試してみましょう。コンテナからはカレントディレクトリの uid/gid だけを見るので、ホストに存在しない uid/gid でも構いません。

$ mkdir /tmp/docker-test
$ sudo chown 1050:200 /tmp/docker-test
$ cd /tmp/docker-test
$ docker run --rm -it -v $PWD:/build -w /build builder
hello, world
builder@3b273a8058e0:/build$ id
uid=1050(builder) gid=200(builder) groups=200(builder)

builder の uid/gid が変わっています。

これでホストの uid/gid を保持したままコンテナ内の予め用意したユーザーを使ってビルドをするためのシステムが構成できました。

次回は Docker によるビルド環境をより便利に活用するための構成方法について検討していきます。

Discussion