LoginSignup
3
2

WSL2でDocker rootlessを試す

Posted at

結論

  • 普通に動きます
  • WSL2特有の問題はほんのわずか
  • rootless自体制約が多い(当たり前だけど)
  • インストールは簡単になった
  • 違和感なく使えるようになるのはもうちょっと先かなぁ

インストール

systemdを有効にする

まず、前準備ですが、世のドキュメントに従うならsystemdを有効にした方がよいでしょう。(ここは必須ではないので、systemdを使わない場合は、docker-rootless.shコマンドを使って起動させればよいと思います)
systemdにはストア版のWSL2が必要ですが、インストールは簡単です。

$ wsl.exe --update

wsl.conf に設定を追加することで systemd を使うようになります。

/etc/wsl.conf
[boot]
systemd = true

設定が終わったらwsl.exe --shutdownで一旦停止してインスタンスを再起動しましょう。

公式に従いDockerをインストール

準備ができたら公式ドキュメントに従いDockerをインストールします。
https://matsuand.github.io/docs.docker.jp.onthefly/engine/install/ubuntu/
特に難しいことはないと思います。
※Ubuntu20.10以降でsystemdを使わない場合には iptables をlagacyに変えないと動かないかもしれません。systemdを使っていれば特に気にしなくて大丈夫です。

rootlessインストール

まずはuidmapをインストールしておきます。

$ sudo apt install -y uidmap

これで公式ドキュメントにある前提条件は満たせるはずです。

あとはドキュメントに従ってインストールします。
https://matsuand.github.io/docs.docker.jp.onthefly/engine/security/rootless/

Docker(rootfull)を停止しておきます。

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

肝心な

$ dockerd-rootless-setuptool.sh install

がエラーになると思います。ip_tablesモジュールが見当たらないと。。。
いやいやWSL2でもiptablesは使えてるし。。。

モジュールがロードされるかどうかでiptablesの有無を判定しているようです。WSL2ではカーネル内に埋められているので機能自体は存在するもののモジュールはロードされません。

そこで、一旦エラーメッセージにあるように--skip-iptalbes を付けてインストールします。

$ dockerd-rootless-setuptool.sh install --skip-iptables

これで完了。ただし、iptablesを使わないモードになってしまうので修正します。

~/.config/systemd/user/docker.serviceを編集して docker-rootless.sh の引数--iptables=falseを削除します。
iptablesは利用可能なので消してしまって問題ありません。というか消さないとiptablesを使えない。

再起動しておきます。

$ systemctl --user daemon-reload
$ systemctl --user restart docker

実はWSL2特有の課題はこれだけ。iptablesが利用可能かどうかのチェック方法だけなのでそのうち解決しそう。

使い勝手が変わる

rootless Dockerを使ったことが無い人には少し戸惑いがあるかもしれません。
ここでは通常Dockerとの違いを見ていきます。

特権ポート(1024未満)が開けない

いままで使っていたコンテナを起動させると以下のようなエラーがでるかもしれません。

Error response from daemon: driver failed programming external connectivity on endpoint proxy (44652ee95396c8c263c730a6509f4bb721c847e8c85c9ef55a6d4b1142becd39): Error starting userland proxy: error while calling PortManager.AddPort(): cannot expose privileged port 443, you can add 'net.ipv4.ip_unprivileged_port_start=443' to /etc/sysctl.conf (currently 1024), or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp4 127.0.0.1:443: bind: permission denied

エラーメッセージに書いてある通りなのですが、443 でリッスンしようとして失敗しています。
一般的に1024未満のポートはrootでしかリッスンできません。これを行うには rootlesskit にケーパビリティをセットするかsysctlでnet.ipv4.ip_unprivileged_port_startを設定しなさいと言われます。
当たり前といえば当たり前です。

$ sudo setcap cap_net_bind_service=ep $(which rootlesskit)

daemon.json の場所が違う

/etc/docker/daemon.json で設定を加えている方も多いと思います。ユーザー権限でdockerdを動かした場合、このファイルは参照されません。
代わりに ~/.config/docker/daemon.json が参照されますのでこちらへ移動させましょう。

ホストからネットワークが見えない

Dockerを起動すると docker0 ネットワーク。さらにコンテナを起動するとコンテナネットワークが作られます。
通常のDockerだと ipコマンドで確認すると以下のようにdocker0ネットワークが見えていました。

5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:e6:ae:fc:4d brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

しかし、rootlessだと見当たりません。また、iptablesを確認してもDocker関連のルールが見当たりません。ないのでしょうか?
いえ、そんなことはありません。コンテナ内にはネットワークインタフェースは間違いなく存在します。

名前空間が切り離されてホストからは見えなくなっているようですがip netnsコマンドで確認しても見つかりません。
ipコマンドで確認するためには /var/run/netns にエントリーが必要だそうです。

$ sudo mkdir /var/run/netns
$ sudo ln -s /proc/$(pgrep dockerd)/ns/net /var/run/netns/docker

$ ip netns
docker

dockerdプロセスの ns/net を /var/run/netns にシンボリックリンクすると見えるようになります。
作成したエントリーから docker名前空間(dockerdプロセスの名前空間)に入ります。

$ sudo ip netns exec docker bash

ここではipコマンドで通常のDocker同様にdocker0やコンテナのインタフェースが確認できます。
iptablesを確認するとこちらも通常と同様にDocker関連のルールを確認できます。

コンテナを起動するとホストに大量のインタフェースが現れて煩雑だったのですが、見えないとすっきりして良いですね。

ホストへアクセスできない

Dockerでは通常デフォルトゲートウェイ=ホストになっています。ところが、先の名前空間が関連するのかこのIPアドレスにアクセスできません。

類似の?問題としてソースアドレスの問題を解決するには以下の方法があるそうなのですが、
https://matsuand.github.io/docs.docker.jp.onthefly/engine/security/rootless/#networking-errors
docker run -p does not propagate source IP addressesの項目
どうもこれとも違うようです。
この設定を行うとゲートウェイにpingを投げて返答を貰えるようになったのですが、ホストで開いたポートへはアクセスできません。

ホストへアクセスするためには、ホストが使用するネットワークのIPアドレスを指定しないといけなさそうです。

ディレクトリマッピングのオーナーがrootじゃない

Dockerで存在しないディレクトリやファイルをマウントすると、rootがオーナーのディレクトリが作成されていたと思います。

実行権限がユーザーなのでここはユーザー権限になります。
逆にいままでroot権限のディレクトリをマウントしてしまっていた場合書き込み(場合によっては読み込みも)できないことがあります。

これだけなら喜ばしい話なのですが、UID/GIDがマップされるのでホスト空間では root が自身のUID, そしてコンテナ内で使用されるUID(例えば1000)には別の番号がマップされるのでホストとコンテナでUIDを揃えていた場合などは崩壊してしまいます。

ここは考え方を変える必要がありますね。

環境変数が必要な場合がある?

インストール時に以下のような情報が表示されます

[INFO] Some applications may require the following environment variable too:
export DOCKER_HOST=unix:///run/user/1000//docker.sock

dockerコマンドの場合には以下のように currentContext が書き込まれ、ユーザーソケットを利用するようになっていますが、他のコマンド等でDOCKER_HOSTを参照する際には環境変数が必要なようです。通常は不要?

~/.docker/config.json
{
        "auths": {},
        "currentContext": "rootless"
}
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2