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

第35回コンテナのネスト[1]

前回は、名前空間ごとに仮想化したcgroupツリーが見える、Linuxカーネルのcgroup名前空間という機能を紹介しました。

今回はそのcgroup名前空間の応用例を紹介することを兼ねて、コンテナ内でコンテナを動作させてみます。つまりコンテナをネストさせて遊んでみようという企画です。

今回はroot権限で動作するコンテナを使ってネストを試してみます。試している環境はUbuntu 16.04です。コマンドの実行例は、実行している環境が、ホストなのかどのコンテナなのかがわかりやすいようにプロンプトまで引用しています。

親コンテナの作成と起動

まずはホスト上にコンテナを作成します。

ubuntu@host:~$ sudo lxc-create -n parent -t download -- -d ubuntu -r xenial -a amd64

ここではネストさせるコンテナの「親」という意味でparentという名前のコンテナを作成しました。ここまでは通常のコンテナを作成する場合と同じです。

コンテナのネストを行うためには、ネスト用の設定を行う必要があります。LXC では標準でそのために設定ファイルnesting.confが付属していますので、lxc-createで作成したコンテナの設定ファイルにincludeの設定を行うだけです。

ubuntu@host:~$ sudo sed -i '$ a lxc.include = /usr/share/lxc/config/nesting.conf' /var/lib/lxc/parent/config

これでコンテナの中でコンテナを起動する準備ができました。このコンテナを起動し、コンテナ内にlxcパッケージをインストールしましょう。

ubuntu@host:~$ sudo lxc-start -n parent (コンテナの起動)
ubuntu@host:~$ sudo lxc-ls -f (コンテナの起動確認)
NAME   STATE   AUTOSTART GROUPS IPV4       IPV6 
parent RUNNING 0         -      10.0.3.169 -
ubuntu@host:~$ sudo lxc-attach -n parent(コンテナ環境に入る)
root@parent:~# apt-get update && apt-get install lxc
(LXCのインストール)
  :(略)

子コンテナの作成と起動

ここまででparentコンテナ内でコンテナを作成する準備が整いました。⁠親」の中に「子」コンテナを作成して、先の例と同様にネスト用の設定ファイルをincludeする設定を足しておきましょう。

root@parent:~# lxc-create -n child -t download -- -d ubuntu -r xenial -a amd64
root@parent:~# sed -i '$ a lxc.include = /usr/share/lxc/config/nesting.conf' /var/lib/lxc/child/config

コンテナ名はchildとしました。

root@parent:~# lxc-start -n child (子コンテナの起動)
root@parent:~# lxc-ls -f (子コンテナ起動の確認)
NAME  STATE   AUTOSTART GROUPS IPV4       IPV6 
child RUNNING 0         -      10.0.4.181 -    

特に問題なく起動しています。

ネストしたコンテナの確認

ところで、lxc-lsコマンドには--nestingというオプションがあり、ネストしたコンテナの状態が表示できます。

一旦、ホスト上のシェルに戻ってlxc-lsコマンドを実行してみましょう。

ubuntu@host:~$ sudo lxc-ls -f --nesting
NAME   STATE   AUTOSTART GROUPS IPV4                 IPV6 
parent RUNNING 0         -      10.0.3.169, 10.0.4.1 -    
\child RUNNING 0         -      10.0.4.181           -    

以上のように、ホスト上で直接parentが実行されており、その中でchildが実行されている様子が確認できます。また、parent内でLXCをインストールしたため、LXCがブリッジを作成しています。コンテナ起動時に作成されるvethインターフェースに割りあたっているアドレスだけでなく、そのブリッジに割りあたっているIPアドレス10.0.4.1も表示されています。

孫コンテナの作成と起動

以上で、コンテナ内でコンテナを起動できることが確認できました。LXCでは、ネストしたコンテナ内でさらにコンテナを起動する機能をサポートしていますので試してみましょう。

さきほど作成した「子」コンテナchild内でさらにコンテナを作成し、起動してみます。⁠親」コンテナから見ると孫ですね。

root@parent:~# lxc-attch -n child (子コンテナに入る)
root@child:~# apt-get update -y && apt-get install lxc (LXCのインストール)
root@child:~# lxc-create -n grandchild -t download -- -d ubuntu -r xenial -a amd64
(コンテナの作成)
root@child:~# lxc-start -n grandchild (孫コンテナの起動)
root@child:~# lxc-ls -f  (孫コンテナ起動の確認)
NAME       STATE   AUTOSTART GROUPS IPV4       IPV6 
grandchild RUNNING 0         -      10.0.3.157 -    

