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

![Whale, Docker](https://thepracticaldev.s3.amazonaws.com/i/1trd1enpr5ocm0vt3xhg.jpeg)

本稿は、Christffer Noring さん ([@chris_noring](https://twitter.com/chris_noring)) の [Learn Docker, from the beginning, part IV](https://softchris.github.io/pages/docker-four.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)**
    - いまここ。
- **[超基礎からの 速習 Docker (5)](https://qiita.com/Gucchiy/items/8d078dde6d890fdd8144)**
    - Docker Compose を用いた複数サービスの管理方法を解説します（その２）

>このパートでは、複数の Docker コンテナーの扱いについて述べます。やがてはたくさんのコンテナーを持ってしまって、すべてを管理できないと感じるようになるはずです。docker run を入力し続けるのは一定のポイントまでで、複数のコンテナを起動し始めると、すぐに頭と指が痛くなります。

TLDR; Docker Compose は巨大なトピックです。そんなわけで、二つのパートに分けました。このパートでは、なぜ Docker Compose で、いつそれは輝くのか説明します。続編では、Docker Compose について、環境変数、Volume、データベースと言った、より高度なトピックについてカバーします。

このパートでカバーしているのは：

- **なぜ Docker Compose？** Docker Compose の理解には重要です。ハイレベルには最低でも、二つの主要なアーキテクチャ、Monolith と Microservice があり、Docker Compose は Microservice をきちんとサポートします。
- **機能** Docker Compose がどんな機能をサポートし、なぜ、そんなに Microservice にフィットするのか、理解してもらいます。
- **Docker が十分じゃないとき** Docker コマンドを使うことがつまらなくて苦痛になる個所と、もっともっと魅力的に見え始める時がいつかについて説明します。
- **アクション** 最後に、スクラッチから docker-compose.yaml をビルドして、Docker Compose を使ったコンテナーの管理といくつかコア コマンドについて学びましょう。

#リソース

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 に入れられますが、クラウドのコンテナー レジストリも可能です。自作イメージをどこかにストアして、一瞬でレジストリから実際のサービスをできるようにすることは凄くないですか？

#なぜ Docker Compose？

Docker Compose は多くのサービスを個別に管理する必要がある時に使われます。これから話すことは Microservice アーキテクチャと言われるものです。

##Microservice アーキテクチャ

このアーキテクチャの特徴を定義していこう。

- **疎結合** （訳注：疎結合の原文は Loosely coupled、緩くカップルになってる感じ）、これは他のサービスの機能に依存せず、必要なデータがそのままそこにあるということです。それらは他のサービスに影響しあえますが、HTTP コールのような外部 API 呼び出しで実現しています。
- **個別にデプロイ可能** 他のサービスに直接影響することなく、スタート、ストップ、リビルドできることを意味します。
- **高いメンテナンスとテストが可能** それぞれサービスが小さいため、理解が容易で、お互いに依存しないため、テストがよりシンプルです。
- **ビジネス ケーパビリティと共にある** 異なるテーマを見つけて試せることを意味します。ブッキングだったり、プロダクト管理だったり、会計だったり・・

なぜ僕らはこのアーキテクチャにしたいのか？という問いから始めましょう。上の特徴リストからも明らかであるように、高いフレキシビリティや、依存度がない点などがあります。それは良いことに聞こえますが、全てのアプリが持っているべき新しいアーキテクチャなのでしょうか。

そういうことは常にモノに依ります。モノリシックなアーキテクチャに対してMicroserviceが輝くかどうかは基準があります：

- **異なる技術スタック/新しい技術** 多くの開発チームがあって、それぞれが自身の技術スタックまたはすべてのアプリを変更するわけではなく、新しい技術を試したい。各チームは自分で選んだ技術を、Microservice アーキテクチャの一部としてサービスをビルドしましょう。
- **再利用** 「会計」と言った、一つのケーパビリティをビルドしたい場合、一つの個別のサービスに分解すれば、他のアプリケーションへの再利用が容易になります。Microservice アーキテクチャでは、異なるサービスを組み合わせたり、そこから多くのアプリを作り出すことが良いです。
- **最小の失敗インパクト** モノシリック アーキテクチャで失敗があると、アプリ全体が落ちてしまうかも知れません。Microservice なら、失敗からできるだけ守ることができます。

なぜ Microservice がモノリシック アーキテクチャを超えるのかについてはたくさんの議論があります。ご興味があれば、[こちら](https://www.cio.com/article/3201193/7-reasons-to-switch-to-microservices-and-5-reasons-you-might-not-succeed.html) のリンク先をお勧めします。

##Docker Compose の事例

Microservice アーキテクチャの記述は、ビジネス ケーパビリティと共にあるサービス群が必要であることを教えてくれます。更に言うなら、個別にデプロイ可能で、異なる技術スタックや更に多くを使うことが可能である必要があるということです。私の意見としては、これは Docker がとても汎用的にフィットしているように聞こえます。Docker を超えて Docker Compose の事例を作る理由は、そのサイズにあります。もし、僕らが二つ以上のコンテナーを持っていたなら、僕らがタイプしなくちゃならないコマンドの数は突然線形に増えていく。次のセクションでは、たくさんのサービスが増えた時、どんな Docker Compose 機能がスケールできるのか、解説しましょう。

#Docker Compose 機能概要

Docker Compose は簡単にいくつかのイメージを一度に簡単にビルドしたり、いくつかのコンテナーその他のものを一度に起動できるという意味で、非常に優れたスケーリングが可能になりました。機能の一覧は以下の通りです：

- **Manages** アプリのライフサイクル全体を管理
- **Start**、Stop、リビルド
- **View** サービス実行のステータスを参照
- **Stream** 実行サービスの log 出力を確認
- **Run** 一度のコマンドでサービスを実行

見ての通り、多くのサービスで構成されている microservice アーキテクチャを管理する必要があるとき、必要のある可能性のあるすべてを提供しています。

#素の Docker では十分じゃないとき

Docker がどのように操作し、どんなコマンドが必要だったか、サービスが一つ二つ追加するときについて見てみましょう。

- **Dockerfile の定義** OS イメージ、インストールするライブラリ、環境変数、開くべき Port、最後にサービス起動の方法
- **イメージのビルド** または Docker Hub からイメージを引っ張ってくる
- コンテナーの生成と実行

Docker Compose でも Dockerfile が一部必要ですが、Docker Compose はイメージをビルドし、コンテナーを管理します。プレーン Docker の場合のコマンドをお見せしましょう：

`docker build -t some-image-name .`

続いて、

`docker run -d -p 8000:3000 --name some-container-name some-image-name`

書くのに大変な量と言うわけではないですが、これを行う必要のある 3つの異なるサービスを持っている場合を想像してみてください。突然6つのコマンドまで増えます。さらに、終了用にコマンドが2つ増えます。これはほんとスケールしない。

![does't realy scale](https://thepracticaldev.s3.amazonaws.com/i/9zdbajjweos0erfkjl2h.gif)

##docker-compose.yaml を書く
ここは本当に Docker Compose が輝いているところ。ビルドしたいサービス毎に二つのコマンドをタイプする代わりに、プロジェクトの全てのサービスを一つのファイル `docker-compose.yaml` で定義できます。`docker-compose.yaml` ファイルの中では以下のトピックを設定できます。

- **Build** ビルド内容と 標準的な名前とは異なるべきである Dockerfile の名前を特定できます。
- **Environment** 多くの必要な環境変数を定義し、値を設定できます。
- **Image** スクラッチからイメージをビルドする代わりに、Docker Hub から引っ張ってきて自身のソリューションに使える、Ready-made イメージを定義します。
- **Networks** Network を作成し、もし必要であれば、各サービスがどのネットワークに属しているべきかを特定できます。
- **Ports** コンテナー内部のどのポートが、どの外部ポートにあたるか、ポート フォワーディングを定義できます。
- **Volumes** もちろん、Volume も定義できます。

#Docker Compose の実行

OK、ここまでで、Docker Compose がコマンドラインでできることならなんでも行うことができ、何を実行するのか知るには `cocker-compose.yaml` ファイルに依存していることをご理解頂けたかと思います。

##docker-compose.yaml をはじめよう

実際にファイルを作ってみて、解説を加えましょう。最初に典型的なプロジェクト ファイルの構造について簡単にレビューしましょう。以下は二つのサービスを構成するプロジェクトで、それぞれディレクトリを持っています。それぞれのディレクトリは、サービスをどうビルドするかを指定した `Dockerfile` があります。

>**訳注**
本章のハンズオンで実際に触るのは、docker-compose.yaml だけで、各イメージやコンテナーを作るソースコードと設定ファイルが提示されていません。パート 5 では、GitHub からそれらサービス用コードをダウンロードしてのハンズオンとなっていますので、本章は「まあ、そんなものか」と読み進んで頂くのが良いかなと思います。

こんな感じになっています：

```
docker-compose.yaml
/product-service
  app.js
  package.json
  Dockerfile
/inventory-service
  app.js
  package.json
  Dockerfile
```

上記で注目すべきは、プロジェクトのルートに `docker-compose.yaml` ファイルを作成するというやり方です。そのようにする理由は、ビルドするすべてのサービスについて、どのようにビルドするか、どのようにスタートするかが一つのファイル `docker-compose.yaml` に定義されているからです。OK、`docker-compose.yaml` を開いて、最初の１行を入力しましょうｊ：

```yaml:docker-compose.yaml
version: '3'
```

ここで何を指定するかは実際重要です。現在、Docker は 3つの異なるメジャー バージョンをサポートしています。3 は最も新しいバージョンで、各バージョンでの違いとそれに付随する異なる機能とシンタックスについては、 [official docs](https://docs.docker.com/compose/compose-file/) をご覧ください。

```yaml:docker-compose.yaml
version: '3'
services:
  product-service:
    build:
      context: ./product-service
    ports:
      - "8000:3000"
```

OK、一度にたくさんあるので、ブレイク ダウンしていきましょう：

- **services:** docker-compose.yaml ファイル全体の中で一つだけあります。`:` で終わっているやり方にも注意ください。これが必要な場合は有効な構文ではあります。そのことはコマンド一般に言えます。
- **product-service:** サービス名として任意で選択した名前。
- **build:** Docker Compose がどのようにイメージを build するかの指定。もし既に Ready-made イメージを持っている場合はこれを指定する必要はありません。
- **context:** Dockerfile がどこにあるかを Docker Compose に伝えるために必要です。このケースでは、product-service ディレクトリ以下にあります。
- **ports:** これはポート フォワーディングで、最初に外部ポート、次に内部ポートを指定します。

これ全ては以下の二つのコマンドに対応します。

```
docker build -t [default name]/product-service .
docker run -p 8000:3000 --name [default name]/product-service
```

ええっと、そりゃだいたいあってるけど、まだ イメージのビルド実行や、コンテナーの作成と実行をする Docker Compose についてちゃんと話してない。どのように開始してどのようにイメージをビルドするかについて学習しましょう。

`docker-compose build`

上記で docker-compose.yaml 内で定義したシングル サービスをビルドします。コマンドの実行結果を見てみましょう。

![output of our command](https://thepracticaldev.s3.amazonaws.com/i/gezojc19cb7cvlda0fcr.png)

上の内容では、イメージをビルドし、最後の行で `compose-experiments_product-service:latest` と言うフルネームが与えられていることが分かります。名前はディレクトリー名 `compose-experiments` からつけられ、他の部分は `docker-compose.yaml` ファイルから与えられます。OK、以下のようにタイプして起動しましょう：

`docker-compose up`

これによって docker-compose.yaml が再度読み込まれますが、今度はコンテナーを作成・実行します。バックグラウンドでコンテナーを実行したい場合、`-d` フラグをつけて呼び出します。フルコマンドは：

`docker-compose up -d`

OK、上記でサービスが生成されました。`docker ps` を実行して新しく作られたコンテナーを確認しましょう：どうやら　8000 ポートで実行されていますね。確認してみましょう：OK、ターミナルからコンテナーを作れれたことを確認できました。docker stop と docker kill でコンテナーを終了できることを知っていますが、docker-compose ではどうやるでしょう：

`docker-compose down`

上記コマンドのログで、コンテナーの停止と削除が分かり、`docker stop [id]` と `docker rm [id]` の両方が行われているらしい。いけてる:smiley: もしやりたいことがコンテナーの停止なら、次のようにするべきだ：

`docker-compose stop`

君のことは知らないけど、僕は `docker build`、`docker run`、`docker stop`、`docker rm` を使って停止の準備ができた。Docker Compose はすべてのライフサイクルをサポートしているように見えます:smiley:

#Docker Compose アピール

ここまでを復習しましょう。Docker Compose は僕らのためにサービス管理の全ライフサイクルをケアしてくれます。最もよく使う Docker コマンドとどれが Docker Compose のコマンドが対応するかリストしましょう：

- `docker build` は `docker-compose build` になり、Docker Compose の方は、`docker-compose.yaml` にあるすべてのサービスをビルドできます。シングル サービスでもビルドできるため、よりきめ細かい制御が可能となります。
- `docker build + docker run` は `docker-compose up` になり、一度に多くのことができます。もし、事前にイメージがビルドされていない場合、ビルドは自動的に行われ、イメージからコンテナーが生成されます。
- `docker stop` は `docker-compose stop` になり、Docker Compose の方はすべてのコンテナーを停止させることも、またはパラメータを与えれば特定のコンテナーを停止することもできます。
- `docker stop && docker rm` は `docker-compose down` になり、最初コンテナーをすべて停止し、その上でコンテナーすべてを削除することで、終了させます。これにより僕らは新たにスタートすることが可能です。

以上はそれ自身すごいことですが、もっとすごいことはソリューションの拡張し続けることや、サービスを更にさらに追加することが容易であることです。

#ソリューションをビルドする

他のサービスを追加して、それが如何に簡単で、以下にスケールしやすいか見て行きましょう。以下が必要です：

- **add** docker-compose.yaml に新しいサービス エントリーを追加します。
- **build** `docker-compose build` でイメージをビルドします。
- **run** `docker-compose up`

`docker-compolse.yaml` ファイルを見てみましょう。必要な情報を次のサービスのために追加しましょう。

```yaml:docker-compose.yaml
version: '3'
services:
  product-service:
    build:
      context: ./product-service
    ports:
      - "8000:3000"
  inventory-service:
    build:
      context: ./inventory-service
    ports:
        - "8001:3000"
```

OK、それじゃあ、新しいサービスを含んだコンテナー達を up して、実行しよう。

`docker-compose up`

待って、`docker-compose build` するんじゃない？ええっと、実際いらないよ。`docker-compose up` が僕らがやるすべてで、イメージのビルド、コンテナーの作成と実行を行ってくれます。

![docker-compose up](https://thepracticaldev.s3.amazonaws.com/i/09jstlay2u68e0uhhllw.gif)

注意、そんなに単純じゃありません。事前にイメージがない場合、最初は build + run として実行されます。ですが、もしサービスが変更されたなら、リビルドが必要で、その場合は `docker-compose build` を最初に行ってから `docker-compose up` する必要があります。

#サマリー

Docker Compose をカバーする最初の半分に来ましたが、とっても内容は多いですね。Docker Compose の背後のモチベーションと Microservice アーキテクチャについて簡単に説明しました。さらに、Docker VS Docker Compose について話し、最後に、Docker Compose と素の Docker との対比と比較ができました。それによって、Docker Compose を使うことと `docker-compose.yaml` ファイルですべてのサービスを特定することで、如何に簡単になるかを見せることができていればと思います。Docker Compose にはさらに、環境変数、Network、Database のようなことがあると言いましたが、それらは次のパートです。

***
**[超基礎からの 速習 Docker (5) に進む](https://qiita.com/Gucchiy/items/8d078dde6d890fdd8144)**