Azure Functions で Go 言語を使おう(Go 1.14.2 待ち)


Azure Functions custom handler というものを使うと、Web API を作れる言語なら、なんでも Azure Functions の上で動かせるようになります!!まだプレビューですけど、楽しみな機能です!!

端的に言うと、Azure Functions がサポートしていない言語での開発や、Azure Functions がサポートしている言語でも特定の Web API を開発するフレームワークを使って開発するといったことが出来ます。サポートしていない言語での開発だと Go 言語で開発出来たり、後者の例だと Node.js で express を使って開発したり C# で ASP.NET Core を使って開発したりといったことが出来ます。

ドキュメント:Azure Functions custom handlers (preview)

仕組み

host.json に裏で動く HTTP サーバーを起動するコマンドを設定しておくと、裏で HTTP サーバーを起動して Azure Functions の関数が呼ばれたときに /関数名 という名前のパスに HTTP リクエスト投げてくれる。レスポンスの JSON を特定の形式に従っておくと、Functions のバインドによしなに渡してくれるといった感じです。

シンプルな HTTP トリガーで起動して HTTP レスポンスを返すだけなら普通に作れば OK です。そうじゃない Queue トリガーなどの他のトリガーや、バインドを使う場合は、ドキュメントからの引用ですが、こんな感じの function.json の場合は

{
  "bindings": [
    {
      "name": "myQueueItem",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "messages-incoming",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "queue",
      "direction": "out",
      "queueName": "messages-outgoing",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

こんな感じの JSON がリクエスト Body に入ってきます

{
    "Data": {
        "myQueueItem": "{ message: \"Message sent\" }"
    },
    "Metadata": {
        "DequeueCount": 1,
        "ExpirationTime": "2019-10-16T17:58:31+00:00",
        "Id": "800ae4b3-bdd2-4c08-badd-f08e5a34b865",
        "InsertionTime": "2019-10-09T17:58:31+00:00",
        "NextVisibleTime": "2019-10-09T18:08:32+00:00",
        "PopReceipt": "AgAAAAMAAAAAAAAAAgtnj8x+1QE=",
        "sys": {
            "MethodName": "QueueTrigger",
            "UtcNow": "2019-10-09T17:58:32.2205399Z",
            "RandGuid": "24ad4c06-24ad-4e5b-8294-3da9714877e9"
        }
    }
}

レスポンスの JSON は

{
  "Outputs": {
    "出力バインドの名前": 出力バインドに渡す値
  },
  "Logs": ["log message1", "log message2"],
  "returnValue": $return に渡す値
}

のような感じになります。

試してみよう

シンプルな HTTP トリガー対応なら純粋に JSON を受け取って JSON を返すようなものがあればいいので、やってみましょう。とりあえず Go 言語で!

package main

import (
    "encoding/json"
    "net/http"
    "os"
    "time"
    "fmt"
    "log"
)

type user struct {
    Name string
    Age  int
}

func handleRequests() {
    http.HandleFunc("/Hello", func(w http.ResponseWriter, r *http.Request) {
        t := time.Now()
        fmt.Println(t.Month())
        fmt.Println(t.Day())
        fmt.Println(t.Year())
        ua := r.Header.Get("User-Agent")
        fmt.Printf("user agent is: %s \n", ua)
        invocationid := r.Header.Get("X-Azure-Functions-InvocationId")
        fmt.Printf("invocationid is: %s \n", invocationid)      
        json.NewEncoder(w).Encode(user{
            Name: "Kazuki from /hello",
            Age:  39,
        })
    })
    httpInvokerPort, exists := os.LookupEnv("FUNCTIONS_HTTPWORKER_PORT")
    if exists {
        fmt.Println("FUNCTIONS_HTTPWORKER_PORT: " + httpInvokerPort)
    }
    log.Println("Go server Listening...on httpInvokerPort:", httpInvokerPort)
    log.Fatal(http.ListenAndServe(":"+httpInvokerPort, nil))}

func main() {
    handleRequests()
}

このコードを go build main.go で main.exe にして、host.json を以下のようにします。

{
    "version": "2.0",
    "httpWorker": {
        "description": {
            "defaultExecutablePath": "main.exe"
        }
    }
}

そして、/Hello でリクエストを受け取るので Hello フォルダーを作って、その中に function.json を作ってシンプルな Http トリガーの関数の定義をします。

{
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}

因みに、ファイルは以下のような位置関係で配置しています。

そして、ローカルで実行してみます。func host start で実行すると、以下のように Hello 関数がちゃんと Http で受け付けるようになっているのがわかります。

実はこの時 main.exe が裏で起動しています。タスクマネージャーで見てみるとちゃんといますね。

関数の URL を叩いてみると Go 言語で返した JSON がちゃんと帰ってきました!

いいね。

Azure 上で動かしてみよう

デプロイ自体は、とりあえず試すだけなら func azure functionapp publish デプロイ先の関数アプリ名 --force というコマンドでデプロイできます。(main.go とかも、もれなく一緒にデプロイされてしまいますが…!!)
ただ、残念ながら Azure App Service (Azure Functions とかが動くサービス)上で Go 言語の 1.14.1 でビルドしたバイナリを動かそうとすると以下のようなエラーが出ます。

調べてみたら Go 1.14.2 で修正されるみたいなので、もうちょっとお預けですね…!

PowerRegisterSuspendResumeNotification error on Azure App Services with go 1.13.7 [1.14 backport]

Issue の最終更新が 16 時間前とか、ホカホカの Issue でした。

ということで Go 1.14.2 がでたら、また試してみようと思います!

まとめ

Azure Functions 上で任意の言語で開発したプログラム動くようになるのは個人的にはアツイ。関数の実行時間が制限時間をオーバーしたときにどうなるの?とか気になる点はありますが、正式リリースのころにはドキュメントなども整備されると思うので、その時にまた調べてみようと思います。