【Azure】Azure Functionsを使ってみる

# はじめに
サーバーレスコンピューティングの代表格、FaaS(Function as a Service)をAzureでやってみたときのメモです。

Azureには"Azure Functions"（関数アプリ）という関数をデプロイするための機能があります。
AWSだと"AWS Lambda"に対応する機能です。

とりあえずローカルで作った何かしらの関数をデプロイするやり方を忘れないようにと思って書き残しておきます。

（やり方はほかにもいくつかあります）
# 用意するもの

  * Visual Studio Code
    * Azure functions拡張機能
    * Azure Account拡張機能
    * python拡張機能
  * Azure subscription

pythonは3.7.3で作ってます。AzureはMicrosoftのサービスなので(??)、VScodeを使って作ります。
※Azure Functionsの拡張機能はまだプレビュー版みたいです(2020/3/26現在)。以後仕様が変わるかもしれません。

# 手順
作るのは「年/月/日を入力したら曜日を返す関数」です。

デスクトップに"sample functions"という材料を入れておく用の空のフォルダを作って、その中で作業していきます。
![スクリーンショット (400).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/051ed9ac-aa0f-b6ef-3b8a-6369fac38197.png)


## ①前作業:azure-functions-core-toolsをインストール
Azure Functions Core Toolsはローカルでターミナルやコマンドプロンプトから関数の開発・テストを行うためのモジュールらしいです。とりあえず最初に入れておきます。

VScodeのターミナルを開いてコマンド打つだけです。

```Powershell
npm install -g azure-functions-core-tools
```

## ②関数を作る
続いてターミナルで

```Powershell
func init
```
と打ちます。
「使用する言語を選んでください」的な画面が出てくるので、'python'を選択します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/903bd217-ed14-16f1-9c4b-6d0ca98fd000.png" width=500>

すると、フォルダの中にいくつかファイルが生成されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/e976db19-23d1-7c5c-30a8-1acef611b20f.png" width=300>
これらはデプロイに必要なファイルです。追加のライブラリが必要であれば`requirements.txt`に追加したりします。

続いて、ターミナルで

```Powershell
func new
```
と打ちます。
そうすると、「使うテンプレートを選んでください」的な画面が出てきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/5317f11d-ef1f-52f3-dabf-b96af264f648.png" width=500>
今回はクエリを投げたら何かの値を返してくれる関数を作りたいので、`Http trigger`を選択します。
「関数の名前を教えて」と言われるので、今回は"WhatDayOfWeek"としておきます。

"sample function"フォルダの中に"WhatDayOfWeek"という名前のフォルダが作られ、中にテンプレートが入っています。
![スクリーンショット (423).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/4bd5eba5-42bc-f6be-a8d0-ffa67f478046.png)

この`__init__.py`がクラウド側で行う処理を記述したものです。
テンプレートでは、HTTPリクエストで文字列を投げたら、『Hello 【入力した名前】!』と返ってくる関数になっています。
loggingはログを扱うためのpythonの標準モジュールです。`requirements.txt`に書いてませんが使えます。（逆に下手に追加するとデプロイできなくなりました。）

加工するのはこの`__init__.py`だけでOKです。

```python:__init__.py
import logging

import azure.functions as func

#うるう年かどうかだけを判定する関数
def LeapOrNot(Y):
    if (Y % 4 != 0):
        return False
    elif (Y % 100 != 0):
        return True
    elif (Y % 400 != 0):
        return False
    else:
        return True

#閏年の回数判定　基準は1970年1月1日木曜日
def HowManyLeap(Y):
    mul4 = Y // 4 - 492 #492 = 1970 // 4
    mul100 = Y // 100 - 19 # 19 = 1970 // 100
    mul400 = Y // 400 - 4 # 4 = 1970 // 400
    return mul4 - mul100 + mul400

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')
    days = ['Thu', 'Fri', 'Sat', 'Sun', 'Mon', 'Tue', 'Wed']
    day_num = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

    Year = req.params.get('Year')
    Month = req.params.get('Month')
    Date = req.params.get('Date')

    if not (Year and Month and Date):
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            Year = req_body.get('Year')
            Month = req_body.get('Month')
            Date = req_body.get('Date')

    Y = int(Year)
    M = int(Month)
    D = int(Date)

    #edit validation
    if Y < 1970:
        return func.HttpResponse('1970年以前には対応していません',status_code=400)
    elif M > 12:
        return func.HttpResponse('日付が間違っています',status_code=400)
    elif LeapOrNot(Y):
        if M == 2:
            if day_num[M-1] + 1 < D:
                return func.HttpResponse('日付が間違っています',status_code=400)
        elif day_num[M-1] < D:
            return func.HttpResponse('日付が間違っています',status_code=400)
    else: 
        if day_num[M-1] < D:
            return func.HttpResponse('日付が間違っています',status_code=400)
    
    #経過日数を計算
    passed_num = (Y - 1970) * 365 + sum(day_num[:M-1]) + D - 1
    if LeapOrNot(Y) and M > 2:
        leap_num = HowManyLeap(Y)
    elif LeapOrNot(Y):
        leap_num = HowManyLeap(Y-1)
    else:
        leap_num = HowManyLeap(Y)

    return func.HttpResponse('{}'.format(days[(passed_num + leap_num) % 7]))
```

