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

第24回LXCの構築・活用[10]─ フック(2)

前回説明したように、lxc-start-ephemeralコマンドはLXCの持つフック機能をコンテナの準備と後始末に利用していました。lxc-start-ephemeralコマンドが使っていたフックは3つだけでしたが、それ以外にもいろいろな場面でフックが実行できるようになっています。

今回は、前回紹介できなかったフックを紹介しながら、もう少しフックについて詳しくみていきましょう。

フックの仕様

LXCで使用できるフックの一覧を表1に示します。

表1 フックの一覧
フック(設定項目) 説明 実行される名前空間
lxc.hook.pre-start コンテナのTTY、コンソールの作成、マウントが実行される前に実行 ホスト
lxc.hook.pre-mount コンテナのrootfsがマウントされる前に実行 コンテナ
lxc.hook.mount マウントが完了後、pivot_root前に実行 コンテナ
lxc.hook.autodev lxc.autodev=1の場合、lxc.hook.mountが実行された後、pivot_rootの前に実行 コンテナ
lxc.hook.start コンテナのinitが実行される直前に実行 コンテナ
lxc.hook.post-stop コンテナがシャットダウンされた後に実行 ホスト
lxc.hook.clone 新しいコンテナにクローンされる際に実行 コンテナ

lxc.hook.clone以外は、コンテナが起動してから終了するまでのある時点で実行されます。

コンテナのフックが実行される際に、コマンドライン引数として以下の値が順に渡されます。この引数をフックとして指定したプログラムから使用できます。

  1. コンテナ名
  2. セクション(常に'lxc')
  3. フックのタイプ
  4. 追加の引数(クローン時のみ)

またフック実行時には表2の環境変数が設定されます。

表2 フック実行時に設定される環境変数
環境変数 内容
LXC_NAME コンテナ名
LXC_ROOTFS_MOUNT rootファイルシステムをマウントする/したパス
LXC_CONFIG_FILE コンテナ設定ファイルのパス
LXC_SRC_NAME クローン時、元のコンテナの名前
LXC_ROOTFS_PATH lxc.rootfsの値

コンテナのルートファイルシステムのマウント処理

ここで、LXCがコンテナを起動する際にルートファイルシステムをどのように準備するかにふれておきましょう。これはフックが実行される際に設定される環境変数LXC_ROOTFS_MOUNTに関係します。

コンテナのルートファイルシステムは、コンテナの設定ファイル中でlxc.rootfsにより設定されます。Ubuntuの場合、デフォルトだとlxc.rootfsの値は/var/lib/lxc/(コンテナ名)/rootfsとなります。

しかし、コンテナを起動する際にはこのパスを直接使うわけではなく、lxc.rootfsを別のディレクトリにバインドマウントします。このrootfsをマウントするディレクトリはlxc.rootfs.mountに設定されたディレクトリが使われます。このlxc.rootfs.mountの値がフックでLXC_ROOTFS_MOUNTという環境変数で参照できます。

前回説明したlxc.hook.pre-mountは、このマウント処理が行われる前に実行されます。

lxc.rootfs.mountは特に設定しなければコンパイル時のデフォルト値が使われます。Ubuntuのx86_64環境の場合は/usr/lib/x86_64-linux-gnu/lxcです。

このマウントが済んだ後に、pivot_rootシステムコールによりルートファイルシステムが変更され、コンテナのルートファイルシステムが設定されます。

lxc.hook.mountlxc.hook.autodevは、マウント処理が済んだ後、pivot_rootによりルートファイルシステムが変更される前に実行されます。


それではコンテナを開始する前、終了した後、クローン時のフックで引数や環境変数がどのように渡っているのかを簡単に見てみましょう。

コンテナ開始前のフック

コンテナを開始させる前にはいくつかの場面でフックを指定できます。コンテナ"ct01"を作成し、フックを以下のように設定してみましょう。

lxc.hook.pre-start = /var/lib/lxc/ct01/hook

/var/lib/lxc/ct01/hookは以下のような内容です。引数を順番に表示し、その後フックで使える環境変数を表示しています。

#!/bin/bash
cat << EOF > /var/log/ct.log
The number of args: $#
Container name: $1
Section: $2
Hook type: $3
Additional arg: $4

LXC_ROOTFS_MOUNT: $LXC_ROOTFS_MOUNT
LXC_CONFIG_FILE: $LXC_CONFIG_FILE
LXC_SRC_NAME: $LXC_SRC_NAME
LXC_NAME: $LXC_NAME 
LXC_ROOTFS_PATH: $LXC_ROOTFS_PATH
EOF

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

$ sudo lxc-start -n ct01 -d 
$ sudo lxc-ls -f
NAME  STATE    IPV4       IPV6  AUTOSTART  
-----------------------------------------
ct01  RUNNING  10.0.3.87  -     NO         

lxc.hook.pre-startはコンテナのルートファイルシステムが設定されるよりも前ですので、/var/log/clone.logはホストOSから見たパスになります。ホスト上で見てみましょう。

$ sudo cat /var/log/ct.log 
The number of args: 3
Container name: ct01
Section: lxc
Hook type: pre-start
Additional arg: 

LXC_ROOTFS_MOUNT: /usr/lib/x86_64-linux-gnu/lxc
LXC_CONFIG_FILE: /var/lib/lxc/ct01/config
LXC_SRC_NAME: 
LXC_NAME: ct01 
LXC_ROOTFS_PATH: /var/lib/lxc/ct01/rootfs

先に説明した通り、引数はコンテナ名("ct01")、"lxc"、フックタイプ("pre-start")の順で3つ渡っており、環境変数も期待通りの値になっています。

