AWSでWebアプリを公開する方法(Python+Flask)

# はじめに
この記事は、私がPythonとFlaskで作成したWebアプリをAWSで公開するまでの方法を記したものになります。
なので、**自作のWebアプリをAWSでWeb上に公開したい人**などにとっては特に参考になる点があると思います。

また、最近の流行りで機械学習のモデルを作成したが、それをどうすれば良いかわからないという人も記事に従ってやってもらえれば、Web上に公開でき、ネットワークといったインフラ側の知識も身につきますので、そういう人にも読んでもらえればなと思います。

最後に紹介までにですが、
今回私が作成したアプリケーションは機械学習を用いた画像認識アプリなので、是非使ってその感想などをいただけますと幸いです。(以下そのURL)
https://www.body-score-checker.ml
(現在はインスタンスを止めている為、アプリケーションは利用できません。)


#本記事の流れ
**・前提**
**・アプリのディレクトリ構成と一部コードの紹介**
**・公開までの流れ**
1.全体像の把握
2.実際の手順
**・１人でWebアプリケーションを作成してみて感じたこと**


#前提
この記事を読むのに当たっての前提の共有をしたいと思います。
・Webアプリの実装方法やそれぞれコードの詳しい解説は省略します。
・AWS初心者なため、アーキテクチャがベストプラクティスではないことはご了承ください。

#アプリのディレクトリ構成と一部コードの紹介

実際のアプリ構成は、以下のようになっています。

```
Flask
|_ app.py
|_ cnn_model.py
|_ photos-newmodel-light.hdf5
|_ templates
  |_ flask_api_index.html(メインのhtmlファイル)
  |_ result.html(結果を表示するページhtmlファイル)
|_ static
  |_ css
    |_main.css
  |_ images(htmlファイルに挿入する画像)
    |_xxxx.jpg
```

細かい説明は省略しますが、
app.pyとcnn_model.py は参考になる部分もあると思うので、載せておきます。


##実際のコード(一部)
```app.py
import numpy as np
from PIL import Image

import cnn_model
from flask import Flask, render_template, request

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('./flask_api_index.html')

@app.route('/result', methods=['POST'])
def result():
    body_list = ["筋肉型","標準型","痩せ型","肥満型"]
    # submitした画像が存在したら、画像データをモデル用に整形
    try :
        if request.files['image']:
            img_file = request.files['image']
            temp_img = Image.open(request.files['image'])
            temp_img = temp_img.convert("RGB") #色空間をRGBに
            #今回は、モデルの精度を上げるために(64,64)で画像を学習させています。
            temp_img = temp_img.resize((64,64))
            temp_img = np.asarray(temp_img)
            im_rows = 64
            im_cols = 64
            im_color = 3
            in_shape = (im_rows,im_cols,im_color)
            nb_classes = 4
            img_array = temp_img.reshape(-1,im_rows,im_cols,im_color)
            img_array = img_array / 255
            model = cnn_model.get_model(in_shape,nb_classes)
            #学習済みモデルを呼び出す
            model.load_weights("photos-newmodel-light.hdf5")
            predict = model.predict([img_array])[0]
            index = predict.argmax()
            body_shape = body_list[index]
            body_score = 90*predict[0]+70*predict[1]+40*predict[2]+40*predict[3]
            body_score = int(body_score)

            return render_template('./result.html', title='結果',body_score=body_score,body_shape=body_shape,img_file=img_file)
    
    except:
        return render_template('./flask_api_index.html')
    
if __name__ == '__main__':
    
    app.debug = True
    app.run(host='localhost', port=8888)

```

```cnn_model.py
# 簡単にモデルを作成できるので、kerasを用います。
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.optimizers import RMSprop

# CNNのモデルを定義する
def def_model(in_shape, nb_classes):
    model = Sequential()
    model.add(Conv2D(64,
              kernel_size=(3, 3),
              activation='relu',
              input_shape=in_shape))
    model.add(Conv2D(64, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))

    model.add(Conv2D(128, (3, 3), activation='relu'))
    model.add(Conv2D(128, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))

    model.add(Flatten())
    model.add(Dense(1024, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(nb_classes, activation='softmax'))
    return model

# コンパイル済みのCNNのモデルを返す
def get_model(in_shape,nb_classes):
    model = def_model(in_shape, nb_classes)
    model.compile(
        loss='categorical_crossentropy',
        optimizer=RMSprop(),
        metrics=['accuracy'])
    return model


```


