Gemini先生と一緒にPythonを勉強する6日目です。
開発サーバから本番環境へ
これまで開発してきたWebサービスを、いよいよインターネットに公開する時が来ました。
しかし、現在使っているFlaskの標準サーバーは、あくまで開発用に設計されています。
たくさんの人がアクセスする本番環境で安定してサービスを動かすためには、いくつかの新しい準備する必要があります。
どのような準備が必要なのかGemeni先生に聞いてみると、以下の準備が必要であることを教えてもらいました。
- ドメイン: Webサイトの「住所」となるものです
- Webサーバ:外部からのアクセスを受け付ける役割を担います
- WSGIサーバ:WebサーバーとFlaskアプリを繋ぐ「通訳」の役割を担います
- Pythonコードの変更:本番環境用にコードを少し変更します
ドメイン名の準備
今回は、私が管理しているドメイン server-memo.net を使います。
サービスの公開用ホスト名を clock.server-memo.net とするため、あらかじめDNSサーバーにAレコードなどを登録しておきました。
Webサーバの準備(Nginx)
Webサーバーには、私が普段から使い慣れていて、高速で安定していることで定評のあるNginxを採用します。
Nginxをサーバーにインストールした後、通信を暗号化してセキュリティを高めるために、Let's Encryptを使って無料のSSL/TLS証明書も取得しておきます。
具体的な手順は以下の記事で詳しく解説していますので、参考にしてみてください。
- Nginxインストール:
https://vpslife.server-memo.net/ubuntuserver2204_nginx_install/ - Let's EncryptでSSL/TLS証明書を取得:
https://vpslife.server-memo.net/letsencrypt_nginx/
WSGIサーバーの選定と導入 (Gunicorn)
FlaskアプリとWebサーバー (Nginx) を繋ぐ「通訳」役を担うWSGIサーバについては、何を使えば良いのかが判断できなかったので、Gemini先生におすすめを聞いてみると以下の理由で、Gunicornをおすすめされました。
- 設定がシンプルでわかりやすい
- Flaskとの相性が良い
- Nginxと連携がしやすい
もう一つの有名なWSGIサーバーに uWSGI がありますが、こちらは非常にパワフルで高性能ですが、出来る項目が多く最初のうちは「どこをどう設定すればいいんだ…?」と迷ってしまうかもしれないということでしたので、今回はシンプルさと導入のしやすさを重視してGunicornを選びました。
Gunicornのインストール
Gunicornをインストールします。
今回は、VSCodeからVPSに接続して作業しています。
まず、プロジェクトディレクトリに移動し、仮想環境を有効化します。
$ cd /srv/time_app $ source .venv/bin/activate
uvを使ってGunicornをインストールします。
$ uv pip install gunicorn
Gunicornの起動テスト
インストールが完了したら、GunicornでFlaskアプリが正しく起動できるかテストしてみます。
起動コマンドの基本的な形式は以下の通りです。
gunicorn --workers [ワーカー数] --bind [アドレス:ポート] [Pythonファイル名]:[Flaskインスタンス名]
- --workers ワーカー数 : 起動すうるワーカープロセスの数を設定
- --bind アドレス:ポート : Gunicornが接続リクエストを受け付けるIPアドレスとポート番号を設定
- Pythonファイル名: 「time_app.py」から.pyを抜いた「time_app」となる
- Flaskインスタンス名: 「time_app.py」内で、「app = Flask(__name__)」とFlaskのインスタンス名を指定しているので「app」となる
実際にGunicornを起動します。
$ gunicorn --workers 3 --bind 127.0.0.1:8000 time_app:app [2025-06-04 21:39:27 +0900] [41950] [INFO] Starting gunicorn 23.0.0 [2025-06-04 21:39:27 +0900] [41950] [INFO] Listening at: http://127.0.0.1:8000 (41950) [2025-06-04 21:39:27 +0900] [41950] [INFO] Using worker: sync [2025-06-04 21:39:27 +0900] [41951] [INFO] Booting worker with pid: 41951 [2025-06-04 21:39:27 +0900] [41952] [INFO] Booting worker with pid: 41952 [2025-06-04 21:39:27 +0900] [41953] [INFO] Booting worker with pid: 41953
VSCodeの便利なポートフォワーディング機能によって、127.0.0.1:8000への接続がVPSの8000番ポートに転送されます。
VSCodeのポップアップウィンドウが表示されるので、「ブラウザーで開く」をクリックするとWebブラウザが起動し動作確認を行うことができます。
無事に動作が確認できたら、「Ctrl」+ 「C」 を押して一度Gunicornを停止させておきます。
^C[2025-06-04 21:44:00 +0900] [41950] [INFO] Handling signal: int [2025-06-04 21:44:00 +0900] [41952] [INFO] Worker exiting (pid: 41952) [2025-06-04 21:44:00 +0900] [41951] [INFO] Worker exiting (pid: 41951) [2025-06-04 21:44:00 +0900] [41953] [INFO] Worker exiting (pid: 41953) [2025-06-04 21:44:00 +0900] [41950] [INFO] Shutting down: Master
Pythonコードを本番仕様に! app.run() の取り扱い
これまで開発に使ってきたFlaskアプリケーションのコード (time_app.py) には、本番環境で動かす上で注意すべき点が一つあります。それは、ファイルの末尾にある以下の部分です。
# このファイルが直接実行された場合に開発用サーバーを起動 if __name__ == '__main__': app.run(debug=True) # debug=True は開発中に便利
「if __name__ == '__main__':」というのは、「このファイルが python time_app.py のように直接実行されたときにだけ、中の処理を動かしてね」という機能です。
問題は、その中の app.run(debug=True) です。これはFlaskに内蔵されている開発用サーバーを起動する命令で、特に debug=True は、エラー発生時に詳細な内部情報をブラウザに表示するなど、セキュリティ上とても危険な状態にしてしまいます。
今回の本番環境では、WSGIサーバーであるGunicornが、「time_app.py」に記述されている「app = Flask(__name__)」というFlaskインスタンスを直接見つけて実行してくれるため、「if __name__ == '__main__':」のブロックの中の「app.run(debug=True)」通常は呼び出されなません。
そのため、そのままでも問題ないように見えるますが、万が一本番環境で意図せず開発サーバーが動いてしまう可能性を考えると、実行されないように変更を行うのが一般的とされているらしいです。
そのため、今回は以下の開発用サーバの起動部分をコメントにして無効化することにしました。
変更後のコードは以下のとおりです。
# このファイルが直接実行された場合に開発用サーバーを起動 # python time_app.pyで動かす場合は以下のコメントを削除する # if __name__ == '__main__': # app.run(debug=True) # debug=True は開発中に便利
これで、Gunicornから起動する本番環境ではもちろん、誤って直接実行してしまっても開発サーバーが起動することはなくなり、より安全になりました。
コードを修正したので、念のため、Gunicornでアプリケーションが正しく起動できるか、改めて確認しておきます。
$ gunicorn --workers 3 --bind 127.0.0.1:8000 time_app:app
ブラウザでアクセスして、これまで通りサービスが表示されれば変更は成功です。
NginxとGunicornの連携設定から自動起動まで
GunicornとNginxを連携させる理由
GunicornでFlaskアプリを動かす準備ができましたが、なぜNginxと連携させる必要があるのでしょうか?
GunicornとNginxを連携させる理由をGemini先生に聞いてみたところ、「それぞれの得意なことを活かして、より速く・安全・安定したWebサイトを作るのが目的」と教えてくれました。
それぞれの役割分担
- Gunicorn (Flaskアプリを動かす専門家)
- Pythonのプログラムを実行するのが仕事
- リクエストに応じて、その都度内容が変わる動的なページを作る
- Nginx (Webサイトの頼れる門番&受付係)
- インターネットからのアクセスを最初に全て受け止める
- 画像・CSS・JavaScriptなど、あらかじめ用意された静的なファイルを超高速でユーザーに届ける
- Gunicornをインターネットの脅威から守る盾(リバースプロキシ)になる
連携するメリット
- スピードUP
- 静的なファイルの配信はNginxが担当
- GunicornはFlaskアプリの処理に集中
- → 結果的に、サイト全体の表示速度が向上する
- 安定性UP
- 大量のアクセスをNginxが効率よくさばいてくれる
- Gunicornの負担が減り、サーバーがダウンしにくくなる
- セキュリティUP
- Gunicornがインターネットに直接晒されないので、攻撃のリスクが減る
- 通信の暗号化 (HTTPS) もNginxが担当
NginxとGunicornの連携設定
NginxとGunicornを連携させるための設定を行っていきます。
プロキシ設定の準備
NginxがGunicornへリクエストを転送する際に、クライアントの元の情報を伝えるための設定ファイルを用意します。
すでに「/etc/nginx/proxy_params」がある場合は、この工程はスキップします。
sudo vi /etc/nginx/proxy_params
設定内容は以下のとおりです。
proxy_set_header Host $http_$host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
Nginxのバーチャルホスト設定
clock.server-memo.net へのアクセスをGunicornに転送するための設定ファイルを作成します。
sudo vi /etc/nginx/conf.d/clock.conf
server { server_name clock.server-memo.net; # root /usr/share/nginx/clock; index index.html; listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/clock.server-memo.net/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/clock.server-memo.net/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot location ^~ /.well-known { root /usr/share/nginx/clock; } location /static { alias /srv/time_app/static; } location / { include proxy_params; proxy_pass http://127.0.0.1:8000/; } } server { if ($host = clock.server-memo.net) { return 301 https://$host$request_uri; } # managed by Certbot server_name clock.server-memo.net; listen 80; return 404; # managed by Certbot }
Nginxへ設定を反映
Nginxを再起動を行い設定の反映を行います。
$ sudo nginx -t $ sudo systemctl restart nginx
動作確認
Webブラウザでにアクセスして時計サービスが表示されることを確認します。
動作確認が終わったら「Ctrl」+「C」でGunicornを停止しておきます。
Gunicornの自動起動設定 (systemd)
Gunicornが常にバックグラウンドで動き続け、サーバー起動時に自動で立ち上がるように、systemd を使ってサービス化します。
systemd用設定ファイルの作成
$ sudo vi /etc/systemd/system/time_app.service
以下の内容を記述します。
今回はテスト目的なので起動ユーザとして自分のアカウントを使用していますが、セキュリティを高めるには、専用ユーザー(例: timeapp_user)を作成して設定することをおすすめします。
[Unit] Description=Gunicorn instance to serve time_app (using TCP Socket) After=network.target [Service] User=tamohiko Group=www-data WorkingDirectory=/srv/time_app Environment="PATH=/srv/time_app/.venv/bin" ExecStart=/srv/time_app/.venv/bin/gunicorn \ --workers 3 \ --bind 127.0.0.1:8000 \ time_app:app Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
サービスの登録と起動
設定ファイルを保存したら、以下のコマンドでサービスを登録し起動します。
$ sudo systemctl daemon-reload $ sudo systemctl start time_app.service
サービスのステータスを確認します。(active (running) なら成功!)
$ sudo systemctl status time_app.service
動作確認
Webブラウザでにアクセスして時計サービスが表示されることを確認します
自動起動設定
動作確認が完了後にサービスの自動起動設定を行います。
$ sudo systemctl enable time_app.service Created symlink /etc/systemd/system/multi-user.target.wants/time_app.service → /etc/systemd/system/time_app.service.
サーバを再起動しGunicornが起動していることを確認します。
$ sudo reboot
サーバを再起動した後に、Webブラウザでにアクセスして時計サービスが表示されることを確認します。
時計サービスが正しく表示されていることが確認できたら作業は完了です!!
今日の学習まとめ:Flaskアプリの本番公開から学んだ、3つの重要なポイント
これまで開発してきたリアルタイム時計サービスを、ついにVPSサーバー上に公開しました。
このデプロイ作業を通して、ローカル環境で動かすだけでは見えてこなかった、Webアプリケーションを安全かつ安定して運用するための重要ポイントを実践的に学ぶことができました。
今回の学びを、3つのポイントにまとめて振り返ります。
- 本番環境の構成は「役割分担」が基本
- 用途に合わせたWSGIサーバーの選択が重要
- Gunicorn:設定がシンプルで導入がしやすい
- uWSGI:高性能・高機能だが設定項目が多く学習コストが高い
- セキュリティのため、開発サーバーは必ず無効化する
Flaskアプリケーションをそのままインターネットに公開するのではなく、「Webサーバ(Nginx)」+「WSGIサーバ(Gunicorn)」という構成を取ることが一般的である。
WebサーバーとFlaskアプリを繋ぐWSGIサーバーにも種類があり、今回は代表的な「Gunicorn」と「uWSGI」について理解を深めました。
Pythonのコード「time_app.py」末尾にある「app.run(debug=True)」 は、あくまで開発用です。
debug=Trueのまま本番環境で意図せず動作してしまうと、エラー発生時に内部情報が外部に漏洩するなど、深刻なセキュリティリスクに繋がります。
本番環境ではGunicornがアプリケーションを起動するため、「if __name__ == '__main__':」ブロックはコメントアウトするか削除し、安全でない開発サーバーが起動する可能性を完全に無くしておくことが不可欠だと学びました。
コメント