まるっとワーク

データ分析・電子工作など気になることを残していきます

できるだけPythonだけでWEBアプリを作る②(Python flask/zappa + AWS)

「できるだけPythonだけでWEBアプリを作る第2弾」は、第1弾から少し構成を変えて、拡張性を考慮したやり方で作っていきます。
今回はZappaというフレームワークを使っており、様々なAWSの設定が簡単になる反面、裏で何をやっているのかが完全に理解できなかったので、とりあえずシステムの作成フローをこのページにまとめて、掘り下げて分かったことは追ってまとめようと思います。
第1弾(前回)記事は以下の通り。
dango-study.hatenablog.jp

目次


WEBアプリをクラウド(AWS)で作成するためのアプローチ

前回はとりあえずWEBアプリを作る!という感じだったので、特に気にしていなかったのですが、本来はどんなアプローチ方法がとれるのかを踏まえた上で適切に選択すべきなので、ここで一旦まとめようと思います。調べた限り、(AWS)サービスの組み合わせ方で言うと、かなり色々な方法があったので、主観ですが大きなくくりでまとめていきます。

FaaS(Function as a Service)サービスを使うアプローチ[AWS Lambda + API Gateway等]

FaaSとは「Function as a Service」の略で、サーバーレスでアプリケーション開発ができるクラウドサービスのこと。
AWS Lambda等のインスタンスを返してサーバーを任意のタイミングで利用するパターンで、サーバー管理はサービスベンダーが行うため、その費用などを抑えることができる。

CaaS(Container as a Service)サービスを使うケース[ECS + Fargate等]

CaaSとは「Container as a Service」の略で、コンテナ化をすることで環境に依存しない開発ができるクラウドサービスのこと。
CaaSサービスであるECSはAmazon EC2インスタンスを用いてDockerコンテナ管理することができるが、Amazon EC2のような仮想サーバーの管理は不要なアーキテクチャを構築できる。

