Docker と LXC


Docker はただ LXC を再発明しているわけではないというお話。DotCloud のファウンダーによる [stackoverflow](http://stackoverflow.com/questions/17989306/what-does-docker-add-to-just-plain-lxc) の質問への回答。

LXC はネームスペースやコントロールグループ等の Linux カーネルケーパビリティ機能を利用し、プロセスを他のプロセスからサンドボックス化し、それらプロセスへのリソースの割当てをコントロールしている。Docker はこのローレベルのカーネル機能を軸に、以下のような機能を提供している。

## LXC に対し Docker が提供している機能

* **どんなマシンへもポータブルデプロイ可能** Docker はアプリケーションをビルドするためのフォーマットが定義されており、その全ての依存関係を (Docker がインストールされている全てのマシンで動作する) 単一のオブジェクトに入れ、それはどこで実行してもアプリケーション実行環境が同一になります。LXC が提供するプロセスのサンドボックスはポータブルデプロイメントにとって必要不可欠な機能ですが、それだけではポータブルではありません。もしあなたが LXC のコンフィグをカスタマイズしており、その LXC 上で動作しているアプリケーションを複製し私に送ったとしても、私の LXC のコンフィグをカスタマイズしたマシンにデプロイしても構成が違うので動作しないでしょう: ネットワーク、ストレージ、ロギング、ディストリビューション、など。Docker はこれらマシン特有の設定を抽象化しているため、どんなマシンへも、どんな構成でも実行できます。
* **アプリケーションが中心** Docker は仮想サーバではなくアプリケーションのデプロイに最適化されています。これは API, UI, デザイン哲学, ドキュメントに反映されています。対照的に LXC ヘルパースクリプトは軽量な仮想サーバにフォーカスしており、基本となるサーバはブートが高速で RAM 使用量もわずかです。私達はよりコンテナよりです。
* **自動ビルド** Docker には開発者がアプリケーションの依存関係、ビルドツール、パッケージング等を利用し、ソースコードからコンテナを自動的にアセンブルするツールが含まれています。それにはマシンの構成に関係なく `make`, `maven`, `chef`, `puppet`, `salt`, debian パッケージ, rpm ファイル, ソースの tarball, その他便利なツールを使えます。
* **バージョニング** Docker にはバージョン間の差異の確認、新しいバージョンのコミット、ロルバックなど、git のようなコンテナのバージョン管理機能が含まれています。また、コンテナが誰によってどのように変更されたか履歴が追えるため、プロダクションサーバからアップストリームの開発者までのすべての記録を辿れます。Docker は `git pull` のような差分アップロードやダウンロード機能があるので、新しいバージョンのコンテナは差分のみ転送すれば OK です。
* **コンポーネントの再利用** すべてのコンテナはよりカスタマイズするための "ベースイメージ" として利用可能です。これは手動でも実行できますが、自動ビルドの一部としても実行可能です。例えば究極の Python 実行環境を作成し、異なる 10 個の Python アプリケーションのベースとして利用できます。他にも究極の PostgreSQL 環境をセットアップしたら、それを将来のプロジェクトで再利用できます。
* **共有** Docker は便利なコンテナがアップロードされているパブリックリポジトリ (http://index.docker.io/) にアクセスできます。redis, couchdb, postgres, IRC Proxy, rails アプリケーションサーバ, hadoop, 各ディストリビューションなど多岐に渡ります。レジストリには Docker チームがメンテナンスしているオフィシャルのスタンダードライブラリーも含まれています。レジストリそのものはオープンソースとして公開されているため、オンプレミスでレジストリを作成し、プライベートなイメージを保存し、そこからデプロイ出来ます。
* **エコシステム** Docker はコンテナの作成とデプロイを自動化し、カスタマイズするための API を提供しています。Docker の機能を拡張する多くのツールがあります。 *PaaS ライクなデプロイメント* ができる [Dokku](https://github.com/progrium/dokku), [Deis](http://deis.io/), [Flynn](https://flynn.io/), *複数ノードのオーケストレーション* [maestro](https://github.com/toscanini/maestro), [salt](http://www.saltstack.com/), [mesos](http://mesos.apache.org/), [openstack nova](http://docs.openstack.org/developer/nova/), *管理用ダッシュボード* [docker-ui](https://github.com/crosbymichael/dockerui), [openstack horizon](http://horizon.openstack.org/), [shipyard](http://shipyard-project.com/), *構成管理* [chef](http://www.getchef.com/), [puppet](http://puppetlabs.com/), *CI* [jenkins](http://jenkins-ci.org/), [strider](http://stridercd.com/), [travis](https://travis-ci.org/) などなど。 

## LXC について

* [LXC](https://linuxcontainers.org/) = **Linux Containers**
* 簡単にいうと "単一の Linux カーネルをプロセス毎に隔離し、複数の仮想サーバを制御する仮想環境"
* _'LXC は Linux カーネルの隔離機能に対するユーザ空間インターフェース。強力な API とシンプルなツールを用いて、簡単にシステムやアプリケーションのコンテナーを作成、管理できる。'_ - [LXC - Linux Containers](https://linuxcontainers.org/)

 * カーネル + パッチ + ユーザ空間ツール: OpenVZ, Linux VServer, etc
 * カーネル + ユーザ空間ツール: LXC, libvirt (LXC driver), systemd, etc

![lxc_architecture.png](https://qiita-image-store.s3.amazonaws.com/422/468/33daa2a8-27fd-7d5b-f75f-26966937aee6.png "lxc_architecture.png")

## LXC, Docker が利用しているカーネルの機能と役割
### カーネルの名前空間 (ネームスペース)

システムリソース等を隔離 (isolation) し、互いに干渉できなくする

| 名前空間 | >= バージョン   | 隔離対象                                         |
|:---------|:----------------|--------------------------------------------------|
| Mount    | 2.4.19          | ファイルシステム                                 |
| UTS      | 2.6.19          | ホスト名, NIS ドメイン名                         |
| IPC      | 2.6.19          | プロセス間通信, POSIX メッセージキュー           |
| PID      | 2.6.24          | プロセスID, コンテナが違えば同一 ID 可           |
| Network  | (2.6.24) 2.6.29 | ネットワークデバイス, IP アドレス, IP テーブル等 |
| User     | (2.6.23) 3.8    | ユーザ (`uid`), グループ (`group`)               |

[![Linux_kernel_map.png](https://qiita-image-store.s3.amazonaws.com/422/468/ea9b9295-9f48-636a-75e4-f55a874581a9.png "Linux_kernel_map.png")](http://en.wikipedia.org/wiki/Linux_kernel#Architecture)

参考: [Namespaces in operation, part 1: namespaces overview](http://lwn.net/Articles/531114/)

### コントロールグループ ([cgroups](http://libcg.sourceforge.net/))

* cgroups は LXC の一部として開発されている
* 前提知識
 * Linux のプロセスモデル
  * 階層型. 子は親の環境 (`PATH` 変数とか), 属性 (オープンファイル記述子とか) を継承
  * 単一のルートプロセス (`init`), `init` プロセスはブート時にカーネルが起動
* タスク (プロセス) 毎にサブシステム (CPU, メモリ, ハードディスク等のリソース) の利用を制限する
* 異なる名前空間に `cgroup` を分離し、他のタスクからサブシステムを不可視にする

主なサブシステム

| サブシステム | 機能                                                         |
|:-------------|:-------------------------------------------------------------|
| blkio        | ブロックデバイスの入出力アクセスを制御 (Read <= N bytes/sec) |
| cpu          | スケジューラにより CPU のアクセスを制御                      |
| cpuset       | マルチコアの場合に利用する CPU, メモリノードを制御           |
| devices      | デバイスへのアクセス制御                                     |
| freezer      | 処理の一時停止, 再開                                         |
| memory       | メモリリソースの制御                                         |
| hugetlb      | N byteまでとかの制限                                         |
| net_cls      | Linux トラフィックコントローラ (tc) がパケットを識別できるよう, クラス識別子 (classid) によりネットワークパケットにタグをつける |
| net_prio     | NIC 別にトラフィックのプライオリティを設定する               |
| <del>ns</del>           | <del>名前空間を制御 (Linux 3.0 で廃止)</del> - @tenforward さんご指摘ありがとうございます。                                               |

### chroot (`pivot_root`)

* プロセスのルートディレクトリを変更する
 * 移動すると、その範囲外のファイルへアクセスできなくなる
 * 初期状態はルートファイルシステムのルート
* `pivot_root` は `root` ファイルシステムを変更する

### Apparmor, SELinux プロファイル

MAC (Mandatory Access Control - 強制アクセス制御) を実現するためのセキュリティ機構

* AppArmor: プログラム (プログラム名ベース) 毎に、アクセス可能なファイルパスやソケットへの操作を明示的に指定し、それ以外の操作は禁止する
* SELinux: ユーザ=ロール, 全てのプロセス=ドメイン, 全てのリソース (ファイル, ソケットなど) =タイプというラベルを付け、ユーザがアクセス可能なドメイン、ドメインが操作可能なリソースを指定する <del>めんどい ＼(^o^)／</del>

### Seccomp ポリシー

* Secure computing mode の略
* プロセスをサンドボックス化し、プロセスのシステムコールをフィルタリング
* Berkley Packet Filter (BPF) がベース
* JIT がサポートされている

### カーネルケーパビリティ

* プロセスに対し、必要最小限の特権を与える
* UNIX システムは一般ユーザ権限, 特権 (root) の 2 種類のみ
* プロセスには様々な特権の割当てが必要。プロセスの不具合、攻撃で乗っ取られたら終了
 * 特権ポート (1024 番ポート以下) の割り当て
 * raw ソケットをオープン
 * システム時刻制御
* 特権をケーパビリティという単位で細分化し、必要最小限の特権をプロセスに割り当てる
* [ケーパビリティ一覧](https://github.com/torvalds/linux/blob/master/include/linux/capability.h)

## LXC と Docker の関係

* Docker <= 0.8 は LXC (や `libvirt`, `systemd-nspawn`) 経由で名前空間, `cgroups` 等を利用していた
* Docker >= 0.9 は [`libcontainer`](https://github.com/dotcloud/docker/tree/master/pkg/libcontainer) ドライバ経由 ([DOCKER 0.9: INTRODUCING EXECUTION DRIVERS AND LIBCONTAINER](http://blog.docker.io/2014/03/docker-0-9-introducing-execution-drivers-and-libcontainer/))

  ![docker-execdriver-diagram.png](https://qiita-image-store.s3.amazonaws.com/422/468/50d4facf-81e9-4836-c452-f3aecd128f17.png "docker-execdriver-diagram.png")

* [`libcontainer`](https://github.com/dotcloud/docker/tree/master/pkg/libcontainer) がデフォ、`lxc` を使う場合は `-e lxc` とオプションで指定する


## 追記

* はてブで "自動ビルド" について言及されていますが、これは Dockerfile に `make`, `apt` 等のコマンドが書けて `docker build` さえすればエンジンが勝手にビルド実行してくれますよって話だと解釈しました