かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

スマートスピーカーのバックエンドとしての Azure Functions のコールドスタート対策

やろうとしてるのは、この記事の内容を API Management を使ってやってみようというやつです!

qiita.com

Azure Functions のコールドスタートよりも早い何かを手前に挟んで初回は定型文を返して二回目以降は本番にディスパッチしましょうというアプローチ。なるほど。

上記記事では Logic App を使ってます。

ということで同じアプローチとして 2018 年の 12 月にプレビューとして公開された Azure API Management の従量課金プランでもやってみようと思います。因みに現時点でプレビューなので、実運用考えると一般提供開始のステータスの Logic App が今のところ現実的だと思います。

やってみよう

とりあえず Google アシスタントで試そうと思います。まずは Azure Functions で現在時間を返すだけの関数とインスタンスを起こすときに呼ぶ関数を作成します。

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace EchoBot
{
    public static class Dialogflow
    {
        [FunctionName("Dialogflow")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req)
        {
            return new JsonResult(new 
            {
                fulfillmentText = $"現在のUTC時間は{DateTime.UtcNow.ToString("yyyy年MM月dd日のHH時mm分ss秒です。")}",
            });
        }

        [FunctionName("Wakeup")]
        public static IActionResult Wakeup(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest request)
        {
            return new OkResult();
        }
    }
}

これを従量課金の Function App にデプロイします。

そして、API Management を Consumption レベルで作ります。日本はまだ無いっぽいので東南アジアで作りました。 くっ、Function App を東日本に作ったのでレスポンス速度的には痛手です。まぁこの場合でもマイクロソフトのバックボーンネットワークでの通信なのでインターネット経由じゃないから早いはず。どうせなのでこのままやってみましょう。

f:id:okazuki:20190309214729p:plain

作成した API Management で API を選んで Function App を選択します。そして Function App をインポートします。

f:id:okazuki:20190309231810p:plain

そして POST DialogflowInbound processing' のPoliciesDefault Welcome Intentの時はWakeup` を呼んで固定の JSON を返して、それ以外はバックエンドに処理を回すような XML を書きます。

<policies>
    <inbound>
        <base />
        <set-backend-service id="apim-generated-policy" backend-id="coldstartapp" />
        <set-variable name="isWelcomeIntent" value="@((context.Request.Body.As<JObject>(preserveContent: true)["queryResult"]?["intent"]?["displayName"]?.ToObject<string>() ?? "Default Welcome Intent") == "Default Welcome Intent")" />
        <choose>
            <when condition="@((bool)context.Variables["isWelcomeIntent"])">
                <set-variable name="wakeupUrl" value="@{
                    var url = context.Request.OriginalUrl;
                    return $"{url.Scheme}://{url.Host}:{url.Port}{context.Api.Path}/Wakeup{url.QueryString}";
                }" />
                <send-one-way-request mode="new">
                    <set-url>@((string)context.Variables["wakeupUrl"])</set-url>
                    <set-method>POST</set-method>
                    <set-header name="Content-Type" exists-action="override">
                        <value>application/json; charset=utf8</value>
                    </set-header>
                    <set-header name="Ocp-Apim-Subscription-Key" exists-action="override">
                        <value>@(context.Request.Headers.GetValueOrDefault("Ocp-Apim-Subscription-Key", ""))</value>
                    </set-header>
                </send-one-way-request>
                <return-response>
                    <set-status code="200" reason="OK" />
                    <set-header name="Content-Type" exists-action="override">
                        <value>application/json; charset=utf-8</value>
                    </set-header>
                    <set-body>@(new JObject(
                            new JProperty("fulfillmentText", "Hello")
                        ).ToString())</set-body>
                </return-response>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

API Management は初心者なので、もっといい書き方があるかも…。

そして、サブスクリプションを追加します。

f:id:okazuki:20190310002317p:plain

サブスクリプションのキーを取得したら Dialogflow の Fulfilment の Webhook の URL に API Management で定義した Dialogflow のエンドポイントの URL と HEADERS に Ocp-Apim-Subscription-Key をキーにしてサブスクリプションキーの値を設定します。

f:id:okazuki:20190310002512p:plain

動かしてみます。

f:id:okazuki:20190310111115p:plain

動いた!!!でもコンサンプションプランの Azure API Management のコールドスタートがどれくらいの時間かかるのかは測ってないので、しばらく時間を置いて後で試してみよう。