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

第33回cgmanagerとLXCFS ─ コンテナ内のcgroup管理[1]

今回は、cgroupを操作したり、コンテナ特有の値の/proc以下のファイルを提供したりするLXC用のソフトウェアを紹介したいと思います。

LXCでは設定ファイル内でcgroupの設定を行い、コンテナに対するリソース制限を行います。この場合、cgroup操作はコンテナの外で行われますので、必ずしもコンテナ内でcgroupを操作する必要はありません。

しかし、LXCではコンテナ内でさらにコンテナを起動する「コンテナのネスト」をサポートしています。この場合、コンテナ内で起動するコンテナに対してリソース制限を行うために、コンテナ内でcgroupを操作する必要がでてきます。つまりコンテナ内でcgroupfsツリーを操作しなければなりません。

LXCでは、最初に第11回で説明したlxc.mount.autoという設定を使用して、ホストのcgroupfsをバインドマウントすることにより、コンテナ内にcgroupfsを提供しました。

しかし、LXCは一般ユーザで起動する非特権コンテナをサポートしています。非特権コンテナの場合、ホスト上にマウントされているcgroupfsを直接操作することになるlxc.mount.autoは使用できません。

そこでLXC 1.0リリース時点(Ubuntu 14.04LTS)では、cgmanagerというソフトウェアを使い、コンテナ内からcgroupの操作を行いました。

その後、cgmanagerでは解決できない問題が出てきたので、2015年初頭からLXCFSというソフトウェアの開発がスタートしました。LXC 1.1リリース時点ではcgmanagerとともに、LXCFSの使用もサポートされました。Ubuntu 15.04では、LXC 1.1とLXCFSの組み合わせでコンテナ内でcgroup操作が行えるようになっていました。

それでは、このcgmanagerとLXCFSについてもう少し詳しく見ていきましょう。

cgmanager

cgmanagerは、LXCでcgroupを管理するために2013年末に開発がスタートし、Ubuntu 14.04 LTSでLXC 1.0とともにインストールされました。この連載でも第10回で簡単に紹介しました。

cgroupはファイルシステムをマウントし、ファイルに値を書き込むことで簡単に利用できます。しかし、この頃から直接cgroupfsに存在するファイルを操作するのではなく、cgroupを管理する仕組みを通してcgroupを管理すべきという議論がなされるようになっていました。

cgmanagerは、D-Busを経由でcgroupに関する操作を受け付けcgroupを操作するデーモンで、この流れにも沿った実装でした。cgmanagerが独自のマウント名前空間を作成し、その中でcgroupfsをマウントし、受け付けたコマンドに沿った操作をcgmanagerで行います。

LXCコンテナは、cgmanager経由でcgroupに関わる操作を行うため、ホストで直接cgroupfsをマウントしていなくてもコンテナ向けのcgroupに関わる操作が行えました。

また、cgmanagerを使うことにより、コンテナ内でLXCを使ってコンテナを作成し、起動できるようになりました。コンテナ内ではcgmanagerへのプロキシが起動し、コンテナ内からはプロキシに対してコマンドを送って、ホスト上で起動しているcgmanagerへコマンドが伝達できました。この機能を使って、コンテナ内でも自身に関わるcgroupが操作でき、コンテナ内でcgroupを使用する必要があるLXCコンテナが起動できました。

ところがコンテナの利用が進み、コンテナを取り巻く環境が変化するとともに、cgmanagerでは対応できない問題が生じてきました。その問題のうち最も大きな問題は、systemdが広く使われるようになったことです。

LXCをインストールする最も主要なディストリビューションであるUbuntuでもinitがUpstartからsystemdに変更されましたので、cgmanagerで対応できない問題はLXCにおいても大きな問題になりました。systemdはcgroupを積極的に活用するため、initがsystemdである場合、cgroupfsがマウントされていることは必須です。

つまりUbuntuをはじめとして、ほとんどのディストリビューションが採用したsystemdがinitとして採用されている場合、コンテナ内でも、通常のcgroupfsと同様のインターフェースを利用してcgroupを操作できることが必須となり、cgmanagerでは対応できなくなりました。

