コンテンツにスキップ

DockerのRootlessを考える

何も考えない場合Dockerはroot権限で動くが、そうしたければ非root権限( = Rootless)で動かすこともできる。

Dockerの実行権限について

Dockerのデーモンとコンテナはプロセスとして動き、それぞれが権限を持つ。

そのためDockerの話をしていてRootlessという単語が出てきたら、何がRootlessなのか(デーモンが?コンテナが?)を意識する必要がある。

以下でDockerデーモン・Dockerコンテナとroot権限・非root権限の組み合わせを考える。

Dockerデーモン Dockerコンテナ 説明
root root デフォルト。何でもできて便利だが脆弱性によってホスト、コンテナともに乗っ取られるかもしれない。
root 非root デフォルトよりマシだが、ホスト側の脆弱性によってホストを乗っ取られるかもしれない。
非root root デフォルトよりマシだが、コンテナ側の脆弱性によってコンテナを乗っ取られるかもしれない。
非root 非root デフォルトに比べてホスト、コンテナともに被害は抑えられるが、ネットワークやファイル読み書きで問題が出ることがある。

権限は安全と利便性の両方を考慮して決めるのが良いと思う。

デーモン・コンテナ共にユーザー権限で間に合うのであればそうすれば良いし、5分以内にコンテナを立ち上げないと地球が爆発してしまう事態であれば両方rootで済ませてしまってもいいかもしれない。

Rootlessモードを使う

Rootlessモードについて

Rootlessモード(Rootless Dockerともいう)を使うとDockerデーモンを非root権限で動作させることができる。

Rootlessモードを使うとセキュリティが向上する一方で、以下のようなデメリットも有る。

  • 80や443などのWell-knownポートを使うには工夫が必要となる
  • 権限が狭まる分、Dockerコンテナが上手く起動しないこともある

docker context lsでDockerのコンテキスト(接続先デーモン)の一覧と権限を確認できる。

アスタリスクの行のDOCKER ENDPOINTunix:///var/run/docker.sockであればroot権限で動作している。

1
2
3
$ docker context ls
NAME        DESCRIPTION                               DOCKER ENDPOINT               ERROR
default *   Current DOCKER_HOST based configuration   unix:///var/run/docker.sock

Rootlessモードをインストールする

Rootless mode | Docker Docs

1
$ sudo apt install uidmap

従属UID/GIDが65536以上であることを確認する。

1
2
3
4
$ grep ^$(whoami): /etc/subuid
ユーザー名:100000:65536
$ grep ^$(whoami): /etc/subgid
ユーザー名:100000:65536

root権限のDockerデーモンが動いている場合は以下のコマンドを実行して止める。

1
2
3
4
5
# Dockerデーモン(docker.service)とソケット(docker.socket)を停止し、無効化する
$ sudo systemctl disable --now docker.service docker.socket

# Dockerソケットを削除する
$ sudo rm /var/run/docker.sock

ユーザーにRootlessモードをインストールする。ユーザー権限でインストールするのでsudoは要らない。

1
$ /usr/bin/dockerd-rootless-setuptool.sh install

Rootlessモード になっているか確認する。docker context lsを実行したとき、アスタリスクの行のDESCRIPTIONRootless modeであればRootlessモードになっている。

1
2
3
4
$ docker context ls
NAME         DESCRIPTION                               DOCKER ENDPOINT                     ERROR
default      Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
rootless *   Rootless mode                             unix:///run/user/1000/docker.sock
1
$ systemctl --user restart docker.service

Rootlessモードを無効にする

Rootlessモードを元に戻したいときは、以下を実行する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# RootlessモードのDockerのデーモンを停止する
$ systemctl --user stop docker.service
$ systemctl --user disable docker.service

# RootlessモードのDockerのソケットを削除する
$ sudo rm /run/user/1000/docker.sock

# コンテキストを通常に戻す
$ docker context use default

# Dockerを再起動する
$ sudo systemctl enable docker.service docker.socket
$ sudo systemctl restart docker.service docker.socket

