Dockerのライフサイクルを理解するハンズオン資料

# 概要

* 手を動かしながら docker コマンドの基礎を学びます。
* Docker イメージを使い、どのようにコンテナを動かすかを理解します。
* イメージを自動構築し、Docker Hub にアップロードします。
* Dockerfile の基本と、CMD と ENTRYPOINT の違いを理解します。
* 所要時間は約15～30分です。

発表資料（[Dockerライフサイクルの基礎](http://www.slideshare.net/zembutsu/docker-underlying-and-containers-lifecycle)）の中からハンズオン部分だけを抽出したものです（コピー＆ペーストの確認用）。こちらを副読資料として、あわせてご覧ください。

* Dockerライフサイクルの基礎　地雷を踏み抜けろ！
 * http://www.slideshare.net/zembutsu/docker-underlying-and-containers-lifecycle

## ハンズオン内容

ドキュメントの [Get Started with Docker](http://docs.docker.jp/linux/) の内容をベースにしています。鯨が喋る [whalesay イメージ](https://hub.docker.com/r/docker/whalesay/)を実行します。また、自分で Dockerfile を使って自分だけの whalesay イメージを Docker Hub のリポジトリ上にアップロードします。

## 事前準備

Docker を動かすための環境が必要です。インストールやセットアップの方法については、各ドキュメントをご覧ください。

* [Linux](https://hub.docker.com/r/docker/whalesay/)
* [Windows](http://docs.docker.jp/engine/installation/windows.html)
* [Mac OS X](http://docs.docker.jp/engine/installation/mac.html)

# ハンズオン

## 練習1. hello-world コンテナの実行

Docker が正常に使えるかどうかを確認します。 `hello-world` という名前のイメージを使い、サンプルプログラムを実行します。

コンテナを実行するには、 `docker run` コマンドを使います。書式は以下の通りです。

```
$ docker run <オプション> イメージ名:タグ <コマンド>
```

それでは、 `hello-world` コンテナを実行します。

```
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
03f4658f8b78: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7
Status: Downloaded newer image for hello-world:latest

Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
 https://hub.docker.com

For more examples and ideas, visit:
 https://docs.docker.com/userguide/
```

初回実行時、ローカル環境上に `hello-world` イメージがないため、Docker Hub からイメージを自動的にダウンロードします。その後、コンテナとしてイメージを実行しました。

もう一度同じ `docker run hello-world` コマンドを実行します。今度はローカルに既にイメージがありあますので、ダウンロードは発生しません。今あるイメージを使い、すぐにコンテナを起動できます。

## 練習2. イメージの確認と取得

ローカル環境上のイメージ一覧を確認するには `docker images` コマンドを使います。先ほどの「hello-world」イメージがダウンロード済みかどうかを確認します。

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              690ed74de00f        6 months ago        960 B
```

次は Docker Hub から `docker/whalesay` イメージをダウンロードします。ダウンロードするには `docker pull` コマンドを実行します。

```
$ docker pull docker/whalesay
```

* これは `docker` ユーザの `whalesay` イメージを取得するという意味です。
* 「ユーザ名/」が無い場合や「library/」は公式イメージ(Docker社の精査済み）です。

再度 `docker images` コマンドを実行し、`whalesay` イメージがローカルにあるかどうか確認します。

```
$ docker images
hello-world         latest              690ed74de00f        6 months ago        960 B
docker/whalesay     latest              6b362a9f73eb        10 months ago       247 MB
```

なお、このイメージが置いてある場所をリポジトリと呼びます。このリポジトリやイメージの詳細な情報は Docker Hub 上でも確認できます。

* https://hub.docker.com/r/docker/whalesay/

## 練習3. イメージの実行

`whalesay` イメージを使い、メッセージを表示するコンテナを実行します。 `whalesay` イメージは Ubuntu 14.04 をベースに、`cowsay`という鯨がメッセージを表示するプログラムが入っています。

`docker` で `whalesay` イメージの中にある `cowsay` プログラムを実行します。 

```
$ docker run docker/whalesay cowsay hello world
 _____________
< hello world >
 -------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/
```

練習4. Dockerfile の作成

Dockerfileは、自動的にDockerイメージを構築(build)する時に必要になるファイルです。イメージを構成する全ての要素を記述します。

作業用ディレクトリを作成し、移動します。

```
$ mkdir mywhale
$ cd mywhale
```

任意のテキストエディタで `Dockerfile` という名前のファイルを作成します。また、中身は以下のようにします。

```
FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune -a | cowsay 
```

* `FROM` `RUN` `CMD` はイメージ構築時の命令です。
* `FROM`は構築時の元になるイメージを指定します。これは先ほどと同じ whalesay です。
* `RUN`はコンテナの中でコマンドを実行します。[fortunes プログラム](https://ja.wikipedia.org/wiki/Fortune_(UNIX))のセットアップです。
* `CMD`はコンテナ実行時にデフォルトで実行する命令です。

## 練習5. イメージの構築

イメージを構築するには `docker build` コマンドを使います。ここでは `docker build` コマンドで「docker-whale」イメージを作成します。

```
$ docker build -t docker-whale .
```

* `-t`は`イメージ名:タグ`を指定するオプションです。
* コマンドの最後に`.`（ドット記号）が必要です。現在(`.`)のディレクトリをコンテキスト（イメージ内容）として指定し、そこにある Dockerfile を使ってイメージの自動構築を命令します。

再び docker images コマンドを実行し、イメージが作成されているかどうかを確認します

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker-whale        latest              2869f3029cd4        About a minute ago   274.9 MB
hello-world         latest              690ed74de00f        6 months ago         960 B
docker/whalesay     latest              6b362a9f73eb        10 months ago        247 MB
```

## 練習6. 新しいイメージを実行

作成したイメージ `docker-whale` を使ってコンテナを実行します。

```
$ docker run docker-whale
 ______________________________________
/ Keep hands and feet away from moving \
\ parts at all times.                  /
 --------------------------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/
```

* Dockerfile で指定した CMD 命令を指定した`fortune`プログラムを実行し、その結果を cowsay プログラムに渡しています。
* `fortune` プログラムは、この docker-whale イメージの中にあるものです。
* 実行するたびに `fortune` プログラムをコンテナの中で実行します。都度、表示するメッセージが変わります。

## 練習7. イメージの詳細を確認

イメージの詳細を表示する docker inspect コマンドで、構築したイメージの情報を調べます。`Cmd`セクションが、先ほど CMD 命令で指定したコマンドになっていることを確認します。

```
$ docker inspect docker-whale
（省略） "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) CMD [\"/bin/sh\" \"-c\" \"/usr/games/fortune -a | cowsay\"]"
```

この方法を使えば、それぞれのイメージが、デフォルトでどのようなコマンドを実行するか、実行前に確認できます。このほかにも、環境変数・ポート情報・ネットワーク・ボリュームに関する各種の情報が確認できます。

次はイメージの構築履歴を `docker history` コマンドで確認します。

```
$ docker history docker-whale
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
2869f3029cd4        21 minutes ago      /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/usr/g   0 B
dece11a04bd5        21 minutes ago      /bin/sh -c apt-get -y update && apt-get insta   27.82 MB
（省略）
```

この1行1行がイメージ・レイヤです。先ほどの Dockerfile に記述した命令と対応していることに注意します。

## 練習8. Docker Hub に登録・ログイン

作成したイメージを Docker Hub で公開するため、まずはアカウントを作成します。既にお持ちであれば、次に進みます。

https://hub.docker.com/ にアクセスし、ユーザ名・パスワード・登録用のメールアドレス（認証に必要）を入力します。

* 登録は無料ですが、プライベート（非公開）のリポジトリを作る場合は課金が必要です。
* メールの件名は「Please confirm email for your Docker Hub account」（Docker Hub アカウント用のメールアドレスをご確認ください）です。本文中のリンクをクリックします。


## 練習9. リポジトリの作成

* Docker イメージを置く場所をリポジトリ(repository)と呼びます。
* ブラウザで Docker Hub を開き、`Create Repository`(作成)ボタンを押します。

![repo.png](https://qiita-image-store.s3.amazonaws.com/0/13002/9bbc13e9-ecc8-63aa-aa42-ca121b89d667.png)

* リポジトリ名は`docker-whale`とします（任意の名称も選択できますが、その場合は後の手順を都度変更願います）。
* 簡単な説明（必須）と詳細説明を追加できます。
* Visibilityは public（公開）か private（非公開）を選べます。
* 最後に「Create」ボタンを押すと、作成完了です。

## 練習10. イメージのタグ付け

改めて docker images で docker-whale のイメージ ID を確認します。

```
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker-whale        latest              2869f3029cd4        About a minute ago   274.9 MB
```

イメージ(ID)には、複数のイメージ名やタグを付けられます。`docker tag` コマンドを使い、docker-whale イメージに Docker Hub 上のユーザ名を割り当て（タグ付け）ます。

```
$ docker tag イメージID ユーザ名/docker-whale:latest
```

例：

![tag.png](https://qiita-image-store.s3.amazonaws.com/0/13002/98a17e50-00f3-a327-1c64-350cafc83a97.png)

## 練習11. Docker Hub にログイン

`docker login` コマンドを実行し、コンソール上から Docker Hub にログインします。

```
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: ユーザ名 ←自分のユーザ名
Password:　←パスワード
Login Succeeded 
```

* 認証情報（アクセス用トークン）は `~/.docker/config.json` に保管されます。

## 練習12. Docker Hub に送信

Docker Hub 上のリポジトリに送信するには docker push コマンドを使います。先ほど `docker tag` でタグ付けした「ユーザ名/docker-whale」イメージを送信します。

```
$ docker push ユーザ名/docker-whale
The push refers to a repository [docker.io/ユーザ名/docker-whale]
1f572a10291b: Pushed
5f70bf18a086: Mounted from docker/whalesay
d061ee1340ec: Mounted from docker/whalesay
d511ed9e12e1: Mounted from docker/whalesay
091abc5148e4: Mounted from docker/whalesay
b26122d57afa: Mounted from docker/whalesay
37ee47034d9b: Mounted from docker/whalesay
528c8710fd95: Mounted from docker/whalesay
1154ba695078: Mounted from docker/whalesay
latest: digest: sha256:b597a0451116a63e5eaaa3b3214c77042f180a31c303522db998b37e2c2ddd12 size: 8095
```

ブラウザでDocker Hub を開き、リポジトリの状態を確認します。

## 練習13. イメージの削除と再実行

ローカルのイメージは `docker rmi` (remove imageの意味)コマンドで削除できます。先ほど作成したイメージを削除します。イメージIDもしくはイメージ名やタグを指定して削除します。

```
$ docker rmi –f docker-whale
$ docker rmi –f ユーザ名/docker-whale
```

* `-f` は強制削除のオプションです。

イメージを Docker Hub から取得・実行します。

```
$ docker run ユーザ名/docker-whale
```

* ローカルにイメージはありません。Docker Hub からイメージをダウンロードして実行します。

以上が、最も簡単なコンテナのライフサイクルになります。

## 後片付け

* 不要なイメージは`docker rmi`コマンドで削除できます。
* `docker run`コマンドの処理内容を思い出します。
 * コマンドを実行する度に、毎回イメージレイヤを作成しています。
 * `docker ps -a`コマンドを実行すると、全てのイメージレイヤを表示します。

コンテナ（のイメージ・レイヤ）を削除するコマンドは`docker rm` removeの意味です。

次のコマンドで、停止中のコンテナを一括削除します。

```
$ docker rm $(docker ps -aq)
```

* 「-a」は全て「-q」はイメージIDのみ表示するオプションで、結果を docker rm に送ります。
* 同様に「docker rmi $(docker images -q)」でイメージの全削除も可能です。

# ハンズオン（Dockerfile活用編）

## CMD 命令の理解

ping を実行するコンテナを通して、挙動の違いを理解していきます。まず、ping を実行する myping コンテナを作成します。そのため、ディレクトリを作成し、移動します。

```
$ mkdir myping
$ cd myping
```

Dockerfile を作成し、中身を以下のようにします。

```
FROM busybox
CMD ["ping","-c","10","127.0.0.1"]
```

* Busybox という小さな Linux ディストリビューション上の `ping` コマンドを実行する命令です。

イメージ `myping` を構築します。

```
$ docker build -t myping .
```

`myping` イメージを使ったコンテナを実行します。

```
$ docker run --rm myping
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.217 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.090 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.193 ms
64 bytes from 127.0.0.1: seq=3 ttl=64 time=0.095 ms
64 bytes from 127.0.0.1: seq=4 ttl=64 time=0.095 ms
64 bytes from 127.0.0.1: seq=5 ttl=64 time=0.173 ms
64 bytes from 127.0.0.1: seq=6 ttl=64 time=0.100 ms
64 bytes from 127.0.0.1: seq=7 ttl=64 time=0.090 ms
64 bytes from 127.0.0.1: seq=8 ttl=64 time=0.092 ms
64 bytes from 127.0.0.1: seq=9 ttl=64 time=0.094 ms

--- 127.0.0.1 ping statistics ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 0.090/0.123/0.217 ms
```

* `--rm`オプションは、コンテナ実行後、自動的にイメージレイヤを破棄します。都度、docker rm を実行する必要がないため、コマンドとして使う場合に便利です。
* Dockerfile の `CMD [“ping”,“-c”,“10”,“127.0.0.1”]`は、自分自身に対して ping を 10 回実行するという意味です。

次は myping コンテナの引数に `sh` を指定して実行します。

```
$ docker run –i -t myping sh
/ #
```

* `-i`はコンテナの標準入出力を有効にします。
* `-t`は疑似ターミナルを有効にします。
* `exit` で終了します。

`sh`コマンドを指定したため、CMD 命令は無視されました。

ちなみに、busybox イメージや Ubuntu イメージを実行時、オプションを何も指定しなくてもシェルが起動するのは、各 Dockerfile の CMD 命令でシェルが指定されているからです。

* Ubuntu イメージの [Dockerfile](https://github.com/tianon/docker-brew-ubuntu-core/blob/028503f527c77921a3eff88216f57025cc611869/trusty/Dockerfile)
 * `CMD [“/bin/bash”]`
* Busybox イメージの [Dockerifle](https://github.com/docker-library/busybox/blob/de53df881f8a7cad77fe9e2042ee0a8d38402ce3/glibc/Dockerfile)
 * `CMD [“sh”]`

このように CMD 命令は、コンテナ実行時にコマンドを指定すると無視されます。

## ENTRYPOINT との比較

Dockerfile を開き、`CMD` 命令を `ENTRYPOINT` に書き換えます。

```
FROM busybox
ENTRYPOINT ["ping"]
```

再度イメージを構築します。バージョン `1.1` をタグ付けします。

```
$ docker build -t myping:1.1 .
```

構築したイメージ `myping:1.1` を実行します。

```
$ docker run --rm myping:1.1
BusyBox v1.24.2 (2016-03-18 16:38:06 UTC) multi-call binary.

Usage: ping [OPTIONS] HOST
(省略）
-q              Quiet, only display output at start
                        and when finished
        -p              Pattern to use for payload
```

何も指定しなくてもコマンドを実行するという意味では、`CMD` も `ENTRYPOINT` も同様の処理です。しかし、`docker run` でコマンドの引数を付けると挙動が違います。

* CMD … docker run に引数があれば、CMD は無視する
* ENTRYPOINT … docker run に引数があれば、ENTRYPOINT のコマンドの引数として引数をあつかう。CMD は無視する。

myping:1.1 に IP アドレスを付けて実行します。 


```
$ docker run --rm myping:1.1 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.149 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.160 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.222 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.149/0.177/0.222 ms
```

* `ctrl+c` で中断します（通常の ping コマンドです）

ENTRYPOINT がある場合、あくまでも ENTYPOINT に記述したコマンドやオプションは docker run 実行時に固定です。これを上書きするには docker run 時に `--entrypoint` オプションを使います。

次の Dockerfile を作成し、 myping イメージを構築します。

```
FROM busybox

CMD ["127.0.0.1"]
ENTRYPOINT ["ping","-c","3"]
```

引数を付けない場合と付けた場合で myping を実行し、それぞれの結果を比較します。

付けない場合：

```
$ docker run myping
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.130 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.311 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.171 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0%
```

付けた場合：

```
$ docker run myping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=56 time=1.812 ms
64 bytes from 8.8.8.8: seq=1 ttl=56 time=1.802 ms
64 bytes from 8.8.8.8: seq=2 ttl=56 time=1.863 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.802/1.825/1.863 ms
```

* ENTRYPOINT と CMD があれば、CMD は ENTRYPOINT に対するオプションになります。
* コンテナ実行時のオプションがあれば、ENTRYPOINT の引数としてオプションを使います。
* ENTRYPOINT は、あたかもコマンドを実行するようにコンテナを扱えるようにします。

以上です。おつかれさまでした :laughing: 

# 更に詳しく知るには？

ドキュメント：

* [Docker ドキュメント日本語化プロジェクト](http://docs.docker.jp/)
 * [Dockerfile リファレンス](http://docs.docker.jp/engine/reference/builder.html)
 * [Dockerfile ベストプラクティス](http://docs.docker.jp/engine/userguide/eng-image/dockerfile_best-practice.html)
 

[元の発表スライド](http://www.slideshare.net/zembutsu/docker-underlying-and-containers-lifecycle)には、もう少し細かい解説や背景も紹介しています。