IaaS(Infrastructure as a Service

IaaSとは「Infrastructure as a Service」の略で、サーバやストレージなどのインフラを提供するサービスのこと。
仮想サーバーを使用できることから開発自由度は上がるが、サーバー管理が必要であること(作業が増える)、コストが他と比較して高い。

LambdaとECS、EC2を様々な観点で比較結果は以下が詳しくまとめられていました。感謝です
EC2 vs ECS vs Lambda:APIのサービングの観点から | Hakky Handbook

つまり、使い分けは、以下の通りと理解しました。
現時点ではLambdaでできる範囲で問題がなさそうなので、コストを重視してLambdaを使う構成で対応します。

おすすめサービス
開発における制限(実行時間やライブラリ利用可否)を重視する場合 ECSかEC2
コストを重視する場合 Lambda


アーキテクチャについて

(おさらい)前回作ったアプリのアーキテクチャ

以下画像のような構成で、Amazon S3に保存したHTMLファイルにアクセス頂き、HTMLページのボタンを押すと、JavaScriptからAmazon API GatewayにHTTPリクエストが送信され、 AWS Lambdaを動かして、計算した結果をJavaScriptでHTMLに上書きするといった動きをします。

今回作るアプリのアーキテクチャ

ここが完全に理解できていないので、現状記載ができない(後述のZappaが理解できていない)。なんで、前回と同じ構成で作らないのか?という疑問が出てくるが、今回はFlaskというPythonのWebアプリケーションフレームワークで作成したWebアプリケーションをAWSでデプロイしたいから、が理由になります。今後も想定すると、1からすべて作る前回の方法よりもフレームワークを使った方がよさそうだなと・・・また、今回はFlaskのWEBアプリケーションをAWSにデプロイするのに、Zappaというフレームワークも使っています。ZappaはAWS LambdaとAPI Gatewayを使って、FlaskやDjangoなどのPython WEBアプリケーションをデプロイすることができます。AWS上の設定などを自動で設定して、構成してくれるのが便利なようです。このような理由から、こちらを使いました。
AWS マネジメントコンソールのAWS Lambda画面では、以下のような画面になっていますが、全体の構成としては、AWS Lambda/EventBridge/API Gatewayの構成になっている?この辺りから怪しいのですが、アクセスするURL(WEBアプリ)はAPI Gatewayで作成するHTTPエンドポイントで、HTTPリクエストをAWS Lambda関数に転送して実行していると思っています。ただ、EventBridgeでAWS Lambdaを定期実行する必要があるのでしょうか?Zappaを使うと、インターフェースの設定を勝手にやってくれるのが便利なのですが、どう設定をしたのかを勉強したい人にとってはつらいですね・・・

分かっていない部分も多いですが、、じゃんけんゲームのアプリを作っていきます。

ちなみに、その他の同じような構成(FaaSサービスを使うケース)例として、以下を紹介しておきます。
AWS での開始方法 | AWS Lambda、Amazon API Gateway、AWS Amplify、Amazon DynamoDB、および Amazon Cognito を使用してサーバーレスウェブアプリケーションを構築する

開発環境

[ローカル]
Microsoft Windows 10 Pro
Python 3.9.0
[AWS]
AWS Lambda ランタイム python3.9
AWS API Gateway
Amazon S3

開発手順

  1. 前準備
  2. アプリ作成(コード作成)
  3. コード実行(動作確認)


実装

前準備

AWS CLIを使えるようにする(省略)

ZappaではAWS CLIを使用する為、使えるようにしておく必要がある。
こちらは色々なページで記載があるので省略します。
AWS CLIの設定(以下)をするところまで対応します。

$ aws configure

↓参考ページ
AWS CLIを利用するメリットと導入方法 | TOKAIコミュニケーションズ AWSソリューション

仮想環境の導入

Zappaを使うには仮想環境の用意が必須となる為、その手順を説明します。
手順は以下の通りコマンドとともに示します。

1. 仮想環境を作る venvモジュールを使用する。pyenvモジュールを使うケースも多いみたいであるが、Windows OSだと問題がありそうなのでこっちを使う(私はpythonバージョン3.9.0が入っていたので、Python 3.9.0の仮想環境ができる)
$ python -m venv <好きなファイル名>
$ cd <上記コマンドで作成したファイルパス>

2. 仮想環境に入る
$ Scripts\activate

必要モジュールのインストール

Zappaでは仮想環境の用意が必須で、アプリを構成するコード類と合わせて仮想環境のモジュールもzip化してAWS Lambdaにアップロードするので、不要なモジュールを入れすぎても負荷を掛けそうなので、最低限のモジュールをインストールする。
ここからの作業は仮想環境に入った状態で対応します。

$ pip install flask
$ pip install zappa

私の環境では、この後のZappaモジュール実行時にurlib3に関するエラーが出た為(バージョン指定)、エラーに従って以下の通り対応した。
$pip uninstall urllib3
$pip install "urllib3>=1.25.4,<1.27"

Zappaの設定

WEBアプリケーションのディレクトリを作成して、その中で初期化コマンドを実行します。

適当にフォルダを作成する
$ mkdir 適当な名前
$ cd <上記コマンドで作成したファイルパス>

Zappaの初期化を行う
$ zappa init


適当に設定すると(後から変えれるので全てEnterでも良い)、現在のディレクトリにzappa_settings.jsonが作成される


アプリ作成(コード作成)

やっとアプリの作成に移り、じゃんけんゲームのアプリを作っていきます。
htmlは2種類、初期画面/じゃんけん結果画面の2つであり、コードを含めたもろもろの構成は以下の通りです。

<flask_sample> 
 ├ <templates>  
 │  ├ home.html 初期画面 
 │  └ result.html じゃんけん結果画面
 └ __init__.py   
my_app4.py アプリで最初に実行されるファイル
requirements.txt 必要モジュールなどを記載したtxt
zappa_settings.json zappa設定ファイル

WEBページの作成

初期画面/じゃんけん結果画面は以下のコードで構成しています。

home.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>じゃんけんゲーム</title>
	<style>
		body {
			font-family: Arial, sans-serif;
			text-align: center;
		}
		h1 {
			font-size: 32px;
		}
		form {
			margin-top: 32px;
			display: flex;
			flex-direction: column;
			align-items: center;
		}
		form label {
			font-size: 20px;
			margin-bottom: 8px;
		}
		form input[type="radio"] {
			margin-right: 8px;
		}
		form button {
			margin-top: 16px;
			font-size: 24px;
			padding: 8px 16px;
			border: none;
			background-color: #1E90FF;
			color: #FFFFFF;
			border-radius: 8px;
			cursor: pointer;
		}
		form button:hover {
			background-color: #4169E1;
		}
	</style>
</head>
<body>
	<h1>じゃんけんゲーム</h1>
    <form action="{{ url_for('play') }}" method="POST">
		<label><input type="radio" name="choice" value="グー" required>グー</label>
		<label><input type="radio" name="choice" value="チョキ">チョキ</label>
		<label><input type="radio" name="choice" value="パー">パー</label>
		<button type="submit">決定</button>
	</form>
</body>
</html>

ハマったところとして、画面遷移が最初はうまくいきませんでした・・・("forbidden 403"が表示)
該当箇所は、

(修正後です)の個所で、初期はパスを直書きしていたことが問題でした。Flaskのurl_for関数を使って関数とURLを紐づけてアクセスできるように変えています。url_for('play')と書くことで、関数:playが実行されます。詳細は以下参照しました。
【Python】Flask url_forについて | しげっちBlog


result.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>じゃんけんゲーム - 結果</title>
	<style>
		body {
			font-family: Arial, sans-serif;
			text-align: center;
		}
		h1 {
			font-size: 32px;
			margin-top: 48px;
			margin-bottom: 32px;
		}
		.result {
			font-size: 24px;
			margin-bottom: 16px;
		}
	</style>
</head>
<body>
	<h1>結果</h1>
	<div class="result">あなたの選択: {{ user_choice }}</div>
	<div class="result">コンピュータの選択: {{ computer_choice }}</div>
	<div class="result">結果: {{ result }}</div>
	<a href="{{ url_for('home') }}">もう一度プレーをする</a>
</body>
</html>

{{}}でくくられた箇所が変数であり、pythonコードから返された結果が表示されます。

pythonコードについて

pythonコードは2ファイル(__init__.pyだけの1ファイルだけでも良かったが・・)に分かれていて、最初にmy_app4.pyが実行される設定にしています。
コードは以下の通りです

my_app4.py

#----------
#Flaskアプリケーション実行ファイル
#----------

from flask_sample import app

if __name__ == "__main__":
    app.run()

__init__.pyを実行するだけの役割ですね


__init__.py

#----------
#Flaskアプリケーション立ち上げ後に呼び出されるファイル
#my_app4.py→__init__.py
#----------

#Flaskとrender_template(HTMLを表示させるための関数)をインポート
from flask import Flask,render_template, request, url_for
import random

#Flaskオブジェクトの生成
app = Flask(__name__)

#「/」へアクセスがあった場合に、「home.html」を返す
@app.route("/")
def home():
    return render_template("home.html")

@app.route("/result", methods=["POST"])
def play():
    # ユーザーの選択を取得
    print("def_play_start")
    user_choice = request.form["choice"]
    
    # コンピュータの選択をランダムに決定
    choices = ["グー", "チョキ", "パー"]
    computer_choice = random.choice(choices)

    print("chiices", choices)
    
    # 勝敗を判定
    result = ""
    if user_choice == computer_choice:
        result = "引き分け"
    elif (user_choice == "グー" and computer_choice == "チョキ") or (user_choice == "チョキ" and computer_choice == "パー") \
        or (user_choice == "パー" and computer_choice == "グー"):
        result = "勝ち"
    else:
        result = "負け"
    
    # 結果を表示
    return render_template("result.html", user_choice=user_choice, computer_choice=computer_choice, result=result)

コチラのコードがメインとなり、初期アクセスでは関数: homeが実行され、後述のhome.htmlが表示されます。

コード実行(動作確認)

アプリのデプロイ

作ったアプリは、zappa_settings.jsonファイルがあるディレクトに移動して、以下コードでAWSにデプロイします。

デプロイする
$ zappa deploy default ←defaultは自分が設定した値

一度デプロイをして、アップデートをしたい場合は以下を実行
$ zappa update default ←defaultは自分が設定した値

アプリケーションを削除
$ zappa undeploy default ←defaultは自分が設定した値

デプロイ時などのエラーを見たい場合
$ zappa tail

デプロイが完了すると、以下文字が出力され、URLが吐き出される。

Your updated Zappa deployment is live!: https:/*******************


動作確認

吐き出されたURLにアクセスする。


決定をクリックする

どこからでもアクセスができるURLでじゃんけんゲームができるようになりました。

まとめ

今回は、前回とは違う構成でWEBアプリを作ってみました。
フレームワークを使うと、便利なのですが若干ブラックボックスな部分が出てくるので、理解には少し時間がかかりそうです・・・
ただ、Flaskを使ったアプリを展開できるようになったので、もう少し複雑なものを作ってみたいと思います。