Othlo Tech Docker/swarm modeハンズオン手順

# Othlo Tech ハンズオン手順

## 1. ハンズオンの目的と方針

Docker プラットフォームの基本動作を、手を動かしながら学びます。はじめに、鯨が喋るプログラムを例に、Docker コンテナの実行と、イメージ構築を行えるようにます。それから、サンプルのウェブアプリケーション、Nginx、WordPress(PHP+MySQL)を例にしてコンテナのネットワークとボリュームについて学びます。

## 2. 事前準備

今回のハンズオン作業は、さくらのクラウド上のサーバで作業を行います。

* さくらのクラウドのコントロールパネルにログインし、仮想サーバの作成
* 仮想サーバへ SSH でログイン
* Docker Hub アカウントの登録と確認

※ 今回の手順は VirtualBox など、皆さんの PC 上で行っても構いませんが、サポートの関係上、今回はクラウド上の仮想サーバを使います。

### 2.1 仮想サーバの作成

#### 2.1.1 さくらのクラウドの、コントロールパネルにログイン

https://secure.sakura.ad.jp/cloud/

ウェブ・ブラウザを起動し、コントロールパネルの URL を開きます。今回のハンズオン用の認証情報を、画面の右側に入力し、ログインできるかどうかを確認します。認証情報の用紙が手元になければお知らせください。

#### 2.1.2 仮想サーバの起動

ログイン状態のクラウドには、何も環境がありません。Docker （ドッカー）を使うための仮想サーバを準備します。

1. 【 さくらのクラウド(IaaS)】をクリックします。
2. メニュー【 サーバ 】から【 追加 】をクリックします。
3. ディスクイメージは【 CentOS 7.4 64bit 】を使います。
4. 「サーバプラン」と「ディスクプラン」を選択します（※今回は変更しません）。
5. 【 管理ユーザのパスワード 】で root パスワードを設定します。８文字以上の「アルファベット」「数字」「記号」の組み合わせが必要です。
6. ホスト名を【 docker 】、作成数【 1 】として【 作成 】ボタンをクリックします。
7. 確認画面では【 作成 】をクリックします。
8. サーバ追加作業が完了するまで待ちます。起動後は【 閉じる 】をクリックします。
9. メニューの【 サーバ 】をクリックし、作成したサーバ「 docker 」を確認します。

#### 2.2 仮想サーバへ SSH でログイン

仮想サーバに接続する IP アドレスを確認の後、SSH （Simple Secure Shell）でログインします。

