LinuxなホストとDockerコンテナとのファイル共有について。

  • Linuxのプロセスは、特定のユーザID(uid)と特定のグループID(gid)に紐づく
  • プロセスが生成するファイルのオーナーは、プロセスに紐付いたuid/gidで決まる
  • Dockerコンテナ内のuid/gid体系は、ホスト側とは別物

以上のことから、コンテナとファイル共有するとき、コンテナ側で作成したファイルにホスト側からアクセスできない、といった問題が生じる。

解決策の1つとして、「コンテナを起動するたびに、自分と同じuid/gidを持ったアカウントを生成する」という作戦があるので、まとめておく。

概要

  • コンテナはrootで開始
  • シェルスクリプトを使って新しいアカウントを生成し、そいつへsuする
  • 新しいアカウントには、ホスト側のアカウントと同じuid/gidをセットする

uid/gid

ホスト側のuid/gidなどをコンテナ側へ伝える手段として、環境変数がパッと思いつく。

HOST_GID=`id -g`
HOST_UID=`id -u`

uidに比べるとgidは融通がきくので、専用のグループ(dockerとか)を作っても良いだろう。

HOST_GID=`getent group docker |cut -d : -f 3`

しかしもっと簡単なのは、共有するディレクトリのオーナー情報を使う方法だ。共有するディレクトリはホスト側が作ったものなので、たとえコンテナ側から見たときでも、それはホスト側のuid/gidになる。仮に/tmp/shareをホストと共有するとしたら、こんな感じ:

SHARED_DIR=/tmp/share
HOST_UID=$(stat -c %u $SHARED_DIR)
HOST_GID=$(stat -c %g $SHARED_DIR)

%U%Gを使えば、ユーザ名やグループ名も分かる。もしUNKNOWNなら、それはホスト側にしか存在しないということになる。UNKNOWN以外だったら、ホスト側と同じuid/gidが、(偶然、)コンテナ側にも存在するということなので、新規に作る必要は無い。

シェルスクリプト

ユーザ名やグループ名までホスト側と合わせる必要な無いと思われる。もし合わせたい場合は、環境変数経由で渡してもらえばいい。

またAlpine linuxでは、新規ユーザ/新規グループを作るコマンドが特殊だ。

dk_user.sh(alpine以外)
#!/bin/bash

if [ -z $SHARED_DIR ]; then
    SHARED_DIR=$PWD
fi
HOST_UID=$(stat -c %u $SHARED_DIR)
HOST_GID=$(stat -c %g $SHARED_DIR)

DK_GROUP=$(stat -c %G $SHARED_DIR)
if [ $DK_GROUP = UNKNOWN ]; then
    DK_GROUP=dk_group
    groupadd --gid $HOST_GID $DK_GROUP
fi

DK_USER=$(stat -c %U $SHARED_DIR)
if [ $DK_USER = UNKNOWN ]; then
    DK_USER=dk_user
    useradd --home-dir /home/$DK_USER --gid $HOST_GID \
        --uid $HOST_UID --shell /bin/bash $DK_USER
fi

su $DK_USER
dk_user.sh(alpine)
#!/bin/sh

if [ -z $SHARED_DIR ]; then
    SHARED_DIR=$PWD
fi
HOST_UID=$(stat -c %u $SHARED_DIR)
HOST_GID=$(stat -c %g $SHARED_DIR)

DK_GROUP=$(stat -c %G $SHARED_DIR)
if [ $DK_GROUP = UNKNOWN ]; then
    DK_GROUP=dk_group
    addgroup -g $HOST_GID $DK_GROUP
fi

DK_USER=$(stat -c %U $SHARED_DIR)
if [ $DK_USER = UNKNOWN ]; then
    DK_USER=dk_user
    adduser -h /home/$DK_USER -G $DK_GROUP \
        -u $HOST_UID -s /bin/sh -D $DK_USER
fi

su $DK_USER

Dockerfile

シェルスクリプトは、PATH上の適当な場所へコピっておく。

COPY dk_user.sh /usr/local/bin/

コンテナ

コンテナを開始するときに、シェルスクリプトを実行する。

デフォルトでは、コンテナの(初期の)作業ディレクトリがSHARED_DIRとして使われる。作業ディレクトリが共有フォルダじゃない場合は、明示的にSHARED_DIRを教えてやる必要がある。

$ docker run --rm -it \
    --env "SHARED_DIR=/tmp/share" \
    --volume $(shell pwd):/tmp/share \
    --name my_container \
    my_image dk_user.sh