めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

RHEL6.0 で LXC (Linux コンテナ)

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