これで関数自体はできました。
"sample function"フォルダ内で`func new`を打てば、また別の関数を作ることもできます。

## (③)ローカルでテスト
ちゃんと期待した動作ができているかテストしたい場合は

```Powershell
func start
```
とやれば、フォルダ内のすべての関数に対して
`WhatDayOfWeek: [GET,POST] http://localhost:7071/api/WhatDayOfWeek`
といったようなURLが得られます。
これを使えば、
`http://localhost:7071/api/WhatDayOfWeek?Year=2020&Month=3&Day=26`
とパラメータを投げて`Thu`と返ってくるといったテストが行えます。

## ④クラウドにデプロイ
ローカルで関数を完成させたらデプロイです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/285b3bd4-1142-a6b4-fbed-f5cc5ce9e3f3.png" width="400">
Azure系の拡張機能を入れると、VScodeのアクティビティバーからAzure系の機能を使えるようになります。サイドバーに"FUNCTIONS"が出てくるので、そこから"Local Project"->"Functions"でローカルで作られた関数が表示されるはずです。

ここで赤枠で示した部分からデプロイできます。（Azureアカウントにloginしてなければここでログインが必要です。）
このボタンだけで、"Local Project"内に入っている関数がまとめてデプロイできます。

ここからはAzure上でのデプロイ作業をVScodeから操作します。
(Azure portalでリソースを一通り作っておけば元々あるAzure Functionsのリソースにそのままデプロイするだけなのですぐです。)

一から作っていきます。
### <1> +Create new Function App in Azure (Advanced)
新しい関数アプリを作るかどうか聞かれます。ログインしているサブスクリプションで作った関数アプリが表示されるので、あらかじめ作っておけば、ここでそれを選ぶだけです。
"Advanced"と書いてない方もありますが、そちらの方が設定事項が少ないです。

### <2> 関数アプリの名前を入力
Azure上で作る関数アプリの名前を聞かれるので、入力します。ここでは"samplefunctions"としておきます。
<del>この名前は'globally unique'でなければいけないらしいですが、この名前で作れるということはあまり利用者がいない説？？</del>

### <3> 言語選択
関数アプリで使う言語を選択します。node.jsやjavaなどが選択できますが、ここではpythonにします。<del>ローカルで作ったときにも聞かれたのにここでも聞かれるの？とは思う</del>

### <4> 課金プラン選択
"Select a hosting plan"と出てきます。これは課金プランのことで、[Azureの公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-scale)に詳しく書いてあります。
ざっくり言うと、

  * 関数の実行ごとに課金（従量課金,「Consumption」)
  * 立てたインスタンスごとに課金（「Premium」）
  * ほかのwebサービスと同一の課金制度にする（「App Service Plan」）

の3択です。
従量課金が一番FaaSっぽい？という所感。今回はConsumptionにしておきます。

### <5> リソースグループを選択
関数アプリが所属するリソースグループを選びます。新しく作ると、おそらく作るリージョンとか聞かれるかと思いますが、ここでは元々あるものを使います。

### <6> ストレージアカウントを選択
データを入れるストレージです。これも元々あるやつを使います。

### <7> Application Insightsを選択
Application Insightsは関数の監視モニター的なものです。ログやパフォーマンス状況が見れるみたいです。（[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-monitoring)）

元々他のApp Serviceで作ってあれば、それを選択して統合することもできるみたいです。
これはSkipできるので、とりあえず関数を作りたいだけならいらないかもしれないです。

ここまでやると、デプロイが開始されます。
デプロイ中のログはVScodeの"出力"から見ることもできます。
![スクリーンショット (420).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/39218e32-1c3d-5da8-d8d1-a61ec1ff5a05.png)

## ⑤作った関数を使ってみる
"Deployment to ～～ completed"と出ればデプロイ終了です。
この時点でAzure portalから関数のページを見ることもできます。
![スクリーンショット (421).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/ce684279-7eb1-220e-ae44-5f94fae8d4c3.png)
この画面で"URL"をクリックすれば、「あなたの関数は動いてますよ！」的な画面が出てくるはずです。

関数のURLは、VScodeの方から、【サブスクリプション名】->【作った関数名】->"Functions"->【関数名】を右クリックから、"Copy Function Url"で取得できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526744/0991e2ca-ab97-59ed-9175-842cf0d2fbfc.png" width=400>

ローカルで適当にpythonコードを作って実験してみます。

```python:python
import requests

url = "【取得したURL】"

param = {'Year':2020,'Month':3,'Date':26}

res = requests.get(url, params = param)

print(res.text)
```

☟結果

```
Thu
```

# さいごに
AzureでFaaSを使う際、すべてCLIでやるやり方もあるらしいです。
自分も結構理解はあやふやです。公式ドキュメントを見ながら試行錯誤してましたが苦労しました。
「とりあえず関数作ってデプロイ」がやりたいだけならこんなもんかな、と思います。