概要
Flaskアプリケーションを立ち上げる手順や関連する設定について記載します。
本記事の内容はローカル環境にDockerなど介さずに直接プロセスを立ち上げるケースとなります。
Dockerを利用したケースについてはDocker編を参照ください。
前提
- ローカル環境でpythonが実行可能であること
- Pythonバージョンが3.8以上であること
- MacOS向けの記載となっている
全体像
記事早見表
- 似たような記事を読んだとき、よくなんで?どういう意味?と感じる -> ○○ってどういうもの/こと?
- 動けばいいや -> 動かしてみる
- gunicornやNginxの設定ファイルについて知りたい -> 設定ファイル、起動/終了コマンドを紐解く
動かしてみる
まずは実際に上記構成で動かしてみましょう。
必要となるパッケージをインストールしておきます。
brew install nginx
pip3 install gunicorn
プロジェクトの構成と各ファイルの内容
PROJECT
├── config
│ ├── gunicorn_settings.py # アプリケーションサーバの設定ファイル
│ └── nginx.conf # Webサーバの設定ファイル
└── flask_app.py # メインとなるアプリケーション
- アプリケーションファイル
今回、アプリケーションの挙動については重要視しないので、文字列を返却するルートを2つだけ用意することとします。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index() -> str:
return 'Index Page!'
@app.route('/health')
def health() -> str:
return 'Health Check OK!!'
- Webサーバ設定ファイル
import os
bind = '127.0.0.1:' + str(os.getenv('PORT', 9876))
proc_name = 'Infrastructure-Practice-Flask'
workers = 1
- Nginx設定ファイル
# 詳細は設定ファイルの項を参照
worker_processes 1;
events {
worker_connections 512;
}
http {
server {
listen 9123;
server_name INFRA-PRACTICE-NGINX;
charset UTF-8;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
location / {
proxy_pass http://127.0.0.1:9876;
}
location /health {
proxy_pass http://127.0.0.1:9876/health;
}
}
}
プロセスを起動する
# Webサーバの起動
nginx -c ${PROJECT_PATH}/config/nginx.conf
# アプリケーションサーバの起動
gunicorn flask_app:app -c config/gunicorn_settings.py
疎通確認する
curl http://localhost:9123/
# -> Index Page!と返ってくればOK
curl http://localhost:9123/health/
# -> Health Check OK!!と返ってくればOK
設定ファイル、起動/終了コマンドを紐解く
gunicorn
設定ファイル
公式ドキュメントでは、オプション形式で記載されています。
設定ファイルを利用する場合、設定項目名をkeyとしたkey=value
形式で表記します(e.g. loglevel = 'debug'
)。設定ファイルの拡張子は任意です。
設定の一部を以下に示します。
key | 意味 | 型 | デフォルト値 |
---|---|---|---|
reload | コードを変更したとき自動的に読み直して再起動する | bool | False |
accesslog | サーバへのアクセスログ出力先 ファイルを指定、「-」だと標準出力を意味する e.g. accesslog = logs/gunicorn_access.log
|
string | None |
errorlog | エラーログ出力先 ファイルを指定、「-」だと標準出力を意味する e.g. errorlog = logs/gunicorn_err.log
|
string | '-' |
proc_name | psコマンドで表示されるプロセス名称 特に指定しないとdefault_proc_nameである「gunicorn」となる |
string | None |
limit_request_line | リクエストの最大サイズ、DDos対策で指定 同様の項目にlimit_request_fields |
int | 4094 |
bind | 接続ホストおよびポート HOST:PORT形式で指定 e.g. bind = os.getenv('HOST') + ':' + os.getenv('PORT')
|
string | 127.0.0.1:8000 |
daemon | gunicornプロセスをデーモン起動する | bool | False |
workers | リクエストをさばくWorkerプロセスの数 親プロセスが1つ起動し、workersの数だけ子プロセスを立てこちらでハンドルする 並列を考慮できていない場合に2以上を指定すると、不具合のもととなる |
int | 1 |
worker_connections | 最大接続数 | int | 1000 |
起動/終了コマンド
【起動】
gunicorn {Flaskアプリケーションファイル名}:{ファイル内のFlaskインスタンス変数名} -c {設定ファイルのパス}
※設定ファイルの項で述べたとおり、Settingは起動時オプションでも指定可能。
【終了】
プロセス起動しているTerminalにおいて、Ctrl+C
Nginx
設定ファイル
公式ドキュメントを見てみると、gunicornのkey=value
形式とは異なり、
Module内部にDirective(命令)をコンテキスト形式(curly bracket: {}
で囲まれるブロック)で記述することがわかります。
Module
Module名 | 記載する内容 |
---|---|
core | ログ、プロセス制御 最も外側のコンテキストがこれにあたる |
events | イベント系処理 |
http | Webサーバにかかわる処理 おそらく最も多く記述するのがこのモジュール |
※そのほか、mailやstreamが存在。
Directive Syntax in http context
httpコンテキスト内にはserverコンテキスト、serverコンテキスト内にはさらにlocationコンテキスト
といったように、コンテキストinコンテキスト形式で記述していきます。
ここではhttpブロックの設定例を示します。例を見ることで、冒頭の設定ファイルが何を意味しているか理解が進むのではないでしょうか。
- 基本の設定
http {
server {
listen 80; # 80番ポートで接続を待つ
server_name MY_SERVER; # サーバ名はMY_SERVER
charset UTF-8; # レスポンスヘッダのContent-typeをUTF-8に指定
error_page 404 /404_not_found.html; # ステータスコードが404のときに404_not_found.htmlへ内部的にリダイレクト
}
}
- リバースプロキシ
http {
server {
# /indexへのアクセスを
location /index {
proxy_pass http://127.0.0.1:8080; # 127.0.0.1の8080ポートへリバースプロキシする
proxy_set_header Host $host; # プロキシされたサーバへ渡すリクエストヘッダのうち、Hostに$hostを設定し直す
proxy_read_timeout 60s; # プロキシされたサーバからの応答が60秒なかったらタイムアウトとする
}
}
}
- アクセス制御/IPフィルタリング
http {
server {
location / {
allow 192.168.0.1; # 192.168.0.1からのアクセスを許可して
deny all; # それ以外のネットワークアクセスを拒否する
}
}
}
- BASIC認証
http {
server {
location / {
auth_basic "Basic Auth closed site"; # BASIC認証が必須の旨を明示し、
auth_basic_user_file conf/htpasswd; # .htpasswdファイルパスを指定する
}
}
}
- Referrerチェック
http {
server {
# リクエストヘッダにRefererがない(none)、ヘッダにあるがhttp://またはhttps://で始まらない(blocked)、
# mintak.comで終わらない(server_names) ルートからのアクセスの場合はinvalidと判定 ->$invalid_refererに1をセット
valid_referers none blocked server_names *mintak.com
if ($invalid_referer) {
return 403; # invalidと判定した場合は403 Forbiddenで返却
}
}
}
Embbed Variable
Nginxサーバにおける環境変数のようなもの。詳しくはこちら。
一部を以下に示します。
variable | 内容 |
---|---|
$host | リクエストヘッダのHost、ない場合はサーバ名 |
$remote_addr | 接続クライアントのアドレス |
$remote_port | 接続クライアントのポート |
$status | レスポンスのステータス |
$server_name | サーバの名称 |
$http_name | HTTPリクエストヘッダのフィールド、nameの部分にヘッダフィールド名を小文字、underscoreで設定 e.g. http_host、http_referer |
設定のバリデーションチェック
-t
をつけることで設定ファイルのバリデーションを行うことが可能です。
nginx -c {設定ファイルの絶対パス} -t
問題ない場合はsyntax is ok
/test is successful
と表示され、設定誤りがあると以下のように問題のある箇所が表示されます。
nginx: [emerg] "proxy_pass" directive is not allowed here in /Users/mintak/infra-server-env-build-practice/config/nginx.conf:29
nginx: configuration file /Users/mintak/infra-server-env-build-practice/config/nginx.conf test failed
起動・終了コマンド
【起動】
nginx -c {設定ファイルの絶対パス}
- 相対パスの場合、nginxのルートからのパスとなるため、
nginx: [emerg] open() "{RELATIVE PATH}" failed (2: No such file or directory)
とメッセージが出ることがあります。
【終了】
nginx -s stop
# 上記に失敗する場合はプロセスを特定してkill
ps ax | grep nginx
kill -9 ${PID}
【起動確認】
ps aux | grep nginx # ここでnginx: master processと出てきた場合はnginxが動いている。
# PORTの確認ができるコマンド
lsof -i -P | grep nginx
#=> nginx 41845 mintak 6u IPv4 0x595e7f947462ada5 0t0 TCP *:9123 (LISTEN)
○○ってどういうもの/こと?
ここまでのファイルを用意してコマンドをたたけば目的は達成できます。しかし、自分のような初学者には難しい言葉や概念が多く出ています。ここからは、そうしたキーワードに関して自分なりの解釈を記載していきます。
どうしてこのような構成になるのか
有識者曰く、かつてはWebサーバとアプリケーションサーバの分割はされていなかったとのこと。この構成が問題となったのは、インターネットの普及に伴い、一般公開しているサービスに対するアクセス数が増えたことでサーバへの負荷が高まり、全体的にパフォーマンスが落ちるという自体に直面したときでした。
そこで、負荷を分散させることで安定稼働することを目指し、後述のように役割に応じてサーバを分割する流れになりました。この思想が現代においては当たり前で、一般的にWebサービスはWebサーバ<-->アプリケーションサーバ<-->DBサーバ
という三層構造で作られているそうです。
役割ごとにプロセス(サーバ)を分けるという根本の考え方は、コーディングの際の設計思想(1クラス/1関数ごとに明確な役割は1つずつ)と通じるところがあると思います。
でもpython flask_app.py
で動くじゃん
これはFlaskというフレームワークがWebサーバ/アプリケーションサーバを兼任した状態で立ち上げることのできるフレームワークであるためです。
開発時の簡単な検証や動作確認をする際など、わざわざWebサーバとアプリケーションサーバを別で立ち上げるのがメンドウな場合に有用です。
このコマンドでの起動時に、WARNING: This is a development server. Do not uses it in a production development.
とログが出てきます1が、あくまで開発向けであり、この構成だと大量アクセス処理でパンクするなどのリスクが大きいから本番ではやめてね、という意味です。
※flask_app.pyにapp.run(host='0.0.0.0, port=9876)
を追記すると上記コマンドでも立ち上がる
Webサーバとアプリケーションサーバの主な役目
それぞれ一言でまとめます。詳しくはReferenceのリンクページを参照ください。
- Webサーバ :要求に対して、だれがアクセスしても変わらないもの(静的処理)を対応。TOPページなどがこれにあたりやすい。
- アプリケーションサーバ:アクセスに応じて返答が変わるもの(動的処理)を対応。会員Myページなど(会員によって表示内容が異なる)。
リバースプロキシってなんでリバース?
リバースがあるってことはフォワードがあるか、というとあります。
通常我々がプロキシと呼称しているものが、フォワードプロキシにあたります。
フォワードプロキシとリバースプロキシの違いはなにか
互いにプロキシであることには変わりありません。クライアント->インターネット間での処理がフォワードプロキシ(サーバ)、インターネット->ターゲットWebサーバ間での処理がリバースプロキシ(サーバ)です。2
直接目的のサーバにつなげばいいじゃん!と思われるかもしれませんが、プロキシサーバをかませることによるメリット(下記参照)を受けることができます。
フォワードプロキシの用途
用途 | 詳細 |
---|---|
省力化 | 1度アクセスしたサイトのデータを一時的にキャッシュしておくことで2回目以降のアクセスでは、Webにアクセスせずにキャッシュからデータを取得するフローとしてデータ取得までの時間を短縮することが可能。 |
コンテンツフィルタリング | 特定のサイトに対するアクセスをここで遮断する。 |
リバースプロキシの用途
用途 | 詳細 |
---|---|
セキュリティ強化 | 特定のアクセス方法(http://、https://など)ではない場合や特定のIPアドレス以外からのアクセスを遮断するなどの機構を持たせることが出来る。 |
負荷分散(ロードバランサ) | 一度クッションとなることで、複数台あるアプリケーションサーバに対してうまいことアクセスを分配する。 |
高速化 | 静的搾取。画像、htmlファイルなどの参照用のコンテンツをキャッシュで保持することでサーバの負荷を軽減したりより速いレスポンスを返したりすることが可能。 |
WSGI(ウィズギー)とは
Web Server Gateway Interfaceの略。通称上杉くん。
DjangoやFlaskはWSGI対応しているフレームワークで、gunicornやuWEGIはWSGI対応のサーバなんだよね、くらいの認識で問題ないかと思います。
登場の経緯は次のような感じ。3
・フレームワークAちゃん > 私イケてるからイケてるWebサーバのαくん以外ムリ
・フレームワークBちゃん > 僕は手堅いβくんがいいな♡
みたいな感じ(??)で、過去、PythonWebフレームワークごとに使用できるWebサーバが制限(逆も然り)されるということがままありました。実はαくんは、Bちゃんに使ってもらいたいのでしたが、ここでAちゃんとの許嫁的関係が足を引っ張ることになります。
この問題を解決しようと登場したのが上杉くんことWSGIです。WSGIは「サーバとフレームワークは自由にお互いを選んで恋愛接続できるようにしようよ」ということで、アプリケーションフレームワーク<-->Webサーバの関係において、どのようにやりとりをするかというルールを定めました。
WSGIとWSGI対応しているサーバ、フレームワークのおかげで、今日のワタシたちは自由にサーバとフレームワークの組み合わせを選ぶことができるようになったのであります。めでたしめでたし。