なぜホストのユーザとコンテナのrootをマッピングしたいのか?
これはDockerコンテナで開発環境を作りたいからです。エディタ、IDE、実行環境、ログ、データベースなどをコンテナ化したいと思っています。
コンテナを使うとバージョンの違う開発環境を容易に取り替えたり、テスト環境を複数のコンテナを組み替えて簡単に作ったり、コンテナ自体もdockerfileで管理したり、コンテナの立ち上げもファイルで管理できます。ワクワクしますよね。
この時、多くの場合ではホストのディレクトリをボリュームとしてコンテナにバインドし作業ディレクトリとするかと思います。
ボリュームをバインドするとホスト側からも容易にファイルやディレクトリにアクセスできますので、いろいろと便利です。
たとえばvimをコンテナとして作るとすると、ファイル編集やファイル新規作成をすることになります。場合によってはディレクトリを作ったりすることもあります。
しかし、コンテナの中のユーザはrootなので、コンテナからホストのボリュームに対してroot権限ファイルが出来上がったり、既存のファイルの所有者がrootに変わってしまったりします。
こうなるとホスト側でもroot権限を持たないとそのファイルにはさわれなくなってしまい不便です。
コンテナの中に一般ユーザを作ってはどうか?
場合によってはそれでもいいかもしれません。
しかしコンテナの中ではroot権限で動けたほうが何かと便利なのも確かです。
コンテナの中でユーザを作ってそのユーザで活動させるとコンテナの中ではroot権限を失ってしまいます。
活動させるユーザが実行するコマンドにsudoをつける手もありますが、結局それはroot権限でファイルが編集・作成させることになります。
ユーザ名前空間
いちいちホストに戻ってファイル操作をしてコンテナに戻り…といった事はなるべくやりたくありません。
そういった苦労なしに、ホストのユーザとコンテナのrootを結び付けれたらな〜と思っていました。
ホストの作業ユーザが作業している感覚と同じように便利にコンテナで開発環境をつくりげたいのです。
それがユーザー名前空間を使うことでできました。
ユーザー名前空間を使う
ユーザー名前空間はもともとコンテナの中のrootをホスト上では一般ユーザとして扱えるようにする技術だそうです。
Linuxカーネルではちょっと前からこの機能が搭載されたそうです。
本来の使い方としてはおそらくセキュリティとしてなんだと思いますが、この機能を使うとホストのユーザとコンテナの中のrootを結びつけることが可能になることがわかりました。
これが正しい方法なのかどうかはちょっとなんとも言えませんが、とりあえずコンテナからファイルを編集したり、新規作成した場合にファイルの所有者が変わったりすることがなくなったので楽になりました。
ユーザ名前空間は無効になっている場合も
@MINOの環境はArch Linuxですが、Archで採用しているカーネルはデフォルトでCONFIG_USERNSがn(無効)になっていました。
Arch以外のディストリビューションで採用されているカーネルがどうなっているのかはちょっとわかりませんので、.configを確認してみてください。
$ zcat /prop/config.gz
このファイルの中に
CONFIG_USERNS=y
の記述があればユーザ名前空間が有効になっています。
無効になっていると以下のような記述があるかもしれません。
# CONFIG_USERNS is not set
もしCONFIG_USERNSが無効なら有効にしてカーネルのリビルドをするか、もしくは有効になっているビルド済みカーネルをダウンロードして使用する必要があります。
ですのでリビルドに慣れていない人には若干ハードルが高いかもしれません。
@MINOもカーネルビルドは初心者なのでガクブルしながらでした。
@MINOはArchのABS経由でカーネルをリビルドしました。意外と難しくはなかったです。
カーネルのリビルドに関してはArchWikiに詳しく手順がありますので、参考にしてみてください。
ユーザ名前空間のマッピングのための準備
マッピングのために2つファイルを用意します。(ディストリビューションによってはすでにあるかもしれません)
/etc/subuid
と /etc/subgid
です。
例えば、コンテナのrootにマッピングしたいホストのユーザがminoで、minoはuid1000、gid1000だとします。
// subuid
mino:1000:65536
// subgid
mino:1000:65536
設定を記述したら保存しておいてください。
設定の意味に関してちょっと補足
ちょっと勉強足らずで間違っている可能性大ですが、
65536というのはどこまでマッピングするかのカウントを表しているようです。
上記のような指定の場合、ホストのuidとコンテナのuidの関係は以下の様になると思います(と思います。自信なし)
ホストuid | コンテナuid |
---|---|
1000(mino) | 0(root) |
1001 | 1 |
1002 | 2 |
1003 | 3 |
… | … |
1100 | 100 |
1101 | 101 |
… | … |
66536 | 65536 |
イメージ的にはオフセットマッピングといった感じでしょうか?
指定したホスト側のuidから指定したカウント分マッピングするお、ということなんだと思います。
複数のマッピング設定を記述し、複雑なマッピングを実現することも(Dockerという意味ではなくLinuxとして)可能なようですが、これについてはもう少し勉強していきたいと思います。
Dockerデーモンをユーザ名前空間を有効にして起動する
さてカーネルでユーザ名前空間を有効にし、subuid、subgidファイルも準備したら、Dockerデーモンもユーザー名前空間を有効にして起動する必要があります。
systemdで管理している場合、serviceファイルにてDockerデーモン起動を行っているかと思います。
そのため、docker.serviceを編集して起動オプションを付加することができます。
docker.serviceは以下のディレクトリに入っているかと思います。
/usr/lib/systemd/system/
ファイルにはいくつかの設定が書かれていますが、ExecStartを探してください。
@MINOの環境では以下のように記述されていました。
~~~~~~~~
~~~~~~~~
ExecStart=/usr/bin/dockerd -H fd://
~~~~~~~~
~~~~~~~~
これを以下のように修正します。
~~~~~~~~
~~~~~~~~
ExecStart=/usr/bin/dockerd --user-remap=1000:1000 -H fd://
~~~~~~~~
~~~~~~~~
–user-remap=1000:1000というのがユーザ名前空間の設定になります。これはホストのuid1000、gid1000のユーザをマッピングに使うという意味です。
つまりコンテナのrootはホスト上でこのuid1000、gid1000のユーザにマッピングされるということです。
以下のようにユーザ名、グループ名での指定も可能です。ホスト側の作業ユーザminoをコンテナのrootとマッピングさせるという意味になります。
~~~~~~~~
~~~~~~~~
ExecStart=/usr/bin/dockerd --user-remap=mino:mino -H fd://
~~~~~~~~
~~~~~~~~
さてdocker.serviceを編集したら、dockerデーモンの再起動です。
がその前にserviceの新しい設定を有効にする必要がありますので以下のようにします。
$sudo systemctl daemon-reload
その後、以下のようにしてデーモンを再起動します。
$sudo systemctl restart docker.service
そうするとユーザ名前空間が有効になった状態でdockerデーモンが起動するはずです。
テストしてみる
実際にマッピングがされているか見てみます。
適当なコンテナを作って適当なコマンドを実行してみます。
$ docker run -it --rm debian /bin/bash
root@042831c699ee:/# sleep 1000
これを別ターミナルやマルチプレクサの別ウインドウにてプロセス確認してみます。
$ ps aux
~~~~~~
~~~~~~
mino 6934 0.0 0.0 4236 684 pts/1 S+ 17:01 0:00 sleep 1000
~~~~~~
~~~~~~
おお!!コンテナの中で実行したsleepがホストのminoで実行されていることになっていますね。
でもコンテナの中からpsで確認するとrootであることがわかります。
普通、コンテナの中で実行したコマンドやプロセスは(別途設定をしない限り)root権限で動きますし、それはホスト側でもrootとして捉えられます。
ユーザ名前空間をONにするとコンテナの中ではroot、ホストでは一般ユーザという状況を作り出すことができます。
ファイルはどうでしょうか?
今度はホストの任意のディレクトリをボリュームとしてバインドし、その中にファイルを作成してみます。
$ docker run -it --rm -v /host/dir/:/condir debian /bin/bash
root@04bf6945c89f:/# cd /condir
root@04bf6945c89f:/condir# echo "username" > username.txt
root@04bf6945c89f:/condir# ls -al
~~~~~
-rw-r--r-- 1 root root 9 Jan 4 08:06 username.txt
~~~~~
~~~~~
この様にコンテナの中から確認するとこのファイルはroot所有になっています。
しかしホスト側からそのファイルを確認すると…
$ ls -al /host/dir
~~~~~
-rw-r--r-- 1 mino mino 9 1月 4 17:06 username.tx
~~~~~
~~~~~
やはり、ホスト側のminoの所有となっていることがわかるかと思います。
これを活用すると、コンテナで開発環境を構築するのが便利になりそうです。
補足・ハマったポイント
ユーザー名前空間をONにしていなかった時のコンテナ作成時のエラーについて
カーネルオプションのCONFIG_USERNSが有効になっておらず、–user-remapを用いてdockerデーモンの起動を行った場合、以下のようなエラーがでます。
$ docker run -it --rm debian Unable to find image 'debian:latest' locally latest: Pulling from library/debian 75a822cd7888: Pull complete Digest: sha256:f7062cf040f67f0c26ff46b3b44fe036c29468a7e69d8170f37c57f2eec1261b Status: Downloaded newer image for debian:latest docker: Error response from daemon: oci runtime error: USER namespaces aren't enabled in the kernel.
「ユーザー名前空間が有効になってないお」
というメッセージですね。
こうなる場合はカーネルの状況を確認してください。
今まで使っていたイメージ・コンテナが見当たらない
ユーザ名前空間を有効にしてデーモンの起動を起動すると、今までroot権限でデーモンを立ち上げていた時のdockerイメージやコンテナが見当たらなくなると思います。
コマンド補完してもイメージ名が出ずに焦るかもしれません。
どうやらユーザ名前空間をONにしてDockerデーモンを起動すると、そのマッピングしたユーザごとにイメージ等が格納されるようです。
dockerの実ファイルが格納されているのは/var/lib/docker/ですが、そこになにやら見慣れないディレクトリが出来上がっています。
sudo ls -al /var/lib/docker/
drwx--x--x 12 root root 4096 12月 14 21:48 .
drwxr-xr-x 20 root root 4096 1月 4 00:00 ..
drwx------ 10 mino mino 4096 12月 14 21:07 1000.1000
drwx------ 10 100000 100000 4096 12月 14 21:48 100000.100000
drwx------ 5 root root 4096 1月 4 15:23 containers
drwx------ 5 root root 4096 11月 17 21:15 devicemapper
drwx------ 3 root root 4096 11月 17 20:46 image
drwxr-x--- 3 root root 4096 11月 17 20:46 network
drwx------ 2 root root 4096 11月 17 20:46 swarm
drwx------ 2 root root 4096 1月 3 15:33 tmp
drwx------ 2 root root 4096 11月 17 20:46 trust
drwx------ 9 root root 4096 1月 3 19:19 volume
1000.1000というディレクトリは先の例でminoとコンテナのrootをマッピングしたために生まれたディレクトリです。
このディレクトリの中を見てみると…
$ sudo ls -al /var/lib/docker/1000.1000
drwx------ 10 mino mino 4096 12月 14 21:07 .
drwx--x--x 12 root root 4096 12月 14 21:48 ..
drwx------ 5 mino mino 4096 1月 4 15:25 containers
drwx------ 5 mino mino 4096 12月 14 21:11 devicemapper
drwx------ 3 root root 4096 12月 14 21:07 image
drwxr-x--- 3 root root 4096 12月 14 21:07 network
drwx------ 2 root root 4096 12月 14 21:07 swarm
drwx------ 2 mino mino 4096 12月 22 19:26 tmp
drwx------ 2 root root 4096 12月 14 21:07 trust
drwx------ 2 mino mino 4096 12月 19 23:08 volume
/var/lib/docker/の内容と同じですね。どうも入れ子になっているようです。
psで見る限りdockerデーモンはrootで動いているように見えるのですが、何らかの力が働いているようですね。
もともと名前空間はさまざまな「分離・隔離」を実現する機能ですから、これは正常な動きなんでしょうね。
この辺はちゃんとした挙動が理解できていませんのでもっと勉強しないと駄目ですね。
ともかくも、こういうふうにユーザ別々にイメージとかが格納されるようになるよということでした。
セキュリティーの懸念
Archがなぜユーザー名前空間のオプションを無効にしているのかですが、どうやら権限昇格に関しての懸念があるからのようです。
セキュリティに配慮して、デフォルトの Arch カーネルでは、非特権ユーザーではコンテナを実行できないようになっています
ArchWiki Linux Containers
Support user namespaces. This allows containers, i.e. vservers, to use user namespaces to provide different user info for different servers.
FS#36969 – [linux] 3.13 add CONFIG_USER_NS
正直@MINOにはこの点についての知識が弱いので、あまり説明できません。
@MINOとしては今回紹介した方法は、ローカルの開発環境を構築するために使うつもりなので、まあ大丈夫かな〜と思っていますが、使用には十分注意してください。
まとめ
Dockerを使った開発環境の構築は楽しいばかりでなく、いろいろ勉強になりますね。
コンテナ技術とは随分良いものを開発してくれたものです。