CUBE SUGAR CONTAINER

技術系のこと書きます。

続: Docker コンテナ内で Docker ホストと同じユーザを使う

以前、Docker コンテナ内で Docker ホストと同じユーザを使う方法として、以下のような記事を書いた。 ちょっと強引だけど /etc/passwd/etc/group をコンテナからマウントすることで不整合をなくしてしまう、というもの。

blog.amedama.jp

ただ、上記のやり方だと不都合が出る場合もあったので、別のやり方についても試してみた。 どちらかというと、今回のやり方の方が後々の面倒がなくて良いかもしれない。 方法というのは、コンテナを起動するタイミングでコンテナ内にユーザを使って各種 ID などを引き継がせてしまう、というもの。

使った環境は次の通り。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
$ uname -r
4.15.0-65-generic

もくじ

ユーザ情報の書かれたファイルを読み込み専用でマウントする方法の問題点について

Docker のインストール方法については前述した記事の中でも扱っているので省略する。

先のエントリでは、次のようにして /etc/passwd/etc/group を読み込み専用でマウントすることで同じユーザを使えるようにしていた。

$ sudo docker run \
    --rm \
    -u "$(id -u $(whoami)):$(id -g $(whoami))" \
    -v $(pwd):/mnt/working \
    -v /etc/passwd:/etc/passwd:ro \
    -v /etc/group:/etc/group:ro \
    -v /home:/home \
    -it ubuntu:18.04

この方法を使うと、たしかに Docker コンテナ内で Docker コンテナと同じユーザが使えるようになる。

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

ただ、この方法には問題がないわけでもない。 例えば、次のようにして一旦 root ユーザで入り直す。

$ sudo docker run \
    --rm \
    -v $(pwd):/mnt/working \
    -v /etc/passwd:/etc/passwd:ro \
    -v /etc/group:/etc/group:ro \
    -v /home:/home \
    -it ubuntu:18.04

そして、試しにユーザのパスワードハッシュを次のようにして usermod(8) で書き換えてみる。

# usermod -p python3 -c "import crypt; from getpass import getpass; print(crypt.crypt(getpass(), crypt.METHOD_SHA512))" vagrant
usermod: cannot open /etc/passwd

しかし、エラーになってしまった。 パスワードハッシュは、現在では一般的に /etc/shadow に書き込まれるようになっている。 しかし、後方互換性を考えて /etc/passwd に書かれていても動作する。 そのため、結局のところ /etc/passwd にも書き込み権限が必要とされるらしい。 このように、読み込み専用でマウントしてしまうことは、後々面倒な問題を招きやすいようだ。

動的にユーザを追加・編集する

そこで、もう一つのやり方を試してみることにする。 具体的には、コンテナを起動するタイミングでユーザを追加して ID などを編集してしまう、というもの。

まずは Supervisord の設定ファイルを用意しておく。 これはコンテナが終了しないようにするためだけのもの。 何らか継続するプロセスであれば別のものを使っても良い。

$ cat << 'EOF' > supervisord.conf
[supervisord]
nodaemon=true
EOF

続いて Dockerfile を用意する。 必要なパッケージなどをインストールして設定ファイルをコピーしているだけなので、そんなに見るべきところはない。 コンテナが起動するときは docker-entrypoint.sh というシェルスクリプトを実行している。 この中でユーザを追加・編集することになる。

$ cat << 'EOF' > Dockerfile
FROM ubuntu:18.04

# Use mirror repository
RUN sed -i.bak -e "s%http://[^ ]\+%mirror://mirrors.ubuntu.com/mirrors.txt%g" /etc/apt/sources.list

