Flaskアプリ起動用シェルスクリプトの引数によって起動するサーバーを選択するスクリプトの作成方法
Qiitaの過去の投稿にWSGIサーバー(waitress)に関するものが何件か有りましたが、運用面から切り込んだ記事が見当たらなかったので、今回は実際に運用する場合に使うスクリプト作成方法を解説いたします。
想定する実行環境
- OS: Linux (Raspberry Pi OS 含む)
-
Python仮想環境を作成し、仮想環境の python で実行すること
- Pythonバージョン: 3.8 ※本番環境は Raspberry Pi OS (Desktop)を想定
- ライブラリ: Falsk, Waitress がインスール済みであること
- 各動作環境サーバーのホストとポート
- 開発時: localhost(127.0.0.1), ポート: 5000
- 運用時: 外部に公開可能なホスト名 (例) raspi-4-dev.local (192.168.0.20), ポート: 12345
※1 起動時に環境変数に設定することを想定
※2 以降、Raspberry Pi OS (Desktop) をラズパイ4(開発機) とします。
以下はこの記事のスクリプトを実行した開発PC(Ubuntu 22.04)の/etc/hosts の抜粋
# これらはOSインストール時に設定されたもの
127.0.0.1 localhost
127.0.1.1 Dell-T7500
# これはFlaskアプリケーション用に設定したもの
192.168.0.101 dell-t7500.local
参考URL
Flask: (ja.readthedocs.io) クイックスタートQuickstart
Waitress: Pylons Waitress 2.1.2 documentation >> Usage
The following will run waitress on port 8080 on all available IPv4 addresses, but not IPv6.
from waitress import serve
serve(wsgiapp, host='0.0.0.0', port=8080)
By default Waitress binds to any IPv4 address on port 8080. You can omit the host and port arguments and just call serve with the WSGI app as a single argument:
from waitress import serve
serve(wsgiapp)
リソース一覧
Flask_to_waitress/
├── flask_to_waitress
│ ├── app_main.py # Flaskアプリケーション
│ └── templates
│ └── index.html
├── run.py # 開発時にFlask組込みサーバー、運用時Waitressサーバーを起動するpythonスクリプト
└── start.sh # Webアプリケーションサーバー起動シェルスクリプト
1. Webサーバー起動シェルスクリプト
start.sh
(1) 環境変数の設定
-
運用環境の引数が指定された場合: "prod" or "production"
- ホスト名を /etc/hostname から取得
- Flaskアプリのホスト名として ".local"を付与したあとに小文字化する
- Flaskアプリホスト名を環境変数に設定
- ポート番号を環境変数に設定
-
FLASK_ENV環境変数に動作環境文字列を設定: "development" or "production"
#!/bin/bash
# ./start.sh -> development
# ./start.sh prod | production ->production
env_mode="development"
if [ $# -eq 0 ]; then
:
else
if [[ "$1" = "prod" || "$1" = "production" ]]; then
env_mode="production"
fi
fi
if [[ $env_mode = "production" ]]; then
host_name="$(/bin/cat /etc/hostname)"
IP_HOST_ORG="${host_name}.local"
export IP_HOST="${IP_HOST_ORG,,}" # to lowercase
# 通常はここでは指定しない
export FLASK_PROD_PORT=12345
fi
export FLASK_ENV=$env_mode
echo "$IP_HOST with $FLASK_ENV"
(2) Flaskアプリ実行
- 環境変数 "PATH_FLASK_WAITRESS" の有無チェック
- 指定されている場合: 環境変数を実行パスに設定 ※開発PCのソースディレクトリを想定
- 設定されていない場合: ユーザーホームの "Flask_to_waitress" ※本番環境を想定
- python仮想環境 (py38_raspi4) に切り替える (activate)
- python仮想環境のpythonで
run.py
を実行する
EXEC_PATH=
if [ -n "$PATH_FLASK_WAITRESS" ]; then
EXEC_PATH=$PATH_FLASK_WAITRESS
else
EXEC_PATH="$HOME/Flask_to_waitress"
fi
. ${HOME}/py_venv/py38_raspi4/bin/activate
python ${EXEC_PATH}/run.py
deactivate
2. 実行環境に対応するサーバーを起動するpythonスクリプト
2-1. Flaskアプリケーション
(1) HTMLテンプレート
<!DOCTYPE html>
<html lang="ja">
<body>
<h2>Flask to Waitress on {{ server_host }}</h2>
<hr/>
<p> {{ current_date }}</p>
</body>
</html>
app_main.py
Flask クラスのインスタンス作成
from datetime import datetime
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
server_host: str = app.config["SERVER_HOST"]
now_dt: datetime = datetime.now()
now: str = now_dt.strftime("%Y年%m月%d日 %H時%M分%S秒")
return render_template("index.html",
server_host=server_host,
current_date=now
)
2-2. 環境に応じたサーバーを起動するpythonスクリプト
run.py
- 起動シェルスクリプトから環境変数を取得する
- Flask実行環境 (FLASK_ENV) の取得: 運用("production") | 開発("development")
- ホスト名 ("IP_HOST") の取得
(A) 設定されている場合: 指定されたホスト名を使用する ※ "0.0.0.0" は使いません
(B) 設定されていない場合: "localhost" を設定
- Flask実行環境
- 運用: waitress の serve関数をインポートし、ホストとポートを設定して起動する
- 開発: Flask組込みサーバーを起動する
import os
from flask_to_waitress.app_main import app
if __name__ == "__main__":
has_prod: bool = os.environ.get("FLASK_ENV", "development") == "production"
IP_HOST: str = os.environ.get("IP_HOST", "localhost")
PORT: str = os.environ.get("FLASK_PROD_PORT", "5000")
app.config["SERVER_HOST"] = f"{IP_HOST}:{PORT}"
port_num: int = int(PORT)
if has_prod:
# Production mode
try:
# Prerequisites: pip install waitress
from waitress import serve
serve(app, host=IP_HOST, port=port_num)
except ImportError:
print("Production Waitress server not found!")
exit(1)
else:
# Development mode
app.run(host=IP_HOST, port=port_num, debug=True)
3. 起動スクリプト実行
3-1 (1). 開発PC: 開発環境として起動
(1) 予め開発PCのアプリケーションパスを環境変数に設定してから実行
(py38_raspi4) $ export PATH_FLASK_WAITRESS=~/project/Qiita/flask_apps/Flask_to_waitress
(py38_raspi4) $ ./start.sh
with development
* Serving Flask app 'flask_to_waitress.app_main'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://localhost:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 972-050-977
[ブラウザで確認] http://localhost:5000
(2) 実行結果
127.0.0.1 - - [26/Nov/2023 17:04:05] "GET / HTTP/1.1" 200 -
3-1 (2) 開発PC: 本番環境として起動
(1) 起動引数に prod を指定して起動
(py38_raspi4) $ ./start.sh prod
dell-t7500.local with production
[ブラウザで確認] http://dell-t7500.local:12345
Waitressサーバーを起動した場合、waitressのコンソールログは出力されません。
※ dell-t7500.local with production
はシェルスクリプトで出力したもの
3-2. ラズパイ4(開発機): 本番環境として起動
pi@raspi-4-dev:~/Flask_to_waitress $ ./start.sh prod
raspi-4-dev.local with production
netstat
で待受けを確認
pi@raspi-4-dev:/etc $ sudo netstat -tp
稼働中のインターネット接続 (w/oサーバ)
Proto 受信-Q 送信-Q 内部アドレス 外部アドレス 状態 PID/Program name
tcp 0 0 raspi-4-dev.local:12345 192.168.0.101:39602 ESTABLISHED 1272/python
tcp 0 0 raspi-4-dev.local:ssh 192.168.0.101:50592 ESTABLISHED 747/sshd: pi [priv]
tcp 0 0 raspi-4-dev.local:12345 192.168.0.101:39610 ESTABLISHED 1272/python
tcp 0 0 raspi-4-dev.local:ssh 192.168.0.101:48736 ESTABLISHED 1077/sshd: pi [priv
[補足] serve(app, host='0.0.0.0', port=12345) とするとどうなるか
ラズパイ側の /etc/hosts に 192.168.0.20 raspi-4-dev
の定義がなくとも、プライベート内のPCのブラウザから下記でアクセスできます
※ブラウザアクセスするPC側では /etc/hosts に 上記 IPアドレスとホスト名の定義が必要です。
http://rasi-4-dev:12345
ホスト名に".local"をつけている理由
ラズパイ4(本番機)がモバイル接続のAndroid端末向けにホスト名が".local"が付与されている前提でリバースプロキシを構築してるからになります。
ラズパイで稼働するFlaskアプリをインターネットにリバースプロキシ経由で公開する方法について下記GitHubで公開しております。
IoT初歩の初歩 Androidアプリからラズパイに繫ぐ: 3.Webサーバによるリバースプロキシの構築
4. 結論
Flaskアプリケーションの起動シェルスクリプトを作成することにより、Webアプリケーションのシステムサービス化が容易になります。
本番環境ではサーバーが起動したら自動でアプリケーションが立ち上がりますよね。
Qiitaでの類似の投稿でこのような内容のものが見つからなかったのてあえて紹介させていただきました。
参考として起動シェルスクリプトを使ったシステムサービス化のコード例を以下に示します
(1) 環境変数ファイル: /etc/default/webapp-flask-to-waitress
FLASK_PROD_PORT=12345
(2) サービス設定ファイル: /etc/systemd/system/webapp-flask-to-waitress.service
※1 コメント部分は通常はいれません。データベースサーバーの後などのサービスを設定します
[Unit]
Description=Flask webapp PlotWeather service
# わざとコメントにしていま ※本番環境ではデータベースサーバの起動後などの設定をするのが一般的
# After=postgres-12-docker.service
[Service]
Type=idle
# FLASK_PROD_PORT
EnvironmentFile=/etc/default/webapp-flask-to-waitress
ExecStart=/home/pi/Flask_to_waitress/start.sh prod >/dev/null
User=pi
[Install]
WantedBy=multi-user.target
システムサービス化に関しては下記Qiita記事もご覧ください。
(Qiita) RaspberryPi Pythonアプリケーションをシステムサービス化する
今回の記事の元になった起動シェルスクリプトは下記GitHubリポジトリでご覧になれます。
GitHub(pipito-yukio) UDP Weather Sensor packet monitor for Raspberry pi 4 src/installer