3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Azure Functions で Flask/WSGI のAPIを動かす

Last updated at Posted at 2021-08-08

はじめに

仕事でバックエンドに AWS API Gateway + AWS Lambda + Mangum + FastAPI で構築したサーバーレスなアプリケーションをプロダクションに持っていくことができた。ひと息したところで、別途 Azure を使うプロジェクトから参加要請が来た。ここはAzureならAzure API Management + Azure Functions だよなぁと思ってたけど、チュートリアル的にはひとつひとつの関数を追加する方法はあるが、のなんらかのフレームワークを利用する方法が探せず。まあ仕事の中で考える・勉強しつつやるかなと思ってたけど。コロナ禍の3連休、マンボーで外にも出たくないし、オリンピックもつまらないし、で Azure の勉強がてら試したメモ。

Azure Functions での WSGI/ASGI 状況 (2021/08)

結局、Azure Functions Python Library を使い、WsgiMiddlewareAsgiMiddleware からアプリケーションを繋いであげれば良い。

過去にテスト的に?

と組み合わせる必要があったが現在は上記不要。

例えば、日本語の記事、Qiita の記事だとこちらが見つかりましたが、結局 ASGI の方は、 Mangum の作者が Azure 向けに Bonnette を開発したが、もうメンテしないんでってことで、そこらへんのアイデアが Azure Functions Python Library の方に取り込まれていったので、特に追加のライブラリーなしで azure-functions だけで使える(v1.7.1以降)。同様に WSGI の方は、 azf-wsgi があったがこちらの方も先行して azure-functions 自体に取り込まれていた(v1.1.0以降)。

動かしてみる

FastAPI で行きたいと思っていたが、すでにアプリケーションの方は Flask で開発が進んでいたので、 Flask と WSGI で。

Azure のリソース(Resource Group, Functions, Blob Storage, etc) の作成方法等には触れません。

雛形を作る

まず、クイックスタート: コマンド ラインから Azure に Python 関数を作成するを参考に進める。

どうも、この手順通りに進めると、プロジェクト名や関数名が UpperCamelCase で.NET的だったり、Python仮想環境の位置がどうなのか?とかPython的な違和感があるので、そこら辺を読み替える。

Functions App プロジェクトと仮想環境の作成

$ mkdir myproject
$ cd myproject

2020/08/08 現在Azure側は私の環境では3.8.6だったので合わせておきました。

$ pyenv local 3.8.6
$ python -m venv .venv
$ source .venv/bin/activate

Azure Functions Core Tools - funcコマンドで雛形作成

$ func init .
Select a number for worker runtime:
1. dotnet
2. dotnet (isolated process)
3. node
4. python
5. powershell
6. custom
Choose option: 4
python
Found Python version 3.8.6 (python3).
Writing requirements.txt
Writing .funcignore
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing /Users/username/Workspaces/myproject/.vscode/extensions.json

関数追加

Http Triggerで名前をfunctions_wsgiとして追加します。普通作ったままだと、/api/functions_wsgi というパスで呼び出す事になりますが、後述するように全部のパスをWSGI/ASGIでフレームワークに渡すので実際にはこの名前はなんでも良いです。

$ func new --template "Http Trigger" --name functions_wsgi

ディレクトリー構成

これで、これまでの作業で、こんな構成になっているかと。

.
├── functions_wsgi
│   ├── __init__.py
│   └── function.json
├── host.json
└── requirements.txt

ここに、普通の?Flask/WSGIのアプリを置きます。

.
├── flask_app           # Flask/WSGI WebApp
│   ├── __init__.py
│   └── app.py
├── functions_wsgi      # Functions entry -> WSGI
│   ├── __init__.py
│   └── function.json
├── host.json
└── requirements.txt

こんな感じで、 デフォルトの Werkzeug なり、Gunicorn なりでちゃんと動くもの。(私は Azure Functions Core Tools の雛形で作成される Azure Functions 環境依存の関数を Flask化しました)

