TenForward

技術ブログ。はてなダイアリーから移転しました

Plamo 7.0 上で非特権な systemd コンテナを起動する

Plamo 7.0 では cgroupfs_mount パッケージと lxc パッケージを提供していますが、非特権コンテナを起動するための調整は特に行っていませんでした。

cgroupfs_mount パッケージは、Plamo 7.0 リリース直後は Ubuntu 14.04 あたりで cgroupfs をマウントするのに使われていた cgroup-lite パッケージを元にした cgroupfs-mount をそのまま入れていました。これはシェルスクリプトで、システム上でサポートされているコントローラに対応したディレクトリを /sys/fs/cgroup に作成し、それぞれコントローラをオプションに指定してマウントするだけのシンプルなスクリプトです。

これで非特権コンテナは起動しますが、コンテナ内の systemd がちゃんと起動しません。今回はコンテナは Ubuntu bionic のイメージを使っています。

誰得?な記事ですが、非 systemd 環境でコンテナを起動する場合は参考になるかもしれません(←やっぱり誰得😂)

非特権コンテナを起動する準備

非特権の lxc コンテナを起動するためには、連載の第17回 で書いた方法と特に変わっていません。しかし一部の設定項目名が変わったりしています。

まずはコンテナを起動したいユーザが使えるサブIDを登録します。ここでは plamo ユーザーとします。

$ sudo usermod -v 200000-299999 -w 200000-299999 plamo

記事では、システム上で使う 65536 個のサブIDを使えるようにしていましたが、今の実装ではちょうどだと「足りない」とエラーが出るようです(詳しい原因は調べてません)。これは LXD のときも同じです。ですので実際に使う以上に余裕を持ってサブIDを確保しておきましょう。

非特権コンテナで veth インターフェースを使うために /etc/lxc/lxc-usernet ファイルにエントリを作成します。これはエディタで直接編集。

$ cat /etc/lxc/lxc-usernet 
plamo   veth    lxcbr0  10

個人用の lxc 設定をコピーします。そして非特権コンテナ用のIDマッピングの設定を追加します。

$ mkdir -p ~/.config/lxc
$ cp /etc/lxc/default.conf ~/.config/lxc/
$ (編集)
$ cat ~/.config/lxc/default.conf
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx

# これ以降が追加した行
lxc.idmap = u 0 200000 65536
lxc.idmap = g 0 200000 65536

記事では lxc.id_map でしたが、現在のバージョンである 3.x 系では lxc.idmap になっていますので注意が必要です。

pam_cgfs

記事の時点では、ユーザーが書き込み可能な cgroup をシェルスクリプトで作成し、各 cgroup に PID を登録していました。

今では lxc に pam_cgfs という PAM モジュールが付属していますので、これを使うとログイン時にユーザー権限の cgroup を作成してくれます。

/etc/pam.d/system-session ファイルの最後に次のように追加します。-c オプションの後にコントローラー名をカンマ区切りで指定するか、all を指定します。all だとシステム上で使えるすべてのコントローラーにユーザー用の cgroup を使います。

session  optional    pam_cgfs.so -c all

一部のコントローラーだけ必要であれば、次のように書きます。Ubuntu なんかでは次のように書かれています。

session  optional    pam_cgfs.so -c freezer,memory,name=systemd

これで freezermemoryコントローラー用の cgroup 配下と、systemd が作る systemd という cgroup 配下にユーザー用の cgroup を作ります。

Plamo の場合、ssh でアクセスしたときは system-session ファイルは使いませんので、/etc/pam.d/sshd ファイルの末尾に上記の行を追加してください。

これでログインすると、こんな感じにユーザー用の cgroup が作られ、ログインしたシェルが登録されます。

$ cat /proc/self/cgroup 
14:rdma:/user/plamo/0
13:pids:/user/plamo/0
12:hugetlb:/user/plamo/0
11:net_prio:/user/plamo/0
10:perf_event:/user/plamo/0
9:net_cls:/user/plamo/0
8:freezer:/user/plamo/0
7:devices:/user/plamo/0
6:memory:/user/plamo/0
5:blkio:/user/plamo/0
4:cpuacct:/user/plamo/0
3:cpu:/user/plamo/0
2:cpuset:/user/plamo/0
1:name=systemd:/user/plamo/0

