今更Docker入門 - コンテナ化することで何が嬉しいか

Dockerは仮想技術の一つ程度しか知らいない状態から、JUG CCC 2018 Spring で「JavaエンジニアのためのDocker入門 ～ 仮想開発・テスト環境構築 ～ 」を聞いて少し勉強してみたのでDockerで環境を作ってみました。

# 環境
* macOS
* Docker for Mac Version 18.03.1-ce-mac65 ([Docker](https://docs.docker.com/))

# 開発時の起こり得る環境差問題
* 自分の環境では動く
* 他のメンバーの環境では動かない

こういった問題の要因として

* 開発機はwindowsだけど、検証・本番環境はLinuxでOSの問題で動かなくなったり
* DBや言語のバージョンが違う
* Webコンテナのバージョン・設定が違う
* etc...

などなど人によって環境がバラバラなことがあるかと。例えば、自分はMySQL5.7で開発しているが、他の人はMySQL5.6で開発してしている。こういったバージョンの違いで生まれる不具合があったり、configを自分専用にカスタマイズしていたりなどなどあるかと思います。そんな状況をDockerで解決につながる。インフラ環境をgitとかで管理して、cloneしてdockerをコマンド一つ実行するだけで皆同じ環境になる。すばらしい  
だけどいきなり、ほぼほどDockerについて知らない人しかいない状況だと導入は難しいので、参考になればと簡単な使い方と環境を作ってみました。

# Docker基礎
## Dockerとは
コンテナ技術を使ったプラットフォーム  
virtualboxなどで仮想環境を構築した場合は、ホストOSの上にゲストOSを新たに立ち上げるので重いしリソースも食われる。
Dockerのようなコンテナの場合は、ゲストOSがホストOSを使うため、オーバーヘッドが小さく高速に動作し起動も早い。

* イメージ  
仮想化するOSやアプリの雛形が[Docker hub](https://hub.docker.com/)で公開されており、準備が楽
* イメージ作成設定  
仮想化するOSやアプリの設定やコマンドをDockerfileに記述できる。

## コンテナとイメージとDockerfile
[冷凍チャーハン](https://qiita.com/rawHam/items/80ba13d2d2d56dba411e)の例がわかりやすい。  

* コンテナ  
Dockerで仮想化したOSやアプリが実行されている環境
* イメージ  
仮想化するOSやアプリの雛形  
[Docker hub](https://hub.docker.com/)で公開されており、準備が楽
* Dockerfile  
イメージを作成するための設定やコマンドを記述するファイル  
[Docker hub](https://hub.docker.com/)にもDockerfileは公開されているので勉強の参考になる。  
Dockerfileがあれば、イメージ・コンテナの作成が簡単にできるので、イメージ・コンテナは使い捨てにできる。  

## Dockerコマンド
### イメージの取得  
Docker hub からイメージを取得  

```
$ docker pull NAME
```

例としてnginxを取得してみる。

```
$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
Digest: sha256:0fb320e2a1b1620b4905facb3447e3d84ad36da0b2c8aa8fe3a5a81d1187b884
Status: Image is up to date for nginx:latest

```

### イメージの一覧表示  
Docker hub から取得したイメージやDockerfileで作成したイメージの一覧を表示

```
$ docker images 
```

pullで取得したイメージを見てみる。

```
$ docker images
REPOSITORY                                      TAG                 IMAGE ID            CREATED             SIZE
nginx                                           latest              ae513a47849c        4 weeks ago         109MB
```

### イメージからコンテナ起動

```
$ docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
```

例としてnginxを起動してみる。

```
$ docker run --name my-nginx -d -p 8080:80 nginx:latest
```
コマンド実行後、http://127.0.0.1:8080/ かhttp://localhost:8080/ にアクセスするとWelcome to nginx!が表示される。
外部からコンテナにアクセスするにはポートフォワードを使うので、コマンドオプションの``-p 8080:80``は、ポート8080でアクセスすると、nginxのポート80にフォワードするということになる。

### 起動中コンテナの一覧表示

```
$ docker ps
```
例としてnginxを起動してpsしてみる。

```
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
b213816ff7e1        nginx:latest        "nginx -g 'daemon of…"   4 minutes ago       Up 4 minutes        0.0.0.0:8080->80/tcp   my-nginx
```

### コンテナ停止
起動中のコンテナを停止させる。

```
$ docker stop CONTAINERID
```

例としてnginxを起動して停止してみる。

```
$ docker stop b213816ff7e1
```
停止したコンテナは、`docker ps -a` で一覧で見れる。

```
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
b213816ff7e1        nginx:latest        "nginx -g 'daemon of…"   13 minutes ago      Exited (0) 3 minutes ago                       my-nginx
```

### コンテナの再起動
停止させたコンテナを起動させる。

```
$ docker start CONTAINERID
```

例として停止させたnginxを起動してみる。

```
$ docker start b213816ff7e1
```

# Dockerfileを使ったビルド
Dockerfileを使ってnginxとMySQLを独自の設定を追加してビルドしてみる。
構成は以下の感じ

```
.
├── mysql
│   ├── Dockerfile
│   └── config
│       └── my.cnf
└── nginx
    ├── Dockerfile
    ├── config
    │   └── default.conf
    └── html
        └── hello.html
```

nginxから

```Dockerfile:./nginx/Dockerfile
FROM nginx:1.14.0

COPY ./config/default.conf /etc/nginx/conf.d/default.conf

COPY ./html /usr/share/nginx/html
```

```conf:./nginx/config/default.conf
server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
```

```HTML:./nginx/html/hello.html
<!DOCTYPE html>
<html>
<body>
<h1>Hello!</h1>
</body>
</html>
```

``FROM``は、ベースとなるイメージを指定します。今回は、nginxのタグが1.14.0をベースイメージという意味になります。
``COPY``で、指定したファイル・ディレクトリをコンテナにコピーしてくれます。1個目のCOPYでは、``./config/default.conf``をコンテナ内にある``/etc/nginx/conf.d/default.conf``に上書きしてます。2個目は、``./html``ディレクトリを``/usr/share/nginx/html``にコピーしてます。

次にMySQL

```Dockerfile:./mysql/Dockerfile
FROM mysql:5.7

EXPOSE 3306

COPY ./config/my.cnf /etc/mysql/conf.d/my.cnf

CMD ["mysqld"]
```

```cnf:./mysql/config/my.cnf
[mysqld]
character-set-server=utf8

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8
```

MySQLのDockerfileでは、``EXPOSE``で外部に公開するポートを指定してます。```CMD```では、指定したコマンドを``docker run``したときに実行してくれます。

これで雛形ができたので、イメージを作成するためにDockerfileを記述したあとは``docker build``コマンドを実行する必要がある。  

nginxからビルドする

```
$ cd ./nginx
$ docker build -t hello-nginx .
```

``docker build``を実行すると以下のメッセージと共にhello-nginxイメージが作成されます。

```
Sending build context to Docker daemon   5.12kB
Step 1/3 : FROM nginx:1.14.0
 ---> f759510436c8
Step 2/3 : COPY ./config/default.conf /etc/nginx/conf.d/default.conf
 ---> 5a2a0f48dc8d
Step 3/3 : COPY ./html /usr/share/nginx/html
 ---> 68797bf4b521
Successfully built 68797bf4b521
Successfully tagged hello-nginx:latest
```

作成したhello-nginxイメージを以下のコマンドで実行してみてブラウザで[http://localhost:8080/hello.html](http://localhost:8080/hello.html)にアクセスしてみてHelloが見れれば正常に動作してます。

```
$ docker run --name hello-nginx -d -p 8080:80 hello-nginx
```

MySQLも同様にビルドする

```
$ cd ../mysql/
$ docker build -t hello-mysql .
Sending build context to Docker daemon  3.584kB
Step 1/4 : FROM mysql:5.7
5.7: Pulling from library/mysql
f2aa67a397c4: Already exists 
1accf44cb7e0: Already exists 
2d830ea9fa68: Already exists 
740584693b89: Already exists 
4d620357ec48: Already exists 
ac3b7158d73d: Already exists 
a48d784ee503: Already exists 
f122eadb2640: Pull complete 
3df40c552a96: Pull complete 
da7d77a8ed28: Pull complete 
f03c5af3b206: Pull complete 
54dd1949fa0f: Pull complete 
Digest: sha256:d60c13a2bfdbbeb9cf1c84fd3cb0a1577b2bbaeec11e44bf345f4da90586e9e1
Status: Downloaded newer image for mysql:5.7
 ---> a8a59477268d
Step 2/4 : EXPOSE 3306
 ---> Running in 4138c3707cad
Removing intermediate container 4138c3707cad
 ---> 5bfedb5fc6c5
Step 3/4 : COPY ./config/my.cnf /etc/mysql/conf.d/my.cnf
 ---> 99d665b6cd01
Step 4/4 : CMD ["mysqld"]
 ---> Running in 1d70da245b37
Removing intermediate container 1d70da245b37
 ---> 8f1c4df89fc8
Successfully built 8f1c4df89fc8
Successfully tagged hello-mysql:latest
```

以下のコマンドを実行してMySQLも実行する。mysqlコマンドなどで接続できれば正常に起動している。

```
$ docker run --name hello-mysql -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 hello-mysql
$ mysql -u root -h 127.0.0.1 -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.22 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
```

DockerのMySQLに接続するときのホストが127.0.0.1なのがミソ。


この状態で、``docker ps``をしてみる。

```
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
e25517d43ede        hello-mysql         "docker-entrypoint.s…"   4 minutes ago       Up 4 minutes        0.0.0.0:3306->3306/tcp   hello-mysql
74b955bfe213        hello-nginx         "nginx -g 'daemon of…"   17 minutes ago      Up 17 minutes       0.0.0.0:8080->80/tcp     hello-nginx
```

# Dockerのホスト
まだまだ勉強不足であまりわかっていませんが・・・
Dockerコンテナが公開しているポートに対し、通常はループバック・アドレス(127.0.0.1)を用いて通信します。
なのでnginxやMySQLに接続する際に、ホストに127.0.0.1を使っています。
また、Linux（Mac）の場合、（多分）デフォルトで``/etc/hosts``に以下のような設定があるので localhostでも見れます。

```:/etc/hosts
127.0.0.1       localhost
```

# Docker compose
``docker run``で一つ一つ起動してもちゃんと動いているいますが、起動するコンテナが増えると``docker run``では辛くメリットがあまりないです。さらに複数のコンテナを使って互いに連携したり依存関係がある場合、起動順番も考慮が必要になってきます。そこで便利なのが``docker-compose``です。
Docker composeは、簡単に言うと、複数のコンテナをまとめて管理してくれるツールです。Docker Compose を使うと複数のコンテナ設定や依存関係をdocker-compose.ymlという設定ファイルに記述し、コマンド一つで複数コンテナを一気に起動できます。


# Docker composeを使って複数コンテナを起動
先程のnginxとMySQLをDocker compose を使ってコマンド一つで起動してみます。
構成はこんな感じ

```
.
├── docker-compose.yml
├── mysql
│   ├── Dockerfile
│   └── config
│       └── my.cnf
└── nginx
    ├── Dockerfile
    ├── config
    │   └── default.conf
    └── html
        └── hello.html
```

docker-compose.ymlが、増えただけです。  
変更、追加したのは以下の2ファイルだけです。

```yml:docker-compose.yml
version: "3.3"

services:
  nginx:
    build: ./nginx
    volumes:
      - ./nginx/log:/var/log/nginx
      - ./nginx/html:/usr/share/nginx/html
    ports:
      - 8080:80

  mysql:
    build: ./mysql
    environment:
      - MYSQL_ROOT_PASSWORD=$MYSQL_DATABASE_PASSWORD
    volumes:
      - ./mysql/data:/var/lib/mysql
    ports:
      - 3306:3306

```
```Dockerfile:./mginx/Dockerfile
FROM nginx:1.14.0

COPY ./config/default.conf /etc/nginx/conf.d/default.conf
```

まずは、docker-compose.ymlから

* version
    + バージョン指定
* services
    + コンテナやイメージを定義
* build
    + 指定ディレクトリ内にあるDockerfileを使ってコンテナを作る
* volumes
    + xxx:yyy形式で記述
    + nginxを例に、ホストの./nginx/logディレクトリにコンテナの/var/log/nginxディレクトリをマウントしてくれる
    + これで、nginxのログファイルが、./nginx/logに吐き出されます
    + volumesを指定しない場合、コンテナを削除したときに、データが消えてしまうので、永続化したいデータはマウントすることがおすすめ(dbのdataとか)
* environment
    + ``docker run``で指定したオプション``-e``に当たる
    + mysqlを例に、MYSQL_ROOT_PASSWORD=$MYSQL_DATABASE_PASSWORDを環境変数として設定してくれる
    + $MYSQL_DATABASE_PASSWORDはホストの環境変数
* ports
    + ``docker run``で指定したオプション``-p``に当たる
    + ポートフォワーディングの設定

./mginx/Dockerfileはhtmlディレクトリをコピーではなくマウントするように変更しただけ


あとは、このdocker-compose.ymlのあるディレクトリでビルドと実行コマンドを叩けば、nginxとmysqlが立ち上がります。

まずは、ビルド

```
$ docker-compose build
Building nginx
Step 1/2 : FROM nginx:1.14.0
 ---> f759510436c8
Step 2/2 : COPY ./config/default.conf /etc/nginx/conf.d/default.conf
 ---> Using cache
 ---> 5fd32d08feb1

Successfully built 5fd32d08feb1
Successfully tagged hello-docker_nginx:latest
Building mysql
Step 1/4 : FROM mysql:5.7
 ---> 0d16d0a97dd1
Step 2/4 : EXPOSE 3306
 ---> Using cache
 ---> de0db8091aa0
Step 3/4 : COPY ./config/my.cnf /etc/mysql/conf.d/my.cnf
 ---> Using cache
 ---> 54afd7dceec3
Step 4/4 : CMD ["mysqld"]
 ---> Using cache
 ---> 6185c4750d81

Successfully built 6185c4750d81
Successfully tagged hello-docker_mysql:latest
```

Successfullyで成功したので、あとは実行

```
$ docker-compose up -d
Creating network "hello-docker_default" with the default driver
Creating hello-docker_mysql_1 ... done
Creating hello-docker_nginx_1 ... done
$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED                  STATUS              PORTS                    NAMES
64a6bb25044e        hello-docker_nginx   "nginx -g 'daemon of…"   Less than a second ago   Up 18 seconds       0.0.0.0:8080->80/tcp     hello-docker_nginx_1
e61fdd163f3b        hello-docker_mysql   "docker-entrypoint.s…"   Less than a second ago   Up 18 seconds       0.0.0.0:3306->3306/tcp   hello-docker_mysql_1
```

``docker ps``で見たところ動いているようなので、あとは動作確認。
nginxから動作を見てみます。http://127.0.0.1:8080/ かhttp://localhost:8080/ にアクセスすると403 Forbiddenになっていると思います。
これは、docker-compose.ymlで``./nginx/html:/usr/share/nginx/html``を指定しているからです。今回はコピーしないで、直接ホストのディレクトリをマウントしているため、index.htmlが無く起きています。

```html:./nginx/html/index.html
<!DOCTYPE html>
<html>
<body>
<h1>index Hello!</h1>
</body>
</html>
```

を作成して更新すると見れるはずです。http://localhost:8080/hello.html も見れれば正常に動いていそうです。

次にMySQL

```
$ mysql -u root -p -h 127.0.0.1 -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.22 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
```

パスワードは、環境変数MYSQL_DATABASE_PASSWORDです。接続できたので、正常に動いていそうです。


最後に、コンテナを終了させます。方法は2つあります。
1つ目  

```
$ docker-compose stop
Stopping hello-docker_nginx_1 ... done
Stopping hello-docker_mysql_1 ... done
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ docker ps -a
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                      PORTS               NAMES
64a6bb25044e        hello-docker_nginx   "nginx -g 'daemon of…"   10 minutes ago      Exited (0) 17 seconds ago                       hello-docker_nginx_1
e61fdd163f3b        hello-docker_mysql   "docker-entrypoint.s…"   10 minutes ago      Exited (0) 15 seconds ago                       hello-docker_mysql_1 
```
正常に止まっているようです。

では、2つ目

```
$ docker-compose down
Stopping hello-docker_nginx_1 ... done
Stopping hello-docker_mysql_1 ... done
Removing hello-docker_nginx_1 ... done
Removing hello-docker_mysql_1 ... done
Removing network hello-docker_default
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

結果が、``stop``と``down``では異なります。``stop``の場合は、終了したコンテナが残ります。``down``の場合は、コンテナを終了させたあとにコンテナを削除してくれます。なので、``docker ps -a``で見ても当たり前ですが残りません。
コンテナを使い捨ての環境として使うのであれば、``down``を使うべきと思います。基本的にはコンテナは使い捨てとして使うべきと思うので、必要なものはvolumesでマウントして、コンテナは、毎回削除するほうがいいと私は思います。

これで、コマンド一つで起動・停止もできるようになったので、あとは開発時に必要に応じてカスタマイズして、docker-compose.ymlやDockerfileを共有して実行すれば、同じ環境を作れるようになります。便利

作成したサンプルは[git](https://github.com/kmiura-1002/hello-docker)にあります。参考までに。。。