また、systemdは起動してくるプログラムをcgroupを用いて管理するため、cgroupが頻繁に利用されます。このように頻繁にcgroupを参照するようなソフトウェアから使用する場合、D-Busという比較的遅いインターフェースを使っていると、パフォーマンス的にも問題となります。

そこで、LXCプロジェクトではcgmanagerに代わってこの後紹介するLXCFSが開発されました。これに伴い、cgmanagerはこれまでにリリースされた、cgmanagerがインストールされる環境をサポートするために保守のみが続けられるようになります。今後、cgmanagerが必要なレガシーな環境がなくなると、開発は終了となるでしょう。

LXCFS

前述のようなcgmanagerが持つ問題を解決するとともに、別に懸案となっていた問題を解決するために開発されたソフトウェアがLXCFSです。

LXCFSはふたつの機能を持っています。

  • コンテナ向けのcgroupfsツリーの提供
  • コンテナ向けの/proc以下のファイルの提供

LXCFSはホスト上で実行されます。ホスト上でどのようにLXCFSが実行され、FUSEファイルシステムがマウントされているかを見てみましょう。以下はUbuntu 14.04LTSに、LXCのオフィシャルなPPAから最新版LTSのLXC、LXCFSをインストールした環境で試しています。

$ sudo add-apt-repository ppa:ubuntu-lxc/lxc-lts
$ sudo apt-get update
$ sudo apt-get install lxc lxcfs
$ sudo apt-get remove cgmanager (cgmanagerは削除する)

LXCFSがどのように起動しているかを確認してみましょう。

$ grep lxcfs /proc/self/mounts
lxcfs /var/lib/lxcfs fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0

以上のように/var/lib/lxcfsにマウントされています。/var/lib/lxcfsを見てみると、

$ ls /var/lib/lxcfs
cgroup  proc
$ ls /var/lib/lxcfs/cgroup/
blkio  cpuacct  devices  hugetlb  name=systemd
cpu    cpuset   freezer  memory   perf_event
$ ls /var/lib/lxcfs/proc
cpuinfo  diskstats  meminfo  stat  swaps  uptime

以上のように/var/lib/lxcfs以下にはcgroupprocというふたつのディレクトリが存在し、cgroup以下は各サブシステムごとのディレクトリと、systemd用のディレクトリが存在します。proc以下は、LXCFSによって仮想化されるファイルが存在します。

LXCコンテナでのLXCFSの利用

以上のように起動したLXCFSが、LXCからどのように利用されるのかを簡単に見ておきましょう。

lxcfsパッケージをインストールすると、LXCコンテナから共通して読み込まれる設定ファイルとして/usr/share/lxc/config/common.conf.d/00-lxcfs.confというファイルがインストールされます。このファイルには第23回第24回で説明したフックが設定されています。

$ cat /usr/share/lxc/config/common.conf.d/00-lxcfs.conf
lxc.hook.mount = /usr/share/lxcfs/lxc.mount.hook
lxc.hook.post-stop = /usr/share/lxcfs/lxc.reboot.hook

このうちコンテナ起動時に読み込まれる/usr/share/lxcfs/lxc.mount.hookを見てみると、/var/lib/lxcfs以下にマウントされたlxcfsをコンテナにバインドマウントしていることがわかります。

  :(略)
