さて、今年もAdvent Calendarの季節になりました。例年通り今年もいろいろなカレンダーがあって盛り上がっていますね。筆者もいくつかエントリしたので、そのための記事を書くのが大変です。
そのエントリしたカレンダーのひとつがLinux Advent Calendar 2016です。一昨年から、この連載の記事をLinux Advent Calendar 2015、2014へのエントリと兼ねて書いてきました。
今年も同様に、この記事をLinux Advent Calendar 2016 13日目の記事として書きました。Linux Advent Calendar 2016には今年もマニアックで面白いエントリが並んでいますね。
昨年のエントリ(第30回)は、Linuxカーネルに新たに追加されたcgroupのpidsサブシステムを紹介しました。今年は、Linuxカーネルが持つコンテナ関連機能としてはcgroupと双璧をなす、名前空間(Namespace)の新しい機能を紹介しようと思います。
今回の実行例はUbuntu 16.04上で実行しています。
前回は、コンテナ内でcgroupfsを利用するための仕組みとして、cgmanagerとLXCFSを紹介しました。
Ubuntu 14.04 LTSでは、cgroupを管理するためのデーモンとしてcgmanagerが導入され、LXC 1.0と共に動作するように設定されました。しかしその後、Ubuntu 15.04ではLXCFSに置き換わりました。LXC 1.0はまだサポート期間ですので、cgmanagerはまだしばらくは使われ続けサポートが続きますが、短命なソフトウェアでしたね。
cgmanagerから置き換わったLXCFSがUbuntu 15.04で導入されたものの、その後リリースされた長期サポート版であるUbuntu 16.04 LTSでは、コンテナ内にcgroupfsツリーを提供する機能については使われなくなりました。LXCFSには前回紹介した通り、/proc
以下をコンテナ向けに仮想化する機能がありますのでなくなるわけではありませんが、cgroup関連機能については今後不要になっていきます。
LXCFSのcgroup関連機能が不要になった理由は、新たにLinuxカーネルにcgroupを仮想化する機能が追加されたためです。これがcgroup名前空間という機能です。
/proc/[PID]/nsディレクトリ
cgroup名前空間を紹介する前に、これまでの名前空間の記事では紹介していなかった/proc/[PID]/ns
ディレクトリについて紹介しておきましょう("[PID]"はプロセスのPIDが入ります)。
各プロセスに関連する情報を格納したファイルが存在する/proc/[PID]
ディレクトリ以下にはns
というディレクトリが存在します。
このディレクトリには、どのような名前空間が使えるか、そしてそのプロセスが属している名前空間がわかる特殊なファイルが置かれています。
ls -l
を実行すると、以下のように名前空間名をファイル名とする特殊なシンボリックリンクが見えます。
リンク先に数字が表示されていますが、これが名前空間を表しており、この数字が同じだと同じ名前空間に属しており、異なると異なる名前空間に属していることになります。
この/proc/[PID]/ns
以下のファイルには、もうひとつ重要な役割があります。
あるプロセスが所属する名前空間に移動するためのシステムコールとしてsetns(2)というシステムコールがあります。このsetns(2)
はファイルディスクリプタと名前空間の種類を引数に取ります。ここで与えるファイルディスクリプタが、/proc/[PID]/ns
ディレクトリ以下に存在するファイルのファイルディスクリプタです。
LXCでsetns(2)
を使うコマンドは、起動中のコンテナ内に入るために使うlxc-attach
です。この/proc/[PID]/ns
以下の実装が完成したのは3.8カーネルの時で、setns(2)
がすべての名前空間に対してきちんと動作し、lxc-attach
コマンドが動作するようになったのがこの時でした。
cgroup名前空間
それではいよいよcgroup名前空間について見ていきましょう。
cgroup名前空間は4.6カーネルで導入された機能です。Ubuntu 16.04 LTSでインストールされるカーネルのバージョンは4.4ですが、このカーネルにもバックポートされており、Ubuntu 16.04でもcgroup名前空間が使用できます。
cgroup名前空間は名前空間ごとにcgroupを仮想化します。具体的には、新たにcgroup名前空間を作成すると、LXCFSが実現していたように自身に関連するcgroupだけが見えるようになります。見え方はLXCFSとは少し違います。
cgroup名前空間が使えるかどうかは、先に紹介した/proc/[PID]/ns
以下にcgroup
というファイルが存在するかを見ればわかります。先の実行例で示したように、Ubuntu 16.04上でns
ディレクトリ以下を見ると、
以上のようにcgroup
というファイルが存在しており、cgroup名前空間が使えることがわかります。cgroup名前空間をサポートしていないカーネルで/proc/[PID]/ns
を見ても、cgroup
というファイルは存在しません。
cgroup名前空間が提供する機能
LXCFSが提供するコンテナ内のcgroupfs
まずは比較のために前回紹介したLXCFSを使うとコンテナ内でcgroupfsがどのように見えたかをおさらいしておきましょう。
以上のように起動した非特権コンテナ"xenial01"用のcgroupは、
- 親環境上で見た場合
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/
に存在
- コンテナ内で見た場合
/sys/fs/cgroup/cpu/user/1000.user/1.session/lxc/xenial01/
に存在
以上のように親であるホスト上の環境でもコンテナ内でも同じパスに存在しました(具体的なパスはユーザ環境により異なります)。
ただし、コンテナ内では"xenial01"以下のディレクトリ以外にはcgroup関連のファイルが存在せず、他のディレクトリは空でした。
つまりLXCFSは、親と同じツリー構造を見せつつ、自分が権限を持つグループだけcgroupの中身を見せていました。
cgroup名前空間が提供するコンテナ内のcgroupfs
それではUbuntu 16.04上で同様に非特権コンテナを起動してみましょう。
cpuサブシステムがマウントされた直下のルートグループを見ると、ディレクトリはありません。つまりルートcgroupのみ存在しています。OSを起動してcgroupfsをマウントしたときと同じですね。
そして、このコンテナ内に存在するcgroupfsのルート以下は、親環境ではルートグループより深いパスに存在していたコンテナ用cgroupの中身が見えているのです。このように、cgroup名前空間を使うと、コンテナ内では自身のcgroupがルートになります。とても自然な動きですね。
つまり非特権コンテナ"xenial01"用のcgroupは、
- 親環境上で見た場合
/sys/fs/cgroup/cpu,cpuacct/user/ubuntu/1/lxc/xenial01
に存在(ログインユーザの環境により異なります)
- コンテナ内で見た場合
/
(ルート)に存在
実際にはコンテナ用のcgroupがコンテナ内でのルートになるのは、マウント名前空間をあわせて使っているからです。この連携はあとで説明します。
cgroupに属するPIDが書かれたtasks
ファイル(とcgroup.procs
ファイル)には、PID名前空間内でのPIDが書かれます。
このように、コンテナ内でもホストOS上と同様のcgroupfsの見せ方を提供する機能がcgroup名前空間です。
cgroup名前空間の機能を簡単に紹介したところで、もう少しcgroup名前空間について詳しく見ていきましょう。
/proc/[PID]/cgroupファイル
プロセスがどのcgroupに属しているかは、/proc/[PID]/cgroup
というファイルを見ればわかります。
まずはテスト用にcgroupを作成します。
以上のようにmemoryサブシステムのルート直下に"test01"グループを作成し、シェルのPIDを登録しました。
cgroupファイルのmemory
行を見ると、/test01
と書かれています。つまり/test01
グループに属しているということですね。
cgroup名前空間と/proc/[PID]/cgroupファイル
cgroupファイルの内容が理解できたところで、unshare
コマンドを使って新たな名前空間を作成してみましょう。
なぜかUbuntu 16.04にインストールされるunshare
コマンドはcgroup名前空間を扱えません。そこで別途util-linux 2.29をソースからコンパイルし、対応するunshare
コマンドを作成しました。あとで使うために同時にマウント名前空間を作成しています。
名前空間を作成してbash
を実行すると、親プロセス(PID:1220)が/test01
グループに所属していたので、その子プロセス(PID:1237)も/test01
所属となりました。
ここで/proc/[PID]/cgroup
ファイルを確認してみましょう。
先ほどは/test01
に所属していたように表示されていた親プロセスも、子プロセスも、名前空間内で見るとルートに属していると表示されます。
このようにcgroupファイルの中身が、名前空間が作成された時点に所属していたcgroupをルートとしたツリーで見えるようになるのが、cgroup名前空間の機能です。
cgroup名前空間とマウント名前空間の連携
cgroupファイルはcgroup名前空間の機能によって、名前空間内での見え方に変わっていることが確認できました。
しかし、新たなcgroup名前空間を作成しても、マウントは特に変更していませんのでそのままの状態です。cgroupfsを確認すると、
依然として/sys/fs/cgroup/memory/test01
が存在しており、親環境と同じツリーが見えています。プロセスのマウント情報を格納しているmountinfo
ファイルでは、4つ目のマウントのルートを表すエントリが/..
(ルートディレクトリの親ディレクトリ)とちょっと変な表示になっていますね。
それではcgroupfsをマウントしなおしてみましょう。
まずは、systemdが起動時にすべてのマウント操作を共有する設定を行っているので、マウント操作を行う前にそれを無効にします。これで、マウント名前空間ごとに独立したマウントが見えるようになります(init
がsystemd以外の場合は不要です)。
そして、アンマウントした後に、再度cgroupをマウントします。
これでマウント情報がどうなったか確認してみましょう。
/proc/self/mountinfo
の内容が変わっており、ルートは"/"と表示されていることがわかります。
実際のcgroupfsを見ても、/sys/fs/cgroup/memory
以下にはcgroupは存在せず、ルートグループのみが存在する状態になりました。
このようにマウント名前空間と連携して、実際のcgroupfsツリーも/proc/[PID]/cgroup
ファイルの記載と一致するツリーとなります。
cgroup名前空間内でグループを作成
このcgroup名前空間内でルート直下に新たにグループを作ってみましょう。
親環境からtest01
グループ内を見てみると、
test01
グループ直下にtest02
グループがありますね。つまり名前空間内では、親と同じcgroupfsツリーを、自身のcgroupをルートとしてそれ以下だけを見せていることがわかります。
cgroup間のプロセスの移動
今度は同じ階層にグループをふたつ作成し、その間を移動して動きを見てみましょう。
test01
とtest02
というグループを同じ深さの階層(ルート直下)に作成し、先ほどと同様にtest01
グループにプロセスを登録し、名前空間を作成しました。すると名前空間内ではルートに所属しています。ここまでは先ほどと同じです。
それでは、自身(PID:1254)をtest02
に移動させてみましょう。自身はtest01
にいますが、cgroupfsを再マウントしていませんので、まだtest02
も見えているはずです。移動はPIDを新たに所属させたいグループのtasks
ファイルに登録しなおすだけでしたね。
test02
へ移動後に、cgroupの所属を確認してみると、/../test02
となっています。つまりルートはtest01
のままで、test01
からの相対パスがcgroup
ファイルに現れます。一度ルートが定まると、名前空間内のツリーから外に移動してもルートは変わりません。
実際にはこのような操作をする意味はないでしょうし、カーネル文書にも推奨されないと書かれていますが、面白い動きですね。
まとめ
今回は新しい名前空間としてcgroup名前空間を紹介しました。
コンテナ内にコンテナ向けのcgroupfsツリーを見せるという少し地味な機能でした。
コンテナ内でも、普通にOSを起動したホスト上でマウントしたcgroupfsと同じような構造に見えるため、特にシステムコンテナ内で自然にcgroupfsを扱えます。cgroupfsを使うsystemdなどのソフトウェアがコンテナ内で実行されても問題ありませんし、コンテナ内でさらにコンテナを起動してもcgroupfsが自然な形で存在していますので、問題なく起動します。
cgroup名前空間がカーネルに実装されため、LXCFSは今後/proc
関連の機能のみが使われるようになっていきます。