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

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


# 前提
- ローカル環境でpythonが実行可能であること
- Webサーバ・Appサーバは[ローカルプロセス編](https://qiita.com/mintak21/items/eeba4654a0db21abcb1c)のものを利用する

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

# ホストPCとしての準備
本記事ではローカルPCをDockerのホストPCとして構築していきます。
これにあたりDockerを動かすための準備が必要です。
いくつかの方法がありますが、`MacOS`の場合は`Docker for Mac`をbrewインストールするのが手っ取り早いと思います。
※ `Docker for Mac`の有料化が叫ばれていますが、個人利用の範疇では引き続き無料で利用が可能です。

```bash:Terminal
brew install docker --cask
```

これでアプリケーションがインストールされるので、ローカルで`Docker.app`を起動します。右上にクジラのようなアイコンが出現して、下記のようにRunningになっていれば完了です。
<img width="244" alt="スクリーンショット 2020-01-08 14.20.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552343/4b2bd3e3-02ad-4ca4-82d2-086c99042274.png">

# 最終的な成果物

## ソースコード
[Githubリポジトリ](https://github.com/mintak21/infra-server-env-build-practice/tree/3_docker)を参照ください。

## ソースコード簡易解説
Dockerコンテナ化にあたって、Step1からの主な変更点は以下の2つです。

- Webサーバ、AppサーバそれぞれのDockerfileを新規作成
- 起動コマンドを記載したMakefileを新規作成

このうち、2つめのMakefileについては、dockerコマンドの入力を簡易的にするだけのものであり、本質的な追加は1つめのDockerfileになります。それぞれのDockerfileの内容について見ていきたいと思います。(Dockerfileそのものについては後述)

### Webサーバ
```dockerfile:Dockerfile
# ベースとなるイメージを指定。この場合はバージョンまで特定のタグを指定している。
FROM nginx:1.17.6-alpine

# ホストPCの設定ファイル(config/nginx.conf)を上で作成したディレクトリへコピーして送る
RUN mkdir -p /etc/nginx
COPY config/nginx.conf /etc/nginx/nginx.conf
```

単にnginxを利用するのであれば、`Docker Hub`に公開されているnginx Official ImageをpullするだけでOKです。今回は手元で作成したnginx設定ファイルを読んで起動させたいので、次のような作業を行っています。

1. ベースとなるイメージを指示
2. 自前の設定ファイルを送るパスを作成
3. 自前の設定ファイルをホストPCから、このイメージへコピー

### Appサーバ

```dockerfile:Dockerfile
# 今回はdebianベースのpythonイメージをベースとする
FROM python:3.10-slim-buster

# 設定ファイルの転送(適当な場所が思いつかないので/app直下)
RUN mkdir -p /app/config
COPY requirements.txt /app/requirements.txt
COPY config/gunicorn_settings.py /app/config/gunicorn_settings.py
COPY flask_app.py /app/flask_app.py

# パッケージのインストール
RUN pip install -r /app/requirements.txt

EXPOSE 9876

WORKDIR /app

# run実行時のコマンド
# ENTRYPOINT->CMDでCMDはrunのときの引数で上書きすることが可能
ENTRYPOINT [ "gunicorn", "flask_app:app" ]
CMD [ "-c", "/app/config/gunicorn_settings.py" ]
```

osのみのベースイメージから始めて、python等をインストールするという手順でもいいのですが、せっかくpythonのOfficialイメージが公開されているので、今回はこれをベースとしてスタートします。
`パッケージのインストール`まではほぼWebサーバと同様ですが、それより下はWebサーバにはなかった記述ですね。次のようなことをしています。

1. Appサーバのコンテナで、9876ポートをLISTEN状態とする
2. コンテナ起動時のベースディレクトリを`/app`に設定する
3. コンテナ起動時、`gunicorn flask_app:app -c /app/config/gunicorn_settings.py`コマンドを実行する


# About Docker
## Docker化する理由
これに関してはいくつかあると思いますが、自分の感じる利点を２つ記載しておきます。

### 開発/実行環境の統一・準備の簡易化
チーム開発を行うにあたって、これまでは開発者がそれぞれのマシンで開発環境を準備してそのうえで開発を開始する必要がありました。手順を示したドキュメントはあるでしょうが、実際はドキュメント不備やツールのアップデートなどの事由によってうまく環境構築ができないなどコストがかかるかと思います。また、個人がそれぞれに言語のバージョンアップなどを行い微妙な違いが発生するというリスクも考えられます。
一方で、あらかじめ用意されたDockerコンテナを利用することにより、

- 少ないコマンド数(`docker run`)で環境が構築できる
- 開発環境の統一が容易

という恩恵を受けることができます。

### 本番環境にもコンテナをのせられる
クラウド上での運用が当たり前となった現在では、GKEなどを利用したコンテナオーケストレーションツールが数多く出回っています。コンテナオーケストレーションというくらいなので、実際に本番環境でもコンテナ上でアプリケーションが起動していることとなります。
もちろん開発、ステージング、本番とで環境変数やエンドポイントなどを変更することが必要ですが、オーケストレーション側でも吸収することはでき、いずれにせよ**開発環境で動いたコンテナがほぼそのまま本番環境でも稼働**することとなります。

## イメージとコンテナ
まずイメージを作成(=ビルド)して、そのイメージをもとにコンテナを作成・起動するといった順番です。
うまい例ではないですが、PCゲームで置き換えると
- Dockerイメージ = ディスク
- Dockerコンテナ = ゲーム起動中のウィンドウ
みたいな感じです。

## [Docker Hub](https://hub.docker.com/)とは
自身が作成したコンテナイメージをアップロードして公開・共有できるサービス。要は*GithubのDocker限定版*みたいなものです。
公開されているイメージは自由にダウンロード、利用することが可能です。自身でカスタマイズしたイメージを作成する際にも、ベースとなるイメージはDocker Hubに登録されているものから選択します。

### イメージの信頼性
Docker Hubには有志によるイメージも数多く登録されています。その中でベースイメージとして選択するとよいものは`Docker Official Images`というタグのついたものとなります。
理由を一言にすると、**信頼性が高いから**なのですが、いくつか箇条書きであげておきます。

- 適時セキュリティ・アップデートが行われる
- 明確なドキュメントが存在する
- Dockerfileのベストプラクティスを提供する
- 必要不可欠なベースイメージを提供する


## Dockerfileとは

作成するコンテナのベースとなるイメージ、アプリケーションインストール、アプリケーション起動の準備などの一連の作業を一括で実施できる命令が記述されたファイルのことです。

なお、Dockerfileの記述に関する詳細は[こちら](https://qiita.com/mintak21/items/a6766e3efd6730c9519d)を参照ください。