LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第36回コンテナのネスト[2]

前回は、root権限で起動するコンテナを使って多段にネストさせたコンテナを起動させたり、LXCコンテナ内でDockerを起動したりしました。

今回は、一般ユーザで起動する非特権コンテナでネストを試してみます。同時に、Ubuntu 16.04で採用された、一般ユーザに書き込み権限のあるcgroupを作るためのpam_cgfsも紹介します。

pam_cgfs

コンテナのネストを試す前に、一般ユーザ権限で起動する非特権コンテナで使用するpam_cgfsというPAMモジュールを紹介します。pam_cgfsはLXCFSに含まれています。

Ubuntuではこれまで、cgmanagerやパッチを適用したsystemd-logindを使って、ユーザ権限で操作できるcgroupを作成し、非特権コンテナを起動してきました。Ubuntu 16.04では、ログイン時にpam_cgfsというPAMモジュールを使って、ユーザ権限で操作できるcgroupを作成しています。

pam_cgfsは、/etc/pam.d以下にある、いろいろなPAMの設定ファイルでincludeされているcommon-sessioncommon-session-noninteractiveで、以下のように定義されています。

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

オプションでユーザ向けのcgroupを作成するcgroupfsツリーを指定しています。16.04ではfreezer、memoryサブシステムと、systemd用のcgroupツリーにユーザ用のcgroupを作成し、initとしてsystemdを採用した非特権コンテナが起動できるように設定しています。

これでログイン時にpam_cgfsによりユーザ所有のcgroupが作成されますので、一般ユーザ権限で起動したコンテナからcgroupが利用できます。

非特権コンテナのネスト

LXCは一般ユーザによる特権を持たないコンテナでもネストをサポートしています。前回に試した特権コンテナと同じように、一般ユーザでネストしたコンテナを作成してみましょう。今回の実行例はUbuntu 16.04上で実行しています。

一般ユーザでコンテナを作成し、起動するための設定は、この連載の第17回をご参照ください。

ここでは、ネストさせるすべての層で、コンテナを非特権コンテナとして起動してみます。この記事の例では、一般ユーザとしてubuntuユーザを利用しています。

非特権コンテナをネストさせる場合のIDマッピング

非特権コンテナ内で非特権コンテナを動かす際には注意が必要です。非特権コンテナの場合、コンテナ内のuid,gidとホストのuid,gidをマッピングさせました。注意が必要なのはその際のマッピングの数です。

Ubuntuではユーザを追加すると、/etc/subuid/etc/subgidに65536個のサブIDの定義を追加します。例えば次のようになっています。

ubuntu@host:~$ grep ubuntu /etc/sub{u,g}id
/etc/subuid:ubuntu:165536:65536
/etc/subgid:ubuntu:165536:65536

これは通常システムを起動すると65536個のIDを使うためです。ホストOS上で上のような設定で非特権コンテナを起動すると、コンテナ(親コンテナ)内で65536個のuid,gidが利用できます。しかし、そのコンテナで65536個すべてのIDを消費してしまいますので、ネストして子コンテナを起動しようとすると、親コンテナから割り当てるIDがありません。

図1 UbuntuデフォルトのID割り当て
図1 UbuntuデフォルトのID割り当て

コンテナをネストさせて、非特権の「親コンテナ」内でさらに非特権の「子コンテナ」を起動させるには、ホストOS上では65536*2=131072個のサブIDを割り当て、コンテナでも同じように設定しておく必要があります。

図2 2段ネストで非特権コンテナを起動するIDの割り当て
図2 2段ネストで非特権コンテナを起動するIDの割り当て

ネストした非特権コンテナの作成と起動

それでは早速コンテナを作成し、起動していきましょう。今回はホスト上にコンテナを作成し、その子どものコンテナまで作成してみましょう。つまり先の例で示したように、ホスト上では、親コンテナ分、子コンテナ分合わせて65536*2=131072個のIDを確保しておく必要があります。まずはユーザに対して131072個のサブIDを使えるようにする設定を行います。

ubuntu@host:~$ grep ubuntu /etc/sub{u,g}id
/etc/subuid:ubuntu:165536:131072
/etc/subgid:ubuntu:165536:131072
(ホストOS上でサブIDを165536から131072個割り当てる許可)

準備ができたらコンテナを作成します。ユーザ権限で起動した親コンテナですのでuser-parentと名づけました。

ubuntu@host:~$ lxc-create -t download -n user-parent -- -d ubuntu -r xenial -a amd64 (親コンテナ作成)
  :(設定ファイルの編集)
ubuntu@host:~$ tail -n3 .local/share/lxc/user-parent/config
lxc.include = /usr/share/lxc/config/nesting.conf (ネストのための設定ファイルのinclude)
lxc.id_map = u 0 165536 131072
lxc.id_map = g 0 165536 131072
(ホストのID 165536から131072個使用する設定)

作成できたら、上のようにネストするためにnesting.confのinclude設定と、131072個のIDのマッピングを設定します。

それでは、このコンテナを起動させてLXCをインストールしましょう。LXCをインストールする時はrootで作業する必要があります。

ubuntu@host:~$ lxc-start -n user-parent
ubuntu@host:~$ lxc-attach -n user-parent
root@user-parent:~# apt-get install lxc (親コンテナにLXCインストール)

LXCのインストール後に、コンテナを作成するユーザに対して、サブIDを許可する設定をしておきましょう。