if [ -d /var/lib/lxcfs/proc/ ]; then
    for entry in /var/lib/lxcfs/proc/*; do
        [ -e "${LXC_ROOTFS_MOUNT}/proc/$(basename $entry)" ] || continue
        mount -n --bind $entry ${LXC_ROOTFS_MOUNT}/proc/$(basename $entry)
    done
fi
  :(略)

以上で挙げた部分は/proc以下でLXCFSが対象とするファイルをバインドマウントしている部分ですが、同様にcgroupをバインドマウントする処理も記述されています。

それでは、先に紹介したふたつの機能を少し詳しく見ていきましょう。

コンテナへのcgroupfsツリーの提供

ひとつめの機能は、cgmanagerが解決できなかったコンテナ向けのcgroupfsツリーをFUSEを使って提供します。

バインドマウントされたcgroupfsツリーが、コンテナ内から参照された際には、そのコンテナに関係するcgroupのみを見せるように提供します。これによりsystemdなどのcgroupfsツリーが必要なソフトウェアに対して、実際にcgroupfsをマウントした時のようなツリーを見せられます。

ただし、cgmanagerはD-Busを使った汎用的な仕組みであったのに対し、LXCFSはLXC向けのソフトウェアであり汎用性はありません。

それではLXCFSがどのようにcgroupfsのツリーをコンテナに見せているかを見てみましょう。

まずはコンテナを起動します。ここではxenial01という名前の非特権コンテナを起動してみました。

$ lxc-start -n xenial01
$ lxc-ls -f
NAME     STATE   AUTOSTART GROUPS IPV4      IPV6 
xenial01 RUNNING 0         -      10.0.3.77 -    

まず、コンテナ内から見たcgroupとホスト上から見たcgroupを比較した時にわかりやすいよう、ホスト上で"test01"というcgroupをcpuサブシステム以下に作成しておきます。

$ sudo mkdir /sys/fs/cgroup/cpu/test01 ("test01" cgroupを作成)
$ ls -d /sys/fs/cgroup/cpu/test01      (作成されたことを確認)
/sys/fs/cgroup/cpu/test01

起動したコンテナ内でcpuサブシステム以下のcgroupがどのようになっているかを見てみましょう。

$ lxc-attach -n xenial01 -- find /sys/fs/cgroup/cpu -type f
(/sys/fs/cgroup以下の全ファイルを表示)
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cpu.stat
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cpu.cfs_period_us
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cpu.cfs_quota_us
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cpu.shares
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/notify_on_release
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/tasks
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cgroup.clone_children
find: ‘/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cgroup.event_control’: Permission denied
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/cgroup.procs
(コンテナ用のcgroup以下にのみcgroup用ファイルが存在している)
$ lxc-attach -n xenial01 -- ls /sys/fs/cgroup/cpu/
user
$ lxc-attach -n xenial01 -- ls /sys/fs/cgroup/cpu/user
1000.user
(他のcgroupには本来存在するはずのcgroup用のファイルがない)

非特権コンテナですので、起動したユーザが書き込めるcgroupであるuser/1000.user/1.session/lxc/xenial01に、コンテナ用のcgroupができています。そして、自身用のcgroupであるxenial01というグループ以下にはcgroup用のファイルが存在しますが、本来各ディレクトリに存在すべきcgroup用のファイルが存在せず、ディレクトリだけが存在しています。先ほど作成した"test01"グループは存在しません。

このように、自身以外用のcgroupを見たり操作したりできないようになっています。この機能自体は第11回で説明したlxc.mount.autocgroup:mixedを指定した場合と同じです。

ここでホストからcpuサブシステム以下に存在するcgroupを見てみます。

$ find /sys/fs/cgroup/cpu -type d (cpu以下のcgroupを確認)
/sys/fs/cgroup/cpu
/sys/fs/cgroup/cpu/test01 (コンテナ内から見えなかったcgroupが存在している)
/sys/fs/cgroup/cpu/lxc    (コンテナ内から見えなかったcgroupが存在している)
/sys/fs/cgroup/cpu/user
/sys/fs/cgroup/cpu/user/1000.user
/sys/fs/cgroup/cpu/user/1000.user/1.session
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01
/sys/fs/cgroup/cpu/user/1000.user/c1.session
$ ls /sys/fs/cgroup/cpu
cgroup.clone_children  cgroup.procs          cpu.cfs_period_us  cpu.shares  lxc                release_agent  test01
cgroup.event_control   cgroup.sane_behavior  cpu.cfs_quota_us   cpu.stat    notify_on_release  tasks          user
(各cgroup直下にもcgroup用ファイルが存在している)

cpu以下には、コンテナ内から見たときには存在していなかったlxcや、さきほど作成したtest01というcgroupが存在しており、各cgroupを表すディレクトリ直下にはcgroup用のファイルが存在します。

このようにホスト上に存在するコンテナと関係のないcgroupは見せずに、コンテナ向けのcgroupだけを操作できるようにcgroupfsを提供する機能をLXCFSが提供しています。

それでは、第11回で説明した、lxc.mount.autoで提供されるcgroupと、LXCFSが提供するcgroupの違いは何でしょう?

筆者はLXCFSの実装をきちんと読んでないのではっきりとはわからないのですが、LXCFSにはsystemd依存の処理が入っているようです。そのため、LXCFSを使って起動した非特権コンテナ内では、systemdが出力するエラーが少し記録されているだけですが、LXCFSを使わずにlxc.mount.autoで起動したコンテナでは、systemdが出力するcgroupのエラーが多数記録されています。

$ lxc-attach -n xenial01 -- journalctl -b -p 4 (コンテナ内のsystemdのログを見てみる)
-- Logs begin at Mon 2016-11-21 12:17:16 UTC, end at Mon 2016-11-21 12:17:16 UTC. --
Nov 21 12:17:16 xenial01 systemd[1]: Failed to reset devices.list on /user/1000.user/1.session/lxc/xenial01/system.slice/systemd-journal-flush.service: Operation not permitted
Nov 21 12:17:16 xenial01 systemd[1]: Failed to reset devices.list on /user/1000.user/1.session/lxc/xenial01/system.slice/networking.service: Operation not permitted
Nov 21 12:17:16 xenial01 systemd[1]: Failed to reset devices.list on /user/1000.user/1.session/lxc/xenial01/system.slice/systemd-tmpfiles-setup.service: Operation not permitted
Nov 21 12:17:16 xenial01 systemd[1]: Failed to reset devices.list on /user/1000.user/1.session/lxc/xenial01/system.slice/systemd-journal-flush.service: Operation not permitted
(同様のエラーが多数記録されている)

コンテナへの/proc以下のファイルの提供

ふたつめの機能は、/proc以下に存在するファイルのいくつかをコンテナ向けカスタマイズして提供します。

これまでもPIDやマウント名前空間により、コンテナ向けの/procが提供できました。しかし、リソースの状態を提供するようなファイルの中身はホストと同じ値が提供されていました。このため、メモリの消費状況を表示するためにリソース表示系のコマンドを使った場合、ホスト上で実行した際と同じ値がそのまま表示されていました。

このため、コンテナ内で消費されているリソースをモニタリングする際は、ホスト上でcgroupが提供するファイルから値を読み取る必要がありました。

LXCFSを使うと、LXCFSがコンテナのcgroupから値を読み取り、コンテナ内で/proc以下の対象となるファイルが読まれた場合に、コンテナごとの値を表示します。この機能により、コンテナ内でもホスト上と同じようにコマンドを実行して、コンテナ内のリソース状態をチェックできます。

LXCFSでは以下のファイルでコンテナ向けの値を提供します。

  • /proc/cpuinfo
  • /proc/diskstats
  • /proc/meminfo
  • /proc/stat
  • /proc/swaps
  • /proc/uptime

では、この機能が有効に働いていることを確認してみましょう。

ホストは以下のようにCPUが2つ、メモリは1GB搭載です。

$ grep processor /proc/cpuinfo 
processor   : 0
processor   : 1
$ grep MemTotal /proc/meminfo 
MemTotal:        1017724 kB

このホスト上で実行するコンテナには以下のようなcgroup関連の設定を行っています。

$ sudo grep cgroup /var/lib/lxc/xenial01/config
lxc.cgroup.memory.limit_in_bytes = 512M (メモリ制限を512MBに)
lxc.cgroup.cpuset.cpus = 0              (CPUは0番のみ使用)

LXCFSが動作していない場合

まずは比較のためにLXCFSを停止させた状態で確認してみましょう。lxcfsパッケージがインストールされた状態では、コンテナ起動時にLXCFS関係のフックが動くように設定されていますので、フックが動かないようにしておく必要があります。

$ service lxcfs status
lxcfs stop/waiting
$ sudo lxc-start -n xenial01

コンテナ内でいくつか/proc以下のファイルを見てみましょう。

$ sudo lxc-attach -n xenial01 -- grep processor /proc/cpuinfo 
processor   : 0
processor   : 1
$ sudo lxc-attach -n xenial01 -- grep MemTotal /proc/meminfo
MemTotal:        1017724 kB

以上のようにホストと同じ値が見えています。/proc/uptimeファイルの中身とfreeコマンドを実行した結果も見ておきましょう。

$ cat /proc/uptime ; sudo lxc-attach -n xenial01 -- cat /proc/uptime
1322.19 563.45
1322.22 563.45
$ free -m (ホスト上で実行)
             total       used       free     shared    buffers     cached
Mem:           993        724        269          0         43        583
-/+ buffers/cache:         97        896
Swap:         1019          0       1019
$ sudo lxc-attach -n xenial01 -- free -m (コンテナ上で実行)
             total       used       free     shared    buffers     cached
Mem:           993        725        268          0         43        583
-/+ buffers/cache:         98        895
Swap:         1019          0       1019

コマンドを実行したタイミングが違いますのでぴったり同じにはなりませんが、/proc/uptimeの中身はホストと同じようですし、freeコマンドを実行した結果もホストと同じ値が見えているようです。

LXCFSが動作している場合

では、LXCFSを起動した状態でどのようになるかを確認してみましょう。

$ service lxcfs status (lxcfsの起動を確認)
lxcfs start/running, process 7965
$ sudo lxc-start -n xenial01
$ sudo lxc-attach -n xenial01 -- grep processor /proc/cpuinfo
processor   : 0
$ sudo lxc-attach -n xenial01 -- grep MemTotal /proc/meminfo
MemTotal:         524288 kB

以上のようにCPU数、メモリ搭載量共にコンテナ向けに設定したcgroupの値が反映されています。

ホストとコンテナの/proc/uptimeファイルの中身を見てみると、明らかにホストとコンテナで異なることがわかります。

$ cat /proc/uptime ; sudo lxc-attach -n xenial01 -- cat /proc/uptime
839.24 563.43
217.0 217.0

freeコマンドもコンテナの値を出力していますね。totalだけでなくusedやfreeの値も異なります。

$ free -m
             total       used       free     shared    buffers     cached
Mem:           993        215        778          0         37        118
-/+ buffers/cache:         59        934
Swap:         1019          0       1019
$ sudo lxc-attach -n xenial01 -- free -m
             total       used       free     shared    buffers     cached
Mem:           512          6        505          0          0          0
-/+ buffers/cache:          6        505
Swap:         1019          0       1019

以上のようにCPU数、メモリ搭載量共にコンテナ向けに設定したcgroupの値が反映されています。

ホストとコンテナの/proc/uptimeファイルの中身を見てみると、明らかにホストとコンテナで異なることがわかります。

$ cat /proc/uptime ; sudo lxc-attach -n xenial01 -- cat /proc/uptime
839.24 563.43
217.0 217.0

freeコマンドもコンテナの値を出力していますね。totalだけでなくusedやfreeの値も異なります。

$ free -m
             total       used       free     shared    buffers     cached
Mem:           993        215        778          0         37        118
-/+ buffers/cache:         59        934
Swap:         1019          0       1019
$ sudo lxc-attach -n xenial01 -- free -m
             total       used       free     shared    buffers     cached
Mem:           512          6        505          0          0          0
-/+ buffers/cache:          6        505
Swap:         1019          0       1019

以上のようにLXCFSにより、/proc以下のファイルがコンテナ向けの値を提供していることがわかります。

コンテナ上でリソースの消費状況などをモニタリングする際に、ホスト上でcgroupのファイルを見ることなく、ホストと同じコマンドが使えるので便利ですね。

まとめ

今回はLXCコンテナ内でcgroupを操作するための仕組みの変遷と、cgmanagerに代わって登場したLXCFSの動作を紹介しました。

LXCFSは今回説明したように、コンテナにcgroupfsと/proc以下のいくつかのファイルを提供するソフトウェアです。ところが、LXCFSのcgroupfs関連機能は、Ubuntuの現時点の長期サポート版である16.04では使われていません。

これは同様の機能がカーネルに実装されたためです。次回はこの機能について紹介したいと思います。

第10回 コンテナ型仮想化の情報交換会@東京

筆者が主催している「コンテナ型仮想化の情報交換会」の第10回目を、10月29日(土)に株式会社インターネットイニシアティブ本社にて開催しました。

勉強会を始めたころは、まさか10回も勉強会が続くとは思っていなかったのですが、最近のコンテナの盛り上がりもあり、勉強会で扱うネタが尽きることはありません。

今回もWindowsのコンテナのお話や、Goでコンテナをライブコーディングするといったセッションまで、色々な幅広いお話を聞くことができました。

当日の動画の公開や資料へのリンクを以下にまとめてありますので、ぜひご覧ください。

おすすめ記事

記事・ニュース一覧