Dockerの基本操作をCentOS 7で確認 その3〜Dockerfileからイメージを作成〜


## 環境

[その1](http://qiita.com/smile-0yen/items/c6957b951ca0fdb8acc7)でコンテナの起動方法、[その2](http://qiita.com/smile-0yen/items/4a35ea9f0279f4d85c42)でコンテナがどんな状態にあるかを確認する方法を書きました。
続くその3ではregistryから取得してきたイメージをもとに自分だけのイメージを作成する方法を書きます。

以下の環境でDockerの基本操作を確認します。

| 項目 | 今回のシステム |
|:---|:---|
| OS | CentOS 7.0 (on VirtualBox) |
| Docker | docker-1.3.2 | 

## Dockerfileからイメージを作る

自分だけのイメージを作成するには`Dockerfile`を用いるのが簡単な方法です。
Dockerには`docker build`という`Dockerfile`を読み込んでイメージを作るコマンドがあります。

[その1](http://qiita.com/smile-0yen/items/c6957b951ca0fdb8acc7)ではbashを起動してコンテナ内でRubyをインストールし、Webサーバを立てました。
ここでは具体例としてそれと同じことを`Dockerfile`を使って実現します。

### Dockerfile作成

```
# "#"で始まる行はコメント行とみなされます。
# Dockerfileは必ずベースとなるイメージを指定するFROMから書きます。
FROM ubuntu:14.04

# MAINTAINERには文字通りDockerfile作成者を記載します。
MAINTAINER smile-0yen smile0yen.1024@gmail.com

# RUNは指定されたコマンドをコンテナ内で実行し、結果をイメージとして保存します。
RUN apt-get install ruby -y

# EXPOSEはコンテナの外に対して開くポートを指定します。
# 親ホストのどのポートにマッピングするかはコンテナ起動時に決定します。
EXPOSE 8080

# WORKDIRによってこの行以降のRUN及びCMDを実行するディレクトリを指定できます。
WORKDIR /opt

# CMDにはコンテナが起動したあとに実行されるコマンドを指定します。
CMD ruby -run -e httpd . -p 8080
```

`Dockerfile`内での命令はインストラクション(Instruction)と呼ばれます。非常にシンプルな記述が特徴です。
大文字である必要はありませんが可読性のために全て大文字で書くことが推奨されています。

|インストラクション|説明
|:---|:---|
| FROM <イメージ>:<タグ>| ベースとなるイメージを指定します。<br>`Dockerfile`の先頭は必ずFROMで始めます。 |
| MAINTAINER <名前> | イメージの作者を明示します。指定するとイメージ内に<br>`Author`情報として記録されます。 |
| RUN <コマンド> | 任意のコマンドを`/bin/sh -c`に渡してコンテナ内で実行します。<br>`/bin/sh`がベースイメージにない場合など`exec`形式で（＊）で<br>コマンドを実行することも可能です。 |
| CMD <コマンド> | コマンドを`/bin/sh -c`に渡してコンテナ**起動時に**実行します。<br>ただし、`docker run`でコンテナを起動する際に別のコマンドを<br>渡したときにはCMDはそちらで**上書きされる**ため実行されません。<br>なお、CMDは1つにDockerfileにつき1回しか指定できません。<br>`/bin/sh`を使いたくない場合はexec形式（＊）で実行します。 |
| ENTRYPOINT　<コマンド> | CMDと同様にコンテナ起動時に実行するコマンドを指定します。<br>こちらは`docker run`で渡された別のコマンドで**上書きされません**。<br>`docker run`で渡されたコマンドはENTRYPOINTで指定したコマンドの<br>後ろへ**コマンドラインオプションとして追加**されます。<br>CMDとENTRYPOINTをどちらも指定した場合、CMDの値は<br>ENTRYPOINTで指定したコマンドのデフォルト引数と解釈されます。<br>`/bin/sh`で起動されたくない場合exec形式（＊）で実行します。 |
| EXPOSE <ポート番号>| コンテナ起動時に内部でLISTENするポートを指定します。<br>親ホストへのポートマッピングは起動時に別途指定が必要です。<br>Dockerfile内で親のポートを予め指定することはできません。 |
| ENV <変数名> <値>| 環境変数を設定します。 |
| WORKDIR <ディレクトリ>| `Dockerfile`内でRUN, CMD, ENTRYPOINTより前に書くと<br>それらインストラクションの実行ディレクトリを指定できます。<br>またENVのあとに書けばENVで設定した環境変数を参照できます。|
| USER <ユーザー> | `Dockerfile`内でRUN, CMD, ENTRYPOINTより前に書くと<br>それらインストラクションの実行ユーザーを指定できます。 |
| VOLUME <ディレクトリ> | このインストラクションでマウントポイントを指定しておくと、<br>そのディレクトリを親ホストや他のコンテナと共有できるように設定します。|
| ADD <コピー元> <コピー先>  |ディレクトリ、ファイル（複数可）をコンテナ内にコピーします。<br>コピー元は`docker build`実行ディレクトリからの相対パス<br>（ただし親ディレクトリは不可）、コピー先はコンテナ内の<br>絶対パスです。指定には正規表現を利用できます。<br>次のCOPYと異なりコピー元にURLも指定できます。また特定の形式<br>(identity, gzip, bzip2, xz)で圧縮されたファイルがコピー元の場合は<br>解凍してからコンテナ内に設置します。 |
| COPY <コピー元> <コピー先> |ディレクトリ、ファイル（複数可）をコンテナ内にコピーします。<br>コピー元は`docker build`実行ディレクトリからの相対パス<br>（ただし親ディレクトリは不可）、コピー先はコンテナ内の<br>絶対パスです。指定には正規表現を利用できます。 |
| ONBUILD <インストラクション> | `Dockerfile`で`build`したイメージをベースに新たにイメージを作るとき<br>実行したいインストラクションを予め書いておくことができます。 |

（＊）exec形式： `<インストラクション> [ "実行ファイル","引数1","引数2" ]`の形式。
　　　例.  `RUN ["/bin/bash", "-c", "echo hello"]`

### イメージ作成(docker build)

**実行例**
`Dockerfile`からイメージを作るには`docker build -t <イメージ名:タグ> <Dockerfileパス>`を使います。

- `-t`オプションで作成するイメージに名前とタグをつけることができます。
- コマンドの最後には`Dockerfile`の置かれているディレクトリを指定します。<br>今回はカレントディレクトリに`Dockerfile`が置いてある想定で「.」を指定しています。


```
$ sudo docker build -t web_server .
```

**実行結果確認**
指定した名称web_serverで新たにイメージができたことが確認できます。

```
$ sudo docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
web_server           latest              455927c8e660        10 minutes ago      210 MB
(以下略)
```

### コンテナ起動
早速作成したイメージを元にコンテナ起動です。

```
$ sudo docker run -d -v /tmp/docker:/opt -p 80:8080 web_server
```

`/tmp/docker/test.txt`へ何かメッセージを記載して、起動したサーバへブラウザからアクセスします。
http://localhost:8888/test.txt
＊ポート8888を指定しているのは今回の環境ではCentOS 7が起動しているVMの80を8888へフォワーディングしているため。
![スクリーンショット 2015-01-17 11.36.01.png](https://qiita-image-store.s3.amazonaws.com/0/61318/dd53b278-4ea0-bf51-c42c-43fbfdee5ceb.png)
[その1](http://qiita.com/smile-0yen/items/c6957b951ca0fdb8acc7)と同様にWebサーバへブラウザからアクセスが確認できれば成功です。
[その2](http://qiita.com/smile-0yen/items/4a35ea9f0279f4d85c42)で見たように`docker logs`コマンドでログ確認してみます。
まずは起動中のコンテナのIDを確認。

```
$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                  NAMES
fb5df2b423ea        web_server:latest   "/bin/sh -c 'ruby -r   18 minutes ago      Up 18 minutes       0.0.0.0:80->8080/tcp   boring_hopper
```

IDを指定してコンテナ内のログ出力を見ると確かに先ほどのアクセスが記録されていました。

```
$ sudo docker logs --tail="3" fb5df
[2015-01-13 15:14:11] WARN  Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
10.0.2.2 - - [13/Jan/2015:15:14:11 UTC] "GET /test.txt HTTP/1.1" 304 0
- -> /test.txt
```

## イメージの中身を確認する

### イメージの更新履歴を確認する docker history

イメージはベースとしたイメージとの差分を積み重ねて作られています。
そのことを端的に確認するには`docker history <イメージ>`を使います。

**実行例**

```
$ sudo docker history web_server
```

**実行例結果**
これまでの変更の歴史が全て記録されています。作成時のコマンドが途中で切られていますが
オプション`--no-trunc`をつけると実行コマンド全体を確認することができます。

```
$ sudo docker history web_server
IMAGE               CREATED             CREATED BY                                      SIZE
455927c8e660        3 days ago          /bin/sh -c #(nop) CMD [/bin/sh -c ruby -run -   0 B
9307eadf28da        3 days ago          /bin/sh -c #(nop) WORKDIR /opt                  0 B
c2bbe183f761        3 days ago          /bin/sh -c #(nop) EXPOSE map[8080/tcp:{}]       0 B
c75722c696c8        3 days ago          /bin/sh -c apt-get install ruby -y              17.29 MB
e82e1a53528e        3 days ago          /bin/sh -c #(nop) MAINTAINER smile-0yen smile   0 B
ed5a78b7b42b        3 weeks ago         /bin/sh -c #(nop) CMD [/bin/bash]               0 B
8c4b1edcceea        3 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kB
9a4594fe74ea        3 weeks ago         /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic   194.5 kB
fe95bf7d5f50        3 weeks ago         /bin/sh -c #(nop) ADD file:c032cb16120c7533b5   192.5 MB
511136ea3c5a        19 months ago                                                       0 B
```

**`--no-trunc`オプションをつけた場合**

```
$ sudo docker history --no-trunc web_server
IMAGE                                                              CREATED             CREATED BY                                                                                         SIZE
455927c8e6600f79ba6dda8e03709051a251a1dfb96f634b95959e8bfb0d5174   3 days ago          /bin/sh -c #(nop) CMD [/bin/sh -c ruby -run -e httpd . -p 8080]                                    0 B
9307eadf28dadd4f21ea14024343c69de924d6766494fa73015aec8daf43ae1f   3 days ago          /bin/sh -c #(nop) WORKDIR /opt                                                                     0 B
c2bbe183f7613773d0dc03670730acc2def499e85efa97074132698918a739d8   3 days ago          /bin/sh -c #(nop) EXPOSE map[8080/tcp:{}]                                                          0 B
c75722c696c8ecb42d8d9fc31f0b260845b881a59cc7881fe1761fc7360ba776   3 days ago          /bin/sh -c apt-get install ruby -y                                                                 17.29 MB
e82e1a53528eaf869bca0a16ea9423853c077d06b0b1bfccfdd6a207790281f2   3 days ago          /bin/sh -c #(nop) MAINTAINER smile-0yen smile0yen.1024@gmail.com                                   0 B
ed5a78b7b42bde1e3e4c2996e02da778882dca78f8919cbd0deb6694803edec3   3 weeks ago         /bin/sh -c #(nop) CMD [/bin/bash]                                                                  0 B
8c4b1edcceea5c11f0a43de6c990036cffbd63f5590cba0075042c66cd90d948   3 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list                           1.895 kB
9a4594fe74ea4c14e642b871556f1460f6d332350bf3c772e76ed0059444ff19   3 weeks ago         /bin/sh -c echo '#!/bin/sh' > /usr/sbin/policy-rc.d                                                && echo 'exit 101' >> /usr/sbin/policy-rc.d    && chmod +x /usr/sbin/policy-rc.d                        && dpkg-divert --local --rename --add /sbin/initctl    && cp -a /usr/sbin/policy-rc.d /sbin/initctl    && sed -i 's/^exit.*/exit 0/' /sbin/initctl                        && echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup                        && echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean    && echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean    && echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean                        && echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages                        && echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes   194.5 kB
fe95bf7d5f5090fdaf5c5d442f9f05211f9cb338107a41ff10c7a46be4be2dea   3 weeks ago         /bin/sh -c #(nop) ADD file:c032cb16120c7533b582d0c03b3e19314915f7674c000eda0cd36853005a880e in /   192.5 MB
511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158   19 months ago                                                                                                          0 B
```

### イメージの詳細を確認する docker inspect

イメージに記録されている詳細な情報を確認するには`docker inspect <イメージ>`を使います。
あまり使うことはないかもしれませんが全ての設定情報をJSON形式で確認できますので便利です。

**実行例**

```
$ sudo docker inspect web_server
```

**実行例結果**

```
[{
    "Architecture": "amd64",
    "Author": "smile-0yen smile0yen.1024@gmail.com",
    "Comment": "",
    "Config": {
        "AttachStderr": false,
        "AttachStdin": false,
        "AttachStdout": false,
        "Cmd": [
            "/bin/sh",
            "-c",
            "ruby -run -e httpd . -p 8080"
        ],
        "CpuShares": 0,
        "Cpuset": "",
        "Domainname": "",
        "Entrypoint": null,
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "ExposedPorts": {
            "8080/tcp": {}
        },
（以下略）
```

## まとめ

- イメージ作成の基本は`Dockerfile`へインストラクションを書いて<br>`docker build -t <イメージ名:タグ> <Dockerfileパス>`
- `Dockerfile`によってイメージをコードとして管理できる。
- イメージの変更履歴を確認するには`docker history <イメージ>`。
- イメージの詳細情報を確認するには`docker inspect <イメージ>`。