1. IP アドレスを確認します。インターフェースを右クリックし【 IP アドレスをコピー 】します。
2. Tera Term （テラ・ターム； [Windowsの設定手順は、こちらをご覧ください](https://mag.osdn.jp/09/02/02/0412259)） やターミナルなどを開き、対象サーバに SSH でログインします。

* ログイン時のIDは「root」、パスワードは作成時に入力したものを指定します
* ターミナルでは「ssh -l root <IPアドレス>」か「ssh root@<IPアドレス>」を実行します

```
$ ssh -l root <IPアドレス>
The authenticity of host '59.106.xxx.xxx (59.106.xxx.xxx)' can't be established.
ECDSA key fingerprint is SHA256:8kqxjRIPV/0S8g5BlKhICZgCo3eJS9jI7shtFoJzxL2.
Are you sure you want to continue connecting (yes/no)? yes  ←【yes】と入力し、接続継続を許可
Warning: Permanently added '59.106.xxx.xxx' (ECDSA) to the list of known hosts.
root@59.106.xxx.xxx's password: ←ここでパスワードを入力
[root@docker ~]#
```

#### 2.3 Docker Hub アカウントの登録と確認

※ 既に Docker Hub（ドッカー・ハブ）アカウントをお持ちの場合は不要です。

Dockerイメージを公開するために、 [Docker Hub] にアカウントを作成します。アカウントの作成・維持に費用はかかりません。

1. Docker Hub のサイト https://hub.docker.com/ にアクセスします。
2. ユーザ名、メールアドレス、パスワードを入力し、 Signup （サインアップ）を押します。
3. 暫くすると、登録したメールアドレス宛に確認用のメールが届きます。届いたら、メール本文にある確認用のリンクをクリックします。
4. Docker Hub にログインできているのを確認します。

※ ここで作成するユーザ名は、あとから変更できません。

## 3 ハンズオン

以降は、実際にサーバ上で Docker の環境構築と作業を進めます。

### A：【Docker 初歩編】セットアップと、初めてのコンテナ実行

ゴール：Docker コンテナを動かす環境を理解します。

#### A-1 Docker Engine のセットアップ

　1. curl（カールまたはシーユーアールエル、サーバから様々なプロトコルを通してデータを転送するツール）を使い、自動的に Docker Engine パッケージをセットアップします。

```
# curl -fsSL get.docker.com -o get-docker.sh
# sh ./get-docker.sh
```

このコマンドを実行すると、サーバ上では次の処理を自動的に行います。

* Docker が提供する Docker CE （ココミュニティ・エディション）のリポジトリを利用可能に設定
* `docker-ce` パッケージをセットアップ（Docker Engine と `docker` クライアントを利用可能にします）

（ `# コマンド` の箇所は、 root 権限でのコマンド実行を意味します。一方 `$ コマンド` の場合は

　2. Docker Engine を `systemctl start docker` で起動し、 `systemctl status docker` で起動状態を確認します。

```
# systemctl start docker
# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: active (running) since 金 2017-10-27 11:54:13 JST; 6s ago
     Docs: https://docs.docker.com
 Main PID: 1343 (dockerd)
   Memory: 17.1M
   CGroup: /system.slice/docker.service
           tq1343 /usr/bin/dockerd
           mq1346 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.soc..
```

　3. `docker version` コマンドを実行し、 `docker` クライアントが Docker Engine サーバ側（ `dockerd` ）に接続できるかどうか確認します。次のように `Server:` のバージョンが表示されれば正常です。

```
# docker version
Client:
 Version:      17.10.0-ce
 API version:  1.33
 Go version:   go1.8.3
 Git commit:   f4ffd25
 Built:        Tue Oct 17 19:04:05 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.10.0-ce
 API version:  1.33 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   f4ffd25
 Built:        Tue Oct 17 19:05:38 2017
 OS/Arch:      linux/amd64
 Experimental: false
 ```

もしも次のようなエラーが出てしまう場合は、 Docker を起動しているかどうか確認します。

```
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
```

　4. サーバ再起動後も、自動的に Docker を起動するように設定します。

```
# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
```

#### A-2 はじめてのコンテナ実行

`hello-world` コンテナを実行しながら、基本的な挙動を見ていきましょう。

　1. コンテナを動かす前に、 `docker image ls` コマンドを実行し、サーバのローカル環境上に Docker イメージがないことを確認します。

```
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
```

このように、インストール直後は Docker イメージが１つもありません。

　2. `docker container run` コマンドを使い、 `hello-world` イメージを実行します。

```
# docker container run hello-world
```

　3. この時、どのような処理を行ったのか確認します。

コマンドを実行すると、Docker が内部でどのような処理を行っているか、画面上に結果を表示します。

```
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
5b0f327be733: Pull complete
Digest: sha256:07d5f7800dfe37b8c2196c7b1c524c33808ce2e0f74e7aa00e603295ca9a0972
Status: Downloaded newer image for hello-world:latest
```

それぞれ上から、次の意味があります。

* イメージ `hello-world:latest` （リポジトリ名 `hello-world` の、 `latest` タグを持つもの）がローカルに無い
* リポジトリ `library/hello-world` から、 `latest` （最新）のタグを持つイメージを取得（ `pull` ）
* イメージ・レイヤ `5b0f327be733` のダウンロード完了
* ダウンロードしたイメージのハッシュ値は `07d5f7800dfe37b8c2196c7b1c524c33808ce2e0f74e7aa00e603295ca9a0972`
* 状態（ `Status` ）は `hello-world:latest` の最新イメージをダウンロード完了

そして、以降のメッセージは `hello-world` イメージ（中に含まれているバイナリ・プログラム）の実行結果が、そのまま標準出力されたものです。

```
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 ID:
 https://cloud.docker.com/

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

つまり、 `docker container run` を実行すると、 `hello-world` イメージ使ったコンテナを実行し、イメージ内に含まれるコマンドを実行の後、コンテナは終了しました。

　4. コンテナの状態を `docker container ls` / `docker ps` コマンドで確認します。

```
# docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

通常は「実行中」のコンテナしか表示しません。

　5. 終了したコンテナを含む、全てのコンテナ情報を `docker container ls -a` で表示します。

```
# docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
d98925cc5735        hello-world         "/hello"            11 minutes ago      Exited (0) 11 minutes ago                       jovial_easley
```

* コンテナにはコンテナ ID が割り当てられます。この例では `d98925cc5735`  に該当します。
* イメージ `hello-world` を使っています
* コマンド `/hello` を実行しています
* 正常に終了 `Exited (0)` しています
* コンテナ名は `jovial_easley` です

このようにして、コンテナの状態を確認できます。

なお、コンテナ名は実行時に `--name` オプションで指定できますが、コンテナの名前を重複して起動できません。また、コンテナ名の指定がなければ、「形容詞」＋「著名な科学者かハッカーの名前」で自動的に割り振られます（[詳細：名前生成のソースコード](https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go)）

　6. 再び `docker container run hello-world` を実行します。

```
# docker container run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
（省略）
For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/
```

何度実行しても、すぐに起動できます。なぜだか分かりますか？ 今回は既に `hello-world` イメージをローカルにダウンロード済みのため、ダウンロード（pull）の処理は行われず、すぐにコンテナを実行できました。

　7. `docker ps -a` で２つめ以降のコンテナが実行されたのを確認します。

```
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
d442c8a19807        hello-world         "/hello"            About a minute ago   Exited (0) About a minute ago                       laughing_curie
d98925cc5735        hello-world         "/hello"            21 minutes ago       Exited (0) 2 minutes ago                            jovial_easley
```

　8. コンテナ（用のイメージ・レイヤ）を `docker container rm` で削除します。

停止中のコンテナは削除用コマンドで削除できます（実行中のコンテナは `-f` オプションを付けて、強制削除できます）。削除後は復旧できません。

```
# docker container rm [コンテナID]
```

あるいは `docker container prune`コマンドを実行すると、停止中のコンテナを全て削除します。

```
# docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
d442c8a198076a2f4d2c648e1241bb73a5ef88b01702664423335ee7b6e9414d
d98925cc5735fc974b80d6ece4c6620b98d023f0b0ef49e792f7b210f7601b1d

Total reclaimed space: 0B
```

　9. コンテナが何も残っていないのを確認します。

```
# docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

### B：docker/whale を使ったDocker コンテナ実行と Docker イメージ構築

ゴール：Docker の基本コマンドとイメージの構築をはじめ、Docker Hub をつかった一通りのライフサイクルを学びます。

#### B-1 docker/whalesay イメージのダウンロードとコンテナ実行

　1. `docker/whalesay` という鯨に何かを喋らせるプログラムが入ったイメージをダウンロードします。イメージをダウンロードするコマンドは `docker image pull` です。

```
# docker image pull docker/whalesay
Using default tag: latest
latest: Pulling from docker/whalesay
e190868d63f8: Extracting  51.81MB/65.77MB
909cd34c6fd7: Download complete
0b9bfabab7c1: Download complete
a3ed95caeb02: Download complete
00bf65475aba: Download complete
c57b6bcc83e3: Download complete
8978f6879e2f: Download complete
8eed3712d2cf: Download complete
Digest: sha256:178598e51a26abbc958b8a2e48825c90bc22e641de3d31e18aaf55f3258ba93b
Status: Downloaded newer image for docker/whalesay:latest
```

先ほどの `hello-world` イメージと同様、ローカルになかったためリモート（Docker Hub）からイメージをダウンロードしました。

　2. イメージがダウンロードされているのを `docker image ls` コマンドで確認します。

```
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              05a3bd381fc2        6 weeks ago         1.84kB
docker/whalesay     latest              6b362a9f73eb        2 years ago         247MB
```

　3. イメージ `docker/whalesay` を使い、コンテナとして実行します。 `docker container run` コマンドを実行します。

```
# docker run docker/whalesay
```

実行しても何も表示しません。このイメージを実行しても、 `/bin/sh` を実行して終了する挙動の設定だからです（ `docker image inspect docker/whalesay` の `Cmd` を参照 ）。

　4. このイメージにある `cowsay` プログラムを実行します。

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

　5. `cowsay` に引数を追加し、何か鯨に喋らせます。

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

#### B-2 もっと賢そうな鯨のイメージを構築

`docker/whalesay` イメージをもとに、カスタマイズした新しい Docker イメージを作ります。今回は入力したテキストではなく `fortune` という名前のプログラム（名言や格言のようなメッセージを表示します）を実行します。

Docker イメージを作る（buildする）には、 ２つの方法があります。

* `docker container commit` コマンドで、コンテナ用のイメージ・レイヤをイメージ化する方法
* `docker image build` コマンドで、 `Dockerfile` の命令に従い、 `docker container commit` を自動実行してイメージ化する方法

ここでは後者の `Dockerfile` でイメージを構築する方法を進めます。

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

```
# mkdir mywhale && cd mywhale
```

　2. Dockerfile を作成します。

```
# cat << 'EOF' > Dockerfile
FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune -a | cowsay
EOF
```

ファイル `Dockerfile` の内容を確認します。

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

上から1行ずつ Docker は読み込み、イメージを自動構築します。

* `FROM` で元にするイメージ名を指定します（ベース・イメージと呼びます）。ローカルにイメージがなければ、ダウンロードします。
* `RUN` は、コンテナを実行し、その中で実行するコマンドです。終了後、コンテナ用のイメージ・レイヤをイメージ・レイヤとしてコミットします。
  * ここでは `apt-get -y update` を実行し、その結果が正常であれば `apt-get install -y fortune` を実行します。
* `CMD` は、そのイメージを使ったコンテナが、デフォルトで何のコマンドを実行するかの指定です。
  * ここでは `fortune` プログラムを実行した標準出力を、 `cowsay` プログラムにパイプします。

　3. `docker image build -t mywhale .` コマンドでイメージを自動構築します。

オプションで `-t イメージ名` と、末尾に `.` （どこに Dockerfile や Docker イメージに格納するかを示すファイルのパス）を指定します。

```
# docker image build -t mywhale .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM docker/whalesay:latest
 ---> 6b362a9f73eb
Step 2/3 : RUN apt-get -y update && apt-get install -y fortunes
 ---> Running in 32219a5feef0
Ign http://archive.ubuntu.com trusty InRelease
Get:1 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:2 http://archive.ubuntu.com trusty-security InRelease [65.9 kB]
（省略）
Step 3/3 : CMD /usr/games/fortune -a | cowsay
 ---> Running in 84a8a20038c5
 ---> f70198e1d8e0
Removing intermediate container 32219a5feef0
Removing intermediate container 84a8a20038c5
Successfully built f70198e1d8e0
Successfully tagged mywhale:latest
```

　4. `docker image ls` コマンドで `mywhale` イメージが作成されているのを確認します。

```
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mywhale             latest              f70198e1d8e0        54 seconds ago      257MB
```

　5. `docker image history mywhale` を実行し、イメージ・レイヤの情報を確認します。

```
# docker image history mywhale
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
f70198e1d8e0        3 minutes ago       /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/u...   0B          
d8f783332f70        3 minutes ago       /bin/sh -c apt-get -y update && apt-get in...   9.83MB      
6b362a9f73eb        2 years ago         /bin/sh -c #(nop) ENV PATH=/usr/local/bin:...   0B          
<missing>           2 years ago         /bin/sh -c sh install.sh                        30.4kB      
<missing>           2 years ago         /bin/sh -c git reset --hard origin/master       43.3kB      
<missing>           2 years ago         /bin/sh -c #(nop) WORKDIR /cowsay               0B          
<missing>           2 years ago         /bin/sh -c git clone https://github.com/mo...   89.9kB      
<missing>           2 years ago         /bin/sh -c apt-get -y update && apt-get in...   58.6MB      
<missing>           2 years ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0B          
<missing>           2 years ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\...   1.9kB       
<missing>           2 years ago         /bin/sh -c echo '#!/bin/sh' > /usr/sbin/po...   195kB       
<missing>           2 years ago         /bin/sh -c #(nop) ADD file:f4d7b4b3402b5c5...   188MB
```

　6. 新しく作成したイメージで、コンテナを実行します。

```
# docker container run mywhale
 _________________________________________
/ "In the event of a percieved failing of \
| the project leadership #debian is       |
| empowered to take drastic and descisive |
| action to correct the failing,          |
| including by not limited to expelling   |
| officials, apointing new officials and  |
| generally abusing power"                |
|                                         |
| -- proposed amendment to Debian         |
\ Constitution                            /
 -----------------------------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/
```

実行するたびに表示するメッセージ内容は変わります。

#### B-3 Docker Hub に送信

作成した Docker イメージを公開イメージ・レジストリ [Docker Hub](https://hub.docker.com/) に公開します。

　1. `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: ← ここで Docker Hub のユーザ名
Password: ←パスワードを入力
Login Succeeded
```

`Login Succeeded` （ログイン成功）が表示されなければ、ユーザ名とパスワードが正しいかどうか確認します。

　2. 認証情報のファイルを確認します。

```
# cat ~/.docker/config.json
{
        "auths": {
                "https://index.docker.io/v1/": {
                        "auth": "**************"
                }
        },
        "HttpHeaders": {
                "User-Agent": "Docker-Client/17.10.0-ce (linux)"
        }
}
```

このファイルがあれば、Docker Hub への送信（push）が可能になります。

　3. イメージを送信する前に、イメージにタグを付けます。

```
$ docker image tag mywhale:latest <DockerHubユーザ名>/mywhale:latest
```

これは、イメージ送信用のコマンド `docker image push` を実行する前に、送信対象のイメージにユーザ名（の名前空間）付きのイメージを準備しておく必要があるためです。

また、１つのイメージに対して複数のタグをつけることができます。そのため、０からイメージを構築する必要はなく、いまあるイメージに対して別のタグを重複して付与しました。

　4. イメージ一覧 `docker image ls` で、同じイメージ ID に対して、複数のタグが付与されているのを確認します。

```
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mywhale             latest              f70198e1d8e0        15 minutes ago      257MB
zembutsu/mywhale    latest              f70198e1d8e0        15 minutes ago      257MB
```

`IMAGE ID` の ID が同じイメージは、実体としては同じ１つのイメージです。

　5. イメージを `docker image push <ユーザ名>/mywhale` でイメージを Docker Hub に送信（push）します。

```
# docker image push zembutsu/mywhale
The push refers to a repository [docker.io/zembutsu/mywhale]
d6e92dbe255a: Pushed
5f70bf18a086: Layer already exists
d061ee1340ec: Layer already exists
d511ed9e12e1: Layer already exists
091abc5148e4: Layer already exists
b26122d57afa: Layer already exists
37ee47034d9b: Layer already exists
528c8710fd95: Layer already exists
1154ba695078: Layer already exists
latest: digest: sha256:82bcd174553c98e2e7c626ffc23cfec4884192d354874bdbc9c5627f2294b360 size: 2613
```

この時、この例では Docker Hub に送信したのは新しいイメージ・レイヤ `d6e92dbe255a` にあたる部分だけです。その他のレイヤは既に Docker Hub 上に存在しているため、重複しての送信は行われていません。

　6. ローカルのイメージを削除します。

イメージを削除する前に、ローカルのコンテナを削除します（利用中のコンテナがあると、削除時に警告が出るためです）。

```
# docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y ←「yを入力」
```

それから `docker image rm mywhale` ( docker rmi mywhale) でイメージを消します。

```
# docker image rm mywhale
Untagged: mywhale:latest
# docker image rm zembutsu/mywhale
Untagged: zembutsu/mywhale:latest
Untagged: zembutsu/mywhale@sha256:82bcd174553c98e2e7c626ffc23cfec4884192d354874bdbc9c5627f2294b360
Deleted: sha256:f70198e1d8e0977780a2ec81293dac31c919757096d51f161d1784bbaf2871c7
Deleted: sha256:d8f783332f70b5878ad9e9d97a8d063551ccff39f3a2ca8884d9e26379469965
Deleted: sha256:7bd915d5a3ae086cd75920ed1bddcadbb440ecd3e94653c212e032badfcf4e2a
```

１回目のイメージ削除では「Untagged」とあり、イメージの実体はそのままで、タグだけが削除されています。２回目のイメージ削除では、タグの削除だけでなく、イメージ・レイヤも不要となったため同時に削除されています。

あとは、 `docker image ls` でイメージが存在していないのを確認します。

```
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              05a3bd381fc2        6 weeks ago         1.84kB
docker/whalesay     latest              6b362a9f73eb        2 years ago         247MB
```

　7. アップロードしたイメージを `docker container run <ユーザ名>/mywhale` で実行します。

```
# docker container run zembutsu/mywhale
Unable to find image 'zembutsu/mywhale:latest' locally
latest: Pulling from zembutsu/mywhale
e190868d63f8: Already exists
909cd34c6fd7: Already exists
0b9bfabab7c1: Already exists
a3ed95caeb02: Already exists
00bf65475aba: Already exists
c57b6bcc83e3: Already exists
8978f6879e2f: Already exists
8eed3712d2cf: Already exists
396d4892383e: Pull complete
Digest: sha256:82bcd174553c98e2e7c626ffc23cfec4884192d354874bdbc9c5627f2294b360
Status: Downloaded newer image for zembutsu/mywhale:latest
 _________________________________
/ Patageometry, n.:               \
|                                 |
| The study of those mathematical |
| properties that are invariant   |
|                                 |
\ under brain transplants.        /
 ---------------------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/
```

　8. `docker logout` でログアウトします。

```
# docker logout
Removing login credentials for https://index.docker.io/v1/
```

### C：Nginxコンテナとネットワーク・ボリュームの理解

#### C-1 Alpine Linux 対応イメージのダウンロード

ゴール：コンテナのポートをホスト側に公開（EXPOSE）する方法、コンテナとホスト間でデータをやりとりする方法を学びます。

　1. `docker image pull nginx:alpine` を実行し、Nginx ウェブ・サーバ用の Docker イメージをダウンロードします。

```
# docker image pull nginx:alpine
alpine: Pulling from library/nginx
b1f00a6a160c: Pull complete
b5441325f46d: Pull complete
049763556f13: Pull complete
555a8317e22d: Pull complete
Digest: sha256:4a97b863a4386ba588cd4f264582d1f306bc9da46fe3e02540bd171709ce09d7
Status: Downloaded newer image for nginx:alpine
```

　2. `docker image ls` で、イメージを確認します。

```
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               alpine              ea7bef82810a        29 hours ago        15.5MB
```

ここでは `nginx` イメージに `alpine` タグを指定して取得しました。タグは通常、何も指定しなければ `latest` となります。 `alpine` は [Alpine Linux](https://alpinelinux.org/) という最小で 4MB 程度の Linux ディストリビューションです。容量がとても小さいながらパッケージ管理ツール（apk）が導入されており、セキュリティも考慮されていることから、コンテナ用の OS として注目を集めています。

#### C-2 コンテナとホスト側との通信

　1. Nginx コンテナを `docker container run -d nginx:alpine` で実行します。

```
# docker container run -d nginx:alpine
4c977341985555599c98085defe9548fbc1e3abbeb1ba87c4c65db81334d32ba
```

新しいオプション `-d` はデタッチド(detach)・モードといい、バックグランドでコンテナ（のプロセス）を実行し、コンテナ ID を 画面に表示します。 `docker container ls` を実行すると、起動中だと分かります。

```
# docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
4c9773419855        nginx:alpine        "nginx -g 'daemon ..."   2 minutes ago       Up 2 minutes        80/tcp              peaceful_raman
```

しかし、このままでは Nginx のサイトをウェブ・ブラウザでは確認できません。確認できるようにするには、 `-P` オプションまたは `-p` オプションで、コンテナとホスト側のポートを割り当て（マッピング）する必要があります。

　2. ホスト側の空きポートを自動的に使ってコンテナを起動するには `-P` オプションをつけ、 `docker container run -d -P nginx:alpine` を実行します。

```
# docker container run -d -P nginx:alpine
de88fc4ba6860b8161a62d8938fb1eedc335b02df247bba763367fa3f8bf886e
```

　3. `docker container ls` コマンドで何番のポートを使っているか調べます。

```
# docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
de88fc4ba686        nginx:alpine        "nginx -g 'daemon ..."   59 seconds ago      Up 58 seconds       0.0.0.0:32768->80/tcp   modest_jackson
```

この例では 「PORTS」列に `0.0.0.0:32768->80/tcp` とあり、 `0.0.0.0` （ホスト側）のポート `32768` が、コンテナ内のポート `80` に `tcp` で接続しているのが分かります。

ブラウザから `http://<IPアドレス>:32768/` を入力すると、Nginx コンテナの応答状況を確認できます。

　4. 何度か `docker container run -d -P nginx:alpine` コマンドを実行し、複数の Nginx 環境が起動しているのを確認できます。

　5. ホスト側のポート 8080 をコンテナ内の 80 に割り当てるには `-p` オプションを使い、 `-p 8080:80` のように指定します。

```
# docker container run -d -p 8080:80 nginx:alpine
2c89576e996254da4d48c7d5b428da93b2e6fd6e2995363a86c604502c51b509
```

ブラウザから `http://IPアドレス:8080` にアクセスすると、Nginx の画面を確認できます。

なお、ホスト側では同じポートを重複して起動できません。起動を試みても次のようにエラーとなります。

```
# docker container run -d -p 8080:80 nginx:alpine
7643383ba268beeb1c7a80b24dc4c6ac6d0b2bb22856bce0fe2d81bbb5e3e218
docker: Error response from daemon: driver failed programming external connectivity on endpoint quizzical_pike (4b68fe9d1b741ac711190e89d1bffa181e0abdc95de7d34966556ba467db90c7): Bind for 0.0.0.0:8080 failed: port is already allocated.
```

既に割り当てられているコンテナを停止後は、再び割り当て可能です。

　6. `docker network ls` でネットワーク情報を確認します。

```
# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
401ef0b7bf29        bridge              bridge              local
924cd8e9ded7        host                host                local
5be81d2d1321        none                null                local
```

　7. `docker network inspect bridge | less`  を実行し、ネットワークの詳細設定を確認できます。

表示された内容から、IP アドレス（ `IPv4Address` の項目 ）を記録しておきます。例： `172.17.0.2`

　8. ホスト側から `ping` コマンドを実行し、疎通しているのを確認します。

```
# ping -c 5 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.088 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.102 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.096 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.077 ms
64 bytes from 172.17.0.2: icmp_seq=5 ttl=64 time=0.074 ms

--- 172.17.0.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4000ms
rtt min/avg/max/mdev = 0.074/0.087/0.102/0.013 ms
```

　9. 新しいブリッジ・ネットワーク `mynet` を作成します。

```
# docker network create --driver bridge \
    --subnet 192.168.10.0/24 \
    --gateway 192.168.10.1 \
    mynet
```

　10. `docker network ls` で、新しいネットワークが追加されているのを確認します。

```
# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
401ef0b7bf29        bridge              bridge              local
924cd8e9ded7        host                host                local
aba8e4b4f857        mynet               bridge              local
5be81d2d1321        none                null                local
```

　11.  `alpine` イメージを使い、新しく作成した `mynet` に接続するコンテナを作成します。

```
# docker run -it alpine /bin/sh
/ #
```

* `-it` は `-i` と `-t` オプションを同時に指定しています。
* `-i` は標準入力を受け付けます
* `-t` は疑似ターミナル(Pseudo-tty)を有効にします

　12. コンテナ内で `ping <先ほどのNginxのIPアドレス>` を実行します。

```
/ # ping -c 5 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.171 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.196 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.171 ms
64 bytes from 172.17.0.2: seq=3 ttl=64 time=0.172 ms
64 bytes from 172.17.0.2: seq=4 ttl=64 time=0.202 ms

--- 172.17.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.171/0.182/0.202 ms
```

ここで疎通しているのは、 `bridge` という名前の、始めからあるブリッジ・ネットワークに接続してコンテナが起動しているからです。

　13. `exit` で終了します。

```
# exit
```

　14. `mynet` ネットワークを指定してコンテナを起動します。

```
# docker run -it --net mynet alpine /bin/sh
/ #
```

　15. 再び `ping` コマンドで疎通を確認します。

```
# ping -c 5 192.17.0.2
PING 192.17.0.2 (192.17.0.2): 56 data bytes
^C
--- 192.17.0.2 ping statistics ---
5 packets transmitted, 0 packets received, 100% packet loss
```

応答がなくなるので `Ctrl+C` で処理を中断します。これは、このコンテナが先ほどの `bridge` ネットワークとは違う `mynet` ネットワークに接続しているからです。

```
/ # ip route
default via 192.168.10.1 dev eth0
192.168.10.0/24 dev eth0  src 192.168.10.2
```

　16. `exit` でコンテナを終了します。

```
# exit
```

このように、コンテナ起動時にネットワークを指定すると、コンテナ間でお互いに接続できない環境を作ることもできます。


#### C-3 コンテナとボリューム

コンテナとコンテナな間はディレクトリが独立（isolate）しているため、お互いのデータを参照できません。参照するためにはボリューム（volume）と呼ぶ特別なディレクトリを使う必要があります。

　1. ボリューム `myvolume` を `docker volume create myvolume` で作成します。

```
# docker volume create myvolume
myvolume
```

　2. ボリューム一覧を `docker volume ls`  で確認します。

```
# docker volume ls
DRIVER              VOLUME NAME
local               myvolume
```

　3. alpine コンテナ実行時に、 `-v` オプションを付け、 `myvolume` ボリュームをコンテナ内の `/data` に割り当てます。

```
# docker run -it -v myvolume:/data alpine /bin/sh
/ #
```

　4. コンテナ内で `df` コマンドを実行します。

```
/ # df
Filesystem           1K-blocks      Used Available Use% Mounted on
overlay               16446312   2753152  12838036  18% /
tmpfs                    65536         0     65536   0% /dev
tmpfs                   508136         0    508136   0% /sys/fs/cgroup
/dev/vda3             16446312   2753152  12838036  18% /data
（省略）
```

　5. `/data` ディレクトリに移動し、ファイルを作成します。

```
/ # cd /data/
/data # echo 'hello my world' > hello.txt
/data # cat hello.txt
hello my world
```

　6. `exit` でコンテナを終了します。

```
/ # exit
```

　7. 手順 3 ～6 を繰り返し、別の Alpine コンテナを実行しても、ディレクトリ内容のデータが保持されているのを確認します。

#### C-4 ボリュームを指定した Nginx コンテナ・PHP コンテナの起動

デフォルトの Nginx コンテナは、ドキュメント・ルートが `/usr/share/nginx/html/` ですが、ここにコンテンツをコピーする必要があります。ここでは、ボリュームのマウント機能を使い、 Nginx のドキュメントルートを指定します。

　1. Nginx コンテナに `myvolume` ボリュームをマウントして起動します。

```
# docker container run -d -v myvolume:/usr/share/nginx/html -p 8081:80 nginx:alpine
```

　2. ブラウザからポート `8081` を開きます。 `http://<IPアドレス>:8081/hello.txt` を開き、先ほど作成した `hello.txt` の内容が見えるかどうか確認します。

　3. 次は、ホスト側のディレクトリをボリュームとしてマウントする方法を試します。作業用ディレクトリを作成します。また、何らかのファイルを作成します。

```
$ cd
$ mkdir mydocroot && cd mydocroot
$ echo 'hello' > onhost.txt
```

　4. `-v ホスト側ディレクトリ:コンテナ側ディレクトリ` のオプションを使い、 nginx コンテナを起動します。

```
# docker container run -d -v `pwd`:/usr/share/nginx/html -p 8082:80 nginx:alpine
```

　5. ディレクトリ内のファイルを編集し、反映されるかどうかを確認します。

　6. 次は PHP のウェブサーバを動かします。

```
# docker run -d -v `pwd`:/var/www/html -p 8083:80 php:7.0-apache
```

　7. ブラウザから `http://<ip>:8083/onhost.txt` を表示します。


8. PHP のファイルを実行します。

サーバ上で `info.php` ファイルを作成します。

```
　# echo '<?php phpinfo(); ?>' > info.php
```

ブラウザから見えるかどうかを確認します。

このように、ボリュームを使えば、コンテナ間のデータ共有や、ホスト上のディレクトリをコンテナ内からも参照できます。


### D：WordPress を動かす複数のコンテナを swarm mode で動かす

ゴール：実際のサービスを動かす例として、swarm モード上で、サービスとして WordPress および MySQL を簡単に動かす方法を学びます。

#### D-1 swarm モードの初期化

　1. `docker swarm init` コマンドを実行します。

```
# docker swarm init
Swarm initialized: current node (3eszf2xqjh5qlmdlxjv3ew8r8) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-4bmfzudkx6y0btzyrcrp55wk167b1fj2zfxb9xxxkz3l0mmz8x-126epkruq47ug5tnc2ighkc4f 59.106.***.****:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
```

画面に出てくるコマンドは、複数の Docker Engine を１つのクラスタ（swarm）として扱うため、クラスタにワーカーとして追加するためのコマンドです（今回は使いません)。

