[無料] AWS EC2 + RDS + Flask でWebアプリを作り HTTPS 化する [gunicorn 導入]

[その11](https://qiita.com/nozomiyamada/items/c759ea24680758f6d551) でFlaskアプリのAWSへのデプロイとHTTPS化という目的は達成したわけですが、[その6](https://qiita.com/nozomiyamada/items/4a0686485835bb0ff7ed) でも触れていた開発用サーバーの問題を解決したいと思います。

## サーバーの種類

「サーバー」と言う語は機械という実体を指したり、その中に存在するシステムを指したり、様々な意味で使われてややこしいです。server は本来「提供するもの」という意味ですから、何をどう提供するかによっていくつか分類できます。以下はその「システムとしてのサーバー」としての分類例です。

- アプリケーションサーバー
- Webサーバー
- データベースサーバー
- メールサーバー

このうち、今回関係するのは「アプリケーションサーバー」と「Webサーバー」です。[こちらの記事](https://qiita.com/mintak21/items/eeba4654a0db21abcb1c) の図が非常にわかりやすいので参考にして下さい。これらを導入することによって分散処理が可能になったり、より堅牢なシステムになったりします。この図にデータベースサーバーも加えて「Web三層構造」と一般的に呼ばれます。

Flask自体にもアプリケーションサーバー機能は組み込まれているのですが、それはあくまで「開発用」のものであり、本番環境へのデプロイ時には「ちゃんとしたものを使ってくれ」というのが出ていた警告の内容でした。ということで今回採用するアプリケーションサーバーが gunicorn です。これは正確にはWSGI(Web Server Gateway Interface)というタイプのもので、Webサーバーとしての機能も持っています。また、Apache や nginx などのさらに本格的なWebサーバー導入の説明は今回はしません。というのも、使っているOSなどのローカル環境の違いによって導入法や設定がいろいろ変わってしまうからです。ちなみに私は普段は macOS Catalina と ubuntu 20.04 を使用しており、windows はコマンドラインの使い方さえわかりません。（WSLとかいうものを導入するんでしたっけ？）

## ローカルへの gunicorn 導入

ディレクトリに移動し、仮想環境を有効化します。(VScode でターミナルを開いているので、実際にこれらのコマンドは入力していません)

~~~
$ cd ~/testapp
$ source .venv/bin/activate
~~~

pip でインストールできます。その後、`gunicorn` コマンドが使えるようになります。

~~~
$ pip install gunicorn
~~~

早速 gunicorn を使って起動します。ターミナルで以下のように入力します。

~~~
$ gunicorn app:app
Starting gunicorn 20.0.4
Listening at: http://127.0.0.1:8000 (85620)
~~~

このように、`gunicorn [pythonファイル名]:[Flaskアプリ名]` とするだけで簡単に起動できます。今回の場合、`app.py` というファイルの中で `app = Flask(__name__)` というアプリ（インスタンス）にしているので、このように両方とも `app` となります。デフォルトのポート番号は 8000 です。

HTTPのデフォルトのポートは80番ですが、gunicorn は低い番号のポートをそのままでは開けません。これは管理者権限とも関係しています。この設定変更はかなり面倒なので、逆に8000番ポートで通信するようにAWS側の設定を変えます。というか最初からそれで設定しておくべきでした。

[その10](https://qiita.com/nozomiyamada/items/5b01b3ca38bf79d70eb0) のロードバランサーの設定でターゲットグループ（EC2インスタンス）との通信方法を設定したわけですが、どのポート番号を使うかという基本設定はパッと見た感じ後から変更できなさそうなんですね（もしできたらごめんなさい）。ということで

- セキュリティグループに8000番を追加
- ロードバランサーを新しく作る
- Route 53 のルーティングを変更する
- gunicorn を EC2 にインストールし、起動

の4つをやります。

## セキュリティグループ編集

gunicorn の8000番ポートを開けます。EC2のトップページから **Security groups** に入り、現在のセキュリティグループを選択します。下側のインバウンドルールはこんな感じになっていると思います。
![Screen Shot 2563-12-14 at 21.58.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/727634/d573c844-682e-f321-8236-bc39e1f682ef.png)
ここで **Edit inbound rule** から **Custom TCP** に 8000番ポートを追加します。
![Screen Shot 2563-12-14 at 22.00.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/727634/1e118eef-451f-61e3-9db2-65948304e0c1.png)
**Outbound rules** の方にも同様に追加します。

## ロードバランサー再作成

途中までは[その10](https://qiita.com/nozomiyamada/items/5b01b3ca38bf79d70eb0) と全く同じです。 **Step 4: Configure Routing** で **Port** に 8000 を設定した新しいターゲットグループを作成して下さい。その後同様にEC2インスタンスを登録して保存します。古い方のロードバランサーは一覧から削除して下さい。
![Screen Shot 2563-12-14 at 20.17.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/727634/2e097962-9a6e-1e28-0450-300435815471.png)
ついでに古いターゲットグループも削除しておきます。左側のメニューの **Target Groups** から入れます。
![Screen Shot 2563-12-14 at 20.21.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/727634/031cb824-9186-ed94-90f7-634d0214553f.png)

## Route 53 ルーティング変更

こちらは簡単です。Route 53のページから、最後に作成した Type: A のレコードを変更し、**Edit** します。真ん中らへんの **Value/Route traffic to** のところを、作成時と同様に自分のリージョン、新規作成したロードバランサーと選び直して保存します。これで AWS側の再設定は終わりです。
![Screen Shot 2563-12-14 at 20.26.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/727634/48084c98-188c-6df5-47d8-28610fcf51df.png)

## EC2への gunicorn 導入と起動

ローカルと全く変わりません。[その6](https://qiita.com/nozomiyamada/items/4a0686485835bb0ff7ed) でやったようにSSHログインし、仮想環境を有効化してインストールします。

~~~
$ ssh -i ~/keypair.pem ec2-user@ec2-18-XXX-XXX-XX.ap-southeast-1.compute.amazonaws.com
[ec2-user@ip-172-XX-XX-XXX ~]$ cd testapp
[ec2-user@ip-172-XX-XX-XXX testapp]$ source .venv/bin/activate
(.venv) [ec2-user@ip-172-XX-XX-XXX testapp]$ pip install gunicorn
~~~

これで導入完了です。起動時にローカルと同様にやってしまうと `127.0.0.1` で起動しますが、これはローカルホストとなり、外部からのアクセスを受け付けません。ですので、以下のように `--bind=0.0.0.0:ポート番号` を付ける必要があります。

~~~
(.venv) [ec2-user@ip-172-XX-XX-XXX testapp]$ gunicorn app:app --bind=0.0.0.0:8000
Starting gunicorn 20.0.4
Listening at: http://0.0.0.0:8000 (18344)
Using worker: sync
Booting worker with pid: 18347
~~~

これでURLを入れるとアクセスできるはずです。一旦 `comman+C` `CTRL+C` で終了します。

gunicorn で起動した場合、ターミナルを閉じるなどしてSSH通信が切れてもアプリは落ちません。もし終了したい場合は `ps aux` コマンドで python が起動しているプロセスIDを探し出し、`sudo kill プロセスID` をする必要があります。

~~~
[ec2-user@ip-172-XX-XX-XXX]$ ps aux
...
root     18278  0.0  0.0      0     0 ?        I    13:45   0:00 [kworker/0:2]
ec2-user 18360  0.2  2.4 234240 24172 ?        S    13:50   0:00 /home/ec2-user/testapp/.venv/bin/python3 /home/ec2-user/testapp/.venv
ec2-user 18364  0.1  2.7 258964 27992 ?        S    13:50   0:00 /home/ec2-user/testapp/.venv/bin/python3 /home/ec2-user/testapp/.venv
root     18365  0.0  0.8 152628  8580 ?        Ss   13:50   0:00 sshd: ec2-user [priv]
~~~

ただ、プロセス残っているからそのままでもいいやではなく、`nohup` のように明示的にバックグラウンド実行（デーモン化ともいいます）するべきです。そのために gunicorn の起動時に `--daemon` オプションを付けます。SSHログインから一連の手順を全てまとめますと、

~~~
$ ssh -i ~/keypair.pem ec2-user@ec2-18-XXX-XXX-XX.ap-southeast-1.compute.amazonaws.com
[ec2-user@ip-172-XX-XX-XXX ~]$ cd testapp
[ec2-user@ip-172-XX-XX-XXX testapp]$ source .venv/bin/activate
(.venv) [ec2-user@ip-172-XX-XX-XXX testapp]$ gunicorn --daemon app:app --bind=0.0.0.0:8000
(.venv) [ec2-user@ip-172-XX-XX-XXX testapp]$
~~~

こんな感じになります。とりあえずここまででそれなりに安定したWebアプリ運営が可能になります。

次回はデータベースの作成を扱います。