Azure Functionsのお勉強メモ(6)Blob Storage出力バインドを使ってBlobにファイルを出力する

最近、Azure Functionsのお勉強をチマチマと始めました。色々と分からないことが多かったのでお勉強メモをまとめて記します。

どこまで続くかわからないお勉強メモ。今日は6回目です。今回はBlob Storage出力バインドを使った実装をSpring Cloud Functionを使って実装しました。過去のものは以下を参照。

概要

これまでは主にFunctionsが動く契機となるTriggerを取り上げていましたが、Azure Functionsで抑えておきたい概念としてバインドという概念もあります。詳細は以下のドキュメントに書かれています。

docs.microsoft.com

ものすごく単純に言えば、予め定義しておいた入出力の設定といった感じでしょう。

バインドには色々なものがありますが、ここではBlob Storage出力バインドを使ってみます。

実装

環境

Blob Storageにファイルを出力するために、開発環境にAzure StorageエミュレーターであるAzuriteを使います。

docs.microsoft.com

Dockerイメージが用意されているので、今回はそれを使います。以下のようなdocker-compose.ymlファイルを作成しておくと良いかと思います。

version: '3'
services:
  storage:
    image: mcr.microsoft.com/azure-storage/azurite
    ports:
      - 10000:10000
      - 10001:10001
    environment:
      AZURITE_ACCOUNTS: "a1:k1;a2:k2"

上記の例ではアカウントキーを短いものにしていますが、今後の動作に不具合が生じることがあったので、実際には長い文字列(azuriteのデフォルトアクセスキーでOK)を指定した方が良いかと思います。

なお、本記事執筆時の最新バージョンであるAzure Storage Explorerの1.18.1には以下のIssueのようにカスタムアカウントへの接続に問題が発生するので、利用される場合は注意が必要です。

github.com

Functions

Azure Functionsの中身を実装してみます。以下のドキュメントを参考にしました。

docs.microsoft.com

接続先を@StorageAccountで指定し、出力パスを@BlobOutputpath属性で指定します。こんな感じで実装しました。

    @FunctionName("hello")
    @StorageAccount("OutputStorage")
    public HttpResponseMessage hello(
            @HttpTrigger(name = "req", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.ANONYMOUS)HttpRequestMessage<String> request,
            @BlobOutput(name = "target", path = "myblob/sample.txt") OutputBinding<String> outputItem,
            ExecutionContext context
            ) {
// 処理内容
}

注意点は以下2点。

  1. @StorageAccountで指定するのはDefaultEndpointsProtocolからはじまる接続文字列ではなく、環境変数名。ローカル実行の場合はlocal.settings.jsonにキーと値を設定する。
  2. Blobには予めコンテナ(上記の例ではmyblob)を作成しておく。

上記1.について@StorageAccountDefaultEndpointsProtocolからはじまる接続文字列を記述した場合、起動時に

Warning: Cannot find value named 'DefaultEndpointsProtocol=http;AccountName=a1(省略)QueueEndpoint=http://127.0.0.1:10001/a1;' in local.settings.json that matches 'connection' property set on 'blob' in '/xxxxx/function.json'. You can run 'func azure functionapp fetch-app-settings <functionAppName>' or specify a connection string in local.settings.json.

という文字列が出力され、Functions実行時に

[2021-04-18T06:19:39.526Z] System.Private.CoreLib: Exception while executing function: Functions.hello. Microsoft.Azure.WebJobs.Host: Storage account connection string 'AzureWebJobsDefaultEndpointsProtocol=http;AccountName=a1(省略)QueueEndpoint=http://127.0.0.1:10001/a1;' does not exist. Make sure that it is a defined App Setting.

と出力されます。

出力バインドに値を出力するには、setValueメソッドを使えば良さそうです。OutputBindingインターフェースには他にはgetValueメソッドしかないので、出力するにはsetValueメソッド一択でしょう。

docs.microsoft.com

こんな感じで実装しました。

public class HelloHandler extends FunctionInvoker<String, String> {
    @FunctionName("hello")
    @StorageAccount("OutputStorage")
    public HttpResponseMessage hello(
            @HttpTrigger(name = "req", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.ANONYMOUS)HttpRequestMessage<String> request,
            @BlobOutput(name = "target", path = "myblob/sample.txt") OutputBinding<String> outputItem,
            ExecutionContext context
            ) {
        context.getLogger().info("***** HTTP Trigger Start *****");
        outputItem.setValue("[" + LocalDateTime.now() + "] This is sample txt.");
        context.getLogger().info("***** HTTP Trigger End *****");
        return request.createResponseBuilder(HttpStatus.OK)
                .body("***** HTTP Trigger Response *****")
                .header("Content-Type", "application/json")
                .build();
    }
}

実行

今回はHTTP Triggerで実装しました。gradlew azureFunctionsRunでアプリを実行し、localhost:7071/api/helloにアクセスすると、以下の画面が出力されます。

f:id:miyohide:20210418162455p:plain

Blobを参照してみると、ファイルが作られていることがわかります。

f:id:miyohide:20210418162611p:plain

中身も意図したものが作成されています。

f:id:miyohide:20210418162653p:plain

再度localhost:7071/api/helloにアクセスすると、ファイルが上書きされていました。

f:id:miyohide:20210418162758p:plain