Docker rootlessで研鯖運用

複数人で共有して使う研究室のサーバでは、rootfulなDockerを用いると権限周りでさまざまな問題が発生します。
Docker rootlessで権限関係の諸問題を解決し、最強の研究室サーバ環境を作りましょう。

筆者の研究室の環境

筆者は東京大学 相澤・山肩・松井研、山﨑研で、院生鯖缶をしています。コンピュータビジョン・マルチメディアを主な研究分野としている研究室で、ほとんどのメンバーはGPUを使用し深層学習のコードを実行しています。

研究室には複数のGPUが刺さったオンプレ計算サーバが10台程度存在し、これらを共有して使用しています。しかし、導入時期によってOSやCUDA、ライブラリなどのバージョンが変わってしまい、新人の環境構築の難易度・コストが上昇していました。そこで、Dockerを用いて簡単に環境構築を行えないか?ということを考えます。*1

しかし、通常のDocker (rootful) を共有環境で使用すると、次のような問題があります。

  • docker run --user 42などとしないと、コンテナがrootで実行される*2
  • 1つのデーモンをrootで実行し、そこに全ユーザがアクセスするという形になっているため、他人のコンテナに自由に触れる
  • デーモンはrootで実行されるため、セキュリティ上様々な懸念が発生する

そこでDocker rootlessを用いてデーモン・コンテナを全てユーザのプロセスとして実行することで、諸問題を解決しながら 安心して便利に Dockerを活用できる環境を作ります。

Docker rootlessとは

Docker 20.10より、Dockerのrootlessモードが正式対応されました。これはデーモンとコンテナを非rootユーザで実行する技術で、以下のようなメリットがあります。

  • デーモン・コンテナを各ユーザで実行するため、ユーザ間で完全に独立したDockerを使用できる
  • 各コンテナの中ではrootユーザとして振る舞えるので、ライブラリのインストールなども可能
    • これはホストのroot権限とは異なる、見せかけのroot権限である
  • デーモンもユーザ権限で実行されるので、Dockerの脆弱性や設定ミスによるセキュリティ上の脅威が軽減できる

NTTの須田さんによる記事が非常にわかりやすいため、あわせて参照ください。 medium.com

Set Up

前提

計算用のサーバはUbuntu Server 16.04, 18.04, 20.04のいずれかがインストールされています。 また、一般ユーザはファイルサーバを各自のホームにマウントして使用しているものとします。 一般ユーザはsudoを行う権限はなく、管理者のみがsudoを使用できるものとします。

管理者が一括で行うこと

必要なパッケージのインストール

こちらに従って、uidmapをインストールします。

また、推奨パッケージである slirp4netns もインストールしておきます*3Ubuntu 20.04の場合はaptでインストール可能です。

それ以前のディストリビューションの場合は、バイナリをDLし、/usr/local/bin などPATHが通っているところに配置します。chmodを忘れずに!

Dockerのインストール

各ユーザがHOME以下にインストールすることも可能ですが、ここでは管理者がシステム全体にDocker 20.10をインストールし、dockerdなどのバイナリは全ユーザで共通のものを使用することにします。

まず、19.03以前のDockerがインストールされている場合は削除します。sudo dpkg -l | grep containerなどするとわかりますが、docker-ce docker-ce-cli containerd.io docker.io containerd docker-engine runc などがあれば削除します。(Dockerのバージョンによってインストールされているパッケージは異なります。) *4

こちら にしたがって、Dockerをインストールします。

rootfulなデーモンは停止しておきます。

$ sudo systemctl disable --now docker.service
$ sudo systemctl disable --now docker.socket

nvidia-docker2のインストール

DockerでGPUを使用するため、記事ドキュメントを参考にnvidia-docker2をインストールします。

また、rootlessモードでGPUを使用するためにはnvidia-container-runtimeの設定変更が必要です。/etc/nvidia-container-runtime/config.tomlを編集し、no-cgroups = trueを記述します。

参考:
nvidia-container-runtime doesn't work with rootless mode · Issue #38729 · moby/moby · GitHub
DockerのRootlessモードでNVIDIAのGPUを使用する - Qiita

uidmapの設定

/etc/subuid/etc/subgidそれぞれで、1ユーザにつき最低65536個のsubuid、subgidを割り当てる必要があります。最初のユーザは100000から割り当てるように記述します。記法は<user name>:<start>:<range>です。サーバごとに割当が異なっていても問題ありません。

以下の例では、各ユーザに65536個のsubuid、subgidを順に割り当てています。

$ cat /etc/subuid
user1:100000:65536
user2:165536:65536
user3:231072:65536
$ cat /etc/subgid
user1:100000:65536
user2:165536:65536
user3:231072:65536

全ユーザを記述するのは非常に大変なので、スクリプトを組むのがオススメです(後述)。

各ユーザで行うこと

Dockerをインストールすると、/usr/bin/dockerd-rootless-setuptool.shが入っているはずです。 *5

$ dockerd-rootless-setuptool.sh install

を実行することで、ユーザごとのデーモンが利用可能になります。serviceが作成・起動されていることは、

$ systemctl --user status docker

でわかります。

デーモンに接続するためのパスを指定するDOCKER_HOSTは環境変数で指定するのが楽ですので、

$ export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock

.bashrcなどに記述すると良いでしょう。各自で記述するのが面倒な場合は、後述のように管理者が/etc/profile.d/以下に設定ファイルを置くこともできます。

