docker composeを始めよう（メリットとよく使う設定）

前回は「今日から始めるDocker入門（必要性を理解してから動かす）」ということで、docker composeを使わずにdockerを動かす方法、またその必要性について解説させていただきました
たくさんの方にいいねやストックいただけて大変嬉しいです、ありがとうございます！

https://qiita.com/yuya_sega/items/0fb78b064a6d64fe0979

今回は、より実践的な内容にしたく、docker composeについて色々解説していきたいと思います

# docker composeとは

https://docs.docker.jp/compose/index.html

まずは公式の説明を引用します

> Compose とは、複数のコンテナを定義し実行する Docker アプリケーションのためのツールです。Compose は YAML ファイルを使い、アプリケーションのサービスを設定します。コマンドを１つ実行するだけで、設定内容に基づいた全てのサービスを生成・起動します。Compose の機能一覧について学ぶには、 機能一覧 をご覧ください。

若干分かりずらいと思いますので、簡単に説明すると
「YAMLファイルに起動のための設定を記述することによって、複数のDockerコンテナをひとつのコマンドで立ち上げることができる」という感じです

## なにが嬉しいのか
docker composeを理解するためにも、「docker composeを導入するとなにが嬉しいのか」を考えてみましょう

こちらのページにある公式サンプルを例に考えてみます（記事内でwebコンテナはnginxを利用する都合でコンテナポートを5000から80に変更しています）

https://docs.docker.jp/compose/index.html#docker-compose

### まずはdocker composeでやると
webコンテナとredisコンテナを作成しようとしていて、webはredisに対してネットワーク上で接続している状態となります
また、webコンテナはlogvolume01という名前のボリュームを別にもっていますね

compose.yaml（webサービスには別途Dockerfileの用意が必要となりますね）
```yml
version: "3.9"  # v1.27.0 からはオプション
services:
  web:
    build: .
    ports:
      - "8000:80"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}
```

#### 起動するには
```console
% docker compose build
% docker compose up
```
これだけでOK！

### docker composeでやってくれることをdockerコマンドだけでやろうとした場合
#### 起動するには
まず、webイメージをビルドする
```console
% docker build -t qiita-docker-compose-nginx -f Dockerfile ./
// docker build -t qiita-docker-compose-nginx ./ と一緒
```
ネットワーク作る
```console
% docker network create -d bridge qiita-docker-compose-network
```
次にボリューム作る
```console
% docker volume create logvolume01
```
redisコンテナ立ち上げる
```console
% docker run -dit --name qiita-docker-compose-redis --network qiita-docker-compose-network -d redis
```
webコンテナを立ち上げる
```console
docker run -dit --name qiita-docker-compose-nginx -p 8000:80 -v logvolume01:/var/log --network qiita-docker-compose-network qiita-docker-compose-nginx
```

### どう考えたって、引数いくつもあるコマンドを何個も順々に実行してくより、設定はファイル化してあってコマンド一発で実行できるほうがいいに決まってる
比べてみてどうでしょう？

READMEとかに、これらのdockerコマンドの記載があって、上から順にコマンドを実行していけばもちろんできますが、いちいち一個ずつコピペするのは面倒ですし、ひとつひとつが独立したコマンドのため、いつかどこかで整合性がとれなくなるタイミングがありそうな気がします

一方、docker composeを利用していれば、「そこにcompose.yamlがあるなら、そこで`docker compose up`を実行すれば良い」ということになります
どこに手順あるかなーなんて悩む必要がないというわけです
さらに、docker composeという世間一般のルールの中で環境を運用できるので、困ったら世の中にドキュメントがたくさんあります

## 使い所は？
「開発者に展開するローカル開発環境として」という用途が主になるかと思います
共通の開発環境などとしてAWS EC2とかで利用することもできなくないですが、ネットワーク設定などはAWSの機能を利用して設定するべきだと思うし、1つのEC2の中に複数のサービスを立ち上げることは最近の動向を考えるとあまり得策だとは言えないのだと思います

もし使うとしたら多少の不完全さやリスクを承知してAWS運用費を抑えたいとかそういう要望がある場合かと思います
その場合はぜひこの辺の設定も確認してみましょう！

https://docs.docker.jp/compose/production.html

# シンプル構成でdocker composeを作ってみよう
officialの内容で作ってみようをやってみるのはつまらないので、もう少し普段使いそうな内容に寄せてdocker composeを作って動かしてみましょう

今回題材に使用する資材はこちらにpushしておきました！

https://github.com/yuya-sega/qiita-docker-compose

## フォルダ構成考える
以下の構成でいこうと思います
app: アプリケーションのルートディレクトリを想定してます
docker: docker関連のファイルを集めた箇所とします

dockerフォルダ配下はそれぞれdocker composeに記載するサービス名に合わせてフォルダを切っていくのが良いのかなと思います
.envはdockerコンテナに渡す環境変数を定義します
Dockerファイルは、各コンテナのビルドスクリプトを記述したものとなり、イメージの元となります

