Linux 4.14 で導入された Namespaced file capabilities(おまけ)
4.14 で設定した Namespaced file capability を 4.13 以前のカーネルに戻したら
まあ、こんな物好きなことをする人はいないでしょうが、確認してみました。
の環境のまま、ホスト環境のカーネルを 4.13 に戻して、コンテナを起動し、コンテナ内で一般ユーザーとなって ping
と ping2
コマンドを両方とも実行しました。
$ ./ping www.google.com -su: ./ping: Numerical result out of range $ ./ping2 www.google.com -su: ./ping2: Numerical result out of range
構造体が変わっていたので読み取れないようですね。
Namespaced file capability に設定された id で User Namespace 外から実行してみる
で、User Namespace 内の root
の id が 200000 に設定されていたので、コンテナ内で設定した File capability も 200000 という情報が記録されていました。そこで User Namespace 内の root
と同じ id を持つホスト上のユーザーから、file capability を設定したコマンドを実行してみました。
今回は unshare
コマンドを使います。
- ホスト上の uid: 1000 のユーザーを User namespace 内の
root
にマッピング
ちょっと色々試行錯誤しながら試したので変なことしてるようだったら教えてください。
ホスト上のユーザーは 1000:100 です。ここで unshare
します。
$ id -u 1000 $ id -g 100 $ unshare --user --setgroups allow nobody@borg:~$ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
マッピングがまだできていないので、この時点では User namespace 内では nobody
ユーザーになります。
ここで別のシェルからホスト上の root
でマッピングを投入します。(Plamo なので gid がちょっと普通と異なるかな)
# echo "0 1000 65534" > /proc/22471/uid_map # echo "0 100 65534" > /proc/22471/gid_map
ここで元の User namespace 内のシェルに戻ると、無事 root
になりました。
$ id uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
これで他の namespace が作れますので、Network namespace を作ります(これを作らないと file capability 設定しても ping 実行できなかったので)。
$ unshare --net # ip link set lo up # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
ここで一般ユーザーになります。ping
コマンドをコピーして実行してみます。当然失敗しますね。
$ su karma $ id -u 1000 $ cd /tmp $ cp /bin/ping . $ ls -l ping -rwxr-xr-x 1 karma users 56,920 3月 24日 01:02 ping* $ ./ping 127.0.0.1 ping: socket: Operation not permitted $ exit
ここで User namespace 内の root
で file capability を設定します。
# setcap cap_net_raw+ep /tmp/ping # getcap -n /tmp/ping /tmp/ping = cap_net_raw+ep [rootid=1000]
id:1000 で Namespaced file capability が設定されています。ここで再度一般ユーザーになります。
# su - karma $ /tmp/ping -c 1 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.012 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.012/0.012/0.012/0.000 ms
User namespace 内で実行できましたね。ここでおもむろに namespace 外のシェルから、id:1000 のユーザーでこの ping を実行してみます。
$ id -u 1000 $ /tmp/ping 127.0.0.1 ping: socket: Operation not permitted
実行はできませんね。
Linux 4.14 で導入された Namespaced file capabilities(2)
User Namespace 外から Namespaced file capability を設定する
前回、
では、非特権ユーザで起動したコンテナ内の root
ユーザーで File capability を設定しました。
ここで、自作コンテナ派の皆様が気になるのは、この capability をコンテナ外から設定するのはどうしたらいいのだろう? というところではないかと思います。コンテナランタイム側からコンテナ内の capability を設定する必要があるかもしれません。
もちろん、これも簡単にできます。先に getcap
コマンドに -n
オプションを付与しましたが、これと同じオプションが setcap
にも存在します。
setcap -n 200000 cap_net_raw+ep ./ping
と言ったように、User Namespace 内の root
の uid を指定して capability を指定します。
まず、コンテナ内からアクセスできるように、ホスト環境上で ping
コマンドをコンテナの rootfs 内にコピーします。LXC の非特権コンテナは次のようなディレクトリ以下に rootfs が置かれますので、ここにコピーします。ping2
という名前でコピーしました。
$ sudo cp /bin/ping ~/.local/share/lxc/c3/rootfs/home/karma/ping2
次にコピーした ping2
バイナリに対して setcap
します。
$ sudo setcap -n 200000 cap_net_raw+ep ~/.local/share/lxc/c3/rootfs/home/karma/ping2 (-nオプションでroot uidを指定) $ sudo getcap -n ~/.local/share/lxc/c3/rootfs/home/karma/ping2 /home/karma/.local/share/lxc/c3/rootfs/home/karma/ping2 = cap_net_raw+ep [rootid=200000] $ sudo getfattr -d -m "security" ~/.local/share/lxc/c3/rootfs/home/karma/ping2 getfattr: Removing leading '/' from absolute path names # file: home/karma/.local/share/lxc/c3/rootfs/home/karma/ping2 security.capability=0sAQAAAwAgAAAAAAAAAAAAAAAAAABADQMA
設定されましたね。
再度コンテナ内に入ってみましょう。
$ lxc-attach c3 # su - karma $ ls -l ./ping* -rwxr-xr-x 1 karma users 56,920 2月 21日 16:30 ./ping* -rwxr-xr-x 1 nobody nogroup 56,920 2月 21日 16:39 ./ping2*
ホスト側の権限でコピーしていますので、ファイルの所有権が nobody:nogroup
になっていますね。
$ sudo getcap -n ./ping2 ./ping2 = cap_net_raw+ep [rootid=200000]
capability を確認すると、設定されているのがわかります。では、実行してみましょう。
$ ./ping2 -c 1 www.google.com PING www.google.com (216.58.196.228) 56(84) bytes of data. 64 bytes from kix06s01-in-f228.1e100.net (216.58.196.228): icmp_seq=1 ttl=49 time=1.22 ms --- www.google.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.226/1.226/1.226/0.000 ms
実行できました。
Namespace 外からでも問題なく設定できますね。
kernel のデータ構造の変更
ここからは素人なので間違ってる可能性あります。詳しくはご自分でどうぞ。
4.13 までは include/uapi/linux/capability.h
を見ると、以下のような構造体がありました。
#define VFS_CAP_FLAGS_EFFECTIVE 0x000001 : (snip) #define VFS_CAP_REVISION_2 0x02000000 struct vfs_cap_data { __le32 magic_etc; /* Little endian */ struct { __le32 permitted; /* Little endian */ __le32 inheritable; /* Little endian */ } data[VFS_CAP_U32]; };
(https://elixir.bootlin.com/linux/v4.13/source/include/uapi/linux/capability.h#L53 付近)
この permitted
と inheritable
に必要な capability を設定するわけですね。effective セットは file capability の場合はオンオフだったと思いますので、magic_etc
に設定します。
effective セットを設定する場合、これ(4.13)までは、
struct vfs_cap_data xattr;
xattr.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE;
のように設定したようです。
4.14 以降で Namespaced file capability が導入されたあとは、
#define VFS_CAP_REVISION_2 0x02000000 #define VFS_CAP_U32_2 2 #define XATTR_CAPS_SZ_2 (sizeof(__le32)*(1 + 2*VFS_CAP_U32_2)) #define VFS_CAP_REVISION_3 0x03000000 #define VFS_CAP_U32_3 2 #define XATTR_CAPS_SZ_3 (sizeof(__le32)*(2 + 2*VFS_CAP_U32_3)) #define XATTR_CAPS_SZ XATTR_CAPS_SZ_3 #define VFS_CAP_U32 VFS_CAP_U32_3 #define VFS_CAP_REVISION VFS_CAP_REVISION_3 struct vfs_cap_data { __le32 magic_etc; /* Little endian */ struct { __le32 permitted; /* Little endian */ __le32 inheritable; /* Little endian */ } data[VFS_CAP_U32]; }; /* * same as vfs_cap_data but with a rootid at the end */ struct vfs_ns_cap_data { __le32 magic_etc; struct { __le32 permitted; /* Little endian */ __le32 inheritable; /* Little endian */ } data[VFS_CAP_U32]; __le32 rootid; };
(https://elixir.bootlin.com/linux/v4.14/source/include/uapi/linux/capability.h#L64 付近)
というように struct vfs_ns_cap_data
という、vfs_cap_data
の最後に rootid
が追加された構造体が追加されています。そう、これが setcap
、getcap
で追加された -n
オプションの正体ですね(たぶん)。
それと同時に、従来の Namespaced されていない file capability なのか、Namespaced な file capability なのかは、magic_etc
に指定する file capability の Revision で指定します。
先のコードと対応する例だと、
struct vfs_ns_cap_data xattr;
xattr.magic_etc = VFS_CAP_REVISION_3 | VFS_CAP_FLAGS_EFFECTIVE;
となるわけですね(たぶん)。
(つづく)
Linux 4.14 で導入された Namespaced file capabilities(1)
Hosting Casual Talks #5
本題に入る前に、このブログエントリのネタを発表してきた勉強会の感想を。
ホスティング業界に関わるエンジニアが カジュアルに 情報交換をするという、全員発表型の勉強会 "Hosting Casual Talks #5" に参加してきました。飲みながら技術について情報交換できる カジュアルな 勉強会ではありますが、内容が全く カジュアルじゃない!!
ひとりあたり10 or 15分の LT サイズの時間割り当てなのですが、皆さんちゃんと解決したい問題があり、仮説を立て、検証して、考察すると言ったストーリー性のあるスライドになっていたり、問題を解決するためにコード書きました、こういうの作りましたというような、とても LT サイズに収まるものではなく、全員持ち時間はどこへやら、ガッツリと 6 時間、ガチな内容がひたすら続くなかなか内容の濃い勉強会になりました。
で、そこで真面目に 10 分サイズで軽めに作ったネタがコレでした。一発芸でオチもなく、Linux カーネルの機能を紹介してきました。次々とすごいガチな発表がされるので、こんなネタで発表するのが徐々に辛くなって…😅
4.14 kernel はいくつかコンテナ関連の機能が追加されているのですが、その中のひとつである "Namespaced file capabilities" の紹介です。
"Namespaced file capabilities" 機能についてはこのあたりが詳しいです。
- Unprivileged File Capabilities (brauner's blog)
- Namespaced file capabilities (lwn.net)
コミットはこちら
File Capability
File Capability については色々と参考になる情報がありますので、そちらをどうぞ。
- capabilities(7)
- ケーパビリティ で権限を少しだけ与える (いますぐ実践! Linux システム管理)
- 明日使えない Linux の capabilities の話 (@nojima's blog)
- Linux Capability - ケーパビリティについての整理 (ローファイ日記)
- (F15)File Capability
ここでは File Capability の説明あるあるの ping コマンドに cap_net_raw
を与えて色々試してみます。
File Capability お試し
まずは普通に。ここでは Linux 4.13.16 上の Plamo Linux 7.0 で試しています。libcap は 2.26。
通常は setuid されている ping をコピーすると setuid は落とされて ping
は一般ユーザーでは実行できません。
$ id -u 1000 (一般ユーザーです) $ ls -l /bin/ping -rwsr-sr-x 1 root root 56,920 2月 12日 20:31 /bin/ping* (setuid されています) $ cp /bin/ping . (コピーする) $ ls -l ./ping (setuid は落ちています) -rwxr-xr-x 1 karma users 56,920 3月 23日 23:35 ./ping* $ ./ping www.google.com (setuid されていないので権限がなく実行できません) ping: socket: Operation not permitted
ここで file capability を設定すると一般ユーザーでも限定的な権限が与えられ、実行できるようになります。
$ sudo setcap cap_net_raw+ep ./ping (cap_net_raw を与えます) $ sudo getcap -n ./ping (cap_net_raw が設定されています) ./ping = cap_net_raw+ep $ sudo getfattr -d -m "security" ./ping (拡張属性領域の確認。File Capability はここに保存されています) # file: ping security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=
これで一般ユーザーでも ping
が実行できます。
$ ./ping -c 1 www.google.com PING www.google.com(kix05s01-in-x04.1e100.net (2404:6800:400a:808::2004)) 56 data bytes 64 bytes from kix05s01-in-x04.1e100.net (2404:6800:400a:808::2004): icmp_seq=1 ttl=51 time=7.08 ms --- www.google.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 7.087/7.087/7.087/0.000 ms
コンテナ内で File Capability を設定してみる(4.13.16 kernel)
これと同じ操作を一般ユーザー権限で実行しているコンテナ内で行ってみます。次のように root が uid: 200000 のユーザーにマッピングされているコンテナがあります。
karma@plamo71:~$ lxc-info c3 Name: c3 State: RUNNING PID: 20569 IP: 10.0.3.122 CPU use: 0.17 seconds BlkIO use: 12.45 MiB Memory use: 16.59 MiB KMem use: 1.89 MiB Link: vethBXNXMY TX bytes: 1.55 KiB RX bytes: 9.86 KiB Total bytes: 11.41 KiB karma@plamo71:~$ sudo cat /proc/20569/{u,g}id_map 0 200000 65536 0 200000 65536 karma@plamo71:~$ ps auxf 20569 : (snip) 200000 20569 0.0 0.0 4500 1600 pts/1 Ss+ 18:38 0:00 \_ init [3] 200000 20772 0.0 0.1 172404 2488 ? Ssl 18:38 0:00 \_ /sbin/rsyslogd -i /run/rsyslogd.pid : (snip)
このコンテナ内に入り、一般ユーザー権限で ping をコピーしてみます。当然実行はできません。
$ lxc-attach c3 # su - karma $ id uid=1000(karma) gid=100(users) groups=100(users),10(wheel),26(audio),28(dialout),29(video),32(cdrom),36(kvm),38(pulse),39(pulse-access),44(mlocate),47(libvirt),50(input) $ uname -a Linux c3 4.13.16-plamo64 #1 SMP PREEMPT Wed Dec 5 15:00:28 JST 2018 x86_64 GNU/Linux $ cp /bin/ping . $ ls -l ./ping -rwxr-xr-x 1 karma users 56,920 2月 20日 19:02 ./ping* $ ./ping -c 1 www.google.com ping: socket: Operation not permitted
ここで capability を設定してみましょう。
$ sudo setcap cap_net_raw+ep ./ping Failed to set capabilities on file `./ping' (Operation not permitted) usage: setcap [-q] [-v] [-n <rootid>] (-r|-|<caps>) <filename> [ ... (-r|-|<capsN>) <filenameN> ] Note <filename> must be a regular (non-symlink) file.
エラーで setcap
コマンドが実行できません。
4.14 以降のカーネルの コンテナ内で File Capability を設定してみる(4.20.10 kernel)
勉強会のときは 5.0.0 カーネルで試したデモを披露しましたが、ここでは 4.20.10 で試した結果です。
ちなみに 4.14 より前のカーネルを試そうと思って Ubuntu 16.04 の 4.4 kernel なら試せる!と思ったりすると、普通にこの機能はバックポートされていますのでご注意ください。つまりUbuntu 16.04 の 4.4 kernel でもこの機能を試せます。(←ハマった人)(2019-03-24 追記)
$ uname -a Linux plamo71 4.20.10-plamo64 #5 SMP PREEMPT Mon Feb 18 16:16:45 JST 2019 x86_64 GNU/Linux $ lxc-start c3 $ lxc-ls -f c3 NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED c3 RUNNING 0 - 10.0.3.122 - true $ lxc-info c3 Name: c3 State: RUNNING PID: 4817 IP: 10.0.3.122 CPU use: 0.17 seconds BlkIO use: 12.55 MiB Memory use: 16.97 MiB KMem use: 2.16 MiB Link: vethH3LNCN TX bytes: 1.28 KiB RX bytes: 9.31 KiB Total bytes: 10.59 KiB $ sudo cat /proc/4817/{u,g}id_map 0 200000 65536 0 200000 65536 $ ps auxf | less : (snip) 200000 4817 0.0 0.0 4500 1632 pts/1 Ss+ 16:24 0:00 \_ init [3] 200000 5019 0.0 0.1 172404 2472 ? Ssl 16:24 0:00 \_ /sbin/rsyslogd -i /run/rsyslogd.pid : (snip)
カーネルのバージョンが違う以外は同じコンテナを実行しています。
$ lxc-attach c3 # su - karma $ id uid=1000(karma) gid=100(users) groups=100(users),10(wheel),26(audio),28(dialout),29(video),32(cdrom),36(kvm),38(pulse),39(pulse-access),44(mlocate),47(libvirt),50(input) $ uname -a Linux c3 4.20.10-plamo64 #5 SMP PREEMPT Mon Feb 18 16:16:45 JST 2019 x86_64 GNU/Linux $ cp /bin/ping . $ ls -l ./ping -rwxr-xr-x 1 karma users 56,920 2月 21日 16:30 ./ping* $ ./ping www.google.com ping: socket: Operation not permitted
当然ですが、ここまでは 4.13 カーネルのときと同じです。ここで 4.13 では失敗した setcap
を試してみましょう。
$ sudo setcap cap_net_raw+ep ./ping $ sudo getcap -n ./ping ./ping = cap_net_raw+ep [rootid=200000] $ sudo getfattr -d -m "security" ./ping # file: ping security.capability=0sAQAAAwAgAAAAAAAAAAAAAAAAAABADQMA
4.13 では失敗した setcap
が成功しています。ここで getcap
に -n
を与えている所がミソで、これは結果のうち [rootid=200000]
を表示させるために使っています。この 200000
は、コンテナの root
にマッピングされている uid ですね。
つまり user namespace 内で setcap
を実行すると、user namespace 内の root
の uid の情報が設定されます。そして、その user namespace 内でのみ、その File capability が有効になり、権限が与えられた状態でコマンドが実行できます。
$ ./ping -c 1 www.google.com PING www.google.com (216.58.197.4) 56(84) bytes of data. 64 bytes from kix06s02-in-f4.1e100.net (216.58.197.4): icmp_seq=1 ttl=48 time=1.30 ms --- www.google.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.303/1.303/1.303/0.000 ms
問題なく実行できていますね。
上記のファイルをコンテナの外で確認してみましょう。
$ id uid=1000(karma) gid=100(users) groups=100(users),10(wheel),26(audio),28(dialout),29(video),32(cdrom),36(kvm),38(pulse),39(pulse-access),44(mlocate),47(libvirt),50(input) $ sudo getcap -n ~/.local/share/lxc/c3/rootfs/home/karma/ping /home/karma/.local/share/lxc/c3/rootfs/home/karma/ping = cap_net_raw+ep [rootid=200000] $ ~/.local/share/lxc/c3/rootfs/home/karma/ping -c 1 www.google.com ping: socket: Operation not permitted
コンテナ外で確認しても getcap
の情報は変わりません。そしてコンテナ(user namespace)外から実行すると、設定したはずの capability は有効ではなく、ping
は実行できません。
(つづく)
Linux 4.14 で導入された cpuset の cgroup v2 mode
(2019-02-23 少し追記しています)
4.14 kernel から cgroup の cpuset コントローラーに cpuset_v2_mode
というオプションが使えるようになりました。
しばらく見ないうちに cpuset
コントローラーをマウントするとファイルがたくさん増えていますが、このオプションを設定すると、従来からある cpuset.{cpus,mems}
ファイルの動きが変わるようです。そしていつの間にか増えている cpuset.effective_{cpus,mems}
ファイルと合わせて使って cpuset の制限を行うようです。
このオプションを設定したときの動きが、cgroup v2 の cpuset の動きになるようです(v2 の cpuset は現時点ではまだマージされていません)。
現在使える cpu や memory が effective
で表示されており、それと cpuset.{cpus,mems}
のマスクを取って、使える cpu, memory を導き出すようですね。
従来の cpuset(v1 mode)
# mount -t tmpfs cgroup /sys/fs/cgroup/ # mkdir /sys/fs/cgroup/cpuset # mount -t cgroup -o cpuset cgroup /sys/fs/cgroup/cpuset/ # cd /sys/fs/cgroup/cpuset/ # mkdir test01 # cd test01 # echo "0-1" > cpuset.cpus # echo "0" > cpuset.mems
この状態で test01
cgroup 内のファイルは、
# for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0-1 cpuset.effective_cpus: 0-1
となっています。ここでおもむろに cpu をひとつオフラインにします。cpu1 です。
# echo 0 > /sys/devices/system/cpu/cpu1/online
すると、
# for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0 cpuset.effective_cpus: 0
cpuset.cpus
と cpuset.effective_cpus
が 0
になります。
ここで再度 cpu1 をオンラインにすると、
echo 1 > /sys/devices/system/cpu/cpu1/online # cat /sys/devices/system/cpu/cpu1/online 1
# for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0 cpuset.effective_cpus: 0
特に変化はありません。
cpuset_v2_mode
付きマウント(v2 mode)
マウントオプションとして cpuset_v2_mode
を与えます。
# mount -t cgroup -o cpuset,cpuset_v2_mode cgroup /sys/fs/cgroup/cpuset/ # cat /proc/self/mounts | grep cpuset cgroup /sys/fs/cgroup/cpuset cgroup rw,relatime,cpuset,cpuset_v2_mode 0 0
この状態では、さきほどと特に変化はありません。
# for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0-1 cpuset.effective_cpus: 0-1
ここで cpu1 をオフラインにします。
# echo 0 > /sys/devices/system/cpu/cpu1/online
すると、
# for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0-1 cpuset.effective_cpus: 0
cpuset.cpus
はそのままで実際の cpuset.effective_cpus
のみ値が変わります。
ここまで来ると動きは予想できますね。再度 cpu1 をオンラインにしてみます。
# echo 1 > /sys/devices/system/cpu/cpu1/online # for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0-1 cpuset.effective_cpus: 0-1
cgroup作成後にCPUが増えた時(2019-02-23 追記)
cgroup作成後にcpuが減って、また戻った場合の動きは見ましたが、cgroup作成後にonline の cpu が増えた場合にどうなるのかが気になったので試してみました。
# echo 0 > /sys/devices/system/cpu/cpu2/online # mkdir test01 # cd test01 # cat cpuset.cpus # cat cpuset.effective_cpus 0-1 # echo "0-1" > cpuset.cpus # echo "0" > cpuset.mems # for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0-1 cpuset.effective_cpus: 0-1 # echo 1 > /sys/devices/system/cpu/cpu2/online # for f in $(ls cpuset.*cpus); do echo -n "$f: "; cat $f; done cpuset.cpus: 0-1 cpuset.effective_cpus: 0-1
上の例のように CPU をひとつ(cpu2) offline にしておいて、cgroup を作成後、cpu2 を online にしても cpuset.effective_cpus
の値は変化しません。
まとめ
CPU/メモリーの online, offline を切り替えると:
- v1 mode:
cpuset.{cpus,mems}
、cpuset.effective_{cpus,mems}
の値も変化するが、その後再度状態が変わってもそのまま - v2 mode:
cpuset.{cpus,mems}
の値は変化しない。cpuset.effective_{cpus,mems}
の値は実際に合わせて変化する
ただし、cpuset.effective_*
の復元は cgroup 作成時の値まで。
おまけ(2019-02-23 追記)
ちなみにこのオプションが使えるようになったコミットは次のものですが、これはオプションの追加と、そのオプションが指定されているときの条件分岐を変更しているだけですので、処理自体はこの前にあらかじめ実装されているようですね。
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
これで freezer
、memory
コントローラー用の 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
ちゃんと起動していますね。pids
、devices
、systemd
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 の変化とともに動きが変わるかもしれません。