コンテナ開始前に実行されるlxc.hook.pre-startlxc.hook.pre-mountlxc.hook.mountlxc.hook.autodevは、コンテナの/(ルート)を移動させる前の処理ですので、/(ルート)はホストOSの/(ルート)と同じです。フックスクリプトのパスもスクリプト内でのパスもホストOS上で見たパスと同じになります。

ただしlxc.hook.pre-startだけはホストOSの名前空間で実行されますので、ここで行った処理はホスト上から見えてしまいます。コンテナ内に隔離したい処理はここで行わないようにしましょう。lxc.hook.pre-mount以降はコンテナの名前空間上での実行になりますので、ホストから見えることはありません。

コンテナ起動時のフックのうちlxc.hook.startだけは、コンテナのルートファイルシステムが移動した後の処理になりますので、ホストOS上の/var/lib/lxc/ct01/hookにスクリプトを置いてもコンテナからは見えませんので、指定するスクリプトはコンテナイメージ内に置いておく必要があり、パスの指定もコンテナ内のパスで指定する必要があります。

実際に見てみましょう。

$ sudo ls -lF /var/lib/lxc/ct01/rootfs/hook  (コンテナのルート以下にスクリプトを置く)
-rwxr-xr-x 1 root root 286 Mar 31 18:33 /var/lib/lxc/ct01/rootfs/hook*
$ sudo grep 'lxc.hook.start' /var/lib/lxc/ct01/config  (hookの指定はコンテナ内のパスで記述)
lxc.hook.start = /hook

以上のように、先ほどと同じスクリプトをコンテナのルートファイルシステム内に置きフックを設定して、コンテナを起動させてみましょう。

$ sudo lxc-start -n ct01 -d  (コンテナの起動)
$ sudo lxc-ls -f  (コンテナの起動を確認)
NAME  STATE    IPV4       IPV6  AUTOSTART  
-----------------------------------------
ct01  RUNNING  10.0.3.87  -     NO         
$ sudo lxc-attach -n ct01 -- cat /var/log/ct.log  (コンテナ内の/var/log/ct.logを確認)
The number of args: 3
Container name: ct01
Section: lxc
Hook type: start
Additional arg: 

LXC_ROOTFS_MOUNT: /usr/lib/x86_64-linux-gnu/lxc
LXC_CONFIG_FILE: /var/lib/lxc/ct01/config
LXC_SRC_NAME: 
LXC_NAME: ct01 
LXC_ROOTFS_PATH: /var/lib/lxc/ct01/rootfs

コンテナイメージ内に置いたスクリプト/hookが実行され、ログもコンテナ内に出力されていることがわかります。環境変数はきちんと渡っていますが、いずれもホストOS上のパスですので使い道はないかもしれませんね。

コンテナ終了後のフック

コンテナ終了時はフックは一つだけです。ホストOSの名前空間とホストOS上でのパスでフックが実行されます。lxc-start-ephemeralで設定されていたように、コンテナ終了後の後始末に使えますね。

コンテナ開始時と同じスクリプトを実行させると、フックタイプの表示が"post-stop"となる以外は先に示した出力と同じになります。

コンテナのクローン時のフック

クローン元となるコンテナにあらかじめフックを設定しておくと、クローン時に必要な処理が実行できます。ベースとなるコンテナを作成しておき、それを何度もクローンして実際に使うコンテナを作成する場合に便利ですね。

クローン時にはコンテナ開始時と終了時のフックで使えた引数、環境変数に加えて追加の引数を与え、スクリプト内でその引数を使って処理を行えます。lxc.hook.cloneに先ほどのスクリプトを与えてみましょう。

lxc.hook.clone = /var/lib/lxc/ct01/hook

以上のように設定した上で、lxc-cloneコマンドに追加の引数"test"を与えて実行してみます。

$ sudo lxc-clone -o ct01 -n clone01 -s -B overlayfs -- test
Created container clone01 as snapshot of ct01

出力されたファイルを見てみましょう。

$ sudo cat /var/log/ct.log
The number of args: 4
Container name: clone01
Section: lxc
Hook type: clone
Additional arg: test

LXC_ROOTFS_MOUNT: /usr/lib/x86_64-linux-gnu/lxc
LXC_CONFIG_FILE: /var/lib/lxc/clone01/config
LXC_SRC_NAME: ct01
LXC_NAME: clone01 
LXC_ROOTFS_PATH: overlayfs:/var/lib/lxc/ct01/rootfs:/var/lib/lxc/clone01/delta0

コンテナ開始時は引数が3つでした。一方、クローンの場合はコンテナ開始時に済っていた引数に加えて、追加で与えた引数 "test" も渡っています。クローン時はこのようにLXCが必ず渡す引数に加えて、追加の引数が4番目以降の引数として渡ります。

overlayfsを使ったクローンですので、LXC_ROOTFS_PATHの値はクローン先でoverlayfsを使う場合のlxc.rootfsの値になっています。

まとめ

今回はLXCで指定できるフックを紹介しました。また、フックを使う際に使える引数や環境変数にどのような値が渡るのかを紹介しました。

フックはコンテナの起動時のいくつかの場面と、終了時、クローン時に実行できます。前回説明したlxc-start-ephemeralコマンドのように、フックを使えば運用時に必要な処理が実行できます。有効に活用できる場面も多いでしょう。

フックを使う場合は、ホストOSの名前空間で実行されるのか、コンテナの名前空間で実行されるのか、その際の/(ルート)はどこなのかを意識しなければいけません。

LXC 1.1.2

今回の記事を書いている途中にLXC 1.1.2がリリースされています。リリースアナウンス日本語訳にもあるように変更点は多くなく、バグフィックスが中心のリリースとなっています。

おすすめ記事

記事・ニュース一覧