$ docker run hello-world
$ docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

を実行して、それぞれ正常に動けばOKです。

コンテナやイメージが置かれるdata-rootは、デフォルトでは~/.local/share/dockerとなりますが、DockerではNFSマウントされたディレクトリをdata-rootとして使用することはできません。 ホームディレクトリがNFSマウントされているケースでは、他のローカルストレージをdata-rootに指定する必要があります。 これに関しても後述します。

運用上のtips

data-rootの場所

筆者の環境では、各ユーザのホームディレクトリはファイルサーバをNFSマウントして用いています。そのためdata-rootを別に指定する必要があります。

デーモンのconfigは~/.config/docker/daemon.jsonです。 ここでdata-rootを指定します。

{
  "data-root" : "/docker_rootless/$(id -un)/docker/"
}

お気づきかもしれませんが、このconfigファイルはHOMEに作成されているため、全サーバで共通のものを用いることになります。 しかしサーバによってdata-rootの場所を変えたいということはよくあります。 そこで/docker_rootless*6シンボリックリンクにして、これが実際にdata-rootの場所を指すようにします。

例えば/work/docker_rootless/$(id -un)/docker/をdata-rootにしたいケースでは、

sudo mkdir -r /work/docker_rootless/
sudo chmod 777 /work/docker_rootless/
sudo ln -s /work/docker_rootless/ /docker_rootless

とします。

なお、シンボリックリンクをdata-rootに指定することも本来はできないようです。 実際、この設定を行った後の初回はアクセスに失敗し、エラーとなります。

systemctl --user restart docker

で一度デーモンを再起動すると、シンボリックリンクをたどってdata-rootが指定されるようになり、動作するようになりました。(正規の挙動ではない可能性がありますので、自己責任でお願いします。また、良い解決策をご存知の方はご教示ください。)

DOCKER_HOST環境変数の一括設定

DOCKER_HOST環境変数は各ユーザで設定しなくとも、管理者がまとめて設定することが可能です。以下のように/etc/profile.d/以下にスクリプトを作成すると、ログイン時にDOCKER_HOSTがセットされます。

$ cat /etc/profile.d/set-docker-rootless-host.sh 
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/docker.sock"

subuid/subgidの一括設定

存在しているユーザをサーチし、それらのsubuid/subgidがセットされていない場合は追記するという内容のスクリプトを作成しました。これを管理者が実行するだけで設定が完了します。

スクリプトでは直にファイルに書き込んでいますが、 usermod コマンドを使ったほうが良いかもしれません。

セットアップの自動化

新しいGPUサーバを導入するたびにこれらのセットアップを手動で行うのは大変なので、ansibleで自動化しました。 ansibleを使うと、パッケージのインストールからconfigファイルの編集まで、あらゆる設定を複数サーバに対してワンコマンドで一括適用することが可能です。

参考:Ansible ドキュメント — Ansible Documentation

以下は実際のansibleタスクです。data-rootのシンボリックリンクの作成以外の各種インストール、設定ファイルの編集を自動で行うようになっています。例えばsubuid/subgidの設定では、前掲のスクリプトを各サーバに配置・実行するようにしています。

ファイルの所有権

実際にDocker rootlessを使用してみます。

ホスト側で以下のようなディレクトリを用意します。通常のユーザのファイルと、rootのファイルが存在します。

$ ll ~/tutorial
合計 4.0K
drwxr-xr-x  2 user01 users   50  56 23:53 .
drwxr-xr-x 31 user01 users 4.0K  56 23:53 ..
-rw-r--r--  1 user01 users    0  56 23:53 my_file
-rw-r--r--  1 root   root     0  56 23:53 root_file

試しにこのディレクトリをマウントして、コンテナを実行してみます。

$ docker run -it -v ~/tutorial:/example ubuntu bash
root@7994a231e0c0:/# ll /example
total 0
drwxr-xr-x 2 root   root    50 May  6 14:53 ./
drwxr-xr-x 1 root   root    21 May  6 14:55 ../
-rw-r--r-- 1 root   root     0 May  6 14:53 my_file
-rw-r--r-- 1 nobody nogroup  0 May  6 14:53 root_file
root@7994a231e0c0:/# touch /example/root_file
touch: cannot touch '/example/root_file': Permission denied

このように、ホストの自ユーザがコンテナ内部ではroot、ホストのrootはnobodyにマップされており、ホストのroot権限が必要なファイルにはアクセスできません。

さいごに

これまでDockerはrootfulな運用が前提になっていたため、複数人でサーバを共有して使う研究室のような環境では活用が難しいという課題がありました。本記事では、rootlessモード導入の手順を紹介しました。rootlessモードにより各ユーザが分離したDocker環境を使用することができ、安心して仮想環境の恩恵を受けることができます。

Special Thanks

y60, hatiparallel

*1:全員がDockerを使うようになればサーバにインストールするライブラリの整備なども不要になり、鯖缶も仕事が減ります(重要)

*2:これに関しては、userns-remapを用いる手もあります

*3:slirp4netnsをインストールしない場合vpnkitで代用されますが、ネットワークパフォーマンスが低下します

*4:apt removeを行っても/var/lib/docker/の中身は削除されません

*5:もし入っていなければ、管理者がdocker-ce-rootless-extrasをaptでインストールします

*6:このパスは任意