#公開までの流れ
##1.全体像の把握
手順に従って進めていけば、最終的には以下のアーキテクチャでアプリを公開できます。
![AWSアーキテクチャ図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612161/a418a5b3-bf7b-0058-e4be-92d45a4bd374.jpeg)



##2.実際の手順
(1)AWSのアカウントを作成する
(2)AWSのサーバー上でpython環境の構築
(3)gitからフォルダをcloneする
(4)パブリックIPアドレスを固定化する
(5)独自ドメインの取得
(6)パブリックIPアドレスと購入ドメインの紐付け
(7)ミドルウェアをインストールする
(8)プロキシパスの設定
(9)HTTPをHTTPSにする

###(1)AWSのアカウントを作成する
ここは、下記の参考書を読めば一撃で理解できると思うので、割愛します。
<a href="https://www.amazon.co.jp/AWS%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86-%EF%BD%9EAWS%E3%81%AB%E3%82%88%E3%82%8B%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%82%921%E3%81%8B%E3%82%8910%E3%81%BE%E3%81%A7%EF%BD%9E-%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-mochikoAsTech-ebook/dp/B07K1FP44W/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=AWS%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86&qid=1606534145&sr=8-1">AWSをはじめよう</a>

あるいは、以下の資料でも大丈夫だと思います。
<a href="https://qiita.com/KeijiYONEDA/items/1c4f55d577877d97560a">AWS EC2でインスタンスを立てる</a>


###(2)AWSのサーバー上でpython環境の構築

```
$ sudo yum -y update
$ sudo yum install python36-devel python36-libs python36-setuptools
$ mkdir body_checker
$ python -m venv body_checker
$ source body_checker/bin/activate

(body_checker)$ pip install --upgrade pip
(body_checker)$ pip install flask
以下それぞれのアプリに必要なライブラリをインストール
```
参考資料
<a herf="https://newtechnologylifestyle.net/amazon-awsec2-flask/">Amazon AWS(EC2)でのFlask環境の作成方法</a>

###(3)gitからフォルダをcloneする
まず、ローカルで作成したフォルダを自分のgithubのリポジトリにあげてください。(githubのアカウント作成の説明などは省略します)
今回行う操作は、EC2上でsshを使ってgit clone して、欲しいフォルダなどを一括で取得するというものです。
**手順**
1.EC2上で公開鍵・秘密鍵の生成
2.特定のフォルダ(私でいうflaskというフォルダ)だけgit cloneする

1に関しては、以下の記事がとても参考になります。
<a ref="https://qiita.com/shizuma/items/2b2f873a0034839e47ce">GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~</a>

2に関しては、以下のコマンドで大丈夫だと思います。

```
(body_checker)$git init
(body_checker)$git config core.sparsecheckout true
(body_checker)$git remote add origin リポジトリのURL(https~)
(body_checker)$vim flask(git cloneしたいフォルダやファイル) > .git/info/sparse-checkout
(body_checker)$git pull origin master
```

ちなみに、私は「git pull origin masetr」でエラーが出たので、masterをmainにしたら出来ました。

参考資料
<a href="https://mseeeen.msen.jp/git-sparse-checkout/">git sparse checkout で clone せずに一部のサブディレクトリだけを pull/checkout する</a>

###(4)パブリックIPアドレスを固定化する
この理由としては、EC2においてIPアドレスはインスタンスが停止すると毎回変わってしまうからです。
それを防ぐために、ElasticIPという静的な(勝手に変わらない)IPアドレスをサーバーに固定のIPアドレスとしてつけましょう。
**手順**
1.「Elastic IP」メニューから「新しいアドレスの割り当て」をクリック
2. ElasticIPをインスタンスに紐付ける

