超基礎からの 速習 Docker (5)

![Whale, Docker](https://cdn-images-1.medium.com/max/800/1*Vju_axcWsgxsBCRf3527Dw.jpeg)

本稿は、Christffer Noring さん ([@chris_noring](https://twitter.com/chris_noring)) の [Learn Docker, from the beginning, part V](https://softchris.github.io/pages/docker-five.html) を翻訳し、分かりやすいように少しだけ追記、サンプルコードの実行上の補足等を行ったものです。シリーズ翻訳の意図については [超基礎からの 速習 Docker (1)](https://qiita.com/Gucchiy/items/16045193baabf3dfa176) の冒頭に掲載しています。併せてご参照くださいませ。

***
[Twitter](https://twitter.com/chris_noring) をフォローして、トピックへのあなたの問い合わせやご質問、提案を頂けるとハッピーです。

#超基礎からの 速習 Docker シリーズ一覧

この文章はシリーズの一つです：

- **[超基礎からの 速習 Docker (1)](https://qiita.com/Gucchiy/items/16045193baabf3dfa176)** 
    - なぜ Docker なのか、コンテナーやイメージ、Dockerfile の基本コンセプトの解説、もちろん、それらを管理するのに必要なコマンド群もカバーしています。
- **[超基礎からの 速習 Docker (2)](https://qiita.com/Gucchiy/items/18d2ea155084419727c6)** 
    - Volume を用いたデータの永続化や、開発環境の Volume 化を通じて、開発をより手軽なものにします。
- **[超基礎からの 速習 Docker (3)](https://qiita.com/Gucchiy/items/679b95715592bb4bb4de)**
    - データベースをコンテナ化し、レガシーなやり方及び新しいやり方である Network を用いて他のコンテナから連絡可能にします。
- **[超基礎からの 速習 Docker (4)](https://qiita.com/Gucchiy/items/8a4421dd087a99b44c78)**
    - Docker Compose を用いた複数サービスの管理方法を解説します（その１）
- **超基礎からの 速習 Docker (5)**
    - **僕らはいまここ**

パート IV で紹介したプロジェクトを引き続き用いて、Docker Compose の機能と、必要になるだろう全てをカバーする本質にせまったプロジェクトのビルドについてのショーケースをお見せします。

このパートでカバーするのは：

- **環境変数**　前回のパートでカバーしていますので、多くはどのようにそれらを Docker Compose にセットするかについてです。
- **Volume** 以前の記事でカバーしていますが、その利用法と Docker Compose でどう使うかについて説明します。
- **Network と データベース** ついにデータベースと Network をカバーします。このパートは少しトリッキーですが、多分、全てを説明することに成功したと思います。

もし、どこかのポイントで混乱を感じたなら、この記事の元になっているリポジトリーは以下にあります。

https://github.com/softchris/docker-compose-experiments

>**注釈**
混乱も何も、パート IV 以降、Docker Compose 解説に用いられていたプログラムは、この GitHub にあるコードが初公開です（お待たせしました）。以降、こちらのコードを用いて確認していきましょう。パート IV のお浚いにも使えるかと思います。

#リソース

Docker を使う事、コンテナー化は、一枚岩をマイクロサービスに分解していくことです。このシリーズのいたるところで僕らは Docker やそのコマンド体系をマスターするために学ぶことになります。そうすれば、きっと君は自作のコンテナーをプロダクション環境で使いたくなるでしょう。その環境は大抵クラウド上にあります。十分な Docker 経験を積んだと思ったなら、次のリンクで Docker をクラウドでどのように活用できるか、ご確認してみると良いと思います。

- [Azure 無料アカウントのサインアップ](https://azure.microsoft.com/ja-jp/free/?wt.mc_id=personal-blog-chnoring) プライベート レジストリのようなクラウドのコンテナーを使うには、無料 Azure アカウントが必要でしょう。
- [クラウドのコンテナー](https://docs.microsoft.com/ja-jp/azure/containers/?wt.mc_id=personal-blog-chnoring) クラウドのコンテナーについて他に知っておくべきことについて網羅する概要ページです。
- [自作コンテナーをクラウドにデプロイ](https://docs.microsoft.com/ja-jp/azure/container-instances/container-instances-tutorial-prepare-app?wt.mc_id=personal-blog-chnoring) 今の Docker スキルをレバレッジしてクラウド上でサービスを動かすことがいかに簡単かを示すチュートリアル。
- [コンテナー レジストリの作成](https://docs.microsoft.com/ja-jp/azure/container-instances/container-instances-tutorial-prepare-acr?wt.mc_id=personal-blog-chnoring) 自作 Docker イメージを Docker Hub に入れられますが、クラウドのコンテナー レジストリも可能です。自作イメージをどこかにストアして、一瞬でレジストリから実際のサービスをできるようにすることは凄くないですか？

#環境変数

以前の記事で示したものの一つに、環境変数の指定方法があります。変数は `Dockerfile` に設定できますが、コマンドラインで設定でき、よって、Docker Compose の中でも可能であり、つまり、`docker-compose.yaml` 内では：

```yaml:docker-compose.yaml
version: '3'
services:
 product-service:
   build:
     context: ./product-service
   ports:
     - "8000:3000"
   environment:  
     - test=testvalue 
 inventory-service:
   build:
     context: ./inventory-service
   ports:
   - "8001:3000"
```
上記では、`-test=testvalue` で環境変数の定義、変数 test の値 `testvalue` を行っています。 

product-service ディレクトリにある app.js ファイル の中で、process.env.test から読み込むことで、簡単にテストできます。

他のテスト方法は、Docker Compose を実行して、どんな環境変数が有効か、以下のように query することです：

```
% docker ps
CONTAINER ID        IMAGE                                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
72a2c0c98dda        docker-compose-experiments_product-service     "docker-entrypoint.s…"   6 minutes ago       Up 56 seconds       0.0.0.0:8000->3000/tcp   docker-compose-experiments_product-service_1
211d3dc8584a        docker-compose-experiments_inventory-service   "npm start"              6 minutes ago       Up 56 seconds       0.0.0.0:8001->3000/tcp   docker-compose-experiments_inventory-service_1
fb095493518d        docker-compose-experiments_db                  "docker-entrypoint.s…"   6 minutes ago       Up 56 seconds       0.0.0.0:8002->3306/tcp   docker-compose-experiments_db_1
% docker exec docker-compose-experiments_product-service_1 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=72a2c0c98dda
test=testvalue
DATABASE_PASSWORD=complexpassword
DATABASE_HOST=db
NODE_VERSION=14.1.0
YARN_VERSION=1.22.4
PORT=3000
HOME=/root
```

まず最初に、Docker Compose セッションのコンテナーを `docker-compose ps` とすることで取得します。ついで、`docker exec [container name] env` として、環境変数をリストします。他のやり方としては `docker exec -it [container name] bash` を実行してコンテナーに入り、環境変数を表示させます。Docker Compose で環境変数を管理するやり方はわずかしかありません。他に何ができるかについては、[official doc](https://docs.docker.com/compose/environment-variables/) を参照ください。

>**訳注**
もし、`docker ps` で何も出てこなければ、まだコンテナーを起動していません。以下の手順でコンテナーを起動します。

1. [GitHub](https://github.com/softchris/docker-compose-experiments) から clone します。[Git をインストール](https://git-scm.com/book/ja/v2/%E4%BD%BF%E3%81%84%E5%A7%8B%E3%82%81%E3%82%8B-Git%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)して、[こんな感じで clone ](https://qiita.com/masamitsu-konya/items/abb572337156e4d003cf)します。
2. 取得した `docker-compose-experiments/product-service` フォルダに移動。
3. `npm install --save-dev nodemon` として nodemon をインストールする。
4. （Windows のみ）wait-for-it.sh ファイルを CRLF から LF に変換する。これには[いろんなやり方](https://qiita.com/t-kuni/items/1c5c7512ee0827f200de)があります。適当なエディタで変換しても OK です。CRLF ってなんじゃいって言う方は[こちら](https://www.atmarkit.co.jp/ait/articles/0403/20/news011.html)。
5. `docker-compose up -d` などとしてコンテナーを起動します。

#Volume

以前のパートで Volume についてカバーして分かった凄いやり方は：

- **永続的スペースを作る** ログファイルや データベースと言った、コンテナーの再起動後も残しておきたいものに最適です。
- **開発環境を Volume 上に展開する** このようにするメリットは、コンテナー開始後のコードの変更を、リビルドやコンテナーの再起動なしに反映する、言わばリアルタイム サーバーにすることができる点です。

>「永続的スペース」は、persistent space を翻訳したものですが、要はそこに書いてある通り、コンテナーの終了後も残せるということをイメージして「永続的」と言っています。

##永続的スペースを作る

Docker Compose で Volume をどのように扱うか見て行きましょう。

```yaml:docker-compose.yaml
version: '3'
services:
 product-service:
   build:
     context: ./product-service
   ports:
     - "8000:3000"
   environment:
     - test=testvalue
 inventory-service:
   build:
     context: ./inventory-service
   ports:
     - "8001:3000"
   volumes:  
     - my-volume:/var/lib/data

volumes:  
  my-volume:
```

ファイル最後の volumes コマンドで volume を作成しています。2つ目の行では、`my-volume` と言う名前を与えています。さらに、inventory-service の個所で、作成された volume の参照と、終了しても永続化したい Volume のディレクリ、`var/lib/data` へのマッピングを行っています。正しくマップされている状態を見てみましょう。

```bash
% docker exec -it 211 bash
root@211d3dc8584a:/app# ls
Dockerfile  README.md  app.js  node_modules  package-lock.json  package.json
root@211d3dc8584a:/app# cd ..
root@211d3dc8584a:/# ls
app  bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@211d3dc8584a:/# cd var/lib/
root@211d3dc8584a:/var/lib# ls
apt  data  dpkg  git  misc  pam  python  systemd  ucf
root@211d3dc8584a:/var/lib# cd data
root@211d3dc8584a:/var/lib/data# ls
```

ご覧になっている通り、上記コマンド、`docker exec` でコンテナーに入り、次にマッピングされたディレクトリに移動しています。ありました。すごいね:smiley:

Volume マッピングがちゃんと動いているかどうか確認するために、ディレクトリにファイルを作りましょう：

`echo persist > persist.log`

上記コマンドは、persist と言う内容で `persist.log` を作っています。派手なものではありませんが、コンテナーの再起動後でも探すことのできるファイルを作成します。

さあ、コンテナーを終了できます。次に、便利な Volume コマンドをお浚いしましょう：

`docker volume ls`

```
% docker volume ls
DRIVER              VOLUME NAME
local               ce29ece31b9a806b4f8f5527ca914c6b695fc28ebea294f61b1fa916b2988bd1
local               docker-compose-experiments_my-volume
```

上記は 現在マウントされている Volume すべてがリストされます。作成された Volume docker-compose-experiments_my-volume も見えます。

更なる詳細へダイブしましょう：

`docker volume inspect docker-compose-experiments_my-volume`

```
% docker volume inspect docker-compose-experiments_my-volume
[
    {
        "CreatedAt": "2020-06-02T16:49:32Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "docker-compose-experiments",
            "com.docker.compose.version": "1.25.5",
            "com.docker.compose.volume": "my-volume"
        },
        "Mountpoint": "/var/lib/docker/volumes/docker-compose-experiments_my-volume/_data",
        "Name": "docker-compose-experiments_my-volume",
        "Options": null,
        "Scope": "local"
    }
]
```

OK、これは `Moutpoint` と言った Volume の詳細を教えてくれます。`Mountpoint` は、Volume マッピング ディレクトリに書き込んだ時、どこにあるファイル群が永続化されるのかが分かります。

それではコンテナーを終了させましょう。
`docker-compose down`
Volume はまだあるはず。それでは再度コンテナーを開始しましょう：
`docker-compose up-d`
コンテナーに入って、persist.log ファイルがあるかどうか見てみましょう。

```bash:
% docker exec -it 648 bash
root@648f3b1ac8f1:/app# ls
Dockerfile  README.md  app.js  node_modules  package-lock.json  package.json
root@648f3b1ac8f1:/app# cd ..
root@648f3b1ac8f1:/# ls
app  bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@648f3b1ac8f1:/# cd var/
root@648f3b1ac8f1:/var# ls
backups  cache  lib  local  lock  log  mail  opt  run  spool  tmp
root@648f3b1ac8f1:/var# cd lib
root@648f3b1ac8f1:/var/lib# ls
apt  data  dpkg  git  misc  pam  python  systemd  ucf
root@648f3b1ac8f1:/var/lib# cd data
root@648f3b1ac8f1:/var/lib/data# ls
persist.log
root@648f3b1ac8f1:/var/lib/data# more persist.log 
persist
root@648f3b1ac8f1:/var/lib/data# 
```

イェーイ、動いていますね。

##カレント ディレクトリを Volume にする

OK、新しい Volume を追加して、僕らのコンピュータのディレクトリと、それと同期するべきコンテナー内の場所を指定する必要があります。`docker-compose.yaml` は次のようになっています：

```yaml:docker-compose.yaml
version: '3'
services:
  product-service:
    build:
      context: ./product-service
    ports:
      - "8000:3000"
    environment:
      - test=testvalue
    volumes:  
      - type: bind  
      source: ./product-service  
      target: /app  
  inventory-service:
    build:
      context: ./inventory-service
    ports:
      - "8001:3000"
    volumes:
      - my-volume:/var/lib/data

volumes:
  my-volume:
```

新しく追加になったのは、`product-service` です。Volume コマンドを一つのエントリーで設定できるようになったのが分かります。では、エントリーをブレイク・ダウンしてみましょう：

- **type: bind** [bind mount](https://docs.docker.com/storage/bind-mounts/) と呼ばれる、ローカル ディレクトリとコンテナー間のファイル同期によりフィットした Volume を作ります。
- **source** 単純に君のファイル群がどこにあるか。ここでは `./product-service` が指定されています。このディレクトリにあるファイルに変更があれば、Docker は直ちにそれを拾い上げます。
- **target** コンテナーのディレクトリです。source と target は同期状態となります。source が変われば、同じ変更が target にも起きます。

##Network とデータベース

OK、それではこれがこの記事でカバーしようとしていたことのラスト パートです。データベースを開始しましょう。多くの主要なベンダーは SQL Server、Postgres、MuSQL などなどのような Docker Image を持っています。このことは、データベースを起動、実行するのに、ビルド ステップは必要ないことを意味します。しかし、環境変数ややりとりするための Port のオープンといった設定は必要です。僕らのソリューションに MySQL データベースを追加しましょう。dockder-compose.yml ファイルで行います。

##データベースの追加
データベースを docker-compose.yml に追加することは、凡そ、既成イメージを追加することです。幸運なことに、MySQL は Ready-made イメージが提供されています。追加するには、ただ サービス以下に別のエントリーを追加するだけです。こんな具合に：

```yaml:docker-compose.yml
product-db:
  image: mysql
  environment:
    - MYSQL_ROOT_PASSWORD=complexpassword
  ports:
    - 8002:3306
```

ブレイクダウンしていきましょう：

- `product-db` は僕らが選んだ新しいサービス エントリーの名前です。
- `image` はビルドの代わりの新しいコマンドです。イメージがすでにビルドされている時に使います。ほとんどのデータベースについて可能です。
- `enviroment` 多くのデータベースはユーザー名、パスワード、もしかするとデータベースの名前（データベースの種類で変わります）といった、接続のためのいくつかの変数を設定する必要があるでしょう。今回のケースでは、MYSQL_ROOT_PASSWORD が設定され、root ユーザーのパスワードが何か、MySQL インスタンスに伝えています。アクセス レベルを変えたユーザーをいくつか作ることを考慮するべきでしょう。
- `ports` オープンするポートを列挙します。これがデータベースと話す入り口になります。`8002:3306` とすれば、コンテナーの Port は 3306 が外部ポート 8002 にマッピングされます。

データベースと残りのサービスを起動、実行しましょう：

```bash:
% docker-compose up -d
Creating network "docker-compose-experiments_products" with the default driver
Creating network "docker-compose-experiments_default" with the default driver
Creating docker-compose-experiments_inventory-service_1 ... done
Creating docker-compose-experiments_db_1                ... done
Creating docker-compose-experiments_product-service_1   ... done
```

確認してみましょう：

`docker-compose ps` または `docker ps`

```bash:
% docker-compose ps
                     Name                                   Command               State           Ports         
----------------------------------------------------------------------------------------------------------------
docker-compose-experiments_db_1                  docker-entrypoint.sh mysqld      Up      0.0.0.0:8002->3306/tcp
docker-compose-experiments_inventory-service_1   npm start                        Up      0.0.0.0:8001->3000/tcp
docker-compose-experiments_product-service_1     docker-entrypoint.sh /wait ...   Up      0.0.0.0:8000->3000/tcp
```

良さそうですね。データベース `docker-compose-experiments_db_1` は Port 8002 で起動、実行しているように見えます。次にデータベースに接続してみましょう。以下のコマンドはデータベースに接続します。指を交差させて:wink:

```bash:
mysql -uroot -pcomplexpassword -h 0.0.0.0 -P 8002
```
![finger crossed](https://cdn-images-1.medium.com/max/498/1*2_db0xR6z_9mcYLLsi-u3w.gif)

>**訳注**
[パート3で述べた通り](https://qiita.com/Gucchiy/items/679b95715592bb4bb4de#%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%89%E3%82%A2%E3%83%AD%E3%83%B3-%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%A8%E3%81%97%E3%81%A6%E3%81%AE-mysql)、Windows では 0.0.0.0 にアクセスすることはできないため、`docker exec -it docker-compose-experiments_db_1 bash` などとして、コンテナー内にログインし、コンテナー内で `mysql -uroot -pcomplexpassword` すれば良いかな、と思います。

勝者は・・：

```bash:
% mysql -uroot -pcomplexpassword -h 0.0.0.0 -P 8002
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.6.48 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, 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> 
```
素晴らしい！次はデータベースに接続するように更新しましょう。

##データベースに接続する

データベースに接続するには、主に 3つの方法があります。

- すでに試した通り、`mysql -uroot -pcomplexpassword -h 0.0.0.0 -P 8002` とする
- `docker exec -it [name of container] bash` を用いてコンテナーに入り、コンテナーの中で `mysql` をタイプする。
- 次章でお見せする通り、`npm library mysql` を用いて、アプリから接続する

3つめの選択にフォーカスして、アプリからデータベースに接続します。データベースとアプリは異なるコンテナーに存在します。どのように接続するのでしょう？その答えは：

- **同じ Network にいる必要がある** 二つのコンテナーがお互い話せるようにするには、同じ Network にいる必要があります。
- **データベースが Ready 状態である必要がある** データベースの起動には時間がかかるし、アプリがデータベースと話せるようになるためには、データベースを正常に起動する必要があります。私が方法を見つけるまで、これは楽しく/興味深く/ツライことでした。だから安心して。僕らは成功します:grinning:
- **connection object を作る** `product-service` の `app.js` で coonection object をセットアップします。

最初のアイテムから始めましょう。どのようにデータベースとコンテナーを同じネットワークに入れるのでしょう。簡単です。Network を作って、それぞれのコンテナーを同じネットワークに配置します。`docker-compose.yaml` の中を見てみましょう：

```yaml:docker-compose.yaml
# excerpt from docker-compose.yaml
networks:
  products:
```

各サービスにネットワークをアサインします。こんな風に：

```yaml:docker-compose.yaml
# excerpt from docker-compose.yaml
services:
  some-service:
    networks:  
      - products
```

今や第二の弾丸ポイントです。データベースが初期化処理を終わっているとどうやって分かるのでしょう？ええっと、`depends_on` というプロパティがあります。あるコンテナーが他のコンテナーが初期化処理を終えるまで待つことを設定できます。こんな風に設定します：

```yaml:docker-compose.yaml
# excerpt from docker-compose.yaml
services:
 some-service:
   depends_on: db
 db:
   image: mysql
```

すばらしい、解決した？いやいやいや、落ち着いてくれ。

![wait](https://cdn-images-1.medium.com/max/575/1*Ons95kxrJQd3lbF9XzKscg.gif)

Docker Compose ver.2 では、サービス ヘルスを確認する代替案が使われます。もし良いヘルスなら、コンテナーを起動します。こんな感じです：

```yaml:
depends_on:
 db:
   condition: service_healthy
```

これは、データベースが完全に初期化するまで待つことを意味します。しかし、これが最後ではありません。ver.3 では、このオプションはなくなりました。ここに、それがなぜなのかについて書かれた doc ページがあります → [control startup and shutdown order](https://docs.docker.com/compose/startup-order/)。要点は、データベースが実行中で接続準備可能であることを確認することは我々の責任だということです。Docker はこの件について、いくつかスクリプトを提案しています。

- [wait-for-it](https://github.com/vishnubob/wait-for-it)
- [dockerize](https://github.com/jwilder/dockerize)
- [wait-for](https://github.com/Eficode/wait-for)

これら全てのスクリプトが、特定の `host` と `port` を監視し、返信があり次第アプリを起動する、という動作をします。そのように動作するためには何をする必要があるでしょう？`wait-for-it` というスクリプトを選択し、やるべきことをリストしましょう：

- **コピー** スクリプトをサービス コンテナーに配置します。
- **実行権限** スクリプトに実行権限を付与する。
- **設定** docker ファイルに、スクリプトが実行できえるよう、データベース ホストとポートを設定し、スクリプトが OK のとき一度サービスが実行するように設定する。

GitHub からスクリプトをコピーして、`product-service` ディレクトリに入れるところから始めましょう。こんな感じに見えるでしょう：

```
/product-service
  wait-for-it.sh
  Dockerfile
  app.js
  package.json
```

Dockerfile を開いて、以下を追加しましょう。

```dockerfile:Dockerfile
FROM node:latest

WORKDIR /app

ENV PORT=3000

COPY . .

RUN npm install

EXPOSE $PORT

COPY wait-for-it.sh /wait-for-it.sh

RUN chmod +x /wait-for-it.sh
```

上記は、`wait-for-it.sh` ファイルをコンテナーにコピーして、下の行で実行権限を与えています。注目するべき点は、Dockerfile から ENTRYPOINT も削除されていることです。代わりに、コンテナーに `docker-compose.yaml` ファイルからスタートを指示します。

```yaml:docker-compose.yaml
# excerpt from docker-compose.yaml

services:
 product-service:
 command: ["/wait-for-it.sh", "db:8002", "--", "npm", "start"]
 db:
 # definition of db service below
```

上記で、wait-for-it.sh ファイルを実行するようにし、`db:8002` をパラメータに与え、期待するレスポンスがあった後に、サービスがスタートアップするよう、`npm start` が実行されています。素晴らしいものに見えますね、上手く動くでしょうか？

全部を公開するために、フルバージョンの `docker-compose.yaml` を見ましょう：

```yaml:docker-compose.yaml
version: '3.3'
  services:
    product-service:
      depends_on:
        - "db"
      build:
        context: ./product-service
      command: ["/wait-for-it.sh", "db:8002", "--", "npm", "start"]
    ports:
      - "8000:3000"
    environment:
      - test=testvalue
      - DATABASE_PASSWORD=complexpassword
      - DATABASE_HOST=db
    volumes:
      - type: bind
      source: ./product-service
      target: /app
    networks:
      - products
   db:
     build: ./product-db
       restart: always
     environment:
       - "MYSQL_ROOT_PASSWORD=complexpassword"
       - "MYSQL_DATABASE=Products"
     ports:
       - "8002:3306"
     networks:
       - products
   inventory-service:
     build:
       context: ./inventory-service
     ports:
       - "8001:3000"
     volumes:
       - my-volume:/var/lib/data

volumes:
 my-volume:

networks:
 products:
```

OK、復習しましょう。`product-service` と `db` が Network `products` に配置されていて、`wait-for-it.sh` スクリプトがダウンロードされ、アプリが起動する前に実行され、データベースの実行準備が整い次第、反応するようにデータベースのホストとポートを監視します。やることが一つ残っています。`product-service` の `app.js` ファイルを調整します。ファイルを開きましょう：

```js:app.js
const express = require('express')
const mysql = require('mysql');
const app = express()
const port = process.env.PORT || 3000;
const test = process.env.test;

let attempts = 0;

const seconds = 1000;

function connect() {
  attempts++;

  console.log('password', process.env.DATABASE_PASSWORD);
  console.log('host', process.env.DATABASE_HOST);
  console.log(`attempting to connect to DB time: ${attempts}`);

 const con = mysql.createConnection({  
   host: process.env.DATABASE_HOST,  
   user: "root",  
   password: process.env.DATABASE_PASSWORD,  
   database: 'Products'  
 });

  con.connect(function (err) {  
   if (err) {  
     console.log("Error", err);  
     setTimeout(connect, 30 * seconds);  
   } else {  
     console.log('CONNECTED!');  
   }

  });

  conn.on('error', function(err) {  
    if(err) {  
      console.log('shit happened :)');  
      connect()  
    }   
  });

}
connect();

app.get('/', (req, res) => res.send(`Hello product service, changed ${test}`))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

引数に object を持つ `createConnection()` を呼び出して接続を作る `connect()` メソッドが定義されていることが分かると思います。引数には `host`、`user`、`password`、`database` が必要です。これは完全に合理的に見えます。`setTimeout()` を呼び出して、次の接続まで 30秒間隔をあけるロジックが僅かに `connect()` メソッドにあります。今回は `wait-for-it.sh` を使うため、この機能は必要ないのですが、接続を得るためにアプリケーション単体でも頼れるようにします。しかし、`conn.on('error')` も呼び出す理由は、接続を失う可能性があるからです。お行儀のよいコードにするべきであり、その接続を取り戻すことができるようにする必要があります。

ところで、僕らのパワーですべてを終えたけど、`Dockerfile` が変わったことを教えてあげなければいけません。`docker-compose build` で全てをリビルドし、その後すべてを実行しましょう：
`docker-compose up`

すると・・・

![run docker-compose up](https://cdn-images-1.medium.com/max/1024/1*-2XpY_avDd46TWcMa8EJHg.png)

ありました。ヒューストン、接続があります。または、私の友人バーニーがこうするみたいに：

![my friend Barney](https://cdn-images-1.medium.com/max/496/1*a3L6-JOjwaH5f5cMjbCmtg.gif)

>**訳注**
すみません、なんのジョークかさっぱり分かりませんでした・・

##データベースの設定 ―― 構造とデータを与える

OK、もしかすると、service db をどう作るのか考えているかも？`docker-compose.yaml` の一部はこんな感じになっています：

```yaml:docker-compose.yaml
db:
  build: ./product-db
  restart: always
  environment:
    - "MYSQL_ROOT_PASSWORD=complexpassword"
    - "MYSQL_DATABASE=Products"
  ports:
    - "8002:3306"
  networks:
    - products
```

特に build を見て欲しい。この記事の最初で、データベースの Ready-made イメージを引っ張ってこれると説明しました。このステートメントは正しいですが、この Dockerfile を作ることによって、データベースを指定するだけでなく、データベースの構造とシード データを作るといったコマンドを実行することもできるのです。`product-db` ディレクトリを見てみましょう：

```
/product-db
  Dockerfile
  init.sql
```

OK、`Dockerfile` があります。最初にそれをみてみましょう：

```dockerfile:Dockerfile
FROM mysql:5.6

ADD init.sql /docker-entrypoint-initdb.d
```

`init.sql` がコピーされ、`docker-entroypoint-initdb.d` にリネームすることを設定しています。これは最初に実行されます。素晴らしい、`init.sql` の中身はどうでしょうか：

```:init.sql
CREATE DATABASE IF NOT EXISTS Products;

# create tables here
# add seed data inserts here
```

今回、多くを持っていませんが、絶対拡張できることが分かると思います。それが重要です。

#サマリー

このシリーズについて一周まわって、最初からすべてを説明しました。基本的な概念、主要コマンド、Volume とデータベースの扱い方、そして、Docker Compose のより効果的に使う方法などです。このシリーズは今後も継続して Docker の世界へ深く深く進んでいきますが、少しでもお役に立てれば幸いです。ここまで読んでくれてありがとうございました。