2011/05/29 執筆中。。。
2011/05/31 コンテナの起動まで書きました!
2011/06/06 cgroups の設定例を追加。これでひとまず完成(?)
2011/06/14 cgroups 設定例に /dev/tty* のアクセス権追加
どうも時代はコンテナらしい・・・ということで、RHEL6.0 + LXC でのコンテナ型の仮想化の方法をまとめていきます。
Linux Study Tokyo #2の発表資料「LXCで始めるケチケチ仮想化生活?!」も参考にしてください。
コンテナとは?
概念的というか実装的には、chroot jail の延長と考えると理解しやすいと思います。
chroot jail の場合は、ファイルシステムの見える範囲をプロセスごとに制限することでプロセス間の擬似的な独立性を実現します。ただし、ネットワークソケットなど、ファイルシステム以外のリソースは他のプロセスと共有なので、(同じポートを Listen する)HTTP サーバを 2 つ立ち上げるなどはできません。また、異なる jail 環境に存在する他のプロセスの動作のようすもしっかりと見えてしまいます。
一方、コンテナの場合は、ホスト名、プロセステーブル、システムユーザ・リスト、ネットワークソケットなど、カーネル内部における、さまざまなリソースの管理表をコンテナごとに用意することで、コンテナ間の独立性をより高めます。
微妙な所ですが、リソースそのものを独立させるのではなく、『リソースの管理表』をコンテナごとに用意して、擬似的な独立性を実現する点がコンテナを理解するポイントです。たとえば、プロセステーブルの場合、コンテナ外部(コンテナのホスト環境)からは、さまざまなコンテナに属するプロセスは、1つのプロセステーブルでまとめて管理されています。ただし、従来のプロセステーブルにない要素として、各プロセスの属するコンテナ名が記録されます。<コンテナ外部のプロセステーブル>
PID プロセス コンテナ名 1 init - 2 sshd - 3 init 001 4 crond 001 5 sendmail 001 6 sshd 002
そして、各コンテナごとに、そのコンテナ用のプロセステーブルが用意されます。<コンテナ 001 のプロセステーブル>
PID プロセス 1 init 2 crond 3 sendmail
コンテナ内で動作するプロセスからは、そのコンテナ用のプロセステーブルしか見えないので、他のコンテナのプロセスの存在は分かりません。
このようなコンテナごとの管理表を用意することを一般に『ネームスペースを用意する』と表現します。Linux カーネルでは、さまざまなコンポーネントについて、徐々にネームスペースが実装されてきており、ようやく実用性の高いコンテナを実現するのに十分なネームスペースの実装がそろいました。古いカーネルでコンテナを利用するにはカーネルパッチが必要でしたが、RHEL6.0 ではデフォルトのカーネルのままで、コンテナが利用できます。
下記は、カーネルのコンテナ対応を確認するツールの実行結果です。すべてが enabled になっています。また、逆にこれを見ると、どのようなリソースのネームスペースがあればコンテナが実現できるのかが見えてきます。
# lxc-checkconfig Kernel config /proc/config.gz not found, looking in other places... Found kernel config file /boot/config-2.6.32-71.el6.x86_64 --- Namespaces --- Namespaces: enabled Utsname namespace: enabled Ipc namespace: enabled Pid namespace: enabled User namespace: enabled Network namespace: enabled Multiple /dev/pts instances: enabled --- Control groups --- Cgroup: enabled Cgroup namespace: enabled Cgroup device: enabled Cgroup sched: enabled Cgroup cpu account: enabled Cgroup memory controller: enabled Cgroup cpuset: enabled --- Misc --- Veth pair device: enabled Macvlan: enabled Vlan: enabled File capabilities: enabled
また、ここには、ネームスペースの実装の履歴が簡単にまとめられています。
なお、コンテナ名は数字に限る必要はありませんが、ここでは、簡単に 001, 002, 003・・・のような通し番号をコンテナ名としています。
LXC (Linux コンテナ・ツール)の導入
現在の Linux カーネルは、コンテナを実現するために必要な機能を備えていますが、実際にコンテナを作成するには、別途、ユーザランドのツールが必要です。LXC は、そのためのツールのパッケージです。カーネル自身のコンテナ機能が LXC というわけではありません。(慣習で、『LXC = Linux カーネル標準のコンテナ機能』と理解されることも多いですが。。。)
RHEL6.0 のパッケージには、残念ながら、まだ LXC は含まれていません。ここでは、Sourceforge の LXC サイトからソースコードを入手して利用します。
まず、コンテナのホストとなる RHEL6.0 を導入して、LXC のビルドに必要な開発系のパッケージグループを導入しておきます。
# yum groupinstall "Development Tools" # yum install libcap-devel # yum install docbook-utils-pdf
ちなみに今回は、RHEL6.0 の KVM のゲスト OS としての RHEL6.0 をコンテナのホストとして利用しています。(べ、別に誰かがEC2でコンテナ使ってたとか聞いて嬉しがってるわけじゃないのよ。・・・まぁ、最近はいろんなテストをするのに KVM ゲスト環境を使うのが普通になってきたので。)
次の手順でソースを入手して、make & install します。
# wget http://lxc.sourceforge.net/download/lxc/lxc-0.7.4.2.tar.gz # tar -xvzf lxc-0.7.4.2.tar.gz # cd lxc-0.7.4.2 # ./configure # make # make install # mkdir -p /usr/local/var/lib/lxc
ここで、先の lxc-checkconfig を実行して、すべて enabled になっている事が確認できます。
なお、RHEL6.0 の SELinux の targeted policy では LXC に対応できていない部分がいろいろあるので、涙をのんで SELinux は disabled にしておきます。また、仮想ネットワークの所で説明するように仮想ブリッジを構成するので NetworkManager サービスを off にします。
最後に、コンテナごとに独立した仮想端末(pts)を使用するために必要な修正を行います。この修正の意味は後ほど説明します。
# cat <<EOF >>/etc/rc.local # Using independent pts rm -f /dev/ptmx >/dev/null 2>&1 ln -s /dev/pts/ptmx /dev/ptmx >/dev/null 2>&1 EOF
ここで一度、サーバを再起動しておいてください。
コンテナ作成の下準備その1(cgroup)
ホストから見るとコンテナ内のプロセスも普通のプロセスと同じですので、コンテナ内のプロセスに割り当てる CPU やメモリの上限は、cgroup を利用して管理することができます。RHEL6.0 では cgconfig サービスを利用して cgroup の管理ができますが、cgconfig が用意する /cgroup ディレクトリは LXC からは利用できません。cgconfig サービスは停止しておきます。
# chkconfig cgconfig off # service cgconfig stop
次の手順で、LXC が利用するための /lxc/cgroup ディレクトリをマウントしておきます。頭のデバイス名 lxc で、LXC が使うための cgroup ディレクトリであることを示しています。マウントポイントは任意です。
# cat <<EOF >>/etc/fstab lxc /lxc/cgroup cgroup defaults 0 0 EOF # mkdir -p /lxc/cgroup # mount /lxc/cgroup/
コンテナ作成の下準備その2(仮想 NIC と仮想ネットワーク)
ネットワークソケットなど、ネットワークに関わるリソースもコンテナごとに独立したネームスペースを持ちます。ただし、1 つの物理 NIC は特定のコンテナにのみ属することができますので、物理 NIC を複数のコンテナで共有することはできません。この問題に対応するために、各コンテナには物理 NIC の代わりに、仮想 NIC (Veth)をアサインして使用します。
Veth は、2 つの仮想 NIC のペアを作成して(あたかもクロスケーブルで直結されたかのように)仮想 NIC の間で通信できるように構成する機能です。LXC では、下図のように KVM でお馴染みの仮想ブリッジを利用して、仮想 NIC と物理 NIC をブリッジ接続して、コンテナ内の仮想 NIC から外部ネットワークへの L2 接続を行います。
物理 NIC 仮想ブリッジ 仮想 NIC のペア ↓ ↓ ↓ eth0 ------- br0 ------- vethXXX ==== vethYYY(コンテナからは eth0 に見える) | ホストのネームスペース ← | → コンテナ 001 のネームスペース |
この他にも物理 NIC を直接コンテナにアサインして、占有させる構成なども可能ですが、上記の方法がいちばん手軽で柔軟性の高い方法でしょう。ここでは、事前準備として、上図の eth0 と br0 を作成しておきます。
/etc/sysconfig/network-scripts/ifcfg-br0
DEVICE=br0 BOOTPROTO=static TYPE=Bridge IPADDR=192.168.122.199 NETMASK=255.255.255.0 GATEWAY=192.168.122.1 ONBOOT=yes NM_CONTROLLED=no
/etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0 HWADDR=52:54:00:D3:34:8B BRIDGE=br0 ONBOOT=yes NM_CONTROLLED=no
仮想ブリッジと Bonding/VLAN を組み合わせることも可能です。仮想ブリッジのより複雑な構成については、プロのためのLinuxシステム・ネットワーク管理技術も参照ください。
ルートファイルシステムの準備
コンテナ内部のプロセスから見えるファイルシステムは、chroot jail と同様に、特定のディレクトリに chroot された環境になります。したがって、事前に各コンテナ用のファイルシステムをマウントしておく必要があります。
LXC では、次の 2 種類の使い方が可能で、それぞれで用意するファイルシステムが異なります。
(1) 特定のプロセスをコンテナ内で実行する。(アプリケーションコンテナ)
(2) 独立した OS 環境一式をコンテナ内で実行する。(システムコンテナ)
(1) の場合は chroot jail と同じような使い方ですので、該当のプロセスの実行に必要なファイルが見えていれば OK です。ホスト環境のファイルシステムから必要な部分を bind マウントするのが簡単でよいと思います。
ここでは、コンテナ 001 が使用するルートファイルシステムを /lxc/rootfs/001 にマウントしておきます。動作テストが目的ですので、男らしくホストの / 全体を bind マウントしておきます。特殊ファイルシステムのマウントについては、別途設定するので、ここでは気にしなくても大丈夫です。
# mkdir -p /lxc/rootfs/001 # mount -o bind / /lxc/rootfs/001
(2) の場合は、実行する OS 環境が丸々インストールされたファイルシステムを用意してマウントする必要があります。KVM の代替に LXC を使ってしまおうという密かな野望があったりするので、ここでは、KVM で作成したゲスト OS の仮想ディスクイメージファイルを loop マウントするという方法をとります。下記は、イメージファイル /mnt/images/rhel60vm.img の第 2 パーティションに含まれるルートファイルシステムを /lxc/rootfs/002 にマウントする例です。コンテナ 002 が使用することを想定しています。
# mkdir -p /lxc/rootfs/002 # losetup /dev/loop0 /mnt/images/rhel60vm.img # kpartx -a /dev/loop0 # mount /dev/mapper/loop0p2 /lxc/rootfs/002
ちなみに、(2) の方法で OS 環境を実行する場合、実際には、コンテナ内部でおもむろに /sbin/init を起動します。したがって、ホスト環境のカーネルとコンテナ内部で実行する OS 環境に互換性がないとうまくいきません。また、/sbin/init から実行される rc.sysinit では、もろもろのハードウェア環境の初期化が行われますが、これはコンテナ環境では不要です。というか、コンテナ環境で実行するとひどい目にあいます。rc.sysinit はコンテナ環境用に書き換えておく必要があります。
下記は、RHEL6.0 の rc.sysinit から不要なものを削り取って LXC ゲスト環境用に修正した例です。
/lxc/rootfs/002/etc/rc.d/rc.sysinit
#!/bin/bash . /etc/init.d/functions HOSTNAME=$(/bin/hostname) [ -f /etc/sysconfig/network ] && . /etc/sysconfig/network [ ! -z "$GATEWAY" ] && route add default gw $GATEWAY #!/bin/bash . /etc/init.d/functions HOSTNAME=$(/bin/hostname) [ -f /etc/sysconfig/network ] && . /etc/sysconfig/network [ ! -z "$GATEWAY" ] && route add default gw $GATEWAY PRODUCT=$(sed "s/Red Hat \(.*\) release.*/\1/" /etc/system-release) echo -e $"\n\t\tWelcome to \\033[0;31mRed Hat\\033[0;39m $PRODUCT\n" action $"Setting hostname ${HOSTNAME}: " hostname ${HOSTNAME} # Clear mtab > /etc/mtab rm -f /etc/mtab~ /etc/mtab~~ mount -f /proc >/dev/null 2>&1 mount -f /proc/sys/fs/binfmt_misc >/dev/null 2>&1 mount -f /sys >/dev/null 2>&1 mount -f /dev/pts >/dev/null 2>&1 mount -f /dev/shm >/dev/null 2>&1 action $"Mounting local filesystems: " mount -a -t nonfs,nfs4,smbfs,ncpfs,cifs,gfs,gfs2 -O no_netdev # Clean up /var. rm -rf /var/lock/cvs/* /var/run/screen/* find /var/lock /var/run ! -type d -exec rm -f {} \; rm -f /var/lib/rpm/__db* &> /dev/null rm -f /var/gdm/.gdmfifo &> /dev/null # Clean up utmp/wtmp/tmp > /var/run/utmp touch /var/log/wtmp chgrp utmp /var/run/utmp /var/log/wtmp chmod 0664 /var/run/utmp /var/log/wtmp rm -rf /tmp/* >/dev/null 2>&1
細かいことを言うと、コンテナ内で poweroff した時に走る /etc/init.d/halt も修正しないと、ホストの swap が off される可能性があります。この後で説明するデバイスファイルへのアクセス制限を行えば、コンテナ内から swap の操作ができなくなりますが、ここでは安全のために以下の部分を削除しておきます。
/etc/init.d/halt(削除部分)
# Turn off swap, then unmount file systems. [ -f /proc/swaps ] && SWAPS=$(awk '! /^Filename/ { print $1 }' /proc/swaps) if [ -n "$SWAPS" ]; then action $"Turning off swap: " swapoff $SWAPS for dst in $SWAPS; do if [[ "$dst" =~ ^/dev/mapper ]] \ && [ "$(dmsetup status "$dst" | cut -d ' ' -f 3)" = crypt ]; then backdev=$(/sbin/cryptsetup status "$dst" \ | awk '$1 == "device:" { print $2 }') /sbin/cryptsetup remove "$dst" fi done fi
もう一つ、TTY コンソール(/dev/tty*)ではなく、システムコンソール(/dev/console)でログインを受け付けるために RHEL6.0 のゲストの場合は下記のファイルを修正します。(RHEL5 ゲストの場合は /etc/inittab の migetty 起動部分を修正します。)
/lxc/rootfs/002/etc/init/start-ttys.conf
# # This service starts the configured number of gettys. start on stopped rc RUNLEVEL=[2345] env ACTIVE_CONSOLES=/dev/tty[1-6] env X_TTY=/dev/tty1 task script . /etc/sysconfig/init initctl start serial DEV=console SPEED=38400 # for tty in $(echo $ACTIVE_CONSOLES) ; do # [ "$RUNLEVEL" = "5" -a "$tty" = "$X_TTY" ] && continue # initctl start tty TTY=$tty # done end script
ネットワークデバイスについては、LXC がコンテナ起動時にコンテナの設定ファイルに従って、仮想 NIC を構成してくれるので、LXC ゲスト内には ifcfg-ethXXX は不要です。
# rm /lxc/rootfs/002/etc/sysconfig/network-scripts/ifcfg-eth*
最後にもう一つだけ。ゲストの /etc/fstab にもファイルシステムの情報は不要です。下記のように特殊ファイルシステムのエントリだけに修正しておきます。
/lxc/rootfs/002/etc/fstab
tmpfs /dev/shm tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0 none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0
なお、大量のゲストを起動する場合は、loop デバイスの数を事前に増やしておきます。こちらの『loop デバイスの追加方法』を参照してください。
アプリケーションコンテナを使ってみる
まずは、(1)の使い方(アプリケーションコンテナ)の例として、コンテナ 001 内部で SSH デーモンを起動してみます。下記のコンテナ設定ファイルを用意します。
/lxc/conf/001.conf
# network configuration lxc.utsname = 001 lxc.network.type = veth lxc.network.name = eth0 lxc.network.flags = up lxc.network.link = br0 lxc.network.ipv4 = 192.168.122.101/24 # file system configuration lxc.rootfs = /lxc/rootfs/001 lxc.mount.entry=/dev /lxc/rootfs/001/dev/ none bind 0 0 lxc.mount.entry=/dev/pts /lxc/rootfs/001/dev/pts none bind 0 0
前半部分はネットワークに関する設定です。前述の仮想 NIC をブリッジ br0 に接続して、コンテナ内では eth0 に見えるように指定しています。ここで、コンテナ内の eth0 にアサインする IP アドレスもあわせて指定しています。後半部分は、ルートファイルシステムに関する設定です。コンテナ内のプロセスからは、/lxc/rootfs/001 に chroot した環境が見えます。また、コンテナを起動する直前にマウントするその他の特殊ファイルシステムもここで指定しています。
なお、/dev ファイルシステムには注意が必要です。/dev ファイルシステム(および /dev/pts ファイルシステム)は、ホスト環境の /dev (および /dev/pts)をそのまま bind マウントして、コンテナ内からも参照できるようにしています。このままでは、コンテナ内からホスト環境のすべてのデバイスファイルにアクセスできてしまうので、コンテナの独立性が保たれない気がします。実は、後述する設定で、コンテナからアクセス可能なデバイスファイルを制限することができます。これにより、/dev ファイルシステムは見えていても、コンテナからは指定されたデバイスしか利用できなくすることが可能です。
いよいよコンテナを起動します。次のコマンドを実行して、何もメッセージが表示されずプロンプトが戻らなければ成功です。
# lxc-execute -n 001 -f /lxc/conf/001.conf /usr/sbin/sshd
ホスト環境(もしくは同じサブネット上の端末)からコンテナの IP アドレス 192.168.122.101 を指定して SSH 接続することが可能です。
# ssh root@192.168.122.101 root@192.168.122.101's password: Last login: Sat May 28 14:11:43 2011 from 192.168.122.1 [root@001 ~]# uname -a Linux 001 2.6.32-71.el6.x86_64 #1 SMP Wed Sep 1 01:33:01 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux [root@001 ~]# ifconfig eth0 eth0 Link encap:Ethernet HWaddr FE:95:25:13:FF:44 inet addr:192.168.122.101 Bcast:192.168.122.255 Mask:255.255.255.0 inet6 addr: fe80::fc95:25ff:fe13:ff44/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:734 errors:0 dropped:0 overruns:0 frame:0 TX packets:232 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:69775 (68.1 KiB) TX bytes:41883 (40.9 KiB) [root@001 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
上記のコマンド出力から分かるようにコンテナ内では、独立したホストネームと IP アドレスがアサインされていることが分かります。残念ながらデフォルト・ゲートウェイは特に設定されていません。SSH 接続後に手動で設定する必要があります。このような詳細な設定を自動的に行う場合は、この後で説明する (2) の方法(システムコンテナ)を利用することをお勧めします。
なお、ホスト環境で ps コマンドを実行すると、lxc-init というプロセスを通して SSH デーモンが起動されていることが分かります。lxc-init はアプリケーションコンテナを使用する際のローンチャのような役割を果たします。lxc-init は、/proc, /sys などの特殊ファイルシステムのマウントなどの下準備をした上で、指定されたコマンドを実行します。
root 1522 1143 0 14:03 pts/0 00:00:00 lxc-execute -n 001 -f /lxc/conf/001.conf /usr/sbin/sshd root 1525 1522 0 14:03 pts/0 00:00:00 /usr/local/lib/lxc/lxc-init -- /usr/sbin/sshd
一方、システムコンテナの場合は、コンテナ内でダイレクトに /sbin/init が実行されますので、後述するように、特殊ファイルシステムのマウントについても設定ファイル内で明示的に指定する必要があります。
このアプリケーションコンテナを停止するには、ホスト上で次のコマンドを実行します。
# lxc-stop -n 001
システムコンテナを使ってみる
続いて、(2)の使い方(システムコンテナ)を試してみます。コンテナ 002 内部で RHEL6.0 を起動します。下記の設定ファイルを用意します。
/lxc/conf/002.conf
# network configuration lxc.utsname = 002 lxc.network.type = veth lxc.network.name = eth0 lxc.network.flags = up lxc.network.link = br0 lxc.network.ipv4 = 192.168.122.102/24 # file system configuration # file system configuration lxc.rootfs = /lxc/rootfs/002 lxc.mount.entry=/dev /lxc/rootfs/002/dev/ none bind 0 0 lxc.mount.entry=devpts /lxc/rootfs/002/dev/pts devpts gid=5,mode=620,newinstance,ptmxmode=0666 0 0 lxc.mount.entry=proc /lxc/rootfs/002/proc proc nodev,noexec,nosuid 0 0 lxc.mount.entry=sysfs /lxc/rootfs/002/sys sysfs defaults 0 0 lxc.mount.entry=none /lxc/rootfs/002/proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0
次のコマンドでコンテナを作成して、起動します。
# lxc-create -n 002 -f /lxc/conf/002.conf # lxc-start -n 002 Welcome to Red Hat Enterprise Linux Server ホストネームを rhel60vm01 に設定中: [ OK ] ローカルファイルシステムをマウント中: [ OK ] 非対話起動モードに移行中 Intel CPU マイクロコードアップデートを適用中: Calling the system activity data collector (sadc): ip6tables: ファイアウォールルールを適用中: [ OK ] iptables: ファイアウォールルールを適用中: [ OK ] ループバックインターフェイスを呼び込み中 [ OK ] auditd を起動中: [失敗] システムロガーを起動中: [ OK ] irqbalance を起動中: [ OK ] rpcbind を起動中: [ OK ] mdmonitor を起動中: [ OK ] システムロガーを起動中: [ OK ] ネットワークパラメーターを設定中... [ OK ] Avahi デーモンを起動中... [ OK ] NFS statd を起動中: [ OK ] RPC idmapd を起動中: Error: RPC MTAB does not exist. その他のファイルシステムをマウント中: [ OK ] acpi デーモンを起動中: acpid: can't open /proc/acpi/event: Device or resource busy [失敗] HAL デーモンを起動中: [ OK ] 失敗した udev イベントを再トリガーする [ OK ] automount を起動中: [ OK ] sshd を起動中: [ OK ] postfix を起動中: [ OK ] abrt デーモンを起動中: [ OK ] crond を起動中: [ OK ] atd を起動中: [ OK ] Red Hat Enterprise Linux Server release 6.0 (Santiago) Kernel 2.6.32-71.el6.x86_64 on an x86_64 rhel60vm01 login:
見事にシステムが起動して、ログインプロンプトが表示されています。コンテナ環境では使えないいくつかのサービスが起動に失敗していますが、ログインしてから chkconfig で停止してしまいましょう。SSH デーモンも起動していますので、外部の端末から SSH でログインすることも可能です。また、ゲストの /etc/sysconfig/network の HOSTNAME と GATEWAY を参照して、ホストネームとデフォルト・ゲートウェイを設定するように rc.sysinit に仕込んでありますので、これらも環境に合わせて設定してください。
ホスト環境からシステムコンテナの状況を確認するために次のようなコマンドが使用できます。それぞれ、特定のコンテナに属するプロセスとコンテナ内部のネットワークソケットの状況を表示します。最後に付与するオプションは、通常の ps、netstat コマンドと同じです。
# lxc-ps --name 002 -ef # lxc-netstat --name 002 -a
なお、lxc-start コマンドに -d オプションを付けるとバックグラウンドでコンテナが起動します。サービスとしてコンテナを起動する際はこちらを利用するとよいでしょう。最後にシステムコンテナを停止して破棄するには、ホスト上で次のコマンドを実行します。
# lxc-stop -n 002 # lxc-destroy -n 002
ただし、lxc-stop を実行すると、ゲストが稼働している状態でいきなりコンテナが停止します。お行儀よく停止する場合は、コンテナ内で poweroff を実行してから、lxc-destory を行います。poweroff が正常に完了すると、コンテナは自動的に停止するので lxc-stop は不要です。コンテナの稼働状況を確認するには、次のコマンドを使用します。
# lxc-ls 003 004 005 006 003 005 006 # lxc-info -n 003 '003' is RUNNING # lxc-info -n 004 '004' is STOPPED
lxc-ls の 1 行目は、lxc-create で定義された STOPPED、もしくは RUNNING 状態のコンテナで、2 行目は、その中で lxc-start が実行されて RUNNING 状態になっているコンテナです。lxc-info はコンテナ個別に状態を確認するためのものです。
システムコンテナにおける仮想端末(pts)について
仮想端末(pts)は、SSH でサーバにログインした際に作成されるデバイスファイル(/dev/pts/<番号>)で、このファイルに出力した内容が SSH 端末上に表示されます。root 権限で、
# echo HOGE > /dev/pts/0
などとすると他人がログインしている端末に強制的にメッセージを表示できたりします。アプリケーションコンテナを作成した際は、/dev/pts は bind マウントしていたので、ホスト環境とコンテナ環境で共通になります。つまり、コンテナ環境からホスト環境の SSH 端末にメッセージが表示できたりすることになります。
一方、システムコンテナの場合は、設定ファイル 002.conf の下記の部分から分かるように新しい /dev/pts ファイルシステムを "newinstance" オプションをつけてマウントしています。
lxc.mount.entry=devpts /lxc/rootfs/002/dev/pts devpts gid=5,mode=620,newinstance,ptmxmode=0666 0 0
これは、ホスト環境とは独立した仮想端末を用意するというオプションで、コンテナ環境からはホスト環境の /dev/pts は見えなくなります。つまり、前述のような『いたずら』はできなくなります。なお、新しく SSH 接続すると、SSH デーモンは /dev/ptmx にアクセスして、新しい /dev/pts/<番号> を作成します。この時、ホスト環境と同じ /dev/ptmx を使うとホスト環境側の /dev/pts/<番号> が作成されてしまうのでうまく行きません。コンテナ側の /dev/pts/<番号> を作成するには、/dev/pts/ptmx を使用する必要があります。このために、LXC のインストール時にホスト環境の /dev/ptmx を /dev/pts/ptmx へのシンボリックリンクに変更するという細工をしてあります。/dev ファイルシステムはホスト環境とコンテナ環境で共通なので、こうしておけば、ホスト環境では、ホスト環境用の /dev/pts/ptmx が使用されて、コンテナ環境では、コンテナ環境用の /dev/pts/ptmx がそれぞれ使用されるようになります。
cgroups によるリソース管理
各コンテナにアサインする CPU やメモリの割合、および、コンテナからアクセスを許可するデバイスの指定は、cgroups の機能を用いて行います。LXC の環境(この記事の手順)では、cgroups のファイルシステムは、/lxc/cgroup に全てのサブシステムがまとめてマウントされています。この下にコンテナ名のサブディレクトリが作成されて、該当のコンテナのプロセスは、自動的にこのグループに属する形になります。したがって、/lxc/cgroup/<コンテナ名>/ 以下のパラメータを調整することで、コンテナのリソース割り当てを変更することができます。
LXC では、コンテナの設定ファイル内にパラメータを指定することも可能です。次は典型的な設定例です。各パラメータの意味は、こちらを参考にしてください。
# resource allocations lxc.cgroup.cpuset.cpus = 0 # CPU0 lxc.cgroup.cpu.shares = 512 # Half of default lxc.cgroup.memory.limit_in_bytes = 268435456 # 256MB lxc.cgroup.memory.memsw.limit_in_bytes = 536870912 # 512MB # device access list lxc.cgroup.devices.deny = a *:* rwm # deny all lxc.cgroup.devices.allow = c 1:3 rwm # null lxc.cgroup.devices.allow = c 1:5 rwm # zero lxc.cgroup.devices.allow = c 5:1 rwm # console lxc.cgroup.devices.allow = c 5:0 rwm # tty lxc.cgroup.devices.allow = c 4:* rwm # tty* lxc.cgroup.devices.allow = c 1:9 rwm # urandom lxc.cgroup.devices.allow = c 1:8 rwm # random lxc.cgroup.devices.allow = c 5:2 rwm # ptmx lxc.cgroup.devices.allow = c 136:* rwm # pts/* lxc.cgroup.devices.allow = c 254:0 rwm # rtc0