Dockerをrootlessに変更してrootユーザーの呪縛から開放される

Posted by 雅楽斎 on Sunday, November 14, 2021

TOC

Podmanでいち早く実装していたrootlessでのコンテナ実行にDockerが追いつく

2019年7月にリリースされたDocker 19.03でexperimentalとして追加されたrootlessモードが2020年12月にリリースされたDocker 20.10で正式機能としてリリースされたので、特に大儀なくroot権限で動かしていたコンテナを一般ユーザーで動かすように変えました。手順が結構多くて大変でした。

手元でちゃんと使っているDockerがRaspberry Pi 4(RAM8GB)なので、この上で動かしているUbuntu 21.10(aarch64)での動作確認です。

余談としては、以前Podmanで一般ユーザーでのコンテナも使っていましたが、docker-composeを使いたかったので最近はPodmanは触っていません…

今、改めてUbuntuでPodmanを使う(2020年11月)

試したDockerのバージョンはDocker Inc.配布のDocker-CE

最近使っていたDockerはこれといって最新機能を使っているわけではなかったのでUbuntuが配布しているもの(パッケージ名はdocker.io)を使っていましたが、こっちだとrootless版として簡単に使えるようになっているように見えなかったので、削除してDocker-CEのrootless版をインストールする手順になります。

Docker デーモンをルート以外のユーザで実行(Rootless モード) — Docker-docs-ja 19.03 ドキュメント

Dockerイメージ・Dockerコンテナは一旦諦め(ボリュームマウントを除く)

Dockerを使う際にビルドするイメージとコンテナについて、rootで動かすDockerで使っていたイメージとコンテナについては一旦諦めることにしました。

パーミッションが変わることで変な影響が出るのが嫌だったためで、普段からイメージをリポジトリ管理していたり、バックアップからのリストア手順を確立していれば問題ないのかなと思います。

ただ、ボリュームマウントはランタイムとは関係なくファイルシステムをループバックマウントしてるに等しいので、ownerの変更だけで使い続けます。

これまで使っていたroot版dockerの停止・削除

動作中コンテナの停止

# docker container ps
CONTAINER ID   IMAGE                             COMMAND                  CREATED        STATUS      PORTS                                       NAMES
b8cd5ce26c84   ghcr.io/linuxserver/calibre-web   "/init"                  3 weeks ago    Up 5 days   0.0.0.0:8083->8083/tcp, :::8083->8083/tcp   calibre-web
43feb8312fa5   nextcloud                         "/entrypoint.sh apac…"   9 months ago   Up 5 days   0.0.0.0:8080->80/tcp, :::8080->80/tcp       nextcloud_app_1
13781e2cf50e   mariadb                           "docker-entrypoint.s…"   9 months ago   Up 5 days   3306/tcp                                    nextcloud_db_1

こんな感じで動作中のコンテナが表示されるので、docker-composeやdockerで片っ端から止めて、動作中コンテナをなくします。

# docker container ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

dockerの停止

# systemctl disable --now docker.service 
Synchronizing state of docker.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable docker
Removed /etc/systemd/system/multi-user.target.wants/docker.service.
Warning: Stopping docker.service, but it can still be activated by:
  docker.socket

# systemctl disable --now docker.socket 
Removed /etc/systemd/system/sockets.target.wants/docker.socket.

dockerの削除

# apt-get remove docker docker.io containerd runc --purge

rootless版のDocker-CEをインストール

aptでインストール

# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] http://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# apt-get install uidmap
# apt-get install docker-ce
# apt-get install docker-ce-rootless-extras

【追記】Raspberry PiでUbuntu 21.10以降の場合は linux-modules-extra-raspi パッケージをインストールする

この記事は元々Ubuntu 21.04用に記載していましたが、Ubuntu 21.10の場合vethで始まるNICがなくなっており、Ubuntu 21.10で追加されたlinux-modules-extra-raspiパッケージをインストールする必要がありました。

# apt-get install linux-modules-extra-raspi

パッケージインストール以外のセットアップ

rootless版に移行した後が面倒なので、インストールしたdockerを停止します。

# systemctl disable --now docker.service
# systemctl disable --now docker.socket

rootless版dockerを動かす一般ユーザーで以下のコマンドを実行します。