各コントローラー用の cgroup 配下に /user/ ディレクトリを挟んでユーザー名のディレクトリ(= cgroup)が作られ、さらにはセッションID名のcgroupができています。自身のシェルプロセスの /proc/self/cgroup ファイルを見ると、上記のようにそのディレクトリにプロセスが登録されているのがわかります。

これで準備は OK のはずなので、Ubuntu コンテナを作成し、起動してみます。

$ lxc-create -t download c2 -- -d ubuntu -r bionic -a amd64
$ lxc-start c2
$ lxc-attach c2
$ lxc-ls -f
NAME STATE   AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED 
c2   RUNNING 0         -      -    -    true         

起動しましたが、待っていてもアドレスは割り振られません。中で確認してみます。

$ lxc-attach c2
root@c2:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.2  76408  5408 ?        Ss   11:15   0:00 /sbin/init
root         7  0.0  0.1  21444  3328 pts/2    Ss   11:16   0:00 /bin/bash
root        10  0.0  0.1  37336  2856 pts/2    R+   11:16   0:00 ps aux

というように init とシェルしかプロセスがありません。

どうやら systemd でホスト側に systemd が作成した cgroup が必要のようです(ちゃんと調べたわけではないですが)。

ホスト側で必要な systemd 用 cgroup を作成

そこで systemd が作る cgroup を自分で作ってみます。

$ sudo mkdir -p /sys/fs/cgroup/systemd/user.slice/user-$(id -u plamo).slice
$ sudo chown $(id -u plamo):$(id -g plamo) /sys/fs/cgroup/systemd/user.slice/user-$(id -u plamo).slice
$ sudo chown $(id -u plamo):$(id -g plamo) /sys/fs/cgroup/systemd/user.slice/user-$(id -u plamo).slice/{cgroup.procs,tasks}

各コントローラー用には次のようなスクリプトをでっち上げます。

UNPRIV_USERS="plamo"
mkdir -p /sys/fs/cgroup/systemd
mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd
mkdir -p /sys/fs/cgroup/systemd/user.slice
for u in $UNPRIV_USERS
do
    USER_CG="/sys/fs/cgroup/systemd/user.slice/user-$(id -u $u).slice"
    mkdir -p $USER_CG
    chown $(id -u $u):$(id -g $u) $USER_CG
    chown $(id -u $u):$(id -g $u) $USER_CG/{cgroup.procs,tasks}
done

for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
    mkdir -p $sys
    if ! mountpoint -q $sys; then
        if ! mount -n -t cgroup -o $sys cgroup $sys; then
            rmdir $sys || true
        else
            echo 1 > /sys/fs/cgroup/$sys/cgroup.clone_children
            for u in $UNPRIV_USERS
            do
                USER_CG="$sys/user.slice/user-$(id -u $u).slice"
                mkdir -p $USER_CG
                chown $(id -u $u):$(id -g $u) $USER_CG
                chown $(id -u $u):$(id -g $u) $USER_CG/{cgroup.procs,tasks}
                echo 1 > $USER_CG/cgroup.clone_children
                if [ $sys = "cpuset" ]; then
                    chown $(id -u $u):$(id -g $u) $USER_CG/cpuset.{cpus,mems}
                    echo 0 > $USER_CG/cpuset.cpus
                    echo 0 > $USER_CG/cpuset.mems
                fi
            done
        fi
    fi
done

つまり各 cgroup 配下に user.slice/user-$(id).slice という cgroup を作って、ユーザー権限を与えるわけですね。

次のようにディレクトリができます。

$ find /sys/fs/cgroup/memory/ | grep user.slice
/sys/fs/cgroup/memory/user.slice
/sys/fs/cgroup/memory/user.slice/memory.memsw.usage_in_bytes
/sys/fs/cgroup/memory/user.slice/memory.use_hierarchy
  :(snip)
