[Azure] 知らなきゃ大損！AzureFunctionsのローカル実行方法

# 本記事のゴール

**AzureFunctionsをローカルで実行できるようになる**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/d4c04c66-25f8-c07b-5de5-23fc371bc73d.png)

# はじめに

皆さん、AzureFunctions使っていますか？ 私は汎用性も高くシンプルでかなりお気に入りのリソースです。

この便利なAzureFunctions、皆さんはどのようにデバッグしてますか？もしかして毎回クラウド上のFunctionAppにdeployして動作確認されたりしてませんか？

**実はこのAzureFunctions,ローカル実行できるんです。**

「パブリックエンドポイントのあるHttpTriggerだけじゃないの？」と思われたあなた！それ以外のタイプのFunctionsにも管理エンドポイントが用意され、開発者の任意のタイミングで実行できます。
クラウドでのデバッグに必要だったデプロイ処理(ファイルのアップロード、クラウド上のFunctionAppへの変更etc)の完了を待つ時間はもう必要ありません。

実際のところは、“実は”というほど隠された情報でもなく、普通にドキュメントに載ってる情報なのですが、「何となく構築面倒くさそう」「そんなにFunctions更新しないし後でいいだろう」という愚かな考えで大量の時間を無駄にしてしまったので、みなさんにはこの記事で「結構簡単だな、サクッとやっとこう」と思っていただければ何よりです！