```console
.
├── .env
├── app
│   └── index.html
├── compose.yaml
└── docker
    ├── apache
    │   └── Dockerfile
    └── db
        └── my.cnf
```

## docker composeファイルを用意する
docker composeはcompose.yamlファイルを用意して、build/up するのみなので、すごく簡単です
dompose.yamlの書き方については覚える必要がありますが、大体使うものは限られてくるので、ひとつずつ地道に学んでいきましょう

```yaml
version: "3"
services:
  web:
    container_name: qiita-docker-compose-web
    build: 
      context: .
      dockerfile: ./docker/apache/Dockerfile
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/local/apache2/htdocs:delegated
    depends_on:
      db:
        condition: service_healthy
        restart: true
  db:
    container_name: qiita-docker-compose-db
    image: mysql:8.2
    command: --default-authentication-plugin=mysql_native_password
    env_file: .env
    volumes:
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    healthcheck:
     test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$MYSQL_ROOT_PASSWORD"]
     timeout: 20s
     retries: 5
```

## webサービス
### container_name
コンテナ名は通常、「ルートディレクトリ名 + サービス名 + n(数字)」で命名され、今回のケースだと「qiita-docker-compose-web-1」として設定されます
ですが、こちらの設定を利用すると、別名をつけることができます

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#container-name

### build

Dockerfileの場所などを指定できます

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#build

### ports
ホスト側ポートと、コンテナ側のポートを接続する設定になります
apacheのデフォルトポート80番とホスト側ポート8000番を接続することによって、 `localhost:8000` でアクセスできるようになります

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#ports

### volumes
ホストのディレクトリをコンテナのファイルシステムにマウントする設定です
これにより、ホストPC内にあるリソース（主に変更のかかるソースファイルなど）をコンテナ内に共有できます

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#volumes

### depends_on
サービス間の依存関係を設定します
今回のケースだと、webサービスがdbサービスを依存先として指定している形となり、コンテナ間で接続も可能となります

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#depends-on

## dbサービス
container_name、volumesは説明を割愛します
リファレンスの詳細リンクも割愛しますので、こちらから検索してみてください

https://docs.docker.jp/compose/compose-file/compose-file-v3.html

### image

buildではDockerファイルを指定して、それによってできたイメージをコンテナとしてupしますが、こちらはイメージそのものを指定できます
docker hubにあるイメージなどが代表的です
今回ではmysqlの8.2タグをイメージとして指定してます

https://hub.docker.com/_/mysql

### command
Dockerfile内のCMD命令を上書きできます

※ CMDはDockerfile内で1度しか利用できないというルールがあるため、「CMDが複数ある場合はどうなんだ」という疑問は的外れになります

### env_file
「変数=値」形式で記述されたファイルを読み込み、コンテナ内の環境変数として設定します

### healthcheck
起動するコンテナが現在正常かどうかをどう確認するかを指定します
今回なぜ指定しているかというと、webコンテナの起動はdbコンテナ内のmysql起動が完了したタイミングで立ち上げたいためです
depends_onの設定のみだとdbコンテナの起動までしか待ってもらえず、mysqlの起動完了を待てないという都合があります
それをhealthcheckとdepends_on.conditionを使って要件を実現しようというとこです

# よくある質問
### compose.yamlファイルを修正したら再度ビルドが必要？
不要です！
ただし、Dockerfileを修正した場合にはビルドが必要になります

### docker compose up -dで立ち上げたコンテナ達はどのように停止するの？
そもそも「- d」オプションとは、バックグラウンドでコンテナを実行するオプションとなりますが、そうすると、「ctrl + C」でコンテナを停止るすことができなくなります

その場合は以下のコマンドでコマンドを停止しましょう

```terminal
% docker compose stop
```

### yamlのファイル名って結局なにが正しいの
今回の記事を書くにあたって私自身も知識をアップデートしたのですが、現状公式が推奨しているファイル名は「compose.yaml」みたいですね

以下も許されているみたいです
- compose.yml
- docker-compose.yaml（下位互換性のため
- docker-compose.yml（下位互換性のため

https://docs.docker.jp/compose/compose-file/index.html#compose-spec-compose-file

そう言えば、コマンドもいつの間にか「docker-compose」から「docker compose」に変わりましたね
ハイフン繋ぎなくしたいのかな？

# Tips
### awesome-compose
docker composeは公式で以下のgithubリポジトリを提示しています
ここには、docker composeの活用例がたくさんあります
是非学習や、自身のプロジェクトに役立ててください！（私もまだちゃんと見れてないのでみてみよーっと）

https://github.com/docker/awesome-compose

### フォルダ構成について
最近はこういう構成もよく目にします
appはバックエンドアプリケーション、フロントエンドアプリケーションなどの単位で括られることがほとんどだと思いますがそれぞれのフォルダにDockerfileが存在する構成はデプロイの単位を考えると合理的な気がしますね！
こうしておくといざappのみ切り出して別のプロジェクトに転用したいときも楽そうです

```console
.
├── .env
├── app
│   └── Dockerfile
├── docker-compose.yml
└── local
    └── db
        └── my.cnf
```