/sys/fs/cgroup/memory/user.slice/user-1000.slice
/sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.memsw.usage_in_bytes
/sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.use_hierarchy
/sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.kmem.tcp.max_usage_in_bytes
/sys/fs/cgroup/memory/user.slice/user-1000.slice/memory.kmem.slabinfo

これでコンテナを起動させてみます。

$ lxc-start c2
$ lxc-ls -f
NAME STATE   AUTOSTART GROUPS IPV4       IPV6 UNPRIVILEGED 
c2   RUNNING 0         -      10.0.3.223 -    true
$ lxc-attach c2
root@c2:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.8  0.4  77040  8312 ?        Ss   11:58   0:00 /sbin/init
root        17  0.0  0.4  78312  9700 ?        Ss   11:58   0:00 /lib/systemd/systemd-journald
root        26  0.0  0.1  42104  3408 ?        Ss   11:58   0:00 /lib/systemd/systemd-udevd
systemd+    31  0.0  0.2  80028  5148 ?        Ss   11:58   0:00 /lib/systemd/systemd-networkd
systemd+    50  0.0  0.2  70616  5128 ?        Ss   11:58   0:00 /lib/systemd/systemd-resolved
root        54  0.0  0.1  31292  3036 ?        Ss   11:58   0:00 /usr/sbin/cron -f
root        55  0.6  0.8 170360 16912 ?        Ssl  11:58   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root        56  0.0  0.2  62008  5508 ?        Ss   11:58   0:00 /lib/systemd/systemd-logind
message+    57  0.0  0.2  49928  4196 ?        Ss   11:58   0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
syslog      58  0.0  0.1 193400  4040 ?        Ssl  11:58   0:00 /usr/sbin/rsyslogd -n
root        59  0.0  0.1  61816  2956 ?        Ss   11:58   0:00 /lib/systemd/systemd-hostnamed
root        63  0.0  0.1  15956  2392 pts/3    Ss+  11:58   0:00 /sbin/agetty -o -p -- \u --noclear --keep-baud pts/3 115200,38400,9600 vt220
root        64  0.0  0.1  15956  2324 pts/1    Ss+  11:58   0:00 /sbin/agetty -o -p -- \u --noclear --keep-baud pts/1 115200,38400,9600 vt220
root        65  0.0  0.1  15956  2308 pts/1    Ss+  11:58   0:00 /sbin/agetty -o -p -- \u --noclear --keep-baud console 115200,38400,9600 linux
root        66  0.0  0.1  15956  2264 pts/2    Ss+  11:58   0:00 /sbin/agetty -o -p -- \u --noclear --keep-baud pts/2 115200,38400,9600 vt220
root        67  0.0  0.1  15956  2296 pts/0    Ss+  11:58   0:00 /sbin/agetty -o -p -- \u --noclear --keep-baud pts/0 115200,38400,9600 vt220
root        70  0.0  0.1  21444  3264 pts/2    Ss   11:58   0:00 /bin/bash
root        73  0.0  0.1  37336  2996 pts/2    R+   11:58   0:00 ps aux

ちゃんと起動していますね。pidsdevicessystemd cgroup 配下に色々と systemd が cgroup を作っているので、この辺りがホストにも必要だったということでしょう。

Plamo 7.x の現時点の最新の cgroupfs_mount パッケージ 1.8 では、上のような処理を反映させたスクリプトにしてありますので、これをインストールして、/etc/sysconfig/cgroupfs-mount ファイル内で非特権コンテナを起動したいユーザーを指定すれば OK です。

$ cat /etc/sysconfig/cgroupfs-mount 
# List of non-root users who want to create owned cgroups
UNPRIV_USERS="plamo"

まとめ

以上のようにダミーで systemd が作る cgroup ツリーを作成すれば、systemd 採用のコンテナは起動しました。

今回はコンテナとして Ubuntu bionic を使っていますが、もう少し古い systemd だと /sys/fs/cgroup/systemd という cgroup ツリーさえあれば起動していた気がします。今後も systemd の変化とともに動きが変わるかもしれません。