LXC 1.0: APIを用いたスクリプト [8/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/2014/02/05/lxc-1-0-scripting-with-the-api/

この文書のライセンスは原文と同じく、[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)

---------

## API

liblxcの最初のバージョンはLXC 0.9で導入されましたが、とても実験的な状態でした。しかしながらLXC 1.0ではLXCのすべての機能をカバーするより完全なAPIを提供する予定です。実際、私たちのすべてんツール（ `lxc-*` ）は、内部の関数を直接呼び出すことなく、このAPIを使って作り直されています。

さらにこのAPIは、継続的なインテグレーションのセットアップの一部として、ディトリビューションへのアップロードの前に実行されるテスト一式も備えています。

C言語で記述したくない人のために、いくつかのバインディングも存在します。UpstreamのソースツリーにはLuaとPython3のバインディングが存在し、ソースツリーの外ではあるものの公式のバインディングとしてGo言語とRubyがあります。

APIのドキュメントは次のURLを参照してください： https://qa.linuxcontainers.org/master/current/doc/api/

もっとも読み易いAPIドキュメントであるとも、とりわけバインディングにおいてサンプルが揃っているとも言えませんが、公開されているすべてのAPIを網羅してはいます。APIドキュメントに対する改善は、いつでも大歓迎です！


## 基本

C言語を使ったLXC APIのとても簡単なサンプルから始めてみましょう。次の例は、「apicontainer」という名前の新しいコンテナー構造体を作成し、ダウンロードテンプレートを使ってルートファイルシステムを作成し、そのコンテナーを起動し、状態とPIDを表示し、削除する前にシャットダウンを試みます。

```c
#include <stdio.h>

#include <lxc/lxccontainer.h>

int main() {
    struct lxc_container *c;
    int ret = 1;

    /* コンテナー構造体の作成 */
    c = lxc_container_new("apicontainer", NULL);
    if (!c) {
        fprintf(stderr, "lxc_container構造体の作成に失敗しました\n");
        goto out;
    }

    if (c->is_defined(c)) {
        fprintf(stderr, "コンテナーが既に存在します\n");
        goto out;
    }

    /* コンテナーの作成 */
    if (!c->createl(c, "download", NULL, NULL, LXC_CREATE_QUIET,
                    "-d", "ubuntu", "-r", "trusty", "-a", "i386", NULL)) {
        fprintf(stderr, "コンテナーのルートファイルシステムの作成に失敗しました\n");
        goto out;
    }

    /* コンテナーの起動 */
    if (!c->start(c, 0, NULL)) {
        fprintf(stderr, "コンテナーの起動に失敗しました\n");
        goto out;
    }

    /* 情報の問い合わせ */
    printf("コンテナーの状態： %s\n", c->state(c));
    printf("コンテナーのPID： %d\n", c->init_pid(c));

    /* コンテナーの停止 */
    if (!c->shutdown(c, 30)) {
        printf("コンテナーのシャットダウンに失敗しました。強制終了します\n");
        if (!c->stop(c)) {
            fprintf(stderr, "コンテナーの強制終了に失敗しました\n");
            goto out;
        }
    }

    /* コンテナーの削除 */
    if (!c->destroy(c)) {
        fprintf(stderr, "コンテナーの削除に失敗しました\n");
        goto out;
    }

    ret = 0;
out:
    lxc_container_put(c);
    return ret;
}
```

見ての通り、それほど難しくありませんし、ほとんどの関数はそのままの使い方であり、エラーの確認もとてもシンプルです（ほとんどの呼び出しの戻り値は真偽値であり、エラーはLXCのログレベルによって標準エラー出力に出力されます）。


## Python3を使う方法

おそらくC言語と同じくらい楽しいでしょうが、私は普段はコンテナーをスクリプトで操作します。そしてその方法としてC言語は本当に最良な言語ではないでしょう。それがPython3が公式にメンテナンスされている理由です。

上記の例と同じことをPython3で書くと次のようになります：

```py3
import lxc
import sys

# コンテナーオブジェクトの作成
c = lxc.Container("apicontainer")
if c.defined:
    print("コンテナーが既に存在します", file=sys.stderr)
    sys.exit(1)

# コンテナーのルートファイルシステムの作成
if not c.create("download", lxc.LXC_CREATE_QUIET, {"dist": "ubuntu",
                                                   "release": "trusty",
                                                   "arch": "i386"}):
    print("コンテナーのルートファイルシステムの作成に失敗しました", file=sys.stderr)
    sys.exit(1)

# コンテナーの起動
if not c.start():
    print("コンテナーの起動に失敗しました", file=sys.stderr)
    sys.exit(1)

# 情報の問い合わせ
print("コンテナーの状態： %s" % c.state)
print("コンテナーのPID： %s" % c.init_pid)

# コンテナーの停止
if not c.shutdown(30):
    print("コンテナーのシャットダウンに失敗しました。強制終了します")
    if not c.stop():
        print("コンテナーの強制終了に失敗しました", file=sys.stderr)
        sys.exit(1)

# コンテナーの削除
if not c.destroy():
    print("コンテナーの削除に失敗しました", file=sys.stderr)
    sys.exit(1)
```

今回の例ですと、Python3はC言語の例に比べてそこまでシンプルになったとは言えませんね。

しかしながら、何かもっと凝ったことをしようとした時、例えばすべての存在するコンテナーをイテレートしながら、それらを（もし起動していなければ）起動したり、ネットワーク接続が完了するまで待ったり、その後アップデートをして、シャットダウンする場合はどうでしょうか？

```py3
import lxc
import sys

for container in lxc.list_containers(as_object=True):
    # 起動していないコンテナーの起動
    started=False
    if not container.running:
        if not container.start():
            continue
        started=True

    if not container.state == "RUNNING":
        continue

    # 接続待ち
    if not container.get_ips(timeout=30):
        continue

    # アップデートの実行
    container.attach_wait(lxc.attach_run_command,
                          ["apt-get", "update"])
    container.attach_wait(lxc.attach_run_command,
                          ["apt-get", "dist-upgrade", "-y"])

    # コンテナーのシャットダウン
    if started:
        if not container.shutdown(30):
            container.stop()
```

上記の例でもっとも興味深いのは、 `attach_wait` コマンドでしょう。これは基本的に、コンテナーの名前空間の中で標準的なPython関数を実行できる機能です。もっとわかりやすい例をお見せしましょう：

```py3
import lxc

c = lxc.Container("p1")
if not c.running:
    c.start()

def print_hostname():
    with open("/etc/hostname", "r") as fd:
        print("ホスト名： %s" % fd.read().strip())

# 最初にホスト上で実行
print_hostname()

# 次はコンテナーの中で実行
c.attach_wait(print_hostname)

if not c.shutdown(30):
    c.stop()
```

上記の実行結果は次のようになります：

```
stgraber@castiana:~$ python3 lxc-api.py
/home/stgraber/<frozen>:313: Warning: The python-lxc API isn't yet stable and may change at any point in the future.
Hostname: castiana
Hostname: p1
```

この機能によって得られる可能性についてはっきりと理解できるまで少し時間がかかるかもしれません。特にいくつかのフラグ（C言語のAPIの `LXC_ATTACH_*` を確認してください）を使えば、AppArmerで抑制されている機能の付与やcgroupの制限の回避といった、アタッチする名前空間の操作すら行えるのです。

この類の柔軟性は他の仮想マシンでは得られないものであり、この機能をバインディングがサポートしていることで、作業負荷のカスタマイズの自動化といった操作をより簡単にしてくれることでしょう。

コンテナーの複製やスナップショットにも、このAPIを利用できます（これを書いている間に見つけたいくつかの小さな不具合のために、この例は動作させるためには、アップストリームの最新版が必要になります）：

```py3
import lxc
import os
import sys

if not os.geteuid() == 0:
    print("Overlayfsを使用するためには特権コンテナーが必要です")
    sys.exit(1)

# （まだ作っていなければ）Ubuntu 14.04イメージを使ったベースコンテナーの作成
base = lxc.Container("base")
if not base.defined:
    base.create("download", lxc.LXC_CREATE_QUIET, {"dist": "ubuntu",
                                                   "release": "precise",
                                                   "arch": "i386"})

    # Customize it a bit
    base.start()
    base.get_ips(timeout=30)
    base.attach_wait(lxc.attach_run_command, ["apt-get", "update"])
    base.attach_wait(lxc.attach_run_command, ["apt-get", "dist-upgrade", "-y"])

    if not base.shutdown(30):
        base.stop()

# （存在しなければ）webという名前の複製を作成
web = lxc.Container("web")
if not web.defined:
    # Overlayfsを使って複製
    web = base.clone("web", bdevtype="overlayfs",
                     flags=lxc.LXC_CLONE_SNAPSHOT)

    # Apacheをインストール
    web.start()
    web.get_ips(timeout=30)
    web.attach_wait(lxc.attach_run_command, ["apt-get", "update"])
    web.attach_wait(lxc.attach_run_command, ["apt-get", "install",
                                             "apache2", "-y"])

    if not web.shutdown(30):
        web.stop()

# webコンテナーをベースにウェブサイトコンテナーを作成
mysite = web.clone("mysite", bdevtype="overlayfs",
                   flags=lxc.LXC_CLONE_SNAPSHOT)
mysite.start()
ips = mysite.get_ips(family="inet", timeout=30)
if ips:
    print("ウェブサイトのアドレス： http://%s" % ips[0])
else:
    if not mysite.shutdown(30):
        mysite.stop()
```

上記はダウンロードイメージを用いてベースコンテナーを作成し、それをOvalayfsを用いて複製し、そこにApache2をインストールし、さらにそれを「mysite」という名前で複製しています。「mysite」は、「base」のオーバーレイ機能を使った複製である「web」をさらにオーバーレイ機能を使って効率的に複製したものです。


上記の例で、APIのもっとも興味深い部分のほとんどをカバーしたつもりですが、まだまだ多くの可能性が存在します。たとえば、上記のような特定のOverlayfsの範囲を超たスナップショットAPI（現在はシステムコンテナーでしか使えません）は扱っていませんし、アタッチ機能の可能性については表面に触れただけです。

LXC 1.0はAPIの安定板と共にリリースされる予定です。次の1.xのバージョン（不具合修正のみのバージョンは1.0.xになります）では機能を追加するつもりですが、API全体が破壊的に大きく変わらないようにしたいと考えています（が、確実に新しい機能を追加するつもりではいます）。