最初の親コンテナから見ると孫になりますのでgrandchildと名づけました。

先の実行例と同様に、ホストからlxc-lsでネストされている状態を確認してみましょう。

ubuntu@host:~$ sudo lxc-ls -f --nesting
NAME         STATE   AUTOSTART GROUPS IPV4                 IPV6 
parent       RUNNING 0         -      10.0.3.169, 10.0.4.1 -    
\child       RUNNING 0         -      10.0.3.1, 10.0.4.181 -    
 \grandchild RUNNING 0         -      10.0.3.157           -    

cgroupの確認

孫コンテナまで3段のネストをさせたところで、これまでに作成したコンテナそれぞれでcgroupfsツリーがどのようになっているかを確認してみましょう。

孫コンテナのcgroup

まずは、最後に作成したネストの一番の末端である孫コンテナgrandchildで、cgroupfsがどのようになっているかを確認してみましょう。

root@grandchild:~# ls -F /sys/fs/cgroup/
blkio/    cpu,cpuacct/  freezer/  net_cls@           perf_event/
cpu@      cpuset/       hugetlb/  net_cls,net_prio/  pids/
cpuacct@  devices/      memory/   net_prio@          systemd/

/sys/fs/cgroup以下は、通常のホストOS上と同じように、サブシステムごとのディレクトリが準備されています。このうちfreezerディレクトリを見てみましょう。

root@grandchild:~# ls -F /sys/fs/cgroup/freezer/
cgroup.clone_children  freezer.parent_freezing  freezer.state      tasks
cgroup.procs           freezer.self_freezing    notify_on_release

以上のようにcgroupとfreezerサブシステム用のファイルが存在します。freezerサブシステムをオプションに指定して、cgroupfsをマウントした直後と同じ状態です。

その他のサブシステムやsystemd用のcgroupについても、ホストOS上で直接cgroupfsをマウントしたときと同じように見えます。

子コンテナのcgroup

孫コンテナのcgroupを確認したところで、ネストのひとつ上のコンテナである子コンテナchildで、cgroupがどうなっているかを確認してみましょう。先の例と同じようにfreezerディレクトリ以下を確認します。

root@child:~# tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
└── lxc
    └── grandchild

今度はサブディレクトリが存在します。LXCコンテナは通常、cgroupのルートにlxcというcgroupを作成し、その下にコンテナ名のcgroupを作成します。child内でgrandchildという名前のコンテナを起動しましたので、そのコンテナ用のディレクトリlxc/grandchildが見えています。

つまり、孫コンテナ内で/sys/fs/cgroup/freezerとして見えていたcgroupは、ひとつ上の子コンテナから見た/sys/fs/cgroup/freezer/lxc/grandchildに相当します。

cgroup名前空間の機能で、⁠孫」コンテナgrandchild用に作られたcgroupが、コンテナ内ではルートとして見えていることが確認できました。

親コンテナのcgroup

親コンテナであるparent内でも同様に確認してみましょう。

root@parent:~# tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
└── lxc
    └── child
        └── lxc
            └── grandchild

コンテナごとにlxc/(コンテナ名)というcgroupが作成されており、ネストしている様子がわかります。

ホストのcgroup

同様にホストから確認しましょう。

ubuntu@host:~$ tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
├── lxc
│   └── parent
│       └── lxc
│           └── child
│               └── lxc
│                   └── grandchild
  :(略)

先の例と同様にcgroupがネストしていますね。

LXCコンテナ内でのDocker

ここまでで、多段にネストしたコンテナを作成し、その場合にcgroupがどのように作られ、コンテナ内ではcgroup namespaceによって自分用のcgroupツリーのみが見えることを確認しました。

ここまでで作成したコンテナはすべてLXCコンテナでしたが、最後にLXCコンテナの中でDockerコンテナを動かしてみましょう。

まずはDockerデーモンを動かすためのLXCコンテナを作成します。LXCコンテナを表すlxcctという名前にしてみました。

ubuntu@host:~$ sudo lxc-create -t download -n lxcct -- -d ubuntu -r xenial -a amd64

Ubuntu 16.04のコンテナを作成しています。このコンテナ内でDockerを動かすための設定を追加します。追加した行は次の2行です。

ubuntu@host:~$ sudo tail -n2 /var/lib/lxc/lxcct/config
lxc.aa_profile = unconfined
lxc.cap.drop =