　2. クラスタ状態の確認のため `docker node ls` を実行します。

```
# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
3eszf2xqjh5qlmdlxjv3ew8r8 *   docker              Ready               Active              Leader
```

#### D-2  swarm mode で WordPress を実行

WordPress を swarm モードで動かします。この基本動作を覚えることで、みなさんの PC 上で作った環境も、インターネット側の環境に、そのままデプロイ可能となります。

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

```
$ cd
$ mkdir wordpress && cd wordpress
```

　2. 以下のコマンドを実行し、 `docker-compose.yml' ファイルを作ります。

```
# cat << 'EOF' > docker-compose.yml
version: '3'

services:

  wordpress:
    image: wordpress
    depends_on:
      - mysql
    ports:
      - 80:80
    volumes:
      - ./data/var/www/html:/var/www/html
    environment:
      WORDPRESS_DB_PASSWORD: example

  mysql:
    image: mysql:5.7
    volumes:
      - ./data/var/lib/mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: example
EOF
```

　3. データ保存用ディレクトリを作成します。

```
# mkdir -p ./data/var/www/html
# mkdir -p ./data/var/lib/mysql
```

　4. `docker stack deploy` コマンドで、WordPress を起動します。

```
# docker stack deploy -c ./docker-compose.yml wordpress
Creating service wordpress_wordpress
Creating service wordpress_mysql
```

　5. `watch` コマンドでサービスの状態を確認します。

```
# watch -n 1 'docker stack ps wordpress'
```

毎秒情報が表示されます。 `DESIRED STATE` （望ましい状態）が `Running` になるまで待ちます。

```
ID                  NAME                    IMAGE               NODE                DESIRED STATE
    CURRENT STATE            ERROR               PORTS
2k2uz809j7fy        wordpress_wordpress.1   wordpress:latest    docker              Running
    Running 14 seconds ago
g9sw449yug8t        wordpress_mysql.1       mysql:5.7           docker              Running
    Running 17 seconds ago
```

ブラウザから `http://<ipアドレス>/` にアクセスし、WordPress の設定画面が開かれるのを確認します。

　6. 停止・終了は次のコマンドを実行します。

```
# docker stack rm wordpress
Removing service wordpress_mysql
Removing service wordpress_wordpress
Removing network wordpress_default
```

なお、データは `./data` 以下に残っていますので、再び `docker stack deploy` コマンドを実行すると、 WordPress の環境を再開できます。