# コンテキストを確認する
$ docker context ls
NAME         DESCRIPTION                               DOCKER ENDPOINT                     ERROR
default *    Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
rootless     Rootless mode                             unix:///run/user/1000/docker.sock

Rootlessモードを有効にする

やっぱりRootlessモードにしたいときは、以下を実行する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Dockerのデーモンを停止する
$ sudo systemctl stop docker.service docker.socket
$ sudo systemctl disable docker.service docker.socket

# Dockerのソケットを削除する
$ sudo rm /var/run/docker.sock

# コンテキストをRootlessにする
$ docker context use rootless

# Dockerを再起動する
$ systemctl --user enable docker.service
$ systemctl --user restart docker.service

# コンテキストを確認する
$ docker context ls
NAME         DESCRIPTION                               DOCKER ENDPOINT                     ERROR
default      Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
rootless *   Rootless mode                             unix:///run/user/1000/docker.sock

ターミナルを開いている間だけRootlessモードを有効にする

上記の手順は恒久的にモードを切り替える方法だが、ターミナルを開いている間だけRootlessモードを有効にしたいときもある。

そういうときは以下を実行する。

1
$ export DOCKER_CONTEXT=rootless

RootlessモードでWell-knownポートを使う

Rootlessモードで80や443などのWell-knownポートを使いたい場合、工夫が要る。

バイナリにWell-knownポートのバインド権限を与えることができるsetcapというコマンドがあるので、それが使える。

RootlessモードのDockerはrootlesskitを使ってポートフォワーディングを行うのでrootlesskitにバインド権限を与えれば良い。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# rootlesskitにWell-knownポートのバインド権限を与える
# cap_net_bind_service=ep: Well-knownポートのバインド権限をep(effective+permitted)
$ sudo setcap cap_net_bind_service=ep $(which rootlesskit)
$ systemctl --user restart docker.service

# 権限を確認する
$ getcap $(which rootlesskit)
/usr/bin/rootlesskit cap_net_bind_service=ep

# 権限を削除する
$ sudo setcap -r $(which rootlesskit)
$ systemctl --user restart docker.service

RootlessモードのDockerデーモンを常駐させる

Rootlessモードならではの注意点の一つとして、SSHセッションを閉じるとDockerデーモンが終了してしまうことがある。

何故かというと、RootlessモードのDockerデーモンは~/.config/systemd/user/docker.serviceで起動しており、これはsystemdのユーザーサービスであり、systemdのユーザーサービスはSSHセッションを閉じると終了してしまうからである。

しかし以下のコマンドでユーザーサービスの常駐を有効化することで、SSHセッションを閉じてもRootlessモードのDockerデーモンを起動し続けることができる。

1
2
3
4
5
6
7
8
9
# ユーザーサービスの常駐を有効化する
$ sudo loginctl enable-linger $(whoami)

# 常駐の状態を確認する
$ loginctl show-user $(whoami) --property=Linger
Linger=yes

# ユーザーサービスの常駐を無効化する
$ sudo loginctl disable-linger $(whoami)

コンテナを非root権限で動作させる

docker

--user: 1000:1000でDockerを動かしているユーザと同じ権限で動作させることができる。

Dockerfile

USERでroot以外のユーザーを指定することで非root権限で動作させることができる。

docker-compose.yml

user: 1000:1000でDockerを動かしているユーザと同じ権限で動作させることができる。

コンテナの実行権限を確認する

コンテナがどの権限で動いているかは以下のコマンドで確認できる。

1
2
$ docker exec $CONTAINER_NAME id
uid=0(root) gid=0(root) groups=0(root)

UIDのマッピング

Rootless DockerではコンテナUIDはホストUIDへリマッピングされる。

コンテナUID ホストUID 補足
0 (root) 1000 (owner) ホストユーザー自身
N (1~65536) 100000 + N subordinate UID範囲
1000 (node) 101000 N=1000のケース

Rootless Dockerはそのコンセプト上、コンテナUIDをrootユーザーのUID(0)へマッピングすることはない。