LXC 1.0: 非特権コンテナー [7/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/01/17/lxc-1-0-unprivileged-containers/

この文書のライセンスは原文と同じく、[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 1.0のもっとも重要な新機能の一つです。

以前の記事で、たとえ名前空間を分離していたとしても、コンテナーの中のUID 0は、コンテナーの外のUID 0と同じであり、procなりsysなり他のシステムコールなりを経由してあらゆるホストリソースにアクセスできたり、コンテナーの外に出てホストの管理者に昇格することもできるので、LXCは安全ではないと考えるべきである、と伝えたことを覚えているかもしれません。

ユーザー名前空間はそのためにデザインされ、実装されました。数年に渡る熟慮と、上流のカーネルに対して必要な数百ものパッチのゆっくりとした提供が行われました。そしてついに3.12で、完全なシステムコンテナーをユーザー権限で起動できるようになったのです。

ユーザー名前空間はどのように動作するのでしょうか？ えっと、簡単に言うと、システム上でそれを利用可能な個々のユーザーは、使用していないUIDとGIDの領域、理想的には65536個全部が割り当てられます。ユーザーは、二つの標準的なツールである `newuidmap` と `newgidmap` を使って、割り当てられたUIDやGIDをユーザー名前空間内の仮想的なUIDやGIDにマッピングした上で、そのUIDやGIDを使うことができます。

つまり、下記のような設定を行ったコンテナーを作ることができるのです：

```
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
```

上記は、コンテナーにそれぞれ一つずつのUIDマップとGIDマップを定義しています。この定義は、コンテナー内部の0から65536までのUID/GIDを、ホスト上の100000から165536までのそれにマッピングすることを意味します。

これを許可するために、システムレベルでユーザーをその領域に割り当てる必要があります（訳注： `stgraber` は記事の著者のアカウント名）：

```
stgraber@castiana:~$ grep stgraber /etc/sub* 2>/dev/null
/etc/subgid:stgraber:100000:65536
/etc/subuid:stgraber:100000:65536
```

すべてのツールがこの非特権コンテナーを理解できるように、LXCは更新されました。標準的なパスも、非特権用のものがそれぞれ用意されています；

* `/etc/lxc/lxc.conf` => `~/.config/lxc/lxc.conf`
* `/etc/lxc/default.conf` => `~/.config/lxc/default.conf`
* `/var/lib/lxc` => `~/.local/share/lxc`
* `/var/lib/lxcsnaps` => `~/.local/share/lxcsnaps`
* `/var/cache/lxc` => `~/.cache/lxc`

UID 0となり、その名前空間に関連するいくつかのリソースに対して管理者権限を持てるような、新しいユーザー名前空間を作成できたとしても、もちろんそのユーザーに、ホストに対するそれ以上の権限を与えることはありません。

そのような例の一つが、ホスト上でネットワークデバイスを作成したり、ブリッジ設定を変更することです。これを回避するために、「 `lxc-user-nic` 」と呼ばれるツールを作成しました。これはLXC 1.0で唯一SETUIDされたバイナリで、一つのシンプルなタスクをこなします。設定ファイルを解析し、その内容に基づいて、ユーザー用のネットワークデバイスを作成し、ブリッジします。悪用を避けるために、ユーザーが要求しブリッジに追加できるデバイスの数を制限することも可能です。

次の例は、私の `/etc/lxc/lxc-usernet` ファイルです：

```
stgraber veth lxcbr0 10
```

これは、「 `stgraber` 」ユーザーが10個までのvethタイプのデバイスを作成し、 `lxcbr0` という名前のブリッジに追加できることを許可しています。

カーネルの中のユーザー名前空間によって提供されるものと、setuidツールによって、多くのディストリビューションを非特権で実行するために必要なツールが揃いました。


## 必要なもの

ここで説明するすべての例と操作方法は、Ubuntu 14.04（コードネーム：trusty）の最新の状態でなら完全に動作するでしょう。リリース前のUbuntuなので、普段使用しているコンピューターをアップグレードするよりは、仮想マシン上で実行するか、スペアマシンを使うと良いかもしれません。

最新のものが必要な理由は、非特権コンテナーを動かすためにはおよそ次のものが必要だからです：

* カーネル：3.13といくつかの（Ubuntuのカーネルでは適用されている）パッチ
* カーネル上でユーザー名前空間が有効化されていること
* subuid/subgidをサポートする `shadow` の最新版
* すべてのコントローラーでユーザー単位のcgroupを使えること（数週間前に有効化しました）
* Per-user cgroups on all controllers (which I turned on a couple of weeks ago)
* LXC 1.0の（二日前にリリースされた）ベータ2かそれ以上
* loginuidパッチが適用されたバージョンのPAM、まだリリースはされていません

これらの必要事項は、二日前のUbuntuの開発版でようやくすべてが揃うようになりました。


## 作成済みのLXCコンテナー

ユーザー名前空間にはいくつかの明らかな制約があります。例えば、ユーザー名前空間内において、ブロックデバイスやキャラクターデバイスを作成するための `mknod` の使用をユーザーに許可することはできません。これを許してしまうと、ホスト上のあらゆるアクセスを許してしまうからです。いくつかのファイルシステムに関する制約もあります。例えば、ブロックデバイスへのアクセスが許されていたとしても、ループバックマウントや、extパーティションのマウントは許可できません。

これらの制約は、日々の使用において必ずしも世界の終わりを意味するものではありません。大きな問題となりうるのは、`debootstrap` や `yum` といった通常これらの制限された操作を行う必要があり、失敗した場合はうまくいかないようなツールを使う必要がある、コンテナーの初回のブートストラップ時です。

いくつかのテンプレートは動作するように修正したり、制約をバイパスするような修正済の `fakeroot` を使うことで回避すれば対応可能です。しかしながら、LXCプロジェクトの目標は、ユーザーにディストリビューションの開発者になってもらうことではありません。もっと簡単な解決方法を用意しました。

私は新しい「download」という名前のテンプレートを作成しました。これはルートファイルシステムを作成・設定する代わりに、主要なテンプレートについて毎日事前作成・設定されたルートファイルシステムを提供する[サーバー](https://images.linuxcontainers.org/)に接続します。

これらのイメージは、私のホームネットワーク上の数台のマシン（いくつかの強力なx86ビルダーと、クアッドコアのARMボード）を使用した[Jenkisサーバー](https://jenkins.linuxcontainers.org/)で作成しています。ビルドプロセスはすごく真っ正直で、基本的なchrootを作成したあと、gitリポジトリのmasterブランチのcurrentをダウンロード・ビルドし、リリースとアーキテクチャに合わせて標準的なテンプレートが実行され、その結果作成されたルートファイルシステムは圧縮され、基本的な設定やメタデータ（有効期限やテンプレートファイルなど）が保存され、それらがメインサーバーに送られ、GPGキーで署名され、Webサーバーで公開されます。

クライアント側はサーバーにHTTPSでアクセスし（ドメインはDNSSECが有効で、IPv6経由でもアクセスできます）、すべての利用可能なイメージの署名されたインデックスを取得し、要求されたディストリビューション、リリース、アーキテクチャの組み合わせをサポートしているかどうかを確認し、サポートしているならルートファイルシステムとメタデータのアーカイブを取得し、署名を検証し、ローカルキャッシュに保存するだけのシンプルなテンプレートです。その後に作成されるあらゆるコンテナーは、キャッシュの有効期限が切れるまではそのキャッシュを使い、切れたら新しいコピーをサーバーから取得します。

現在のイメージリストは次のとおりです（--listを渡せば取得できます）：

```
---
DIST      RELEASE   ARCH    VARIANT    BUILD
---
debian    wheezy    amd64   default    20140116_22:43
debian    wheezy    armel   default    20140116_22:43
debian    wheezy    armhf   default    20140116_22:43
debian    wheezy    i386    default    20140116_22:43
debian    jessie    amd64   default    20140116_22:43
debian    jessie    armel   default    20140116_22:43
debian    jessie    armhf   default    20140116_22:43
debian    jessie    i386    default    20140116_22:43
debian    sid       amd64   default    20140116_22:43
debian    sid       armel   default    20140116_22:43
debian    sid       armhf   default    20140116_22:43
debian    sid       i386    default    20140116_22:43
oracle    6.5       amd64   default    20140117_11:41
oracle    6.5       i386    default    20140117_11:41
plamo     5.x       amd64   default    20140116_21:37
plamo     5.x       i386    default    20140116_21:37
ubuntu    lucid     amd64   default    20140117_03:50
ubuntu    lucid     i386    default    20140117_03:50
ubuntu    precise   amd64   default    20140117_03:50
ubuntu    precise   armel   default    20140117_03:50
ubuntu    precise   armhf   default    20140117_03:50
ubuntu    precise   i386    default    20140117_03:50
ubuntu    quantal   amd64   default    20140117_03:50
ubuntu    quantal   armel   default    20140117_03:50
ubuntu    quantal   armhf   default    20140117_03:50
ubuntu    quantal   i386    default    20140117_03:50
ubuntu    raring    amd64   default    20140117_03:50
ubuntu    raring    armhf   default    20140117_03:50
ubuntu    raring    i386    default    20140117_03:50
ubuntu    saucy     amd64   default    20140117_03:50
ubuntu    saucy     armhf   default    20140117_03:50
ubuntu    saucy     i386    default    20140117_03:50
ubuntu    trusty    amd64   default    20140117_03:50
ubuntu    trusty    armhf   default    20140117_03:50
ubuntu    trusty    i386    default    20140117_03:50
```

このテンプレートはPOSIX互換のシェルと `wget` が動作するあらゆるシステム上で動くように注意深く作成しています。GPGは推奨されていますが、持っていなければ（あなたの責任において）無効化することは可能です。

同じテンプレートを、個人のサーバーに対しても使うことができます。中央に配置するようなテンプレートを作成すればエンタープライズ向けのデプロイで便利でしょうし、この有効期限のメカニズムを使ってすべてのホストから自動的に最新に保つように取得できれば良いだろうと考えています。

このテンプレートは非特権コンテナーの制約を回避するようにデザインされていますが、システムコンテナーに対しても同じように使えます。例えば非特権コンテナーをサポートしていないシステムであっても、次コマンドは実行できます。

```
lxc-create -t download -n p1 -- -d ubuntu -r trusty -a amd64
```

これにより、amd64版のUbuntu 14.04の最新ビルドが動作する新しいコンテナーを取得できます。


## 非特権のLXCを使用する

そうですね、実際に始めてみましょうか。既に伝えているとおり、すべての作業はごく最近のUbuntu 14.04（trusty）でのみ確認しています。[デイリービルド](http://cdimage.ubuntu.com/daily-live/)イメージを取得し、仮想マシン上で起動してください。

必要なパッケージをインストールします：

* `sudo apt-get update`
* `sudo apt-get dist-upgrade`
* `sudo apt-get install lxc systemd-services uidmap`

新しいcgroupマネージャーがUbuntuに取り込まれるまでの簡単な回避策として、下記の設定を `/etc/init/lxc-unpriv-cgroup.conf` に記述します：

```
start on starting systemd-logind and started cgroup-lite

script
    set +e

    echo 1 > /sys/fs/cgroup/memory/memory.use_hierarchy

    for entry in /sys/fs/cgroup/*/cgroup.clone_children; do
        echo 1 > $entry
    done

    exit 0
end script
```

これはlogindが、LXCが要求する `use_hierarchy` と `clone_children` を設定しないために必要です。

これらのcgroup設定を正しく反映するために、一度マシンを再起動します。

あなた自身を、UITとGIDのグループに割り当てます：

* `sudo usermod --add-subuids 100000-165536 $USER`
* `sudo usermod --add-subgids 100000-165536 $USER`

`~/.config/lxc/default.conf` に次の設定を行います：

```
lxc.network.type = veth
lxc.network.link = lxcbr0
lxc.network.flags = up
lxc.network.hwaddr = 00:16:3e:xx:xx:xx
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
```

さらに `/etc/lxc/lxc-usernet` に次の設定を追加します：

```
<あなたのユーザー名> veth lxcbr0 10
```

これで必要な作業は完了です。それでは最初の非特権コンテナーを作成しましょう：

```
lxc-create -t download -n p1 -- -d ubuntu -r trusty -a amd64
```

ダウンロードテンプレートが次のようなメッセージを出力するはずです：

```
Setting up the GPG keyring
Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs

---
You just created an Ubuntu container (release=trusty, arch=amd64).
The default username/password is: ubuntu / ubuntu
To gain root privileges, please use sudo.
```

これで最初のコンテナーの作成は成功したはずです。起動すると次のようになるはずです：
So looks like your first container was created successfully, now let’s see if it starts:

```
ubuntu@trusty-daily:~$ lxc-start -n p1 -d
ubuntu@trusty-daily:~$ lxc-ls --fancy
NAME  STATE    IPV4     IPV6     AUTOSTART
------------------------------------------
p1    RUNNING  UNKNOWN  UNKNOWN  NO
```

「起動中」の状態になりました！　この時点で、 `lxc-console` コマンドでコンソールに入ったり、ARPテーブル（ `arp -n` ）にあるIPアドレスを探すことでSSHを使うことができます。

上記の出力を見たとき、コンテナーのIPアドレスが表示されていないことに気づいたでしょう。これは、残念なことに現在のLXCが非特権コンテナーの名前空間にアタッチできないことが理由です。同じ理由で、 `lxc-info` のいくつかのフィールドは空になり、 `lxc-attach` は使用できません。しかしながら、近い将来にこの問題を解決できるような手段を模索中です。

カーネル内のジョブコントロールやPAMにもいくつかの問題があり、デタッチしない `lxc-start` は、コンソールの表示がおかしくなり、 `sudo` はおそらく失敗するでしょう。SSHもいくつかのディストリビューションでは失敗するかもしれません。これを解決するパッチはアップストリームに送信済みですが、それを適用したとしてもすべての問題を解決できるわけではないようですし、まだリリースしていません。

非特権コンテナーに対してはもっとたくさんの改善を、来月の1.0リリースまでに行う予定です。ただし、非特権コンテナーに割り当て可能なすべての作業量を予測できているわけではないので、もっと大掛かりな改善やもっと興味深い使用方法のためのより良いものが残っているかもしれません。