[イメージ図付]Nginx+gunicorn+FlaskをDocker化[後編]

# 本記事の概要
Flaskアプリケーションを立ち上げる手順や関連する設定について記載します。
本記事の内容は、ローカルにてWebサーバおよびAppサーバをDockerコンテナで起動するケースとなります。
[前編](https://qiita.com/mintak21/items/d956389ee9338e6c0fe0)はプロセスをそれぞれ`docker`コマンドで起動する、後編は`docker-compose`で一括起動するといった内容を扱います。

# 前提
- [前編](https://qiita.com/mintak21/items/d956389ee9338e6c0fe0)記事の続きとしての記載となります。

# 全体像
![Flask_Nginx_unicorn_diagramdrawio-Step3-docker-compose.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/61574bda-951c-2abb-6fe8-5191e57a91b6.png)

# 最終的な成果物
## ソースコード
[Githubリポジトリ](https://github.com/mintak21/infra-server-env-build-practice/tree/4_docker-compose)を参照ください。

## 簡易解説
前編にて、Webサーバ、Appサーバをそれぞれコンテナで起動するよう`Dockerfile`を作成しました。これらのコンテナ起動を(起動コマンドという点での)コストを下げるために、`docker-compose`コマンド１つで２コンテナ起動するようにします。
主な変更点は以下です。

- docker-compose設定ファイル追加
- `nginx`設定ファイルの接続先修正

### docker-compose.ymlファイル
今回のケースにおいて、コンテナ間の関係、コンテナ・ホスト間の関係として以下のことを指定する必要があります。

- Appサーバ
  - Webサーバと同一ネットワークに属している
- Webサーバ
  - コンテナのポートをホストの特定ポートとでポートフォワーディングする
  - Appサーバと同一ネットワークに属している
  - Appサーバの起動後である必要がある

```yml:docker-compose.yml
version: "3.7"  # composeファイルのバージョンを指定する
services:       # このレイヤーの配下にコンテナ単位で記述を追加していく
###########ここからAppサーバの設定###########
    app-server:   　　　　　　　　　　 # サービスの名前、ホスト名にもなりうる
        container_name: app-server  # コンテナの名前
        build:                      # 自分で用意したDockerfileでビルドを行う場合必要なブロック
            context: ../../         # docker buildするときのパスを指定、ymlファイルのあるところからの相対パス
            dockerfile: deployment/dockerfiles/app-server/Dockerfile # 利用するDockerfile。contextのパスから指定
        networks:                   # このサービスが属するネットワークの設定
            my_host_network:        # 後述のmy_host_networkに属する
                aliases:
                    - uwsgi-server  # my_host_network上ではこの名前で判別できるようになる
        expose:
            - 9876                  # このポートをLISTEN状態にする
        restart: always             # サービス再起動ポリシー
###########ここまでAppサーバの設定###########
###########ここからWebサーバの設定###########
    web-server:
        container_name: web-server
        build:
            context: ../../
            dockerfile: deployment/dockerfiles/web-server/Dockerfile
        networks:
            my_host_network:
                aliases:
                    - nginx-server
        ports:
            - 9090:9123     # ホストPCの9090番をコンテナの9123番にポートフォワーディング
        depends_on:         # 依存関係を指定。web-serverの起動より前にapp-serverを起動するようになる
            - app-server
        restart: always
###########ここまでWebサーバの設定###########

###########ここからネットワークの設定###########
networks:
    my_host_network:            # 上の方のサーバ設定で使うときのエイリアス名
        name: my-nginx-network  # ネットワークの名前
        driver: bridge          # ドライバ種別、bridgeかoverlay
```

なお、ネットワークについては１つのホストPC上に複数のコンテナを展開するパターンのため`bridge`を採用しています。ホストが複数に分かれる場合は`overlay`を採用してください。


### nginx.confファイル
これまではホストPC上に(ダイレクトに)両サーバが起動していたので、リバースプロキシ先に`localhost`と指定していました。本Stepで接続先をAppサーバコンテナへと変更する必要があります。

```conf:nginx.conf
http {
	server {
　　　　　   # 「uwsgi-server」はdocker-compose.ymlで設定したmy_host_networkネットワーク上のAppサーバのエイリアス名称
    		location / {
        		proxy_pass http://uwsgi-server:9876;
        		# before: proxy_pass http://127.0.0.1:9876;
    		}

		location /health {
        		proxy_pass http://uwsgi-server:9876/health;
        		# before: proxy_pass http://127.0.0.1:9876/health;
    		}
    	}
}
```

## 起動/コンテナ削除

- 起動

```bash:Terminal
docker-compose -f deployment/docker-compose/docker-compose.yml up
# -fオプションで設定ファイルを指定
```

`up`でymlファイルの設定にしたがってイメージをビルド、コンテナが作成される。
`build`+`start`のイメージ。
また、`-d`オプションを加えることでバックグランドで実行することが可能。

- 停止

フォアグラウンドで実行した場合は、`Ctrl+C`、
バックグランドで実行した場合は`docker-compose -f deployment/docker-compose/docker-compose.yml stop`でコンテナを停止できる。

- コンテナ削除

```bash:Terminal
docker-compose -f deployment/docker-compose/docker-compose.yml down
```

`--rmi all`オプションを追加することで、もととなったイメージもすべて削除される。

# About docker-compose
## 便利になる点
大雑把に２点記載すると、以下のようになります。

1. 複数のコンテナを１コマンドで起動・停止することができるようになる
1. コンテナ同士の関係性等を設定ファイルで明記することができる

1については、10個のコンテナを起動する必要がある場合を想定してみてください。`docker run {container}`を10回入力するのはそこそこのコストですよね。ひょっとしたら１つ起動を忘れてしまうかもしれません。それが`docker-compose up`の1コマンドで完了するようになるので、起動漏れも起こらないですし、何よりラクですね。(ここだけにフォーカスすると、シェル書けばいいじゃんともなりますが)

2について、今回のStepを例にとってみます。
それぞれのコンテナについて`Dockerfile`での記載は出来ず、`docker run`の場合は起動時オプションとして指定する必要のある起動条件がいくつかありました。

- Webサーバ
  - コンテナのポートをホストの特定ポートとでポートフォワーディングする
  - Appサーバと同一ネットワークに属している
  - Appサーバの起動後である必要がある
- Appサーバ
  - Webサーバと同一ネットワークに属している

`docker-compose`を使用することで、上記の設定を１つのymlファイルに定義することができるため、コンテナ間、コンテナーホスト間の関係性も含めて構築難易度を下げることが可能になります。
とくに以下のケースで利用価値が高くなります。

- コンテナ、ホスト間でvolume共有をしたい
- network設定を行いたい
- 環境変数を多く設定したい


## docker-composeはProductionで利用できるか
個人的な見解となりますが、利用しないor局所的利用　が大半かと思います。
というのも、コンテナを作成、コンテナ間の関係性を定義できれば、これを本番にのせて終わり・・・ではないためです。
本番(およびステージング)環境でサービスを展開させるためには、ここまでのStepに加えて、`スケーラビリティ、ロードバランシング、ネットワーク、ランタイム`などなどといったカタカナ用語たちを考えなければなりません。`docker-compose`を利用することで解決できる分野もありますが、すべてをこなすことは厳しく思えます。
オーケストレーションとして利用されるものとしては、やはり`kubernetes`となるでしょう。
ただ、ローカル環境で`kubernetes`クラスタを展開することがスペック的に厳しい場合や、単にアプリケーションの挙動を確認する上では`docker-compose`の方が手軽に利用することができます。