AzureCLIでAzureFunctionsの構築

# PythonでAzureFunctionsを動かす

「サーバーレスがやってみたい」ので、AzureFunctionsをPythonで動かします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176243/594999b7-e430-79a7-6fc2-422f8b2e259d.png)


AzureFunctionsでのPython対応は割と最近なようです。[情報](https://azure.microsoft.com/ja-jp/updates/python-support-in-azure-functions-is-now-available/)
個人利用レベルでまとめたものを以下に記載します。
チーム開発での利用方法や本番運用の方法などはまた後日しらべます。

# 動作環境

Windows10 / Powershell


>Python 関数アプリではポータルにおける関数の編集はサポートされていません。開発には CLI または VS Code を使用してください。[詳細情報](https://github.com/Azure/app-service-announcements/issues/157)

とのことなので、Pythonを用いて開発するにはCLIかVSCodeを用いる必要があり、今回はCLIで進めます。
CLIでの操作の方が、自分が今何をしているのか理解できるからいいですね（Gitもコマンドライン派です）

また、AzureFunctionsがpython3.6.xにしか対応していないようです。（2019/11/01現在）
私の環境は現在3.8が入っているので、切り変える必要があります。
バージョンの切り替えは、簡単にですが[以下](#pythonの仮想動作環境の作成)に記載しました。　参考にしてください。

# AzureFunctionsとは？

そもそもAzureFunctionsはどんなものでしょうか。

> Azure Functions は、インフラストラクチャを明示的にプロビジョニングしたり管理したりする必要がない、イベントトリガー型のコードを実行できるサーバーレス コンピューティング サービスです。

`Function as a Service (FaaS)`と呼ばれるもので、`AWS Lambda`や`Google Cloud Functions`と似た機能です。

## 関数アプリのプラン

関数アプリには実行環境の異なる3種類のプランがあり、通常のCLI構築では`従量課金プラン`が選択されます。


|プラン|英名|概要|課金体系|
|---|---|---|---|
|従量課金プラン|Consumption plan|受信イベントの数に基づいて動的に追加および削除|関数の実行中にのみ課金|
|Premium プラン|Premium plan|従量課金プランと同じように、Azure Functions ホストのインスタンスが、受信イベントの数に基づいて追加および削除|課金は、実行や消費されたメモリごとの課金ではなく、必要なインスタンスや事前ウォーミングされたインスタンスで使用されたコア秒数とメモリに基づいています。 1 つのプランにつき、少なくとも 1 つのインスタンスが常にウォーム状態である必要があります。 つまり、実行数に関係なく、アクティブなプランごとに最小の月額コストがかかります。|
|App Serviceプラン|Dedicated plan|他の App Service アプリ (Basic、Standard、Premium、Isolated SKU) と同じ専用 VM 上でも実行できます。|App Service プランの関数アプリに対する支払いは、Web アプリなどの他の App Service リソースの場合と同じ|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176243/d9957c4a-e87a-42aa-d5d4-338dd30a400c.png)

## AzureFunctions(2.x)の対応言語

2019/11/05現在では、以下の言語に対応しています。

C#(.NET Core 2.2) / JavaScript(Bide 8,10) / F#(.NET Core 2.2) / Java(Java8) / PowerShell(PowerShell Core 6) / Python(Python 3.6.x) / TypeScript

# AzureFunctionsの作成

本題に入ります。
AzureFunctionsをローカル開発するには、`Azure Functions Core Tools`が必要です。

>Azure Functions Core Tools を使用すると、ローカル コンピューター上のコマンド プロンプトまたはターミナルから関数を開発およびテストできます。 ローカル関数はライブ Azure サービスに接続できるため、完全な Functions ランタイムを使用してローカル コンピューター上で関数をデバッグすることができます。 Azure サブスクリプションに関数アプリをデプロイすることもできます。

npmを利用してインストールします。（要 Node.js）

```powershell:in
npm install -g azure-functions-core-tools
```

# 関数アプリの作成

## 関数アプリプロジェクトの作成

関数アプリを作成するためのプロジェクトを作成します。
前述でインストールした`Azure Functions Core Tools`を利用します。

`func init <project_name>`でプロジェクトを作成します。

```powershell:in
func init MyFunctionProj
```

実装言語の選択肢が表示されるので、今回は`python`を選択します。

```powershell:out
Select a worker runtime:
dotnet
node
python
powershell
```

選択後、フォルダが作成されるので、フォルダを移動します。

```powershell:in
cd  MyFunctionProj
```

## 関数アプリの作成

関数アプリを作成します。

AzureFunctionsでは、起動トリガーを選択する必要があります。
起動トリガーは、何を起因として関数アプリを起動させるかの設定で、以下の種類があります。
本記事では、よく利用される`HTTP trigger`を利用します。

|トリガー|概要|
|---|---|
|Azure Blob Storage trigger|Azure Blob Storage 内でファイルがアップロードまたは変更されると実行|
|Azure Cosmos DB trigger|Azure Cosmos DB データベースでドキュメントが追加または変更されると実行|
|Azure Event Grid trigger|Event Gridのルーティング棋院で関数を実行|
|Azure Event Hub trigger|Event Hubがメッセージを受信すると関数を実行|
|HTTP trigger| Web アプリで HTTP 要求が発生すると関数を実行(今回のもの)|
|Azure Queue Storage trigger|Azure Storage Queueに新しいアイテムが追加されると関数を実行|
|Azure Service Bus Queue trigger|Azure Service Bus キューに新しいアイテムが追加されたら関数を実行|
|Azure Service Bus Topic trigger|Azure Service Bus Topicへの新しいメッセージの到着に応答して関数を実行|
|Timer trigger|定義したスケジュールに従って定期的に Azure 関数を実行|


関数アプリには、以下コマンドを利用します。

```powershell:in
func new
```

起動トリガーの選択肢が表示されるので、`HTTP trigger`を選びます。

```powershell:out
Select a template:
Azure Blob Storage trigger
Azure Cosmos DB trigger
Azure Event Grid trigger
Azure Event Hub trigger
HTTP trigger
Azure Queue Storage trigger
Azure Service Bus Queue trigger
Azure Service Bus Topic trigger
Timer trigger
```

選択後、関数名を記載要求されるので任意の関数名を記載します。
以下では、「httpTrigger」を記載しています。

```powershell:in
Select a template: HTTP trigger
Function name: [HttpTrigger]　httpTrigger
```

### 関数アプリのフォルダ構成

関数アプリのフォルダ構成は以下のような構成になっています。
1つのプロジェクトには複数の関数アプリを作成することも可能です。

```
/
│  .gitignore
│  host.json
│  local.settings.json
│  requirements.txt
│
├─.vscode
│      extensions.json
│
├─<function_name_1>
│      function.json
│      __init__.py
│
└─<function_name_2>
       function.json
       __init__.py
```

関数アプリで重要となってくるのは以下のファイルです。
中身の詳細については、後述します。


|ファイル|役割|
|---|---|
|_＿init_＿.py|関数アプリプログラム|
|function.json|関数アプリの設定|
|host.json|すべての関数アプリに影響するグローバル設定|
|local.settings.json|ローカル環境でのアプリケーション変数|

# 関数アプリの動作確認とデプロイ

前述までで処理で、既に動作可能なサンプルアプリが構築されています。
動作を確認してAzureへデプロイします。

## 関数アプリの動作確認

以下コマンドで、ローカル環境での実行を行います。

```powershell:in
func host start
```

```:out
                  %%%%%%
                 %%%%%%
            @   %%%%%%    @
          @@   %%%%%%      @@
       @@@    %%%%%%%%%%%    @@@
     @@      %%%%%%%%%%        @@
       @@         %%%%       @@
         @@      %%%       @@
           @@    %%      @@
                %%
                %

Azure Functions Core Tools (2.7.1724 Commit hash: faf97bb64a53fbf1c7ef7e5c608c392c8662951b)
Function Runtime Version: 2.0.12763.0

...

Http Functions:

        httpTrigger: [GET,POST] http://localhost:7071/api/httpTrigger

...

```

上記のように実行され、表示される`http://localhost:7071/api/httpTrigger`が関数アプリのURLです。
構築されているアプリは、クエリストリングを読み取って画面を表示するアプリです。

#### `http://localhost:7071/api/httpTrigger` へアクセス

```:html
Please pass a name on the query string or in the request body
```

#### `http://localhost:7071/api/httpTrigger?name=TanakaTaro` へアクセス

```
Hello TanakaTaro!
```

## Azureへの関数アプリのデプロイ

動作していることが確認できたので、Azureへ関数アプリをデプロイします。

### 前提条件
ResousGroup　作成済
AzureStorageAccount　作成済
未作成の場合は、[以前書いた記事](https://qiita.com/kadoi82uma/items/1359bb1e37449fa5eaff)を参考にしてみてください。

### Azure側での関数アプリの作成

デプロイ先となるAzureの関数アプリを以下のコマンドで作成します。

```powershell:
az functionapp create --resource-group <resource_group> --os-type <os_type> --consumption-plan-location <c_plan_location> --runtime <runtime> --name <app_name> --storage-account <storage_account>
```

コマンドのオプションは以下のものを利用します。

|値|概要|
|---|---|
|--resource_group (-g)|作成済のリソースグループ|
|--os_type|functionの動作環境のOS Linux/Windows|
|--consumption_plan_location (-c)|アプリを動作させるロケーション（※注）|
|--runtime|利用言語 dotnet/java/node/powershell/python|
|--storage_account (-s)|作成済のAzureStorageAccount|

(※注)
ロケーションにより、対応言語が異なる場合があります。
2019/11/05現在、pythonの従量課金プランは日本では提供されていないようです。（プランは以下参照）

```:リージョン未対応の場合の表示
Requested features are not supported in region. Please try another region.
```

以下の構成で作成します。

```powershell:in
az functionapp create -g <resource_groupe> --os-type Linux -c eastus --runtime python --name <function_name> --storage-account <storage_account>
```

```powershell:out
Your Linux function app '<function_name>', that uses a consumption plan has been successfullycreated but is not active until content is published usingAzure Portal or the Functions Core Tools.
```


###  Azureへのデプロイ実施

以下コマンドで、ローカル環境の関数アプリをAzure環境へデプロイします。

```powershell:in
func azure functionapp publish <app_name> --build remote
```


```:out
Getting site publishing info...
Creating archive for current directory...
Performing remote build for functions project.
Uploading 11.16 MB [###########################----------------------------------------]

...

Remote build succeeded!
Syncing triggers...
Functions in function_name:
    httpTrigger - [httpTrigger]
        Invoke url: https://function_name.azurewebsites.net/api/httpTrigger?code=<接続キー>
```

上記のようにURLが表示されれば、デプロイ成功です。
表示されているURLで、ローカル環境での接続と同じような結果が表示されます。

実行されたコードは、デプロイ時に同時に生成されるApplicationInsightsにログが残ります。




# 各種ファイルの詳細

前述までで、作成~デプロイは出来ましたが、あくまでもドキュメントの手順通りに実施したもので、関数アプリを使いこなせているとは言えません。
関数アプリを自由に使いこなすために、各ファイルの中身を確認していきます。

## `__init__.py`

`__init__.py` は、関数アプリのプログラム本体です。
引数に、`azure.functions.HttpRequest`　返値に、`azure.functions.HttpResponse`　を定義しています。
その他、特に気になる部分は無し。　普通のHTTP通信のやりとりのように思えます。

```py:__init__.py
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
        )

```

## `function.json`

関数アプリのもう一つの重要な要素であり、関数アプリに関わる各種設定を記載するファイルです。

```json:function.json
{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}
```

まず、2つの要素があります。

`scriptFile` は、関数アプリのプログラムファイル名を記載しています。
`bindings`は、関数アプリに対する　input と output　のトリガーとバインド情報を設定します。　
詳細は[公式](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-http-webhook)を参照するのが良いかと思います。

`bindings`は、以下のような構成になっており、これを踏まえたうえで公式情報を見ればすんなりと理解できそうです。

```json:bindings
{
    "type":"<type_name>",
    "direction":"in または out",
    "トリガー毎の個別設定":"個別設定"
}
```

本記事で構成している内容だと、

```json:in
{
  "authLevel": "function",
  "type": "httpTrigger",
  "direction": "in",
  "name": "req",
  "methods": [
    "get",
    "post"
  ]
}
```
『
`input`のトリガーのタイプは`HTTP Trigger`
認証レベルは"function"レベル
inputの`azure.functions.HttpRequest`を`req`と変数定義する
対応するメソッドは`GET`または`POST`
』

と読み取ることができます。

認証レベルについての[公式情報](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-http-webhook#authorization-keys)


```json:out
{
  "type": "http",
  "direction": "out",
  "name": "$return"
}
```

『
`output`のタイプは`http`
返却値は関数の戻り値
』
と読み取ることができます。

`$return`についての[公式情報](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-http-webhook#output)
　→ `$return`を指定すると返り値を応答時の値と認識できるそうです。(C# 以外)

環境設定の所得
```
az functionapp config appsettings list -g resourcegroupus -n azuresamplefunction -o table
```


上記に記載したのは、`HTTP Trigger`時かつあくまでもサンプルの内容なので、他トリガーを利用する際はドキュメントをご覧ください。


## host.json

`host.json`は、関数アプリ全体に影響を及ぼす設定です。
初期構築の際は、関数アプリのバージョンを表す情報しか記載されていません。

```json:host.json
{
    "version":  "2.0"
}
```

関数アプリの同時実行やログの出力設定など、細かい調整を行うもので、本番運用する際には記載必須になるかと思います。
今回は特に気にせず進めるため、詳細は[公式情報](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json)をご覧ください。



## `local.settings.json`

`local.settings.json`は、ローカル環境構築時に利用される設定情報です。[公式情報](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local#local-settings-file)
デプロイ後の動作には影響しません。

```json:local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "AzureWebJobsStorage": "{AzureWebJobsStorage}"
  }
}
```

`Values`の値は、関数アプリの環境変数です。
環境変数は、Azure側に存在するので必要に応じて記載します。


`{AzureWebJobsStorege}`となっている箇所は、ストレージ アカウントの接続文字列です。[公式情報](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-app-settings#azurewebjobsstorage)
以下の形で記載またはAzureから環境情報を取得して設定する必要があります。

|Key|値の例|
|---|---|
|AzureWebJobsStorage|	DefaultEndpointsProtocol=https;AccountName=[name];AccountKey=[key]|

### Azure環境情報取得

```powershell:in
az functionapp config appsettings list -g <resource_groupe> -n <function_name> -o table
```

```powershell:out

Name                            SlotSetting    Value
------------------------------  -------------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
FUNCTIONS_WORKER_RUNTIME        False          python
FUNCTIONS_EXTENSION_VERSION     False          ~2
AzureWebJobsStorage             False          <接続文字列>
AzureWebJobsDashboard           False          <接続文字列>
WEBSITE_NODE_DEFAULT_VERSION    False          10.14.1
APPINSIGHTS_INSTRUMENTATIONKEY  False          <Application Insightsとの接続キー>

```

# 終わりに

AzureFunctionsを初めて利用した際に利用した情報を記載させていただきました。
`FaaS`は抽象度が高く、どのように動いているのか掴みづらい部分はありますが、CLIを通して、設定ファイルの内容を見つめることで少しづつ理解できるものかと思います。
まだ全然使いこなせていないので、引き続き学習していきます。

誤りがありましたらご連絡いただければ幸いです。



# Pythonの仮想動作環境の作成

Pythonの仮想環境切り替えに`venv`を利用します。

### 現在のバージョン確認

```powershell:in
py --version
```

```powershell:out
Python 3.8.0
```

### 仮想環境の作成

# 3.6系仮想環境の作成

別バージョンの仮想環境を作成する場合も、対象のバージョンのPythonをインストールしておく必要があります。
現在インストールされているバージョンを確認してない場合には[ダウンロード](https://www.python.org/downloads/release/python-368/)しておきます。

```powershell:in
 py --list
```

```powershell:out
 -3.8-32 *
 -3.7-32
 -3.6-64
```

## 仮想環境作成

```powershell
py -3.6 -m venv <venv_name>
```
```powershell
py -3.6 -m venv venv
```

```powershell:in
.\venv\Scripts\activate
```

```powershell
(venv) PS C:\**>
```

再度バージョン確認
3.6系になってます

```powershell:in
py --version
```

```powershell:out
Python 3.6.8
```