Serverless で何か処理を行いたいときに便利な Azure Function App。
Web API から Azure CosmosDB をアップデートするという操作を行いたく、新しく対応した Python で実装してみましたので、手順を紹介します。
苦労したところ
- Azure Functions で Python を利用できるのは Linux サーバー、Function App の開発をするのは Windows ということで
- Visual Studio Code + Azure Functions Extention を利用して開発を進めていたのですが、仮想 Web サーバー(venv) が自動起動せず、結局手動で起動して進めました
- Azure へデプロイする際に ローカルの Docker (Linux Container) が必要でした
実行環境
- Windows 10
- Python 3.6.x
- Azure CLI v2.x
- Azure Function Core Tools v2.x
- Visual Studio Code
- Docker for Windows (Linux containers)
- Azure サブスクリプション
手順
1. Azure Function Python App を Azure 上に作成
2. Azure Function Python App をローカルで作成
- 2-1. Function Python App の新規作成
- 2-2. Function Python App のテスト
-
2-3. Function App で CosmosDB にアクセスする
3. ローカルで作成した Azure Function App を Azure にデプロイ
1. Azure Function App を Azure 上に作成
Azure Portal から Azure サブスクリプション が 紐づいたアカウントでサインインします。
[+新規作成] をクリック、Function App と検索して Function App を新規作成します。
Python を選択するには、Linux サーバーを選択する必要があります。
今回は PyCosmosFunc という名前で作成しています。
2. Azure Function App をローカルで作成
2-1. Function Python App の新規作成
Azure Function Core Tools (ターミナル) を使う方法と、Azure Function ツール (GUI) を使う方法、いずれかで Function の作成を行ってください。
2-1-a. Azure Function Core Tools を使う場合
ターミナルから作成を行います。
Visual Studio Code を開き、Open Folder をクリック、プロジェクトを配置する任意のフォルダーを指定して開きます。
Azure Function をローカルで作成、起動するには venv の環境が必要です。
ツールバーの [Terminal] > [New Terminal] でターミナルを開き、venv をインストールして起動します。
> python -m venv venv
> venv\scripts\activate
venv を起動した状態で、Azure Function App を作成します。
(venv)> func init
runtime は python を選択します。
Select a worker runtime: python
Azure Function のスターターテンプレートから App (Project) が作成されます。
Azure Function App に Function を作成します。
(venv)> func new
Template は HTTPTrigger, name は デフォルトのまま (HTTPTrigger) にします。
Select a template: HTTP trigger
Function name: [HttpTrigger]
Azure Function App 内に HttpRequest テンプレートから Function が作成されます。
2-1-b. Azure Function ツールを使う場合
Visual Studio Code に統合されたツールを利用します。
Visual Studio Code を開き、Azure Tools のタブを開きます。
Azure Function の Create New Project をクリックして Function App を作成します。
今の Windows で開くには Open in current window を選択します。
Azure Function をローカルで作成、起動するには venv の環境が必要です。
.env フォルダに venv がインストールされているので、Terminal を開いてコマンドプロンプトで起動します。
> .env\scripts\activate
再び Azure Tools のタブにある Azure Function の Create New Function をクリックして Function を作成します。
Function の種類は HTTPTrigger を選択します。
Function Name はデフォルト (HTTPTrigger) のままにします。
Authorization Level は Functions を選択します。
Azure Function App 内に HttpRequest テンプレートから Function が作成されます。
2-2. Function Python App のテスト
Function App が起動すると、HttpTrigger > __init__.py
が実行されます。
HTTP GET または POST を行うと、送信されたパラメータ(name)を取得して Hello {name}! と表示するという簡単なものです。
import logging
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello {name}!")
else:
return func.HttpResponse(
"Please pass a name on the query string or in the request body",
status_code=400
)
ターミナルから起動して動作を確認します。
(venv)> func start
以下のようなメッセージが表示されたら起動完了です。ブラウザーを起動して HttpTrigger の URLにアクセスします。
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
Http Functions:
HttpTrigger: [GET,POST] http://localhost:7071/api/HttpTrigger
まず、なにもパラメータがない場合はメッセージが表示されます。
このようにパラメーターを追加すると名前が表示されるようになります。
http://localhost:7071/api/HttpTrigger?user=annie
ターミナルから Ctrl+C で一旦アプリを停止します。
2-3. Function App で CosmosDB にアクセスする
Azure Cosmos DB を操作する API を作成していきます。
パラメーターが取得できた場合、Azure Cosmos DB のデータベース、コレクション(コンテナー)を参照して、ドキュメント数を表示するようにします。
パラメーターは database, container とします。
Azure CosmosDB 用パッケージの追加
CosmosDB を操作するには、Python 用のモジュール(azure-cosmos)が公開されていますので、それを利用します。コマンドプロンプトから pip でインストールします。
(venv)> pip install azure-cosmos
このとき表示される azure-cosmos のバージョン(2018年12月現在、v3.0.2)を確認します。
(venv)> pip install azure-cosmos
Collecting azure-cosmos
:
Installing collected packages: idna, certifi, chardet, urllib3, requests, azure-cosmos
Successfully installed azure-cosmos-3.0.2 certifi-2018.11.29 chardet-3.0.4 idna-2.8 requests-2.21.0 urllib3-1.24.1
この情報を requirements.txt に追加します。
azure-cosmos==3.0.2
外部スクリプトの作成
HTTPTrigger と同じ階層に SharedScripts という名前でフォルダを追加、その中に Cosmos.py という名前でファイルを作成します。
Cosmos.py を記述します。ポイントは
- import azure.cosmos.cosmos_client でモジュールをインポートしています
- CosmosDBの接続情報は冒頭、config で設定しています。(※本来であれば、別途 config ファイル等に記載すべきですが、動作確認のため直接記載しています)
- ENDPOINT および PRIMARYKEY にお持ちの Azure CosmosDB の設定を入力してください
- CountDocuments で database および container を引数として、document の数を返します
import azure.cosmos.cosmos_client as cosmos_client
def CountDocuments(database,container):
config = {
'ENDPOINT': 'YOUR_COSMOSDB_ENDPOINT',
'PRIMARYKEY': 'YOUR_COSMOSDB_KEY',
'DATABASE': database,
'CONTAINER': container
}
# Initialize the Cosmos client
client = cosmos_client.CosmosClient(url_connection=config['ENDPOINT'], auth={
'masterKey': config['PRIMARYKEY']})
# Connect to a database
database_link = 'dbs/' + config['DATABASE']
db = client.ReadDatabase(database_link)
# Connect to a container
container_definition = {
'id': config['CONTAINER']
}
container_link = database_link + '/colls/{0}'.format(config['CONTAINER'])
container = client.ReadContainer(container_link)
# Query these items in SQL
query = {'query': 'SELECT * FROM server s'}
options = {}
options['enableCrossPartitionQuery'] = True
options['maxItemCount'] = 2
result_iterable = client.QueryItems(container['_self'], query, options)
count = 0
for item in iter(result_iterable):
count = count +1
#return count
msg = str(count) + ' document(s) found in DB'
return msg
init.py の修整
database および container というパラメーターを取得して処理を行うように __init.py__
を修正します。ポイントは
- 作成した SharedScripts > Cosmos.py から CountDocuments を import します
- database および container のパラメーターが取得できた場合は CountDocuments を呼び出します
- どちらかのパラメーターが取得できない場合は、パラメーターが必要な旨のメッセージを表示します
import logging
import azure.functions as func
from ..SharedScripts import cosmos
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
database = req.params.get('database')
container = req.params.get('container')
if not database:
try:
req_body = req.get_json()
except ValueError:
pass
else:
database = req_body.get('database')
if not container:
try:
req_body = req.get_json()
except ValueError:
pass
else:
container = req_body.get('container')
if database is not None and container is not None:
msg = cosmos.CountDocuments(database,container)
return func.HttpResponse(
msg,
status_code=200
)
else:
return func.HttpResponse(
"Please pass database and container name on the query string or in the request body",
status_code=400
)
再びテスト実行
ここで再度実行します。
(venv)> func start
HTTPTrigger の URL をブラウザーで開きます。
http://localhost:7071/api/HttpTrigger
パラメーターがないときは、パラメーターが必要な旨のメッセージが表示されます。
パラメーターとしてお持ちの Azure CosmosDB の Database 名と Container 名をセットします。
http://localhost:7071/api/HttpTrigger?database=YOUR_COSMOSDB&container=YOUR_CONTAINER
CosmosDB 内のドキュメント数が返されれば OK です。
3. ローカルで作成した Azure Function App を Azure にデプロイ
Linux 環境が必要となるため、Docker for Windows (Linux Container) を起動しておきます。
Visual Studio Code のターミナルから Azure CLI で Azure にサインインします。
> az login
ブラウザーが開くので、1. と同じアカウントでサインインします。
Login Success と表示されたら Visual Studio Code に戻ります。
複数サブスクリプションを持っている場合は、使用するサブスクリプション(YOUR_SUBSCRIPTION)をセットします。
> az account set --subscription YOUR_SUBSCRIPTION
Visual Studio Code のターミナルから Azure Function App (今回は PyCosmosFunc) へ Azure Function Core Tools を使ってデプロイします。
ポイントは、--nozip
オプション および --build-native-deps
オプションで、(Docker を利用して)ビルド後にデプロイすることです。
> func azure functionapp publish PyCosmosFunc --nozip --build-native-deps
以下のようなメッセージが表示されたらデプロイ完了です。
Upload completed successfully.
Functions in PyCosmosFunc:
HttpTrigger - [httpTrigger]
Invoke url: http://YOUR_FUNCTION_NAME.azurewebsites.net/api/httptrigger?code=YOUR_FUNCTION_ACCESSCODE
表示されている URL にブラウザーからアクセスします。code というパラメーターでアクセスコードが付与されています。
http://YOUR_FUNCTION_NAME.azurewebsites.net/api/httptrigger?code=YOUR_FUNCTION_ACCESSCODE
database と container のパラメーターを入力していないので、パラメーターが必要な旨のメッセージが表示されればOKです。
URL の後ろに &database=YOUR_DATABASE&container=YOUR_CONTAINER
の形式でパラメーターを入力して再度アクセスします。
http://YOUR_FUNCTION_NAME.azurewebsites.net/api/httptrigger?code=YOUR_FUNCTION_ACCESSCODE&database=YOUR_DATABASE&container=YOUR_CONTAINER