[Azure Functions をローカルで開発して実行する](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-local#local-storage-emulator)

本記事で紹介した関数についてはコードサンプル(TypeScript)を筆者のrepositoryにあげてあるので良ければ参考になさってください
https://github.com/shunexe/AzureFunctionsLocalDebugTemplates

# 準備

Functionsは作られている前提としていますが、記事末尾の[こちら](https://qiita.com/shunexe/items/a33365c40d17ae606372#functions%E4%BD%9C%E6%88%90%E6%96%B9%E6%B3%95)に作成方法を記載したので気になる方は参考にしてください。以下についても環境変数を使わない場合はやらなくて大丈夫です！

- 関数内の処理で利用する環境変数を`local.settings.json`に記述する
環境変数`MY_ENV`を利用したい場合
    
    ```json
    {
      "IsEncrypted": false,//デフォルトで存在する値
      "Values": {
        "FUNCTIONS_WORKER_RUNTIME": "node",//デフォルトで存在する値
        "AzureWebJobsStorage": "",//デフォルトで存在する値
        "MY_ENV":"hello,world"//独自の環境変数
      }
    }
    
    ```
   

    `func init` でプロジェクトを作成した場合は自動で生成されますが、手動で作成する場合はプロジェクトディレクトリのルートに配置してください。
    
    ```
    .
    ├── YourFunction
    ├── host.json
    ├── local.settings.json ←ここ
    ├── package-lock.json
    ├── package.json
    └── tsconfig.json
    ```
    

# 関数の実行方法
## 大まかな流れは3ステップ!
大体次の3ステップです
1. (必要であれば)azuriteを起動
2. 関数を起動
3. ローカルの関数エンドポイントにリクエストを投げる

以下で関数のタイプを２つに大別してやり方を紹介していきます！

## HttpTrigger

1. パッケージのインストール
    
    ```bash
    npm i
    ```
    
2. 関数の起動
    
    ```bash
    npm start
    ```
    
3. エンドポイントにGETまたはPOSTリクエストを投げる
    
    エンドポイントが表示されるのでそこに対してGET/POSTリクエストを行う(portはデフォルトで7071)
    
    ```bash
    http://localhost:7071/api/{function_name}
    ```
    

[Azure Functions Core Tools の操作](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#http-and-webhook-triggered-functions)

## HttpTrigger以外の場合

1. azuriteのセットアップ
    - azuriteとは
        
        > Azurite オープンソース エミュレーターには、Azure Blob、Queue Storage、Table Storage アプリケーションをテストするための無料のローカル環境が用意されています。
        > 
        
        [ローカルでの Azure Storage の開発に Azurite エミュレーターを使用する](https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?tabs=npm)
        
        上記にあるように、Azureが提供しているAzureStorageのエミュレーターです。基本的にはこちらを常時起動させておくことになります。
        
    - インストール
        
        ```bash
        npm install -g azurite
        ```
        
    - 実行
    —location、—debugオプションには任意のディレクトリを指定してください。指定しない場合カレントディレクトリに各種ファイルが生成されます。
        
        ```bash
        azurite --silent --location ./azurite --debug ./azurite/debug.log
        ```
        
    - local.settings.jsonで環境変数を変更する
    `AzureWebJobsStorage`に`"UseDevelopmentStorage=true"`を指定する
        
        ```json
        {
          "IsEncrypted": false,
          "Values": {
            "FUNCTIONS_WORKER_RUNTIME": "node",
            "AzureWebJobsStorage": "UseDevelopmentStorage=true"
          }
        }
        ```
        
        [Azure Functions をローカルで開発して実行する](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-local#local-storage-emulator)
        
    
    ※TimerTrigger,Azure Queue Storage trigger,BlobStorageTriggerの場合、BlobStorageを内部的に使うため必須となっていますが、他の関数タイプの場合必要ない可能性があります。現時点では全ての関数タイプのローカル実行を検証できておらず確認出来次第更新したいと思います。
    
2. パッケージのインストール
    
    ```bash
    npm i
    ```
    
3. 関数の起動
    
    ```bash
    npm start
    ```
    
4. 管理エンドポイントにPOSTリクエストを投げる
    - 管理エンドポイントの形式(デフォルトportは7071)
        
        ```bash
        http://localhost:7071/admin/functions/{function_name}
        ```
        
    - Curlでリクエストを送る場合
        
        ```bash
        curl --request POST -H "Content-Type:application/json" --data '{}' 'http://localhost:7071/admin/functions/MyTimerTrigger'
        ```
        

[Azure Functions Core Tools の操作](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#non-http-triggered-functions)

# バインドするサービスがある場合

AzureFunctionsを利用する方の中には、組み込みの機能であるBindingを利用してAzureの各種サービスと連携していることもあるかと思います。

その場合もリソースごとに以下のような手段でデバッグが可能です。

- AzureStorageとのバインディング
    
    azuriteの機能によりAzureStorageをエミュレートし、ローカルで動作確認することが可能
    
- その他のリソース
ローカルエミュレートはできないため、クラウド上のリソースと接続し動作確認が可能
この場合はクラウド上のリソースの接続文字列を環境変数に設定する必要があります。

[Azure Functions をローカルで開発して実行する](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-develop-local#triggers-and-bindings)

本記事では前者のAzureStorageのうちBlobStorageとのバインディングをローカルで行う際の方法について説明します。

## 追加の準備

※AzureStorageのエミュレート自体はazuriteが行ってくれるので追加の手順は必要ありません。逆に言えばazuriteは起動させ続けておく必要があります。

- local.settings.json内の環境変数の設定を変更する
    - `AzureWebJobsStorage`
    TimerTriggerの場合と同様に`”UseDevelopmentStorage=true”`をセット。
    - MyStorageConnectionString
    AzureStorageエミュレーターへ接続する際の接続文字列はドキュメント上に公開されておりこれを指定します。
        
        > Blob service にのみ接続する場合、接続文字列は次のようになります。
        > 
        > 
        > `DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;`
        > 
        
        [ローカルでの Azure Storage の開発に Azurite エミュレーターを使用する](https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?tabs=visual-studio#http-connection-strings)
        
    
    設定後のlocal.setttings.json
    
    ```json
    {
      "IsEncrypted": false,
      "Values": {
        "FUNCTIONS_WORKER_RUNTIME": "node",
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "MyStorageConnectionString":"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
      }
    }
    ```
    
    また`”MyStorageConnectionString”`は任意の文字列に置き換えていただいても構いません。ただし、function.jsonのconnectionに指定する文字列とだけは一致させてください。
    
    例:blobトリガーの場合のfunction.json
    
    ```json
    {
      "bindings": [
        {
          "name": "myBlob",
          "type": "blobTrigger",
          "direction": "in",
          "path": "test-container/samples-workitems/{name}",
          "connection": "MyStorageConnectionString"←ココにはlocal.settings.jsonで接続文字列のキーとなっている文字列と同じものを入れる
        }
      ],
      "scriptFile": "../dist/BlobTrigger/index.js"
    }
    ```
    
- AzureStorageExplorer(デスクトップアプリ)
今回は、azuriteでエミュレートしたAzureStorage(Queue Storage、Blob Storage、Table Storage)にアクセスするのに使います。ローカルのストレージにファイルを追加したいとき、関数から出力したファイルを確認することができます。
    
    [Storage Explorer の概要](https://learn.microsoft.com/ja-jp/azure/vs-azure-tools-storage-manage-with-storage-explorer?tabs=macos#download-and-install)
    

## 例:BlobStorageの各種バインドごとの実行手順

### BlobStorageトリガー

BlobStorageでのファイルの変更を実行のトリガーにできるバインディングです。

[Azure Functions の Azure Blob Storage トリガー](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=in-process%2Cextensionv5&pivots=programming-language-javascript)

1. azurite&関数を準備し、実行
    - local.settings.json
    ”追加の準備”セクションと同じ
    - function.json
        
        ```json
        {
          "bindings": [
            {
              "name": "myBlob",
              "type": "blobTrigger",
              "direction": "in",
              "path": "test-container/samples-workitems/{name}",
              "connection": "MyStorageConnectionString"
            }
          ],
          "scriptFile": "../dist/BlobTrigger/index.js"
        }
        
        ```
        
    - index.ts
        
        ```tsx
        import { AzureFunction, Context } from "@azure/functions"
        
        const blobTrigger: AzureFunction = async function (context: Context, myBlob: any): Promise<void> {
            context.log("Blob trigger function processed blob \n Name:", context.bindingData.name, "\n Blob Size:", myBlob.length, "Bytes");
        };
        
        export default blobTrigger;
        ```
        
    
    詳細はこちらを参照
    
    [AzureFunctionsLocalDebugTemplates/BlobTrigger at main · shunexe/AzureFunctionsLocalDebugTemplates](https://github.com/shunexe/AzureFunctionsLocalDebugTemplates/tree/main/BlobTrigger)
    
2. AzureStorageExplorer上で指定された場所にファイルを追加
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/fc5dd9ee-1ae7-99e9-d31b-393e96e15b24.png)
    
3. 関数が実行されば成功!
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/440789fd-b496-0813-a93f-42b9f9ead203.png)

    

### BlobStorage入力バインディング

関数実行時にBlobStorageのファイルを取り込むことができるバインディングです。

[Azure Functions における Azure Blob Storage の入力バインド](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob-input?tabs=in-process%2Cextensionv5&pivots=programming-language-javascript)

1. azurite&関数を準備し、実行
    - local.settings.json
    ”追加の準備”セクションと同じ
    - function.json
        
        ```json
        {
          "bindings": [
            {
              "authLevel": "function",
              "type": "httpTrigger",
              "direction": "in",
              "name": "req",
              "methods": [
                "get",
                "post"
              ]
            },
            {
              "type": "http",
              "direction": "out",
              "name": "res"
            },
            {
              "name": "inputBlob",
              "type": "blob",
              "direction": "in",
              "path": "test-container/samples-workitems/input/input.txt",
              "connection": "MyStorageConnectionString"
            }
          ],
          "scriptFile": "../dist/BlobIuputBind/index.js"
        }
        ```
        
    - index.ts
        
        ```tsx
        import { AzureFunction, Context, HttpRequest } from "@azure/functions"
        
        const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
            context.log(context.bindings.inputBlob);
            context.res = {
                // status: 200, /* Defaults to 200 */
                body: "done"
            };
        
        };
        
        export default httpTrigger;
        ```
        
    
    詳細はこちらを参照
    
    [AzureFunctionsLocalDebugTemplates/BlobIuputBind at main · shunexe/AzureFunctionsLocalDebugTemplates](https://github.com/shunexe/AzureFunctionsLocalDebugTemplates/tree/main/BlobIuputBind)
    
2. AzureStorageExplorer上で指定された場所にファイルを配置
今回は”Hello,world!!!”とだけ記述したinput.txtを配置
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/5f0e1077-5e08-0652-0a48-9755c91ff7b9.png)

    
3. 関数実行時に中身が参照されてれば成功!
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/b7e096ef-2ce3-7e31-861d-05ee2bc8ce58.png)

    

### BlobStorage出力バインディング

BlobStorageにファイルを出力することができるバインディングです。

[Azure Functions における Azure Blob Storage の出力バインド](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob-output?tabs=in-process%2Cextensionv5&pivots=programming-language-javascript)

1. azurite&関数を準備し、実行
    - local.settings.json
    ”追加の準備”セクションと同じ
    - function.json
        
        ```json
        {
          "bindings": [
            {
              "authLevel": "function",
              "type": "httpTrigger",
              "direction": "in",
              "name": "req",
              "methods": [
                "get",
                "post"
              ]
            },
            {
              "type": "http",
              "direction": "out",
              "name": "res"
            },
            {
              "name": "outputBlob",
              "type": "blob",
              "direction": "out",
              "path": "test-container/samples-workitems/output/output.csv",
              "connection": "MyStorageConnectionString"
            }
          ],
          "scriptFile": "../dist/BlobOuputBind/index.js"
        }
        ```
        
    - index.ts
        
        ```tsx
        import { AzureFunction, Context, HttpRequest } from "@azure/functions"
        
        const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
            const randomCsv = 'id,name\n1,John'
        
            context.bindings.outputBlob = randomCsv;
        
            context.res = {
                status: 200,
                headers: {
                    "content-type": "text/csv"
                },
                body: "CSV created!"
            };
        
        };
        
        export default httpTrigger;
        ```
        
    
    詳細はこちらを参照
    
    [AzureFunctionsLocalDebugTemplates/BlobOutputBind at main · shunexe/AzureFunctionsLocalDebugTemplates](https://github.com/shunexe/AzureFunctionsLocalDebugTemplates/tree/main/BlobOutputBind)
    
2. 関数が実行後に指定したパスにファイルが出力されてれば成功！
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/c75b1386-a468-7e7e-9102-cea0804f5699.png)

   

# SDKを利用する場合

バインドを使わない場合もSDKを使ってAzureのリソースに接続する場合もあるかと思います。その際は、基本的にはコネクションに必要な接続文字列を環境変数に設定しておくだけで大丈夫です。

例: BlobStorage用のSDK:`@azure/storage-blob`を使う場合

- local.settings.json
エミュレートされたBlobStorageに繋ぐ場合はデフォルト接続文字列が用意されているのでそれを記載
    
    ```json
    {
      "IsEncrypted": false,
      "Values": {
        "FUNCTIONS_WORKER_RUNTIME": "node",
        "AzureWebJobsStorage": "",
        "MyStorageConnectionString":"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
      }
    }
    ```
    
- index.ts
    
    ```tsx
    import { AzureFunction, Context, HttpRequest } from "@azure/functions"
    import {BlobServiceClient} from "@azure/storage-blob"
    
    const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
        const blobClient = BlobServiceClient.fromConnectionString(process.env["MyStorageConnectionString"])
    
        // 何らかの処理
    
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: 'ok'
        };
    
    };
    ```
    

# トラブルシュート

- Could not create BlobContainerClient for ScheduleMonitor.
    
    ```bash
    [2023-01-15T05:42:35.992Z] The listener for function 'Functions.MyTimerTrigger' was unable to start.
    [2023-01-15T05:42:35.996Z] The listener for function 'Functions.MyTimerTrigger' was unable to start. Microsoft.Azure.WebJobs.Extensions.Timers.Storage: Could not create BlobContainerClient for ScheduleMonitor.
    ```
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/6c665478-e062-2d41-40ce-b30b5448c514.png)

    
    以下の設定し忘れが原因。下記を追記してfunctionを再起動
    
    ```bash
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
    ```
    

# Functions作成方法

- まずプロジェクトを作る
    
    ```bash
    func init プロジェクト名 --typescript
    ```
    
- プロジェクトに内包する関数をテンプレートから作る
  １つのプロジェクトには複数の関数をデプロイすることができます。ここでは関数をテンプレートから作成する方法を紹介します。
    
    ```bash
    func new --name 関数名 --template テンプレートの種類
    ```
    
    テンプレートの種類は下記のコマンドで取得できます
    
    ```bash
    func templates list
    ```
    
    テンプレート例
    
    - "HTTP trigger"
    - “Timer trigger”
    - “IoT Hub (Event Hub)”
    - "Azure Blob Storage trigger"


# 終わりに
いかがでしたでしょうか。あらゆる種類の関数タイプについては説明できませんでしたがローカル実行の大枠は把握できたのではないかと思います。私は1年以上AzureFunctions使っていて、ずっとクラウド上でデバッグして本当に無駄な時間を過ごしました。「このデバッグ時間無駄すぎるのでは？いい加減ローカル環境作ろう」と重い腰をあげたのが3,4週間前でした。何事もそうかもしれないですが、やってみると意外と簡単だったりするので、ぜひ構築してみてください。デプロイ待っていたのが本当にバカバカしくなります。この記事で皆さんのFunction開発が少しでも快適になれば幸いです。