AppArmorを無効にする設定と、無効にするケーパビリティがない状態にしています。そして、LXCコンテナ内でDockerを利用する際に必要な、overlayfs用のモジュールをホスト上でロードしておきます。

ubuntu@host:~$ sudo modprobe -v overlay
insmod /lib/modules/4.4.0-79-generic/kernel/fs/overlayfs/overlay.ko

これで準備は完了です。コンテナを起動し、コンテナ内でDockerパッケージをインストールします。

ubuntu@host:~$ sudo lxc-start -n lxcct (LXCコンテナの起動)
ubuntu@host:~$ sudo lxc-attach -n lxcct (LXCコンテナ環境に入る)
root@lxcct:~# apt-get update
root@lxcct:~# apt-get -y install docker.io (Dockerをパッケージインストール)

インストールはUbuntuのパッケージを使ってインストールします。動作を確認してみましょう。

root@lxcct:~# docker info
  :(略)
Server Version: 1.12.6
Storage Driver: overlay
 Backing Filesystem: extfs
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: null host bridge overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Security Options: apparmor seccomp
  :(略)

動作していることが確認できました。ストレージとしてoverlayfsが使われていることがわかります。

それではDockerコンテナを起動してみましょう。

root@lxcct:~# docker run -t -i ubuntu /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
  :(略)
Status: Downloaded newer image for ubuntu:latest
root@54fa51853d2c:/# ls -la .dockerenv 
-rwxr-xr-x 1 root root 0 Jul 10 11:33 .dockerenv

Ubuntuイメージを使ってDockerコンテナが起動しました。/.dockerenvファイルがあるので確かにDockerコンテナ内にいるようですね。

Dockerコンテナ内からcgroupを確認してみます。

root@54fa51853d2c:/# ls -F /sys/fs/cgroup/freezer/
cgroup.clone_children  cgroup.procs  freezer.parent_freezing  freezer.self_freezing  freezer.state  notify_on_release  tasks

freezerサブシステムがマウントされており、特に子cgroupは存在しない状態です。このままDockerコンテナが起動している状態で、ホスト上のシェルからfreezerサブシステムのツリーの様子を確認してみます。

ubuntu@host:~$ tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
├── lxc
│   └── lxcct
│       └── docker
│           └── 54fa51853d2c1e5dc0a6ca0488c5522b6bf8a77ac24c471ee2044daa845e255d
  :(略)

lxc/lxcctがLXCが作成したcgroup、docker/(コンテナ名)がDockerが作成したcgroupです。

LXCコンテナ上からみると、次のようになります。

root@lxcct:~# tree -d /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
└── docker
    └── 54fa51853d2c1e5dc0a6ca0488c5522b6bf8a77ac24c471ee2044daa845e255d

Dockerコンテナを起動した場合も、LXCコンテナと同様にcgroup namespaceの機能により、コンテナ自身が所属するcgroup以下のツリーのみが見えていることがわかります。

まとめ

今回は、cgroup namespaceの実際の動きを確認することを兼ねて、コンテナをネストさせて遊んでみました。LXCでは多段のネストもサポートしています。そこで、実際に何段もコンテナをネストさせることはあまりないかもしれませんが、多段のネストも試してみました。

Linuxカーネルの機能的に見ると、cgroupは階層構造が作れますし、名前空間内に名前空間を作れますので、コンテナをネストさせることで問題になることはありません。

ただ、今回紹介したようにコンテナをネストさせることができるかどうかは、コンテナランタイムの実装次第でしょう。

LXCは、カーネルで実装されているコンテナ関連の機能をフルに活かすように実装されていますので、今回紹介したようなネストができるというわけです。

LXCと同じく、linuxcontainers.orgで開発されているLXDでも、ライブラリとしてLXCを使っていますので、コンテナをネストできます。LXDコンテナ上でDockerコンテナを起動させる記事がありますので、興味がある方は是非そちらもご覧になってみてください。

さて、次回も今回に続いてコンテナのネストの話題です。一般ユーザ権限で起動する非特権コンテナでコンテナをネストさせてみる予定です。お楽しみに。

第11回 コンテナ型仮想化の情報交換会@大阪

6/17(土)に大阪で、この連載でも何度か紹介しているコンテナの勉強会を開催しました。

当日は、自作コンテナ実装のお話から、LXCやLXDの活用事例、Dockerの監視の話まで色々なコンテナに関わるお話を聴くことができ、大変勉強になりました。

当日のセッションそれぞれの動画の公開や発表資料へのリンクがありますので、勉強会ページをぜひご覧ください。

おすすめ記事

記事・ニュース一覧