# Install prerequisite packages
RUN apt update \
 && apt -yq dist-upgrade \
 && apt install -yq --no-install-recommends \
      sudo \
      supervisor \
 && apt clean \
 && rm -rf /var/lib/apt/lists/*

# Boot process
COPY supervisord.conf /etc
COPY docker-entrypoint.sh /var/tmp
CMD bash -E /var/tmp/docker-entrypoint.sh
EOF

続いてが肝心のコンテナが起動するときに呼び出されるシェルスクリプト。 この中では変数を元にユーザを追加して ID の類を編集している。 そして、最終的に Supervisord が起動するようになっている。

$ cat << 'EOF' > docker-entrypoint.sh
#!/usr/bin/env bash

set -Ceux

assert_env_defined () {
  if [ ! -v $1 ]; then
    echo "please define '$1' environment variable"
    exit 1
  fi
}

: "Add non administrative user" && {

  : "Add user" && {
    assert_env_defined USER_NAME
    set +e  # avoid error check temporarily
    id ${USER_NAME}  # existence check
    if [ $? -ne 0 ]; then
      useradd -s /bin/bash -m ${USER_NAME}
    fi
    set -e  # enable error check
  }

  : "Set home directory" && {
    export HOME=/home/${USER_NAME}
  }

  : "Change user id" && {
    assert_env_defined USER_ID
    if [ $(id -u ${USER_NAME}) -ne ${USER_ID} ]; then
      usermod -u ${USER_ID} ${USER_NAME}
    fi
  }

  : "Change group id" && {
    assert_env_defined GROUP_ID
    if [ $(id -g ${USER_NAME}) -ne ${GROUP_ID} ]; then
      usermod -g ${GROUP_ID} ${USER_NAME}
    fi
  }

  : "Add common groups" && {
    usermod -aG sudo ${USER_NAME}
  }

}

: "Start supervisord" && {
  supervisord -c /etc/supervisord.conf
}
EOF

上記のコンテナを起動するための Docker Compose の設定ファイルを用意しておこう。

$ cat << 'EOF' > docker-compose.yml
version: "3"
  
services:
  override:
    image: example/override-ids
    build: .
    container_name: override
    environment:
      - USER_NAME
      - USER_ID
      - GROUP_ID
    volumes:
      - ./:/mnt/working
EOF

まずはコンテナイメージをビルドする。

$ sudo docker-compose build

そして、次のように現在のユーザ名や ID の類をシェル変数ごしに教えてやりながらコンテナを起動する。

$ USER_NAME=$(whoami) \
  USER_ID=$(id -u) \
  GROUP_ID=$(id -g) \
  sudo -E docker-compose up
...
override    | + : 'Start supervisord'
override    | + supervisord -c /etc/supervisord.conf
override    | 2019-10-02 14:54:43,036 CRIT Supervisor running as root (no user in config file)
override    | 2019-10-02 14:54:43,038 INFO supervisord started with pid 14

起動したコンテナにログインする。 このとき su(1) を使って追加されたユーザにログインし直す。

$ sudo docker exec -it override su - $(whoami)

すると、次のようにユーザ名や ID が引き継がれたように見える状態でコンテナが起動する。

$ whoami
vagrant
$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant),27(sudo)

試しに、何かファイルを作ってみよう。

$ cd /mnt/working/
$ echo "Hello, World" > greet-docker.txt

ちゃんと Permission Denied にならずにファイルが作られた。

Docker ホストのファイルも、以下のようにユーザがちゃんと状態で表示されている。 これは、コンテナとホストでユーザ名や識別子が一致しているため。

$ ls -lF
total 20
-rw-rw-r-- 1 vagrant vagrant 466 Oct  2 14:39 Dockerfile
-rw-rw-r-- 1 vagrant vagrant 215 Oct  2 14:51 docker-compose.yml
-rw-rw-r-- 1 vagrant vagrant 978 Oct  2 14:46 docker-entrypoint.sh
-rw-rw-r-- 1 vagrant vagrant  13 Oct  2 14:53 greet-docker.txt
-rw-rw-r-- 1 vagrant vagrant  28 Oct  2 14:39 supervisord.conf

ちなみに、このやり方なら Docker for Mac でも使える。 めでたしめでたし。