$ python flask_app/app.py
 * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!

Azure Functions と WSGI/ASGIを統合

functions_wsgi/function.jsonhost.json ファイルを次の様に変更し、デフォルトのパス /api をやめて、全てのパスをルーティングされるように変更。

   "scriptFile": "__init__.py",
   "bindings": [
     {
+      "route": "{*route}",
       "authLevel": "function",
       "type": "httpTrigger",
       "direction": "in",
   "extensionBundle": {
     "id": "Microsoft.Azure.Functions.ExtensionBundle",
     "version": "[2.*, 3.0.0)"
+  },
+  "extensions": {
+    "http": {
+      "routePrefix": ""
+    }
   }
 }

で、 scriptFile: に設定されている、functions_wsgi__init__.py の中身を次の様にする。多分FastAPI/ASGIなどなら azure.functions.AsgiMiddlewareで良いはず。

import azure.functions as func

from flask_app.app import app


def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:

    return func.WsgiMiddleware(app.wsgi_app).handle(req, context)

これで、 全てのリクエストがWSGI/ASGIアプリの方に流れていく。

できたもの

ローカルで動かす

Azure Functions Core Tools 上で実行。

$ func start
Found Python version 3.8.6 (python3).

Azure Functions Core Tools
Core Tools Version:       3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d  (64-bit)
Function Runtime Version: 3.0.15584.0
.
.
.
	functions_wsgi: [GET,POST] http://localhost:7071/{*route}

http://localhost:7071/{*route} となっている通り、全てのルートが functions_wsgi に流され、 Flask なりに渡されるので、そっちで指定したパスで動ける。

/pet/{pet_id} みたいなURLは試してないけど、

  • GET /hello?name={user_name}
  • POST /hello
  • GET /foo
  • POST /bar

を用意して、動作してるんで、あとはFlaskの話なのでまあいけるんじゃないかと。

$ curl -X GET 'http://localhost:7071/hello'
This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.
$ curl -X GET 'http://localhost:7071/hello?name=hoge'
Hello, hoge. This HTTP triggered function executed successfully.
$ curl -X POST 'http://localhost:7071/hello' -H "Content-Type: application/json" -d '{"name": "hoge"}'
Hello, hoge. This HTTP triggered function executed successfully.
$ curl -X GET 'http://localhost:7071/foo'  
test
$ curl -X GET 'http://localhost:7071/bar'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
$ curl -X POST 'http://localhost:7071/bar' -H "Content-Type: application/json" -d '{"name": "hoge"}'
{"name":"hoge"}

デプロイ

Azure 自体にデプロイというか発行し、ローカル同様に動作してるんですが…

$ func azure functionapp publish xxxxxxxx
Getting site publishing info...
Creating archive for current directory...
Performing remote build for functions project.
Deleting the old .python_packages directory
Uploading 4.83 KB [###############################################################################]
Remote build in progress, please wait...
Updating submodules.
.
.
.
Detecting platforms...
Detected following platforms:
  python: 3.8.6
Version '3.8.6' of platform 'python' is not installed. Generating script to install it...
.
.
.
Resetting all workers for xxxxxxxx.azurewebsites.net
Deployment successful.
Remote build succeeded!
Syncing triggers...
Functions in xxxxxxxx:
    functions_wsgi - [httpTrigger]
        Invoke url: https://xxxxxxxx.azurewebsites.net/{*route}?code=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

あとで調べる

デプロイ時に、

Invoke url: https://xxxxxxxx.azurewebsites.net/{*route}?code=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

なんだか知らないけど、codeが発行されるが1、これをパラメーターとして追加しないと404になる。これをどうしたら良いのか不明。チュートリアあるにある素の Functions では発行されなかったが。

あとは、API Management、ここはインフラとしてかな。JWT検証とか不要になりそうなんで便利そう。

  1. コメント欄参照 Azure Functions の HTTP トリガー

3
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?