Docker の学習 ～ runC を使ったコンテナの起動

新しいコンテナランタイムである runC を使ってみたので、手順をまとめます。

# runC とは？
runC とは Docker社が発表した新しいコンテナランタイム(CLIツール)。今までの Docker は、Docker サーバーがデーモンとして Linux ホストに常駐し、Docker クライアント(docker コマンド)はそれに接続して操作するという形態だった。

runC ではデーモンが不要になり、runC という CLI ツールだけでコンテナの起動が可能になった。コンテナは、runC の子プロセスとして起動される。

# 検証環境の準備
いつもの通り、AWS EC2上の Amazon Linux インスタンスで検証してみる。まずインスタンスの起動し、ログインして必要なパッケージ(Google Go、git)をインストールする。

```bash
$ sudo yum install -y golang git

# Go のバージョンは 1.4.2だった。
$ go version
go version go1.4.2 linux/amd64

# GOPATH の設定
$ mkdir $HOME/go
$ export GOPATH=$HOME/go
```

## runC のコンパイルとインストール
runC はまだ鋭意開発中なので、パッケージからインストールするのではなく、ソースコードからコンパイルする必要がある。

```bash
# 作業用ディレクトリの作成
$ mkdir -p $GOPATH/src/github.com/opencontainers
$ cd $GOPATH/src/github.com/opencontainers

# runC のソースをクローン
$ git clone  https://github.com/opencontainers/runc

# コンパイル ＆ インストール
$ cd runc
$ make
$ sudo make install
```

動作確認してみる。

```bash
$ runc help
NAME:
   runc - Open Container Project runtime

runc is a command line client for running applications packaged according to the Open Container Format (OCF) and is a compliant implementation of the Open Container Project specification.
(以下略…)
```

# コンテナ起動のための準備

## コンテナ用ディレクトリの作成
現時点の runC はコンテナを起動する際に、Docker イメージからではなく、イメージに含まれるファイルが展開されたディレクトリから起動する必要がある(っぽい)。このコンテナ用ディレクトリは、今までの Docker Engine を使うと簡単に作成できる。

以下の例では、~/test/centos をコンテナ用ディレクトリとしている。

```bash
# Docker のインストールとデーモンの開始
$ sudo yum install docker -y
$ sudo service docker start

# Docker イメージの Pull
$ sudo docker pull centos

# コンテナの起動
$ sudo docker run -d --name dummy centos /bin/bash

# コンテナ用ディレクトリの作成
$ mkdir -p ~/test/centos
$ cd ~/test/centos

# コンテナの中身を tar ファイルにエクスポートする
$ sudo docker export -o centos.tar dummy

# エクスポートした tar を展開する。
$ tar xf centos.tar

# コンテナ用ディレクトリの中身はこんな感じ。
$ ls ~/test/centos/
bin         dev  home  lib64       media  opt   root  sbin  sys  usr
centos.tar  etc  lib   lost+found  mnt    proc  run   srv   tmp  var
```

もう、起動したコンテナや tar は不要なので削除してよい。Dockerのデーモンも停止していい。

```bash
$ sudo docker stop dummy
$ sudo docker rm dummy
$ rm centos.tar
$ sudo service docker stop
```

## config.json の作成

runC は、コンテナの定義情報が書かれたJSONファイルに従いコンテナを作成する。デフォルトでは、カレントディレクトリにある config.json というファイルが読み込まれる。

この config.json は runc spec コマンドを使うと簡単に作成できるようだが(※1)、今回は中身を確認するためにも手作業で用意した。
(※1) https://runc.io/ を参照。

```bash
cd ~/test
vi config.json
```

以下のようなJSONを記述する。とはいえ、変更が必要な個所は root/path くらい。
ただし、仕様がまだ固まってないようで今後変わる可能性があるため要注意。

```json
{
  "version": "pre-draft",
  "platform": {
    "os": "linux",
    "arch": "amd64"
  },
  "process": {
    "terminal": true,
    "user": {
      "uid": 0,
      "gid": 0,
      "additionalGids": null
    },
    "args": [
      "sh"
    ],
    "env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "TERM=xterm"
    ],
    "cwd": ""
  },
  "root": {
    "path": "/home/ec2-user/test/centos",
    "readonly": true
  },
  "hostname": "my-host",
  "mounts": [
    {
      "type": "proc",
      "source": "proc",
      "destination": "/proc",
      "options": ""
    },
    {
      "type": "tmpfs",
      "source": "tmpfs",
      "destination": "/dev",
      "options": "nosuid,strictatime,mode=755,size=65536k"
    },
    {
      "type": "devpts",
      "source": "devpts",
      "destination": "/dev/pts",
      "options": "nosuid,noexec,newinstance,ptmxmode=0666,mode=0620,gid=5"
    },
    {
      "type": "tmpfs",
      "source": "shm",
      "destination": "/dev/shm",
      "options": "nosuid,noexec,nodev,mode=1777,size=65536k"
    },
    {
      "type": "mqueue",
      "source": "mqueue",
      "destination": "/dev/mqueue",
      "options": "nosuid,noexec,nodev"
    },
    {
      "type": "sysfs",
      "source": "sysfs",
      "destination": "/sys",
      "options": "nosuid,noexec,nodev"
    }
  ],
  "linux": {
    "uidMappings": null,
    "gidMappings": null,
    "rlimits": null,
    "systemProperties": null,
    "resources": {
      "disableOOMKiller": false,
      "memory": {
        "limit": 0,
        "reservation": 0,
        "swap": 0,
        "kernel": 0
      },
      "cpu": {
        "shares": 0,
        "quota": 0,
        "period": 0,
        "realtimeRuntime": 0,
        "realtimePeriod": 0,
        "cpus": "",
        "mems": ""
      },
      "blockIO": {
        "blkioWeight": 0,
        "blkioWeightDevice": "",
        "blkioThrottleReadBpsDevice": "",
        "blkioThrottleWriteBpsDevice": "",
        "blkioThrottleReadIopsDevice": "",
        "blkioThrottleWriteIopsDevice": ""
      },
      "hugepageLimits": null,
      "network": {
        "classId": "",
        "priorities": null
      }
    },
    "namespaces": [
      {
        "type": "process",
        "path": ""
      },
      {
        "type": "network",
        "path": ""
      },
      {
        "type": "ipc",
        "path": ""
      },
      {
        "type": "uts",
        "path": ""
      },
      {
        "type": "mount",
        "path": ""
      }
    ],
    "capabilities": [
      "AUDIT_WRITE",
      "KILL",
      "NET_BIND_SERVICE"
    ],
    "devices": [
      "null",
      "random",
      "full",
      "tty",
      "zero",
      "urandom"
    ]
  }
}
```

# コンテナの起動
あとは、runC コマンドを実行するだけ。カレントディレクトリにある config.json を読み込んで、コンテナを起動してくれる。

```bash
$ cd ~/test
$ sudo /usr/local/bin/runc
sh-4.2# hostname
my-host
sh-4.2# ps
  PID TTY          TIME CMD
    1 ?        00:00:00 sh
    9 ?        00:00:00 ps
```

# 参考サイト
[Github runc](https://github.com/opencontainers/runc)
[runC Official Site](https://runc.io/)
[Getting Started with runC](https://www.cloudgear.net/blog/2015/getting-started-with-runc/)