###(5)独自ドメインの取得
これは、私のサイト(https://www.body-score-checker.ml) でいう**body-score-cheker.ml**の部分です。
これは以下のサイトで無料で作成できます。
<a href="https://www.freenom.com/ja/index.html">Freenom</a>

###(6)パブリックIPアドレスと購入ドメインの紐付け
この作業はRoute53というサービスを使います。

**注意事項**
全体像では、Route53からALBを経由してDNSを解決していますが、ここでは直接ドメインをEC2のElastic IPに紐づけてDNSを解決してます。ALBを使用したDNS解決は、(9)で説明しています。
目的としては、最初は簡単なアーキテクチャにして、なるべく早くwebでアプリを見れるようにするためです。



以下のサイトがわかりやすかったので、そちらを参照して進めてみてください。
<a href="https://avinton.com/academy/route53-dns-vhost/">AWS Route 53を使って独自ドメインのWebページを表示させてみよう</a>

###(7)ミドルウェアをインストールする
全体像の図でも示している通り、Apacheをインストールします。
以下インストールの方法
<a href="https://qiita.com/ryuutamaehara/items/2ce8fb5d78bca5306153">EC2インスタンスにApache HTTP Serverをインストールする</a>

ミドルウェアをインストールする理由は、クライアントからのアクセスとflaskサーバーの仲介役を担ってもらうためです。

**以下のその簡単な流れ**
・「Application Load Balancer」でクライアントからのリクエストがポート80番に送られます。
・Apacheはポート80番に対応するので、そのアクセスを受け取ると静的コンテンツをクライアントに返します。(flask_api_index.htmlなど)
・もし動的な操作が必要な場合は、Apacheからflaskサーバー(ポート番号:8888)にリクエストが送られます。
・そこで、先ほど立てたflaskサーバーが動作する。(私の場合だと、送られてきた画像を機械学習のモデルに入れて点数を算出する。)

###(8)プロキシパスの設定
(6)で通信の簡単な流れを説明しましたが、そこで「Apacheからflaskサーバーにリクエストが送られます。」とありますが、これはこちらで設定する必要があります。
それが、**プロキシパスの設定**です。

・confファイルを修正する

```
$ sudo vi /etc/httpd/conf.d/vhost.conf
```


・修正内容

```
NameVirtualHost *:80

DocumentRoot "/var/www/html/"
ServerName body-score-checker.ml


ProxyRequests Off
ProxyPass / http://localhost:8888/
ProxyPassReverse / http://body-score-checker.ml/

```

・httpd再起動

```
$ sudo service httpd restart
```
ここまでで、Web上でアプリケーションが動作するようになると思います。
・以下実行コマンド

```
(body_checker)$ python app.py
```

また、sshでの接続が切れても、serverを立ち上げたままにするためには以下のコマンドを使用します。

```
(body_checker)$ nohup python app.py &
```
以下参考資料
<a href="https://applingo.tokyo/article/1159">SSH切断した後でもVPS上でPythonを実行したままにする方法</a>

###(9)通信方式をHTTPからHTTPSにする
基本的にはこれで問題なくWeb上で動作するのですが、私の場合は個人の画像を取り扱うのでhttpからhttpsに通信方式を変えています。
このタイミングで、全体像の真ん中にあるALB(Application Load Balancer)を導入しています。

ロードバランサーの作成は以下の資料が参考になると思います。
<a href="https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/create-application-load-balancer.html">Application Load Balancer を作成する </a>
<a href="https://dx.nissho-ele.co.jp/blog/aws-beginner-ec2-elb_20190410.html">AWS ELBでWebサーバの負荷分散を実現！</a>


ロードバランサーの作成が終わったので、最後に通信方式をHTTPからHTTPSにします。
<a href="https://aws.amazon.com/jp/premiumsupport/knowledge-center/elb-redirect-http-to-https-using-alb/">Application Load Balancer を使い HTTP リクエストを HTTPS にリダイレクトするにはどうすればよいですか?</a>
<a href="https://dev.classmethod.jp/articles/alb-redirects/">[新機能]Webサーバでの実装不要！ALBだけでリダイレクト出来るようになりました！</a>



#１人でWebアプリケーションを作成してみて
このように最後まで１人で作成してみて感じる事は、Webアプリケーションの仕組みを体系的に理解するには自分で手を動かして全て触ることが一番いいということです。
今回は私は、フロントからバックエンドまで全て自分でコードを書いて実装しましたが、Web上で実際にどのような処理が行われているのかなど、そうゆう大局の部分の理解がかなり捗ったと思います。
なので、これからももっといろんな技術に触れてみたいと思います。


もしご指摘等ありましたら、バシバシ言っていただけると私自身の勉強になりますのでお願いいたします。
そして、最後まで読んでくださりありがとうございます。少しでも何か参考になる部分がありましたら幸いです。


最後に、このアプリやこの記事を作るにあたって参考にした記事を紹介して終わりにしたいと思います。
<a href="https://qiita.com/yusuke-saruya/items/ee39e99ee1e412751cac">バナナの食べ頃を機械学習モデルで判定するWEBアプリを作ってみた</a>