ubuntu@user-parent:~$ cat /etc/sub{u,g}id
ubuntu:65536:65536
ubuntu:65536:65536
(親コンテナ上でサブIDを65536から65536個割り当てる許可)

設定ができたら、その一般ユーザでログインします。

ここでuser-parentコンテナ上で、一般ユーザでコンテナを操作する際には少し注意が必要です。一般ユーザ(ここではubuntuユーザ)になった後に、/sys/fs/cgroup以下に存在するサブシステム用のディレクトリmemoryfreezer配下のツリーにユーザ権限のcgroupが作成されているかチェックしてください。

親コンテナにopenssh-serverをインストールしてubuntuユーザでsshログインするか、lxc-consoleコマンドで親コンテナのコンソールにアクセスしubuntuユーザでログインすると、pam_cgfs経由でユーザが権限を持つcgroupが作られるので確実だと思います。筆者が試した所、lxc-attachコマンドでコンテナに入り、suコマンドでubuntuユーザにスイッチした場合はcgroupが作られずにコンテナの起動に失敗しました。

$ ls -l /sys/fs/cgroup/freezer/user/ubuntu/
total 0
drwxr-xr-x 2 ubuntu ubuntu 0 Oct  4 10:51 0 (←ubuntuユーザ権限で書き込めるcgroupが存在している)
  :(略)

cgroupが存在していることが確認できたらコンテナを作成します。user-childと名づけました。

ubuntu@user-parent:~$ lxc-create -t download -n user-child -- -d ubuntu -r xenial -a amd64
(親コンテナ上で非特権コンテナ"user-child"を作成)
ubuntu@user-parent:~$ grep id_map .local/share/lxc/user-child/config 
lxc.id_map = u 0 65536 65536
lxc.id_map = g 0 65536 65536
(親コンテナのID 65536から65536個のIDを割り当てる設定)

上のように、コンテナ作成後に65536個のIDマッピングを設定します。これで子コンテナ"user-child"が起動するはずです。

ubuntu@user-parent:~$ lxc-start -n user-child
ubuntu@user-parent:~$ lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4      IPV6 
user-child RUNNING 0         -      10.0.4.62 -    

無事起動しました。

ネストした非特権コンテナのcgroup

前回、特権コンテナでネストさせたときに確認したのと同様に、非特権コンテナでネストした場合にcgroupツリーがどうなるかを確認しておきましょう。

ホスト上からfreezerサブシステムの様子を見てみます。

ubuntu@host:~$ tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
└── user
    ├── root
    │   └── 0
    └── ubuntu
        └── 0
            └── lxc
                └── user-parent (親コンテナ)
                    └── user
                        ├── root
                        │   └── 0
                        └── ubuntu
                            └── 0
                                └── lxc
                                    └── user-child (子コンテナ)

14 directories

ここで、ツリー中の(ユーザ名)/(数字)pam_cgfsがユーザ用に作成したcgroupです。⁠数字」はログインセッションのIDです。

親コンテナからfreezerサブシステム以下を見ると、上の実行例のツリーで、user-parentより下のツリーが見えていることがわかります。非特権コンテナの場合でも、cgroup namespaceが機能していることがわかります。

ubuntu@user-parent:~$ tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
└── user
    ├── root
    │   └── 0
    └── ubuntu
        └── 0
            └── lxc
                └── user-child

7 directories

user-childコンテナ内から見ると、普通にホスト上でcgroupfsがマウントされたときのようにcgroupのrootだけがある状態です。

root@user-child:~# tree /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
├── cgroup.clone_children
├── cgroup.procs
├── freezer.parent_freezing
├── freezer.self_freezing
├── freezer.state
├── notify_on_release
└── tasks

0 directories, 7 files

まとめ

以上のように、今回は一般ユーザによる非特権コンテナをネストさせました。

今回紹介したネストでは、いずれのコンテナも一般ユーザ権限でネストさせました。しかし、一般ユーザ権限で動いているコンテナ内でさらにコンテナを起動させる場合は、今回のようなややこしい設定を行わずに、そのままroot権限で起動させても問題がないケースも多いでしょう。

なぜなら、ネスト元のコンテナが一般ユーザ権限で動いていれば、ネスト元のコンテナの親であるホストやコンテナ上では、そのネスト元のコンテナを実行しているユーザが持つ権限しかないからです。

今回紹介したような、ネストさせたコンテナを一般ユーザ権限で起動するのは、ネストしたコンテナの親のコンテナに対しても権限を制限したい場合です。このようにネストさせるケースはあまりないかもしれません。LXCではこのように柔軟に権限を設定できるのだということを頭の片隅にでも置いておいていただいて、必要になった際にこちらを参照いただければと思います。

その他に、ユーザ権限でのcgroupを作るための仕組みとしてpam_cgfsを紹介しました。pam_cgfsは今の所LXCFSに含まれていますが、LXCFSに密接に結びついているモジュールではありませんので、将来的にはLXCに含まれるようになるようです。

さて、前回の記事を書いてから今回の記事を書くまでの間に、LXCの新しいバージョンである2.1が公開されました。2.1では表面的には大きな機能の追加はないものの、設定項目がかなり変化していたり、細かい改良が行われていたりします。変更点については今後この連載で紹介したいと思います。

おすすめ記事

記事・ニュース一覧