Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
97
Help us understand the problem. What are the problem?

Nginx+gunicorn構成でFlaskを使う[ローカル環境編]

概要

Flaskアプリケーションを立ち上げる手順や関連する設定について記載します。
本記事の内容はローカル環境にDockerなど介さずに直接プロセスを立ち上げるケースとなります。
Dockerを利用したケースについてはDocker編を参照ください。

前提

  • ローカル環境でpythonが実行可能であること

全体像

Flask_Nginx_unicorn_diagramdrawio-Step1-Server (1).png

記事早見表

動かしてみる

まずは実際に上記構成で動かしてみましょう。
必要となるパッケージをインストールしておきます。

brew install nginx
pip install gunicorn

プロジェクトの構成と各ファイルの内容

PROJECT
├── config
│   ├── gunicorn_settings.py # アプリケーションサーバの設定ファイル
│   └── nginx.conf           # Webサーバの設定ファイル
└── flask_app.py             # メインとなるアプリケーション
  • アプリケーションファイル
    今回、アプリケーションの挙動については重要視しないので、文字列を返却するルートを2つだけ用意することとします。
flask_app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Index Page!'

@app.route('/health')
def health():
    return 'Health Check OK!!'
  • Webサーバ設定ファイル
gunicorn_settings.py
import os

bind = '127.0.0.1:' + str(os.getenv('PORT', 9876))
proc_name = 'Infrastructure-Practice-Flask'
workers = 1
  • Nginx設定ファイル
nginx.conf
# 詳細は設定ファイルの項を参照
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;
    }
  }
}

プロセスを起動する

Terminal
# Webサーバの起動
nginx -c ${PROJECT_PATH}/config/nginx.conf

# アプリケーションサーバの起動
gunicorn flask_app:app -c config/gunicorn_settings.py

疎通確認する

Terminal
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

起動/終了コマンド

【起動】

Terminal
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ブロックの設定例を示します。例を見ることで、冒頭の設定ファイルが何を意味しているか理解が進むのではないでしょうか。

  • 基本の設定
config
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へ内部的にリダイレクト
    }
}
  • リバースプロキシ
config
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フィルタリング
config
http {
    server {
        location / {
            allow 192.168.0.1;    # 192.168.0.1からのアクセスを許可して
            deny  all;            # それ以外のネットワークアクセスを拒否する
        }
    }
}
  • BASIC認証
config
http {
    server {
        location / {
            auth_basic             "Basic Auth closed site";  # BASIC認証が必須の旨を明示し、
            auth_basic_user_file   conf/htpasswd;               # .htpasswdファイルパスを指定する
        }
    }
}
  • Referrerチェック
config
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をつけることで設定ファイルのバリデーションを行うことが可能です。

Terminal
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

起動・終了コマンド

【起動】

Terminal
nginx -c {設定ファイルの絶対パス}
  • 相対パスの場合、nginxのルートからのパスとなるため、nginx: [emerg] open() "{RELATIVE PATH}" failed (2: No such file or directory)とメッセージが出ることがあります。

【終了】

Terminal
nginx -s stop

# 上記に失敗する場合はプロセスを特定してkill
ps ax | grep nginx
kill -9 ${PID}

【起動確認】

Terminal
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

Flask_Nginx_unicorn_diagramdrawio-Step1-Porxy.png

直接目的のサーバにつなげばいいじゃん!と思われるかもしれませんが、プロキシサーバをかませることによるメリット(下記参照)を受けることができます。

フォワードプロキシの用途

用途 詳細
省力化 1度アクセスしたサイトのデータを一時的にキャッシュしておくことで2回目以降のアクセスでは、Webにアクセスせずにキャッシュからデータを取得するフローとしてデータ取得までの時間を短縮することが可能。
コンテンツフィルタリング 特定のサイトに対するアクセスをここで遮断する。
会社PCでえっちなサイトが見られないのは大体こいつのせい

リバースプロキシの用途

用途 詳細
セキュリティ強化 特定のアクセス方法(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対応しているサーバ、フレームワークのおかげで、今日のワタシたちは自由にサーバとフレームワークの組み合わせを選ぶことができるようになったのであります。めでたしめでたし。

References


  1. FLASK_ENV=developmentと設定していない場合。 

  2. 今回はNginxがプロキシサーバとWebサーバを兼任している形です。 

  3. こんなアホな説明の仕方はここにしかないと思います。。。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
97
Help us understand the problem. What are the problem?