Docker Meetup Kansai 19.06 ハンズオン手順 

# 概要

この手順は 2019 年 6 月 29 日（土）開催の [Docker Meetup Kansai Hands-on Training 19.06 ](https://dockerkansai.connpass.com/event/135523/) における、開発環境構築や Docker、Docker Hub を利用するハンズオン向けです。サンプルのウェブアプリケーション、Nginx、WordPress(PHP+MySQL)を例にして Docker コンテナのネットワークとボリュームについて学びます。ハンズオンを通し、一般的な開発に必要なスキルの習得を目指します。

# 1. さくらのクラウド仮想マシン環境構築

## 目的

- 開発環境の土台となる Linux の仮想マシンをクラウド上に作成、起動し、ログインできるように準備します。
- Docker イメージ保管用の Docker Hub にアカウントを作成します。

## 概要

- クラウド・コンピューティング（Cloud Computing）とは、「共用計算資源（コンピューティング・リソース）の集まり、たとえば、ネットワーク、サーバ、ストレージ、アプリケーション、サービスに対して、どこからでも簡単に、必要に応じて、インターネットのようなネットワーク経由で接続可能にするモデル」（[NISTによる定義](https://csrc.nist.gov/publications/detail/sp/800-145/final), [IPA訳のNIST定義](https://www.ipa.go.jp/files/000025366.pdf) を参考）です。
- VPS（Virtual Private Server）、すなわち仮想マシン（サーバ）とは、ハードウェアを仮想化する技術を用います。１つの（物理的な）マシン上に複数の仮想マシン環境を作ることができます。
- クラウド上の仮想マシンとVPSとの違いは、利用目的や期間です。VPSであれば、物理サーバのように長期的に安定して実行し続けます。一方のクラウド上の仮想マシン（VM）の場合は、VPSのように使うこともできますし、必要に応じて短時間で使い捨て型としての利用も可能であり、短期間でのスケールアップ・ダウンも可能です。

## 手順

※補足：今回は「さくらのクラウド」の仮想マシンを使いますが、]VirtualBox](https://www.virtualbox.org/) や [Vagrant](https://www.vagrantup.com/) でローカル PC 上でも仮想マシンを作成、実行することもできます。

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

さくらのクラウド・コントロールパネル： https://secure.sakura.ad.jp/cloud/

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

### 1.2. 仮想サーバ（マシン）作成と起動

ログイン直後のクラウド環境上には、サーバも含めた一切の計算資源はありません。今回、開発環境向けとして、Docker （ドッカー）を使うための仮想サーバを準備します。

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


### 1.3. 仮想サーバに 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アドレス>」を実行します

```console
$ 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 ~]#
```

### 1.4. Docker Hub アカウントの作成

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

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

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

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


# 2. 【Docker入門】Docker セットアップと、初めての Docker コンテナ実行

## 目的

- Docker の動作環境を Linux 上に構築する手順を理解し、 `docker` コマンドでの操作に慣れていきます。

## 手順

### 2.1. Docker Engine のセットアップ

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

```console
# 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（dockerデーモン）を起動します。Docker Engine を起動するには `systemctl start docker` を実行、 `systemctl status docker` で起動状態を確認します。

```console
# 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 金 2019-06-21 14:14:42 JST; 3s ago
     Docs: https://docs.docker.com
 Main PID: 19613 (dockerd)
    Tasks: 12
   Memory: 31.7M
   CGroup: /system.slice/docker.service
           mq19613 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
(省略）
```

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

```console
# docker version
Client:
 Version:           18.09.6
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        481bc77156
 Built:             Sat May  4 02:34:58 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.6
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       481bc77
  Built:            Sat May  4 02:02:43 2019
  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 を起動するように設定します。

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

### 2.2. `Hello World` コンテナの実行

`hello-world` コンテナを実行しながら、基本的な挙動を見ていきましょう。hello-world コンテナは画面上に「Hello World」を表示するだけの、アセンブリ言語で書かれたプログラムが入っています。

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

```console
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
```

このように、「REPOSITORY」などの行しか表示されず、インストール直後は Docker イメージが１つもありません。

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

```console
# docker run hello-world
```

　3. この時、画面上で Docker がどのような処理を行ったのか確認します。

```console
# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:41a65640635299bab090f783209c1e3a3f11934cf7756b09cb2f1e02147c6ed8
Status: Downloaded newer image for hello-world:latest
```

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

* イメージ `hello-world:latest` （リポジトリ名 `hello-world` の、 `latest` タグを持つもの）がローカルに無い
* リポジトリ `library/hello-world` から、 `latest` （最新）のタグを持つイメージを取得（ `pull` ）
* イメージ・レイヤ `5b0f327be733` のダウンロード完了
* ダウンロードしたイメージのハッシュ値は `41a65640635299bab090f783209c1e3a3f11934cf7756b09cb2f1e02147c6ed8`
* 状態（ `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.
    (amd64)
 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://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

```


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

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

```console
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
8e90d1973f23        hello-world         "/hello"            2 minutes ago       Exited (0) 2 minutes ago                       amazing_gates
```

ここでは、コンテナ ID `8e90d1973f23` で、イメージに `hello-world` を使ったものが、2分前に作成され、ステータス（状態）が `Exited` になっているのがわかります。

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

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


　5. 今度は `docker ps` コマンドを実行します。


```console
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

このように、`docker ps` は「実行中」のコンテナしか表示しません。`docker ps -a` は `-a` オプション (all) があるので、停止中のコンテナを含めたすべてを表示します。


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


```console
# docker 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` で２つめ以降のコンテナが実行されたのを確認します。

```console
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
399573d69055        hello-world         "/hello"            3 seconds ago       Exited (0) 2 seconds ago                        gallant_sutherland
8e90d1973f23        hello-world         "/hello"            10 minutes ago      Exited (0) 10 minutes ago                       amazing_gates
```

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

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

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

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

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

Total reclaimed space: 0B
```

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

```console
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

### 2.3. 様々な Linux ディストリビューションのコンテナを実行

　1. CentOS のコンテナを起動します。

```console
# docker run -it centos:7 /bin/bash
Unable to find image 'centos:7' locally
7: Pulling from library/centos
8ba884070f61: Pull complete
Digest: sha256:b5e66c4651870a1ad435cd75922fe2cb943c9e973a9673822d1414824a1d0475
Status: Downloaded newer image for centos:7
[root@2f3c37eb6e00 /]#

```

　2. `ps ax` や `pwd` や `ls -al /` などのコマンドを実行します。何か気付きましたか？

　3. `exit` を入力してコンテナを終了します。


同様の手順で、他の Linux コンテナを起動します。

- `ubuntu:18.04`
- `debian:9`
- `alpine:latest`



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

## 目的

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

## 手順

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

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

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

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

```console
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker/whalesay     latest              6b362a9f73eb        4 years ago         247MB
```

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


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

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

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

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

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

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

### 3.2. Dockerfile の利用～もっと賢そうな鯨のイメージを構築

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

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

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

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

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

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

　2. Dockerfile を作成します。

```console
# 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` の内容を確認します。

```console
# 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 build -t mywhale .` コマンドでイメージを自動構築します。

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

```console
# docker 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 images` コマンドで `mywhale` イメージが作成されているのを確認します。

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

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

```console
# docker 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. 新しく作成したイメージで、コンテナを実行します。

```console
# docker 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          __/
        \    \        __/
          \____\______/
```

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


### 3.3. Docker Hub に送信

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

　1. `docker login` で Docker Hub と通信し、認証トークンを取得します。

```console
# 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. 認証情報のファイルを確認します。

```console
# 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. イメージを送信する前に、イメージにタグを付けます。

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

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

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

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

```console
# docker images
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）します。

```console
# 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. ローカルのイメージを削除します。

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

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

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

```console
# docker rmi 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` でイメージが存在していないのを確認します。

```console
# 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 run <ユーザ名>/mywhale` で実行します。

```console
# docker 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` でログアウトします。

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

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

## 4.1. Alpine Linux 対応 Nginx イメージのダウンロード

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

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

```console
# docker 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 images` で、イメージを確認します。

```console
# docker images
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 として注目を集めています。


## 4.2. コンテナとホスト側との通信


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

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

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

```console
# docker ps
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 run -d -P nginx:alpine` を実行します。

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

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

```console
# docker ps
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 run -d -P nginx:alpine` コマンドを実行し、複数の Nginx 環境が起動しているのを確認できます。

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

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

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

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

```console
# docker 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` でネットワーク情報を確認します。

```console
# 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` コマンドを実行し、疎通しているのを確認します。

```console
# 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` を作成します。

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

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

```console
# 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` に接続するコンテナを作成します。

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

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

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

```console
/ # 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` で終了します。

```console
# exit
```

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

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

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

```console
# 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` ネットワークに接続しているからです。

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

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

```console
# exit
```

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

## 4.3. コンテナとボリューム

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

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

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

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

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

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

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

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

```console
/ # 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` ディレクトリに移動し、ファイルを作成します。

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

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

```console
/ # exit
```

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


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

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

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

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

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

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

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

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


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

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

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

```console
# 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` ファイルを作成します。

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

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

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


# 5. Docker Compose

## 5.1. Docker compose のセットアップ

```console
# yum install docker-compose
```

## 5.2. WordPress


```console
# mkdir mywp
# cd mywp
# vi docker-compose.yml
```


```yaml
version: '3.1'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
```


```console
# docker-compose up -d
```


# 参考

- Docker ドキュメント https://docs.docker.com
- 今日使用したスライド等
    - [Docker Compose 徹底解説](https://www.slideshare.net/zembutsu/docker-compose-guidebook)
    - [Dockerfile を書くためのベストプラクティス解説編
](https://www.slideshare.net/zembutsu/explaining-best-practices-for-writing-dockerfiles)
    - [Dockerfileを改善するためのBest Practice 2019年版
](https://www.slideshare.net/zembutsu/dockerfile-bestpractices-19-and-advice)