$ dockerd-rootless-setuptool.sh install
[INFO] Creating /home/hogehoge/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
+ systemctl --user start docker.service
+ sleep 3
+ systemctl --user --no-pager --full status docker.service
● docker.service - Docker Application Container Engine (Rootless)
     Loaded: loaded (/home/hogehoge/.config/systemd/user/docker.service; disabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-11-14 11:18:26 JST; 3s ago
       Docs: https://docs.docker.com/go/rootless/
   Main PID: 379478 (rootlesskit)
      Tasks: 41
     Memory: 51.4M
        CPU: 1.163s
     CGroup: /user.slice/user-1001.slice/user@1001.service/app.slice/docker.service
             ├─379478 rootlesskit --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
             ├─379489 /proc/self/exe --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
             ├─379508 slirp4netns --mtu 65520 -r 3 --disable-host-loopback --enable-sandbox --enable-seccomp 379489 tap0
             ├─379515 dockerd
             ├─379536 containerd --config /run/user/1001/docker/containerd/containerd.toml --log-level info
             └─379571 [iptables]

11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.245684816+09:00" level=info msg="ccResolverWrapper: sending update to cc: {[{unix:///run/user/1001/docker/containerd/containerd.sock  <nil> 0 <nil>}] <nil> <nil>}" module=grpc
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.245778762+09:00" level=info msg="ClientConn switching balancer to \"pick_first\"" module=grpc
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.248753408+09:00" level=info msg="parsed scheme: \"unix\"" module=grpc
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.248845522+09:00" level=info msg="scheme \"unix\" not registered, fallback to default scheme" module=grpc
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.248918931+09:00" level=info msg="ccResolverWrapper: sending update to cc: {[{unix:///run/user/1001/docker/containerd/containerd.sock  <nil> 0 <nil>}] <nil> <nil>}" module=grpc
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.248970302+09:00" level=info msg="ClientConn switching balancer to \"pick_first\"" module=grpc
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.670007593+09:00" level=warning msg="Unable to find cpu controller"
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.670146523+09:00" level=warning msg="Unable to find io controller"
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.670214524+09:00" level=warning msg="Unable to find cpuset controller"
11月 14 11:18:29 rpi4 dockerd-rootless.sh[379515]: time="2021-11-14T11:18:29.671131046+09:00" level=info msg="Loading containers: start."
+ DOCKER_HOST=unix:///run/user/1001/docker.sock /usr/bin/docker version
Client: Docker Engine - Community
 Version:           20.10.10
 API version:       1.41
 Go version:        go1.16.9
 Git commit:        b485636
 Built:             Mon Oct 25 07:42:23 2021
 OS/Arch:           linux/arm64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.10
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.9
  Git commit:       e2f740d
  Built:            Mon Oct 25 07:40:56 2021
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.4.11
  GitCommit:        5b46e404f6b9f661a205e28d59c982d3634148f8
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
+ systemctl --user enable docker.service
Created symlink /home/hogehoge/.config/systemd/user/default.target.wants/docker.service → /home/hogehoge/.config/systemd/user/docker.service.
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger hogehoge`

[INFO] Creating CLI context "rootless"
Successfully created context "rootless"

[INFO] Make sure the following environment variables are set (or add them to ~/.bashrc):

export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1001/docker.sock

で、後々最後の行の環境変数DOCKER_HOSTを手動で設定する必要があります。そういう設定をするのがインストールコマンドの役割だと思うんですが。

起動してみる

$ docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.6.3-docker)

Server:
ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
errors pretty printing info

実行できるはずの一般ユーザーで動作しているDocker daemonに接続できないというエラーが出ます。

環境変数DOCKER_HOSTは”unix://$XDG_RUNTIME_DIR/docker.sock”らしいので、dockerコマンドを実行する一般ユーザーでdockerコマンド実行前に設定する必要がありますが、dockerコマンドとdocker-composeコマンド専用の環境変数を設定するのも癪なので、エイリアスを作るのに~/.bash_aliasesに以下の内容を記載します(ない場合はファイルを作成します)

alias docker='DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock docker'
alias docker-compose='DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock docker-compose'

また、OS起動時にdocker-composeのrestart: alwaysによるコンテナの自動起動でも実現するため、ディレクトリの作成と環境変数定義をします。

$ mkdir ~/.config/environment.d
$ vi ~/.config/environment.d/rootless-docker.conf

~/.config/environment.d/rootless-docker.confは以下の内容で作成します。こうすることで、systemdのユーザーのユニットファイルの実行時に定義した環境変数が適用されます。

DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

systemd/ユーザー - ArchWiki

$HOME ディレクトリが存在するユーザーの場合、~/.config/environment.d/ ディレクトリに NAME=VAL という形式で環境変数を記述した .conf ファイルを作成する。ユーザーのユニットファイルにのみ影響します。詳しくは environment.d(5) を見てください。

bashでログインし直してもう一度実行します。

$ docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.6.3-docker)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 20.10.10
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: false
  userxattr: true
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 5b46e404f6b9f661a205e28d59c982d3634148f8
 runc version: v1.0.2-0-g52b36a2
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
  rootless
  cgroupns
 Kernel Version: 5.13.0-1009-raspi
 Operating System: Ubuntu 21.10
 OSType: linux
 Architecture: aarch64
 CPUs: 4
 Total Memory: 7.626GiB
 Name: rpi4
 ID: EZR5:44EP:CKMV:GEMI:NYWH:SG6Y:7CSF:BHUS:NCOW:SJWV:TCQY:YPJZ
 Docker Root Dir: /home/hogehoge/.local/share/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No cpu cfs quota support
WARNING: No cpu cfs period support
WARNING: No cpu shares support
WARNING: No cpuset support
WARNING: No io.weight support
WARNING: No io.weight (per device) support
WARNING: No io.max (rbps) support
WARNING: No io.max (wbps) support
WARNING: No io.max (riops) support
WARNING: No io.max (wiops) support
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled

hello-worldコンテナを実行

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
93288797bd35: Pull complete 
Digest: sha256:cc15c5b292d8525effc0f89cb299f1804f3a725c8d05e158653a563f15e4f685
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

一般ユーザーでdockerコンテナを実行できることが確認できました。

【追記】ユーザーがログインしていない状態でもdockerコンテナを実行できるようにloginctlで実行ユーザーをlinger指定する

systemdを--userで実行する場合、誰かがそのユーザーでsshなりGUIでログインしないと実行されません。1

今回はrootless docker実行ユーザーであるhogehogeユーザーをlinger指定します。

# loginctl enable-linger hogehoge

イメージ・コンテナの格納場所をUSB接続のHDDに変更する

Raspberry Pi 4で運用しているDockerはそのまま使うとMicroSDの容量を食う上に寿命も物凄い速さで縮むので、Dockerイメージのような大容量データはUSB接続のHDDに格納していましたので、同じ様にrootless接続のDockerでもUSB接続のHDDを使用するようにします。

NextcloudをRaspberry Pi 4にセットアップ(docker-composeで運用)、WebDAVでの動作確認も。

具体的な変更箇所は、OS起動時にHDDのマウントを待ってからユーザー権限のdocker daemonが起動するようにすることと、格納場所としてHDDを指定することの2つです。

まず、格納場所のディレクトリを作成してownerとgroupを変更します。

# mkdir /mnt/usbhdd1/var/docker-image-rootless
# chown hogehoge:hogehoge docker-image-rootless/

ユーザーデーモンのsystemdの設定ファイルは~/.config/systemd/user/docker.serviceなので、以下のように変更します。

【追記】Docker23にて-gオプションが廃止されたため、下記変更内容を--data-rootオプションに変更しています。

Dockerを23にアップグレードしたら-g(–graph)オプションが廃止されてdockerdが起動できなくなっていた

--- /home/hogehoge/.config/systemd/user/docker.service.org	2021-11-14 13:31:04.376227308 +0900
+++ /home/hogehoge/.config/systemd/user/docker.service	2021-11-14 13:35:47.598313568 +0900
@@ -1,10 +1,11 @@
 [Unit]
 Description=Docker Application Container Engine (Rootless)
 Documentation=https://docs.docker.com/go/rootless/
+After=mnt-usbhdd1.automount
 
 [Service]
 Environment=PATH=/usr/bin:/sbin:/usr/sbin:/home/hogehoge/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
-ExecStart=/usr/bin/dockerd-rootless.sh 
+ExecStart=/usr/bin/dockerd-rootless.sh --data-root /mnt/usbhdd1/var/docker-image-rootless
 ExecReload=/bin/kill -s HUP $MAINPID
 TimeoutSec=0
 RestartSec=2

設定ファイルの変更を反映します。

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

変更が反映された事を確認します。

$ systemctl cat --user docker

もう一回hello-worldコンテナを実行すると、格納場所にはさっきダウンロードしたイメージがないので、再度イメージをダウンロードします。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
93288797bd35: Pull complete 
Digest: sha256:cc15c5b292d8525effc0f89cb299f1804f3a725c8d05e158653a563f15e4f685
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

ボリュームマウントをしていたDockerコンテナを移行してみる

旧来のroot版Dockerで動かしていたコンテナ(ボリュームマウントを使っていたもの)にCalibre Webがあるので、これを試しに移行することにします。

電子書籍ライブラリとしてCalibre WebをRaspberry Pi 4でDockerで運用する

docker-compose.yamlの上げ下げだけで起動・停止ができているので、2箇所のボリュームマウント

  • /mnt/usbhdd1/var/docker-calibre-web-config:/config
  • /mnt/usbhdd1/var/docker-calibre-web-library:/books

について、動作を比較してみます。

一旦、docker-calibre-web-configとdocker-calibre-web-libraryに相当するディレクトリdocker-calibre-web-config-rootlessとdocker-calibre-web-library-rootlessを作成して、docker-compose.yamlの記述をこの2つに変更した状態でdocker-compoose up -ddocker-compose downしてみた結果、configの方はファイルが生成されたので見てみると…

# ls -l docker-calibre-web-config docker-calibre-web-config-rootless
docker-calibre-web-config:
合計 220
-rw-r--r-- 1 ubuntu ubuntu 143360 10月 19 20:20 app.db
-rw-r--r-- 1 ubuntu ubuntu  43196 11月 14 10:29 calibre-web.log
-rw-r--r-- 1 ubuntu ubuntu      3  6月 18 22:51 client_secrets.json
-rw-r--r-- 1 ubuntu ubuntu  32768  6月 18 22:52 gdrive.db

docker-calibre-web-config-rootless:
合計 156
-rw-r--r-- 1   166535   166535 118784 11月 14 17:25 app.db
-rw-r--r-- 1   166535   166535   1188 11月 14 17:25 calibre-web.log
-rw-r--r-- 1   166535   166535      3 11月 14 17:22 client_secrets.json
drwxr-xr-x 1 hogehoge hogehoge      0 11月 14 17:22 custom-cont-init.d
drwxr-xr-x 1 hogehoge hogehoge      0 11月 14 17:22 custom-services.d
-rw-r--r-- 1   166535   166535  32768 11月 14 17:24 gdrive.db

rootlessの方のowner/groupは166535を使うようです。custom-cont-init.dcustom-services.dという中身が空でownerが作業ユーザーになっているディレクトリが気になりますが、そこは無視して以前使っていた方のowner/groupを166535に変更します。

# chown -R 166535:166535 docker-calibre-web-config docker-calibre-web-library
# ls -l docker-calibre-web-config 
合計 220
-rw-r--r-- 1 166535 166535 143360 10月 19 20:20 app.db
-rw-r--r-- 1 166535 166535  43196 11月 14 10:29 calibre-web.log
-rw-r--r-- 1 166535 166535      3  6月 18 22:51 client_secrets.json
-rw-r--r-- 1 166535 166535  32768  6月 18 22:52 gdrive.db

docker-compose.yamlファイルの内容を元に戻してdocker-compose up -dで起動します。問題なく起動することを確認したらdocker-compose downで終了します。ボリュームマウントがどうなっているか確認すると…

# ls -l docker-calibre-web-config 
合計 232
-rw-r--r-- 1   166535   166535 155648 11月 14 17:47 app.db
-rw-r--r-- 1   166535   166535  44062 11月 14 17:53 calibre-web.log
-rw-r--r-- 1   166535   166535      3  6月 18 22:51 client_secrets.json
drwxr-xr-x 1 hogehoge hogehoge      0 11月 14 17:46 custom-cont-init.d
drwxr-xr-x 1 hogehoge hogehoge      0 11月 14 17:46 custom-services.d
-rw-r--r-- 1   166535   166535  32768  6月 18 22:52 gdrive.db

custom-cont-init.dcustom-services.dがやっぱり作られているのが気になりますが、動作は問題ないのでこのままrootless版で使います。

スポンサーリンク


  1. これrootless dockerのバグなんじゃないかと思いこんでました…恥ずかしい [return]

comments powered by Disqus