こんにちは。クレスコ・デジタルテクノロジーズのN.Oです。
業務ではネットワーク構築をメインに行っていますが、クラウドにも興味があり、AZ-104取得に向けた勉強も兼ねてプライベートでAzureを触ってみました。クラウド環境の構築やアプリ開発は全くの初心者でしたが、思いのほか簡単にWebアプリのインターネット公開が出来ました!
- Webアプリの開発環境
-
- 環境変数の不一致
- Pythonのインストール方法、インストール先の不一致
- Pythonのパッケージ不足やバージョンの不一致
よって、開発環境を簡単に配布するためDockerを活用しました。DockerであればイメージやDockerfileで簡単に開発ツールやパッケージをそろえることができます。
ここまでの構成をまとめると次図のようになります。
- ローカル環境でのテスト
まずは、ローカルでWebアプリを作成してテストしてみます。
dockerfileとdocker-compose.yamlは次の通りです。
dockerfile :
FROM Python:3.9.8 |
WORKDIR /opt/build |
ADD requirements.txt /opt/build/ |
RUN Python -m pip install --upgrade pip |
RUN pip install -r requirements.txt |
RUN pip install Flask==2.1.0 |
RUN pip install MarkupSafe==2.0.1 |
docker-compose.yaml:
version: "3.9.8" |
services: |
app: |
build: . |
ports: |
- "80:80" |
container_name: CDT-blog |
volumes: |
- ./:/opt/build |
command: flask run --host 0.0.0.0 --port 80 |
Webアプリのソースコード等については後述します。
この内容で
docker-compose up |
を実行し、ブラウザからlocalhostにアクセスすると、作成したWebアプリが表示されます。
- Azure App Serviceで継続的デプロイ
ローカルでのテスト後、いよいよ作成したWebアプリをAzureにデプロイしますが、次の方法が考えられます。
-
- Webアプリのみをデプロイする
- コンテナをデプロイする
①Container Registryからデプロイする
②GitHub Actionsでデプロイする
「1. Webアプリのみをデプロイする」ではAzure App Serviceで定められている言語のWebアプリしかデプロイできません。一方、「2. コンテナをデプロイする」の場合、言語の指定はなく、コンテナ内でWebアプリが起動できればOKです。ただし、Webアプリの起動に失敗した場合はコンテナのデプロイに関する問題なのか、Webアプリの問題なのか切り分けが必要になります。
PythonはAzure App Serviceで定められている言語であるため、今回は「1. Webアプリのみをデプロイする」を試してみました。
デプロイした際のパラメータは次の通りです。
Azure App Serviceは、特定条件下であれば「無料」で利用できます。手軽にWebアプリをデプロイできるのは嬉しいですね。
Azure App Serviceのインスタンスを作成したら、継続的デプロイの設定をします。今回はGitHub Actionsを利用します。GitHub内のリポジトリに変更があれば自動的にデプロイが開始されるので便利です。
継続的デプロイの設定を保存すると自動でデプロイが開始されます。
デプロイに成功するとログに「成功」と表示されます。
デプロイしたWebアプリにアクセスすると、期待通りに表示されました!
- 今回作成したWebアプリとFlaskの仕様について
今回はPythonのWebアプリ用フレームワークを活用してアプリを作成しました。フレームワークには様々な種類がありますが、有名なものでいうとDjangoかFlaskでしょうか。Djangoは多機能なフルスタック・フレームワークですが、今回はそこまでの機能を必要としないため、シンプルな機能を備えているFlaskを利用しました。
作成したソースコード(抜粋)は次の通りです。
app.py :
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, Response |
app = Flask(__name__) |
from datetime import datetime |
site_title = 'Test Blog' |
date = datetime.now() |
@app.route('/') |
def index(): |
return render_template('index.html',site_title = site_title, year = date.year) |
@app.route('/work/page1/') |
def work_c1(): |
return render_template('page',page_title = 'page1',site_title = site_title, year = date.year) |
@app.route('/work/page3/') |
def work_c3(): |
return render_template('hogehoge', page_title = 'page3',site_title = site_title, year = date.year) |
@app.errorhandler(404) # 404エラーが発生した場合の処理 |
def error_404(error): |
return render_template('error/error.html', error_code = '404', site_title = site_title, year = date.year), 404 |
@app.errorhandler(500) # 500エラーが発生した場合の処理 |
def error_500(error): |
return render_template('error/error.html', error_code = '500', site_title = site_title, year = date.year), 500 |
if __name__ == '__main__': |
app.run(host='0.0.0.0',port=80, debug=True) |
PythonでWebアプリを作成するメリットの一つは「Pythonで学んだことをそのまま生かせる」点だと思います。例えば、ソースコード7行目の「date = datetime.now()」は
Pythonのdatetimeライブラリをそのまま利用しています。既にPythonを学んでいる方にとっては言語を学びなおす必要がないため、Webアプリ作成のハードルが低くなります。
ソースコード中の”render_template”は作成済みのページを表示するモジュールです。ここでは、「index.html」と「page」、「error.html」が作成済みページとして存在するものとします。(「hogehoge」は意図的に作成していません。理由は後述します。)
デコレータ関数”@app.errorhandler()”内の render_templateモジュールは、末尾にステータスコードを記載するとHTTPステータスラインにステータスコードを出力します。つまり、ステータスコードを記載しないとエラーページがステータス200(OK)としてクライアントに認識され、検索エンジンにエラーページが載るなどの事故が発生するので注意です!
次に、作成済みページの一例として「page」のソースコードを示します。
page :
{% include "header.html" %} |
{% if page_title == 'page1' %} {% include "contents/page1/" %} {% elif page_title == 'page2' %} |
This page is {{page_title}} . |
{% else %} |
No specified page. |
{%endif%} |
{% include "footer.html" %} |
{{}}内の変数には、app.pyのrender_templateモジュールで渡されたパラメータが入ります。
また、{% include “ページ名”%}で作成済みページの挿入ができたり、{%if ~%}のように制御構文を挿入できたりします。
ちなみにCSSも内製ですが、詳細は割愛します。
app.pyのソースコードでも触れましたが、「hogehoge」というページを意図的に作成しませんでした。このページにアクセスしようとした際にどのような結果になるのでしょうか?
実際にアクセスすると次のようになります。
存在しないページを読み込もうとしたので内部エラーが発生したようです。この500エラーのページを表示させるために「hogehoge」というページを作成しませんでした。
では、その500エラーのページは本当に500エラーなのでしょうか?
開発者ツールで確認してみると確かに「状態」が500になっており問題ないようです。
開発者ツールの画面を見ると、CSSが読み込まれているのが分かります。このCSSは「/static/css/」に配置されていますが、「/static/」配下にあるファイルは全て閲覧可能なのでしょうか? 今回は「secret.txt」というファイルを作成し、「/static/」に配置してみました。アクセスすると次のようになります。
ファイルの中身が表示されました。デフォルトでは「/static」配下のコンテンツはインターネットからアクセス可能であるため注意が必要です。
では、今回使用した作成済みページにはインターネットからアクセスできるのでしょうか?
CSSが「/static」配下に配置されていた点から推測すると、作成済みページは「/templates」にありそうです。では確認してみましょう。
アクセスできませんでした。「/templates」配下のコンテンツにはインターネットからアクセスできないようです。
このように、Flaskには「インターネットからアクセス可能な場所」「インターネットからアクセスできない場所」が存在します。コンテナのデプロイであれば権限設定も容易ですが、GitHubからのデプロイの場合は権限設定に注意が必要です。
- さいごに
今回、入社1年目の私が業務では経験のないサービスや技術を扱いましたが、Webアプリの作成からデプロイまで、思いのほか簡単に行うことができました。プログラミング学習やESP32の開発のために学んでいたPythonの汎用性の高さを改めて実感したほか、アジャイル開発で重要となるCI/CDを体験することもできました。また、クラウドサービスでは、課金も気になるところですが、今回のように無料で活用できるサービスがあるほか、アラート設定により適切にコントロールすることもできるので、興味ある方は是非チャレンジしてみてほしいです。
最後までお読みいただきありがとうございました。