LXC 1.0: コンテナーのさらに高度な使い方 [4/10]

## この文書について

この文書は、連載記事「[LXC 1.0: Blog post series](https://www.stgraber.org/2013/12/20/lxc-1-0-blog-post-series/)」の一つである以下の記事を翻訳したものです。連載の目次や注意点は[こちら](http://qiita.com/m-shibata/items/ee9b295a2596824da819)を参照してください。

* https://www.stgraber.org/2013/12/23/lxc-1-0-some-more-advanced-container-usage/

この文書のライセンスは原文と同じく、[Creative Commons BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/)のもとに提供されています。

![CC-BY-NC-SA 2.5](http://i.creativecommons.org/l/by-nc-sa/2.5/88x31.png)


## 異なるアーキテクチャで実行する

LXCの初期設定では、ホストでサポートされるアーキテクチャのうち一つのコンテナーのみを実行できるようになっています。これはCPUが何をすべきかわからない以上、理にかなっています。

ただし、「 `qemu-user-static` 」という、かなりの数のアーキテクチャのエミュレーターを提供する便利なパッケージを使えば別です。もっとも一般的で便利なのは、ほとんどのarmv7バイナリーをx86上で実行できる、 `qemu-arm-static` でしょう。

「ubuntu」テンプレートは、 `qemu-user-static` を使う方法を知っています。まず「 `qemu-user-static` 」パッケージをインストールしたあと、次のコマンドを実行してください：

```
sudo lxc-create -t ubuntu -n p3 -- -a armhf
```

かなり長いブートストラップのあと、ほとんどarmhf版のUbuntuを実行するp3コンテナーが得られるはずです。「ほとんど」と言ったのは、qemuによるエミュレーションにはいくつかの制約があること、一番大きなのはptrace()システムコールやnetlinkを使うようなソフトウェアは失敗することから、LXCはコンテナーがうまく起動できるように、Upstartといくつかのネットワークツールについてホストアーキテクチャのバージョンをインストールするためです。

```
stgraber@castiana:~$ file /bin/ls
/bin/ls: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, ""BuildID[sha1]"" =e50e0a5dadb8a7f4eaa2fd715cacb9842e157dc7, stripped
stgraber@castiana:~$ sudo lxc-start -n p3 -d
stgraber@castiana:~$ sudo lxc-attach -n p3
root@p3:/# file /bin/ls
/bin/ls: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, ""BuildID[sha1]"" =88ff013a8fd9389747fb1fea1c898547fb0f650a, stripped
root@p3:/# exit
stgraber@castiana:~$ sudo lxc-stop -n p3
stgraber@castiana:~$
```


## フック

私たちは、人々が各自のコンテナーをスクリプトで操作したがること、そして私たちの設定がすべての利用方法に適合するとは限らないことを知っています。そこで、そのような利用方法のために「フック（hook）機能」を導入しました。

フックとは、コンテナーが動いている時の特定のタイミングでLXCが実行する、単なる実行可能ファイルのパスの集合です。実行可能ファイルには、便利な環境変数一式も渡されるので、どのコンテナーが実行したかや何をすべきかなどが簡単にわかります。

現時点で利用可能なフックは次のとおりです（詳しくは[lxc.conf(5)](http://qa.linuxcontainers.org/master/current/doc/man/lxc.conf.5.html)を参照してください）：

* lxc.hook.pre-start （あらゆる初期化処理が実行される前に呼び出されます）
* lxc.hook.pre-mount （マウント名前空間が作成されたあと、何かがマウントされる前に呼び出されます）
* lxc.hook.mount （マウントされたあと、pivot_rootの前に呼び出されます）
* lxc.hook.autodev （mountの時と同じですが、autodevを使っている時のみ呼び出されます）
* lxc.hook.start （コンテナーが/sbin/initを実行する直前に呼び出されます）
* lxc.hook.post-stop （コネナーがシャットダウンしたあとに呼び出されます）
* lxc.hook.clone （コンテナーが複製された時、新しいコンテナーの中で呼び出されます）

上記に加えて、ネットワークセクションには追加のフックが存在します：

* lxc.network.script.up （インターフェースが作成されたあとに、ネットワーク名前空間の中で呼び出されます）
* lxc.network.script.down （インターフェースが破棄される前に、ネットワーク名前空間の中で呼び出されます）

これらのフックはすべて、設定ファイルの中で何度でも設定可能ですので、それぞれのフックのタイミングで複数回呼び出すこともできます。

単純な例として、「p1」コンテナーに以下を追加してみましょう：

```
lxc.hook.pre-start = /var/lib/lxc/p1/pre-start.sh
```

さらにフックスクリプト `/var/lib/lxc/p1/pre-start.sh` を作成します：

```bash
#!/bin/sh
echo "arguments: $*" > /tmp/test
echo "environment:" >> /tmp/test
env | grep LXC >> /tmp/test
```

このスクリプトに実行権限を与え（ `chmod 755` ）、コンテナーを起動してください。 `/tmp/test` を確認すると、次のように表示されるはずです：

```
arguments: p1 lxc pre-start
environment:
LXC_ROOTFS_MOUNT=/usr/lib/x86_64-linux-gnu/lxc
LXC_CONFIG_FILE=/var/lib/lxc/p1/config
LXC_ROOTFS_PATH=/var/lib/lxc/p1/rootfs
LXC_NAME=p1
```


## Androidコンテナー

しばしば、LXCコンテナーの中でAndroidを起動することは可能なのかということを尋ねられます。まぁ、短く答えると、可能です。しかしながら、そこまでシンプルな話ではなく、そこで何をやりたいかに依存します。

まずはじめに、Androidカーネルを実行するためのマシンを入手する必要があります。さらに、コンテナーを起動する前にAndroidで必要なモジュールをビルドしロードする必要もあるでしょう。

そこまでできたのなら、次はコンテナーを手動で作成します。ここでは `/var/lib/lxc/android/` に作成することにしましょう。次のような設定ファイルを作成します（訳注：例えば `/var/lib/lxc/android/config` ）：

```
lxc.rootfs = /var/lib/lxc/android/rootfs
lxc.utsname = armhf

lxc.network.type = none

lxc.devttydir = lxc
lxc.tty = 4
lxc.pts = 1024
lxc.arch = armhf
lxc.cap.drop = mac_admin mac_override
lxc.pivotdir = lxc_putold

lxc.hook.pre-start = /var/lib/lxc/android/pre-start.sh

lxc.aa_profile = unconfined
```

面白いのは、 `/var/lib/lxc/android/pre-start.sh` です。これは次のような内容を含む、実行可能なシェルスクリプトです：

```bash
#!/bin/sh
mkdir -p $LXC_ROOTFS_PATH
mount -n -t tmpfs tmpfs $LXC_ROOTFS_PATH

cd $LXC_ROOTFS_PATH
cat /var/lib/lxc/android/initrd.gz | gzip -d | cpio -i

# Create /dev/pts if missing
mkdir -p $LXC_ROOTFS_PATH/dev/pts
```

さらに、デバイス用の `initrd` を入手し、 `/var/lib/lxc/android/initrd.gz` に置いてください。

この時点でLXCコンテナーを起動すると、Androidの `initrd` が（Androidのramfsのような）tmpfsに展開されます。Androidのinitが起動され、Androidが要求するいくつかのパーティションをマウントし、いくつかのサービスを起動します。

AppArmorがないので、cgroupやネットワークの設定などは適用されません。そのためコンテナーには数多くの権限が残ったままで、場合によってはマシンを破壊してしまうこともあるかもしれません。もしinitプロセスが起動するだけでは必要なことを満たせないのであれば、残念ながらAndroidがどのように動いているかに精通し、臆することなくinitスクリプトを変更する必要があるでしょう。

これには何がやりたいかや、Androidのどのバージョンやどのデバイスを使用しているかといった条件に依存するため、ここで一般的な手法を提供することはできません。しかし、Ubuntu Touchでどんなことをやっているか見れば、そのあたりの知見を得られるかもしれません（訳注：Ubuntu TouchはAndroidカーネルとLXCを使って、ホストでUbuntuを、コンテナーの中でいくつかのAndroidサービスを動かしています）。

最後に、Androidのinitスクリプトは `/sbin/init` ではないので、LXCにそれを知らせる必要があるでしょう：

```
lxc-start -n android -- /init
```


## Androidデバイス上でのLXC

LXCの中でAndroidを起動する方法を見てきましたので、今度はAndroid上のLXCの中でUbuntuを起動する方法についてお話しましょう。

LXCはbionic（AndroidのCライブラリー）に移植済みで、glibcによるビルドと完全に機能が一致するわけではないものの、十分に実用的になっています。

残念ながら、LXCが低レベルのアクセスを必要とすることと、私たちのメインターゲットはAndroidではないことから、インストール方法はそこまで簡単ではありません……。Google PlayでLXCを見つけることはできませんし、.apkファイルを提供することもありません。

その代わりに、アップストリームのgitブランチが変更されるごとに、私たちは新しtarballを作成し公開しています： http://qa.linuxcontainers.org/master/current/android-armel/lxc-android.tar.gz

このビルドはAndroidの4.2以上で動作します。ただしおそらくより古いバージョンでも動作するでしょう。

動作させるためには、そのデバイスのカーネルコンフィギュレーションを取得し、それがLXCを使えるかどうか確認するためにlxc-checkconfigを実行する必要があります。不幸なことにもし使えないと判断されたら……、その場合はまずそのデバイスのカーネルソースを取得し、足りていないフラグを追加した上で再構築し、新しいカーネルで起動する必要があるでしょう。

恐ろしいことのように聞こえますが、デバイスがアンロックされていて、既にCyanogenのようなカーネルのgitツリーが簡単に利用可能な別のROMを使用しているのであれば、それほど難しいことではありません。

LXCが動くカーネルになれば、あとはtarballをroot権限でデバイスの `/` ディレクトリに展開し、ARMコンテナーを `/data/lxc/containers/<コンテナー名>` にコピーし、 `/data/lxc` の中で、「 `./run-lxc lxc-start -n <コンテナー名>` 」を実行するだけです。数秒後に、ログインプロンプトが表示されるはずです。