docker と docker-compose の初歩

dockerとdocker-composeの基本が理解できるような記事にしたい。

技術系の記事投稿先を自前のBlogに移しました。初心者向け,AWSなど幅広いTechジャンルで記事投稿がんばってますので、よろしかったらこちらも参照ください！

https://hiyuzawa.jp/


# 準備
[docker for mac](https://www.docker.com/docker-mac) を利用します。
インストールはおそらく簡単だと思います。うまくインストールされればメニューバーにクジラが現れ "Docker is running" となるはずです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/36803/bd21b2cf-722a-18dd-4929-d4346bdbd1cb.png" width=30%>
docker および docker-compose コマンドも合わせてインストールされているはずです

* docker

```console
$ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.5
 Git commit:   9ee9f40
 Built:        Thu Apr 26 07:13:02 2018
 OS/Arch:      darwin/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.5
  Git commit:   9ee9f40
  Built:        Thu Apr 26 07:22:38 2018
  OS/Arch:      linux/amd64
  Experimental: true
```

* docker-compose 

```console
$ docker-compose version
docker-compose version 1.21.1, build 5a3f1a3
docker-py version: 3.3.0
CPython version: 3.6.4
OpenSSL version: OpenSSL 1.0.2o  27 Mar 2018
```

# docker

[Docker Hub](https://hub.docker.com/)というdockerで利用できる様々なサーバイメージが保存されている。
主に使い方としては

* 利用したいものをそのまま利用する (Databaseやkvsなどすでにアプリケーションが機能として盛り込まれたイメージもたくさんある)
* 利用したい環境の土台となるイメージを利用し、それをカスタマイズして自分用に追加で機能が盛り込まれたイメージ作成し利用する

## お試し起動(コンテナの起動と停止)

まずは docker の基本を抑えたいので、[CentOS のOSがインストールされたシンプルなイメージ](https://hub.docker.com/_/centos/)を利用してみる。

```console
$ docker run centos
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
469cfcc7a4b3: Pull complete
Digest: sha256:989b936d56b1ace20ddf855a301741e52abca38286382cba7f44443210e96d16
Status: Downloaded newer image for centos:latest

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
9a9ebeb47eb4        centos              "/bin/bash"         7 seconds ago       Exited (0) 10 seconds ago                       boring_hamilton
```

３つコマンドを実行した。 
まず `docker run centos` で centos というイメージを起動するというコマンドを実行。
ローカルに指定のイメージがある場合はそこから起動、ない場合は前述のDocker Hubからダウンロードして起動する



次に `docker ps`。 このコマンドで実行中のdocker(コンテナ)の一覧が表示される。が、何も表示されないはず。

最後に `docker ps -a`。 -a オプションをつけることで終了したコンテナも含めて一覧される。ここに最初に起動したコンテナが終了済みとしてリストされるはず。 終了済みのコンテナに関しても再度復帰できるので停止状態のコンテナとして残り続ける。マシンのリソース(HDD)を消費してしまうので、再び起動しないならば、以下のコマンドで削除するとよい。

```console
$ docker rm 9a9ebeb47eb4
9a9ebeb47eb4

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

ここで `9a9ebeb47eb4` というあたいはコンテナIDと呼ばれ、特定のコンテナを指し示すIDである。よって、各環境でこれは異なるはずなので `docker ps (-a)` を実行した時に表示されるIDを利用する。 (NAMEを指定してわかりやすいラベルでコンテナを特定することも可能)

### Tips:コンテナIDにラベルをつける

```console
# name をコンテナに付与して操作する例

$ docker run --name mycentos centos
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
4f2b3e7b91b5        centos              "/bin/bash"         3 seconds ago       Exited (0) 7 seconds ago                       mycentos
$ docker rm mycentos
mycentos
```

## コンテナ内での操作

centos イメージで起動したはずが即時終了してしまいこれでは意味がない。これは ps で見た時に確認できるが `COMMAND = "/bin/bash"` とあり、centosを起動し `/bin/bash`を実行 (この最初に起動するコマンドはイメージに仕込まれている)、それが即時（TTYがないので) `exit(0)` されたのでコンテナも終了した状態となっている。 

コンテナを起動しつつ、操作可能にするには `docke run　` に　`-it` オプションを付与して起動すればよい

```console
$ docker run -it centos

[root@c7827f28ee8d /]#
[root@c7827f28ee8d /]# ls  /
anaconda-post.log  bin  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@c7827f28ee8d /]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
[root@c7827f28ee8d /]# exit
exit

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
c7827f28ee8d        centos              "/bin/bash"         5 minutes ago       Exited (0) 15 seconds ago                       suspicious_liskov
```

`-it` を付与して起動したらプロンプトが変化する。コンソールはコンテナの中を操作する状態になる。 centos 環境にログインして操作しているのと同様の操作が可能でになる。作業を `exit` で終了すると、mac側に制御が戻り、またコンテナは停止の状態となる。

### Tips:Docker Imageの選択方法とImageの管理

今回利用したimageは centos であり初回利用時に Docker Hub からダウンロードしたと前述した。そのimageを確認するには `docker images`　コマンドを利用する

```console
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              e934aafc2206        7 weeks ago         199MB
```
イメージは REPOSITORY:TAGで一意に識別される。今回 `centos` とのみ指定してるがそれは `centos:latest` でありそれは [Docker Hub](https://hub.docker.com/_/centos/)に記載されている通り centos7 を意味する。 centos6を利用したい場合は以下のようにTAGでそれを指定すればよい。以下はcentos6を選択した場合の操作になる。

```console
$ docker run -it centos:6
Unable to find image 'centos:6' locally
6: Pulling from library/centos
987d765a926d: Pull complete
Digest: sha256:67b491e26d566ee9c55578bfd6115554a6e1b805a49502ead32cb1a324466f2c
Status: Downloaded newer image for centos:6

[root@01a74d994685 /]# cat /etc/redhat-release
CentOS release 6.9 (Final)
[root@01a74d994685 /]# exit
exit

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              6                   70b5d81549ec        7 weeks ago         195MB
centos              latest              e934aafc2206        7 weeks ago         199MB
```

不要なimage もリソースの無駄なので削除することも可能。その場合は `docker rmi` コマンドを利用する。

```console
$ docker rmi 70b5d81549ec
Error response from daemon: conflict: unable to delete 70b5d81549ec (must be forced) - image is being used by stopped container 01a74d994685
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
01a74d994685        centos:6            "/bin/bash"         2 minutes ago       Exited (0) 2 minutes ago                        nifty_swanson
c7827f28ee8d        centos              "/bin/bash"         20 minutes ago      Exited (0) 14 minutes ago                       suspicious_liskov
$ docker rm 01a74d994685
01a74d994685
$ docker rmi 70b5d81549ec
Untagged: centos:6
Untagged: centos@sha256:67b491e26d566ee9c55578bfd6115554a6e1b805a49502ead32cb1a324466f2c
Deleted: sha256:70b5d81549ec19aa0a10f8660ba5e1ab9966008dbb1b6c5af3d0ecc8cff88eef
Deleted: sha256:e0dec291ae94d0f486c2a6a98732bac23e45b0914f8f9e67c57c5e6a2cd7fed7
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              e934aafc2206        7 weeks ago         199MB
```

イメージの削除は`docker images`で表示されるイメージIDを指定する。上の例では `docker rmi` で centos6 のイメージの削除を試みている。 しかし `Error` となり削除が失敗していることが確認できる。これは停止中のコンテナが利用しているimageのためである。停止中のコンテナを削除してからイメージの削除した後、再び実行して正常に削除されていることが確認できる。

## コンテナへのアタッチとデタッチ

さて、手順通り進めた場合、現在のコンテナの状況はcentos7のコンテナが停止中というステータスだと思う。

```console
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
c7827f28ee8d        centos              "/bin/bash"         25 minutes ago      Exited (0) 20 minutes ago                       suspicious_liskov
```

これを再び起動するには `docker restart` を実行する

```console
$ docker restart c7827f28ee8d
c7827f28ee8d
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c7827f28ee8d        centos              "/bin/bash"         27 minutes ago      Up 21 seconds                           suspicious_liskov
$
```

ステータスが Up と表示され起動中に再びなったことがわかる。しかしコンソールの制御はmacにあり、起動したコンテナ内ではない。再びコンテナの中に入るには `docker attach` コマンドを実行すればよい

```console
$ docker attach c7827f28ee8d
[root@c7827f28ee8d /]# ls
```

作業中に一度macに制御を戻したい場合、 exit とすると先ほどの通りコンテナが停止状態になり再び利用したい場合 `docker restart`からやり直さなければならず非効率である。コンテナを起動したまま制御をmacに戻したい場合は、コンテナ内で `Ctrl + p q` (Ctrlを押したまま p q と続けて押す)とすればよい。コンテナを起動したままmacに制御が戻る。この操作をアタッチの逆でデタッチと呼ぶ。 

## コンテナ(イメージ)のカスタマイズ

コンテナのカスタマイズをしてみる。起動中のCentOSにhttpdをインストールしてウエブサーバ化してみる。(DockerHubには [httpdのイメージ](https://hub.docker.com/_/httpd/)も当然存在しこれを利用すればウエブサーバは簡単に利用できる。が今回はお勉強なので素のCentOSから自前で構築する )

```console
$ docker attach c7827f28ee8d
[root@c7827f28ee8d /]# yum update -y
[root@c7827f28ee8d /]# yum install httpd -y
```

上のコマンドで httpd がコンテナの中にインストールされた。(普通にcentosを扱う感じで作業すればよい)
ただし起動に関しては通常と異なる。普段の場合はsystemctl等でhttpdサービスを起動するがコンテナ内でこのコマンドを実行すると

```
# systemctl
Failed to get D-Bus connection: Operation not permitted
```

と許可されない旨のメッセージが表示される。コンテナを起動した場合、通常にその制御が可能ものは起動プロセス(この場合は `/bin/bash` 以下であり、上位プロセスの制御は許可されないためである。これを回避する起動方法もあるが一般には利用しないケースが多い )
では、どうやってhttpdを起動するのかというと以下のように起動させる。

```
# /usr/sbin/httpd -DFOREGROUND
```
フォワグランドで起動しているためコンソールの制御がhttpdのループに入ってしまい操作できない。この対処はあとで解説するとしてまずは起動がされているかを確認する。 macで別のコンソールを開きそこでこのコンテナ内でコマンドを実行する

```console
$ docker exec c7827f28ee8d ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  11828  2908 pts/0    Ss   04:01   0:00 /bin/bash
root        15  0.0  0.3 224028  7612 pts/0    S+   04:01   0:00 /usr/sbin/httpd -DFOREGROUND
apache      16  0.0  0.2 224028  6052 pts/0    S+   04:01   0:00 /usr/sbin/httpd -DFOREGROUND
apache      17  0.0  0.2 224028  6052 pts/0    S+   04:01   0:00 /usr/sbin/httpd -DFOREGROUND
apache      18  0.0  0.2 224028  6052 pts/0    S+   04:01   0:00 /usr/sbin/httpd -DFOREGROUND
apache      19  0.0  0.2 224028  6052 pts/0    S+   04:01   0:00 /usr/sbin/httpd -DFOREGROUND
apache      20  0.0  0.2 224028  6052 pts/0    S+   04:01   0:00 /usr/sbin/httpd -DFOREGROUND
root        33  0.0  0.1  51716  3508 ?        Rs   04:02   0:00 ps -aux

$ docker exec c7827f28ee8d curl localhost
...
$ docker exec -it 00320d5b01de /bin/bash
↑ exec を -it 付きで /bin/bash 起動するとコンテナの中に入れる
```

確かにhttpdが起動していることがわかる。 (※ `docker exec` というコマンドを利用したがこれは指定したコンテナの中で引数のコマンドを実行する操作である。別のmacで`docker attach`をした場合は目的のことはできないので、この場合は `exec` とする。 `attach` した場合どうなるのか試してみるのもよい)

さて、起動はできたものの、これでは実用的にイマイチ感がある思うのでさらに進める

## イメージの作成

centosのイメージに自前でhttpdをインストールしたコンテナを作成した。これをイメージとして保存する。まずは起動中のhttpdを停止し(Ctrl+c) exitでコンテナを抜け、コンテナを停止状態にする

```console
[root@c7827f28ee8d /]# exit
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
c7827f28ee8d        centos              "/bin/bash"         About an hour ago   Exited (0) 7 seconds ago                       suspicious_liskov
```

この停止したコンテナ(c7827f28ee8d)をイメージ化する。イメージ化するには `docker commit` コマンドを利用する

```console
$ docker commit c7827f28ee8d mycentos_with_httpd
sha256:d1b7ab953d99d9675e2b8ef7b02bbed765845474c6fe1d7195e66f3640f7f3d2
$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED                  SIZE
mycentos_with_httpd   latest              d1b7ab953d99        Less than a second ago   453MB
centos                latest              e934aafc2206        7 weeks ago              199MB
```

以上により httpd入りのcentosイメージ mycentos_with_httpd がローカルに作成された。試しにこのイメージでコンテナ起動してみるとhttpdがインストール済みであることがわかる。

```console
$ docker run -it mycentos_with_httpd
[root@2184a0e23ad1 /]# ls -l /usr/sbin/httpd
-rwxr-xr-x 1 root root 523688 Apr 20 18:11 /usr/sbin/httpd
[root@2184a0e23ad1 /]# exit
exit
```

## コンテナのデーモン起動

作成したhttpd入りコンテナをhttpdを起動した状態でコンテナ起動させてみる。前回はログインした後、コマンドにて `/usr/sbin/httpd -DFOREGROUND` と手動にて起動したがこれを `docker run` の引数に与えることでそれが実現できる。さらに加えて `-d` のオプションを加えるとコンテナの起動をデーモン化できる。実際にやってみると

```console
$ docker run -d mycentos_with_httpd /usr/sbin/httpd -DFOREGROUND
9dee4a08b3e674031c142ffaafacf616743e57648b80af4276cf9eb19b10cfda
$ docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
9dee4a08b3e6        mycentos_with_httpd   "/usr/sbin/httpd -DF…"   3 seconds ago       Up 11 seconds                           practical_goldstine
```

上のように `docker run`のあとも引き続きmacの制御下であるが `docker ps`をみるとコンテナが起動中である。前の実験で試した別コンソールで...という確認をやってみても同様の結果となりコンテナ内でhttpdが動作していることが確認できるはず.

この状態で起動したコンテナを停止させる場合は `docker stop` をすればよい。

```console
$ docker stop 9dee4a08b3e6
9dee4a08b3e6
```

### Tips: -rm でコンテナ起動でお掃除ラクラク

操作を試行錯誤すると `docker ps -a` で確認できる停止中のコンテナが量産される。これを都度 `docker rm` で消去するのは手間である。これを回避するために、停止した段階でrmも合わせて行ってくれる(再び起動する必要が無い場合は活用すると便利)

```console
$ docker run -it --rm centos
[root@fc9882d2aa18 /]# exit
exit
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
※停止中のコンテナは無い！
```

## PortマッピングとVolumeマッピング

これまでの起動方法ではコンテナと母艦であるmacとの関連が無いが、開発を行う上ではソースファイルの共有および動作確認等でmac側とコンテナの関連をもたせたいニーズはある。まずは先のhttpd入りのコンテナ上で起動したhttpをmac側のブラウザから確認するポートマッピングを行う。

```console
$ docker run --rm -d -p 8080:80 mycentos_with_httpd /usr/sbin/httpd -DFOREGROUND
```

`-p 8080:80` がそれにあたり、ローカル(mac)側の8080ポートをコンテナの80ポートにマッピングする指定である。これによりmac側でブラウザを開き `http://localhost:8080/` でのアクセスでコンテナ内のhttpdにアクセスできる。

次にVolumeマッピングについて説明する。 ポートと同様にmacのファイルシステムをコンテナ側にマッピングする。

```console
$ mkdir -p httpd/log
$ mkdir -p httpd/www
$ echo 'hello world' > httpd/www/index.html

$ docker run --rm -d -p 8080:80 -v `pwd`/httpd/log:/var/log/httpd -v `pwd`/httpd/www:/var/www/html mycentos_with_httpd /usr/sbin/httpd -DFOREGROUND
ef6eda9a9abdfa72ebc5bb536f0d5682138cafe6323044af90d8b619c8c96d17
```

これでmacより`http://localhost:8080/`でアクセスするとmac側の`httpd/www/index.html`で保存された'hello world'が表示され、かつ、httpdのアクセスログがmac側の `httpd/log/access_logに記録される

```console
$ tail -f  httpd/log/access_log
172.17.0.1 - - [26/May/2018:05:04:04 +0000] "-" 408 - "-" "-"
172.17.0.1 - - [26/May/2018:05:04:04 +0000] "-" 408 - "-" "-"
172.17.0.1 - - [26/May/2018:05:04:04 +0000] "-" 408 - "-" "-"
```

## Dockerfile

簡単なhttpd入りのイメージを作成したがInfrastructure as Codeの考えでいくとこのプロセスはコードとして手順化されポータブルなものであるのが望ましい。それを実現するに `Dockerfile`　を説明する。
[Dockerfile](https://docs.docker.com/engine/reference/builder/)とは規定の文法で記載されたDocker Imageの作成が記録されたファイルである。詳細まで説明すると長くなるので基本のみ説明する。上で作成したcentosをベースにhttpdインストールされたイメージを構成するDockerファイルは以下のようになる。

（記事を書いた当初はcentosのlatestバージョンは7でしたが現在は8のため明示的にFROM centos:7 としなければいけません。2020.9.3）


```sh:Dockerfile
FROM centos:7

RUN yum update -y \
    && yum install httpd -y \
    && echo "hello world from dockerfile" > /var/www/html/index2.html \
    && yum clean all

ADD ./httpd/www/index.html /var/www/html/

ENTRYPOINT ["/usr/sbin/httpd", "-DFOREGROUND"]
```

若干異なる点はイメージのサイズを削減するために`yum clean all`を行った上でイメージ化する。 index.html はmac上にあるファイルをイメージに含ませる。 index2.htmlを新たに作った。 ~~外部に公開するPortも指定し(EXPOSE)~~、 ENTRYPOINTにて`docker run`したときにデフォルトの起動コマンドもここで指定できる。このファイルを `Dockerfile`というファイル名で保存する

（Dockerfile内の EXPOSE 記述は不要であるとのご指摘がありましたので削除いたしました。 2021.8.18） 

ここからimageの作成は`docker build`コマンドで行う。具体的には

```console
$ docker build . -t mycentos_with_httpd_from_dockerfile
...
$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED                  SIZE
mycentos_with_httpd_from_dockerfile   latest              348670313724        Less than a second ago   403MB
mycentos_with_httpd                   latest              d1b7ab953d99        About an hour ago        453MB
centos                                latest              e934aafc2206        7 weeks ago              199MB
```

とすればimageが作成される。起動時にはENTRYPOINTを指定ずみのため

```console
$ docker run -d --rm -p 8080:80 mycentos_with_httpd_from_dockerfile
```

で起動できる. `http://localhost:8080/index.html` と `http://localhost:8080/index2.html` どちらもmacのブラウザから確認できる。

Dockerfileは Docker Hubで公開されているImageにもそれぞれのイメージの元となるDockerfileがありそれも確認できるのでDockerfileの描き方の参考になる。

### Tips: Docker HubのMySQL入りのイメージを使ってみる

docker-composeの説明で簡単なアプリケーションを作成するがその前にそれに利用するDockerHubにある[MySQLイメージ](https://hub.docker.com/_/mysql/)の使い方に慣れておく。 [DockerHub MySQLのページ]((https://hub.docker.com/_/mysql/))を確認すると latest　は最近リリースされたMySQL8であることがわかる(2018/5現在)
今回はMySQL5.7を利用する。

まずは単純にdocker run してみる

```console
$ docker run mysql:5.7
Unable to find image 'mysql:5.7' locally
5.7: Pulling from library/mysql
f2aa67a397c4: Pull complete
...
Digest: sha256:f030e84582d939d313fe2ef469b5c65ffd0f7dff3b4b98e6ec9ae2dccd83dcdf
Status: Downloaded newer image for mysql:5.7
error: database is uninitialized and password option is not specified
  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD
```

imageはダウンロードされたがエラーがでて起動できない。ROOTパスワードを指定しなさいと書いてある。なので正解は

```console
$ docker run -d --rm --env MYSQL_ROOT_PASSWORD=secret mysql:5.7
2e1ae9100124010771716cb93b2a0f843261fc3e4f28bfedee50bd7f12002b7f
```

である。これでMySQLコンテナがデーモンで起動する。 `--env`にて環境変数を与えて起動する方法はよくあるので覚えとくと良い。

実際にコンテナ内のMySQLを利用してみる 

```console
$ docker exec -it 2e1ae9100124 /bin/bash
root@2e1ae9100124:/#
root@2e1ae9100124:/# mysql -u root -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> show variables like 'version';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| version       | 5.7.22 |
+---------------+--------+
1 row in set (0.00 sec)
```

ここでcreate table で Tableを作成するも当然、コンテナが破棄され再び起動した場合はイメージから再び起動されるのでそのテーブルは存在しない(stopからrestartした場合はファイルシステムは生きたままなので残る)

```console
mysql> create database test;
mysql> connect test;
Connection id:    3
Current database: test
mysql> create table sample ( msg varchar(64) );
Query OK, 0 rows affected (0.03 sec)

mysql> insert into sample values ('hello');
Query OK, 1 row affected (0.01 sec)

mysql> insert into sample values ('world');
Query OK, 1 row affected (0.01 sec)

mysql> select * from sample;
+-------+
| msg   |
+-------+
| hello |
| world |
+-------+
2 rows in set (0.00 sec)

mysql>
```

データベースの情報を永続化するためにはmac側にDB情報を保存すればよく、それにはVolumeマッピングを利用すれば良い。

```console
$ mkdir -p mysql/data
$ docker run -d --rm -v `pwd`/mysql/data:/var/lib/mysql --env MYSQL_ROOT_PASSWORD=secret mysql:5.7
54cf2b615ca1e9178966ff347354f1752b968351f0ed72f73865ffc0072c170a
$ ls mysql/data/
auto.cnf		client-cert.pem		ib_logfile0		ibtmp1			private_key.pem		server-key.pem
ca-key.pem		client-key.pem		ib_logfile1		mysql			public_key.pem		sys
ca.pem			ib_buffer_pool		ibdata1			performance_schema	server-cert.pem
```

mysql のコンテナを起動した際に初期DBが作成されそのファイルがvolumeマッピングしたmac上に保存されていることがわかる。テーブルを作成してコンテナを停止(破棄)した後、同コマンドで再び起動するとデータが永続化されていることが確認できる。

また今回使用したMySQL5.7の[Dockerfile](https://github.com/docker-library/mysql/blob/fc3e856313423dc2d6a8d74cfd6b678582090fc7/5.7/Dockerfile)を確認すると起動時に[docker-entrypoint.sh](https://github.com/docker-library/mysql/blob/fc3e856313423dc2d6a8d74cfd6b678582090fc7/5.7/docker-entrypoint.sh)というスクリプトが実行され

* `$DATADIR` が存在しない場合のみMySQLの初期DBの構築は行う。
* `/docker-entrypoint-initdb.d/` に保存されたユーザ定義の初期化スクリプトを起動時に実行する(初期tableデータ作成などに利用)

といった挙動をすることもわかる。

# docker-compose

[docker-compose](https://docs.docker.com/compose/) は複数のコンテナを組み合わせて１つのアプリケーションとして構成を定義するファイルである。
具体的なイメージが湧きづらいので簡単なアプリケーションをこれを用いて構築する過程で説明する。

## 作成するアプリケーション
簡単な２層アーキテクチャのアプリケーションを想定する。フロントエンドとしてPHPが動作するhttpdサーバであり、そこから接続するMySQLという構成にする。PHPからMySQLに接続し、Userからのリクエストの延べ回数をカウントしDBに保存する。ユーザにはカウンタの値を表示し、DBは永続してそのカウンタ情報を保持する。

```
User <-−８０--> HTTPD w/PHP <--3306--> MySQL
```

## アプリケーションを構成するファイル群

```
./app
./app/counter.php
./mysql
./mysql/init
./mysql/init/init.sql
./mysql/data
./htdocs
./docker-compose.yml
./build
./build/phpapp
./build/phpapp/Dockerfile
```

`docker-compose.yml` の中身は以下の通りになる

```docker-compose.yml
version: "2"
services:
  mysql:
    image: mysql:5.7
    ports:
      - "3306:3306"
    volumes:
      - "./mysql/init:/docker-entrypoint-initdb.d"
      - "./mysql/data:/var/lib/mysql"
    environment:
      - MYSQL_ROOT_PASSWORD=secret
    command: --innodb-use-native-aio=0
  phpapp:
    image: phpapp
    build: ./build/phpapp
    volumes:
      - "./app:/var/www/html"
    ports:
      - "8080:80"
```

※services.mysq.command: --innodb-use-native-aio=0 の項目はwindows環境下で動作上必要との指摘ありましたので追記します (2020.9.2)

このdocker-composeファイルには以下のアーキテクチャの"仕様"が盛り込まれている

* このシステムは２つのコンテナ(サービス)で構成されそれぞれ "mysql"、"phpapp" という名称とする。
* "mysql"サービスはDocker Hubの mysql:5.7 というimageを利用する
* "phpapp"サービスはphpappというimageを利用する。そのimageは `./build/phpapp/Dockerfile` を利用して作成されたものを利用する
* "mysql" コンテナは `./mysql/init`ディレクトリを `/docker-entrypoint-initdb.d` にマウントする。 mysqlイメージの仕様からそこにある init.sql がコンテナ作成時に実行される
* "mysql" コンテナは `./mysql/data`ディレクトリを `/var/lib/mysql` にマウントする。これによりコンテナ内のMySQLデータはmac上で管理されそれは永続化される
* MySQLのROOTパスワードを指定する
* "phpapp"コンテナは`./app`を `/var/www/html`にマウントする. app内のファイルはphp実行ファイルでありhttpd上で動作するアプリケーションとなる
* "phpapp"の80ポートはmac上の8080ポートにバインドする。

以下にそれぞれのファイルの中身を記す。

（Dockerfile内の EXPOSE 記述は不要であるとのご指摘がありましたので削除いたしました。 2021.8.18） 


```docker:./build/phpapp/Dockerfile
FROM centos

RUN yum update -y \
    && yum install httpd php php-mysql php-pdo -y \
    && echo "Hello from httpd" > /var/www/html/index.html \
    && echo "<?php phpinfo(); " > /var/www/html/index.php \
    && yum clean all

ENTRYPOINT ["/usr/sbin/httpd", "-DFOREGROUND"]
```

phpappを構成するDockerfileは先のhttpdで作ったものに類似している。異なる点は phpおよびphpからmysqlに接続するドライバ類(php php-mysql php-pdo)を合わせてインストールしている点である。

```sql:./mysql/init/init.sql
CREATE DATABASE IF NOT EXISTS test;
CONNECT test;

CREATE TABLE IF NOT EXISTS counter (
    id INT AUTO_INCREMENT PRIMARY KEY,
    create_at TIMESTAMP
);
```
MySQLのアプリケーションで利用するTable定義である。単純にアクセスを記録するTableである。

```php:./app/counter.php
<?php
    ini_set( 'display_errors', 1 );
    date_default_timezone_set('Asia/Tokyo');

    $pdo = new PDO(
        'mysql:dbname=test;host=mysql;charset=utf8mb4',
        'root',
        'secret'
    );

    $stmt = $pdo->prepare("INSERT INTO counter () VALUE ()");
    $stmt->execute();

    $stmt = $pdo->prepare("SELECT * FROM counter ORDER BY id DESC limit 1");
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    print_r($row);

?>
```
ユーザがアクセスするHTTPD上で動作するPHPアプリケーション。MySQLに接続してTableにアクセスを記録、取得している。
特筆する点として、PDO内で記載の `host=mysql` の部分である。 docker-conpose で起動した各コンテナに関して、その割り当てられたサービス名をホスト名として相互にIP解決が可能なネットワーク内でコンテナが起動されることになる。

## docker-composeの起動/停止

`docker-compose.yml`が置かれているディクトリ内で以下のコマンドを実行する

```console
$ docker-compose up -d
Starting app_phpapp_1 ... done
Starting app_mysql_1  ... done
```

mysql:5.7のイメージは先の手順通りに行った場合はDockerHubからインストル済み, phpappのイメージの作成がまず行われると思われる。

起動が終わるとmac側のブラウザで `http://localhost:8080/counter.php` にて確認するとリロードごとにカウントUpされるようなアプリケーションが確認できる。

<img src="https://qiita-image-store.s3.amazonaws.com/0/36803/3d5b1bd1-0815-0a5a-e8e8-c10f137c933d.png" width=50%> 

停止は以下で行う

```console
$ docker-compose down
Stopping app_mysql_1  ... done
Stopping app_phpapp_1 ... done
Removing app_mysql_1  ... done
Removing app_phpapp_1 ... done
Removing network app_default```
```

再び起動すると前回のカウンタから+1された値が表示されMySQLデータが永続化されていることもわかる。 

起動中のコンテナに入る場合は以下で行うサービス名を指定すればよい `-it` のパラメータは不要

```console
$ docker-compose exec mysql /bin/bash
root@ad984396ba7f:/#
root@ad984396ba7f:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
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> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.04 sec)
mysql> connect test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Connection id:    5
Current database: test

mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| counter        |
+----------------+
1 row in set (0.00 sec)

mysql> select * From counter;
+----+---------------------+
| id | create_at           |
+----+---------------------+
|  1 | 2018-05-26 06:57:47 |
|  2 | 2018-05-26 06:57:48 |
|  3 | 2018-05-26 06:57:49 |
|  4 | 2018-05-26 06:57:52 |
|  5 | 2018-05-26 07:03:58 |
+----+---------------------+
5 rows in set (0.00 sec)

mysql>
```

# おわりに

docker や docker-compose の基本がわかるような流れで解説をかいてみた。これらによりシステム(サービス)がアーキテクチャ含めてコードにより管理される事を体感できたのではないかと。
dockerってなんかよく分からない...
具体的な活用イメージが湧かない..と思ってる方への一助になれば幸いです。

技術系の記事投稿先を自前のBlogに移しました。初心者向け,AWSなど幅広いTechジャンルで記事投稿がんばってますので、よろしかったらこちらも参照ください！

https://hiyuzawa.jp/