はじめに
Microsoft AzureのFaaSであるAzure Functionsにおいて、GoやRustはネイティブにサポートされておらず、Custom Handlerという仕組みを使って実現されています。
また、Visual Studio CodeとAzure Functions エクステンションを使うことで、Functions プロジェクトの作成を含めて、開発しやすくなっています。
本記事では、HTTP トリガーで起動するGoの関数アプリを例に、コマンドラインでAzure Functions Custom Handlerにデプロイする手順を紹介します。
環境情報
開発環境:ローカル
関数の開発およびテストはLinux、macOS、Windowsで行えます。私はmacOS Monterey 12.3.1です。
動作環境:Azure
関数のデプロイ先にはWindowsとLinuxが選択できます。本記事ではLinuxを選択しています。
Windowsユーザーの皆様へ
上記の理由により、本記事におきましては、実行バイナリーのファイル名にはファイル拡張子「.exe」をつける、コマンドプロンプトでファイルの一覧を表示する際は dir
コマンドを使うなど、適宜の読み替えをお願い致します。
事前に必要なもの
ツール類は、記事を書いた時点で最新のバージョンを使用しています。インストールやセットアップにつきましては、インターネット上の各種記事をご参照ください。
% az version
{
"azure-cli": "2.36.0",
"azure-cli-core": "2.36.0",
"azure-cli-telemetry": "1.0.6",
"extensions": {}
}
% func version
4.0.4483
本記事では、関数の作成にGoを使用しています。
% go version
go version go1.18.1 darwin/amd64
作業手順
プロジェクト ディレクトリーの準備
カレント ディレクトリーの確認
本記事にて、最初のカレント ディレクトリーは次の通りです。適宜ご自分の環境に読み替えてください。
% pwd
/Users/luigi/go/src/github.com/qt-luigi/serverless-go
プロジェクト ディレクトリーの作成
本記事では、プロジェクト ディレクトリーを「httpTrigger」としています。
mkdir
コマンドでプロジェクト ディレクトリーを作成した後、cd
コマンドでカレント ディレクトリーを作成したプロジェクト ディレクトリー配下に移動します。
% mkdir httpTrigger
% cd httpTrigger
Functions プロジェクトの準備
Functions プロジェクトの作成
func init コマンドにて、ランタイムを「カスタム ハンドラー」とするFunctions プロジェクトを生成します。
-
--worker-runtime
:ランタイムの種類。今回は「カスタム ハンドラー」です。
% func init --worker-runtime custom
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing /Users/luigi/go/src/github.com/qt-luigi/serverless-go/httpTrigger/.vscode/extensions.json
オプションを指定しなかった場合、対話形式で設定できます。
- ランタイムの選択にて、今回は「カスタム ハンドラー」なので「6」を選択します。
% func init
Select a number for worker runtime:
1. dotnet
2. dotnet (isolated process)
3. node
4. python
5. powershell
6. custom
Choose option: 6
custom
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing /Users/luigi/go/src/github.com/qt-luigi/serverless-go/httpTrigger/.vscode/extensions.json
ls
コマンドにて、各ファイルが作成されていることを確認します。
% ls -la
drwxr-xr-x 6 luigi staff 192 5 7 17:34 .
drwxr-xr-x 5 luigi staff 160 5 7 17:34 ..
-rw-r--r-- 1 luigi staff 470 5 7 17:34 .gitignore
drwxr-xr-x 3 luigi staff 96 5 7 17:34 .vscode
-rw-r--r-- 1 luigi staff 428 5 7 17:34 host.json
-rw-r--r-- 1 luigi staff 117 5 7 17:34 local.settings.json
% ls -la .vscode
drwxr-xr-x 3 luigi staff 96 5 7 17:34 .
drwxr-xr-x 6 luigi staff 192 5 7 17:34 ..
-rw-r--r-- 1 luigi staff 80 5 7 17:34 extensions.json
拡張機能バンドルの指定を削除
Azure Functionsでは、HTTP トリガー以外のトリガーを使用する時など、必要な機能を追加して拡張できます。
今回、カスタム ハンドラー/macOS/Goの組み合わせが原因かどうかわかりませんが、私の場合、明示的に手動でインストールする方法でないと上手く動作しない、および、今回の関数はHTTP トリガーであり拡張機能を使用するコーディングは行なっていないため、次の手順にて、自動での拡張機能バンドルの追加を回避します。(残しておくと、この後の func new
や func start
でエラーになります)
お好みのテキスト エディターで host.json
ファイルを開いて "extensionBundle"
ブロックの記述を削除します。
変更前
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[2.*, 3.0.0)"
},
"customHandler": {
"description": {
"defaultExecutablePath": "",
"workingDirectory": "",
"arguments": []
}
}
}
変更後
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"customHandler": {
"description": {
"defaultExecutablePath": "",
"workingDirectory": "",
"arguments": []
}
}
}
関数用の設定ファイルを作成
func new コマンドにて、関数用の設定ファイルを作成します。
-
--name
:関数名、今回は「hello」とします。 -
--template
:関数を起動させる種類。今回は「HTTP トリガー」です。 -
--authlevel
:認証レベル。今回は「anonymous」(不特定多数がアクセス可能)。
% func new --name hello --template "HTTP trigger" --authlevel anonymous
Select a number for template:HTTP trigger
Function name: [HttpTrigger] Writing /Users/luigi/go/src/github.com/qt-luigi/serverless-go/httpTrigger/hello/function.json
The function "hello" was created successfully from the "HTTP trigger" template.
オプションを指定しなかった場合、対話形式で設定できますが、作成された function.json
ファイルを手直しする必要があります。
- テンプレートの選択にて、今回は「HTTP トリガー」なので「5」を選択します。
- 関数名にて、今回は「hello」なので「hello」を入力します。
% func new
Select a number for template:
1. Azure Blob Storage trigger
2. Azure Cosmos DB trigger
3. Azure Event Grid trigger
4. Azure Event Hub trigger
5. HTTP trigger
6. IoT Hub (Event Hub)
7. Azure Queue Storage trigger
8. RabbitMQ trigger
9. SendGrid
10. Azure Service Bus Queue trigger
11. Azure Service Bus Topic trigger
12. SignalR negotiate HTTP trigger
13. Timer trigger
Choose option: 5
HTTP trigger
Function name: [HttpTrigger] hello
Writing /Users/luigi/go/src/github.com/qt-luigi/serverless-go/httpTrigger/hello/function.json
The function "hello" was created successfully from the "HTTP trigger" template.
お好みのテキストエディターで hello/function.json
ファイルを開いて "bindings"
- "authLevel"
の値を "Anonymous" に修正します。
変更前
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
変更後
{
"bindings": [
{
"authLevel": "Anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
ls
コマンドにて、各ファイルが作成されていることを確認します。
% ls -l
drwxr-xr-x 3 luigi staff 96 5 7 15:11 hello
-rw-r--r-- 1 luigi staff 316 5 7 15:10 host.json
-rw-r--r-- 1 luigi staff 117 5 7 15:05 local.settings.json
% ls -l hello
-rw-r--r-- 1 luigi staff 280 5 7 15:11 function.json
関数の作成
本記事では、関数の作成にGoを使用しています。Custom Handlerは、RustやDenoで作成した関数にも対応しています。
Goプロジェクトの初期化
go mod init
コマンドにて、Goのプロジェクトを初期化します。
% go mod init
go: creating new go.mod: module github.com/qt-luigi/serverless-go/httpTrigger
go: to add module requirements and sums:
go mod tidy
ls
コマンドにて、各ファイルが作成されていることを確認します。
% ls -l
-rw-r--r-- 1 luigi staff 67 5 7 15:25 go.mod
drwxr-xr-x 3 luigi staff 96 5 7 15:11 hello
-rw-r--r-- 1 luigi staff 366 5 7 15:18 host.json
-rw-r--r-- 1 luigi staff 117 5 7 15:05 local.settings.json
Goコードの作成
お好みのテキストエディターで、Goの関数をコーディングします。
今回は、次のサイトのソースコードを使用しています。(ソースコードの著作や修正の反映を考慮して、リンクで紹介しています。)
アプリケーションは、GETメソッドでリクエストが届いたらレスポンスとして ”hello world" の文字を返す関数です。
ソースファイル名において、上記のリンク記事では「handler.go」となっていますが、こちらでは「server.go」として話を進めます。
go build
コマンドにて、ソースファイル「server.go」をコンパイルして、実行バイナリー「server」を生成します。
% go build server.go
ls
コマンドにて、各ファイルが作成されていることを確認します。
% ls -l
-rw-r--r-- 1 luigi staff 67 5 7 15:25 go.mod
drwxr-xr-x 3 luigi staff 96 5 7 15:11 hello
-rw-r--r-- 1 luigi staff 366 5 7 15:18 host.json
-rw-r--r-- 1 luigi staff 117 5 7 15:05 local.settings.json
-rwxr-xr-x 1 luigi staff 6161472 5 7 15:26 server
-rw-r--r-- 1 luigi staff 636 5 7 15:14 server.go
関数を設定ファイルに登録
お好みのテキスト エディターで、host.json
ファイルの以下の箇所を修正します。
-
"customHandler"
-"description"
-"defaultExecutablePath"
の値を "./server" に変更します。 -
"customHandler"
ブロック内にて、"description"
と同じ階層に「"enableForwardingHttpRequest" : true」の一行を追加します。(お互いの項目を区切るためのカンマ「,」の記述をお忘れなく!)
変更前
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"customHandler": {
"description": {
"defaultExecutablePath": "",
"workingDirectory": "",
"arguments": []
}
}
}
変更後
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"customHandler": {
"description": {
"defaultExecutablePath": "./server",
"workingDirectory": "",
"arguments": []
},
"enableForwardingHttpRequest" : true
}
}
ローカルでの動作確認
関数アプリケーションの起動
func start コマンドにて、関数アプリケーションを起動します。
% func start
Azure Functions Core Tools
Core Tools Version: 4.0.4483 Commit hash: N/A (64-bit)
Function Runtime Version: 4.1.3.17473
Functions:
hello: [GET,POST] http://localhost:7071/api/hello
For detailed output, run func with --verbose flag.
[2022-05-07T06:19:32.753Z] Go server Listening on: 56196
[2022-05-07T06:19:32.766Z] Worker process started and initialized.
[2022-05-07T06:19:37.081Z] Host lock lease acquired by instance ID '000000000000000000000000000B9DD5'.
動作の確認
提示されたURL "http://localhost:7071/api/hello" に、Web ブラウザーや curl
コマンドでアクセスします。「hello world」の文字が表示されたら成功です。
curl
コマンドの場合、別途、端末画面を立ち上げてアクセスします。
% curl http://localhost:7071/api/hello
hello world
アクセスが成功した場合、ログが出力されます。
[2022-05-07T06:20:10.476Z] Executing 'Functions.hello' (Reason='This function was programmatically called via the host APIs.', Id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
[2022-05-07T06:20:10.570Z] Executed 'Functions.hello' (Succeeded, Id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, Duration=151ms)
関数を停止する場合は、[Ctrl]
+ [C]
キーを押下します。
^C[2022-05-07T06:20:38.090Z] Go server Listening on: 56196
[2022-05-07T06:20:38.114Z] Worker process started and initialized.
Azureへのデプロイ
この後の、リソース グループ、ストレージ アカウント、関数 アプリケーションの作成などは、事前にWebブラウザーにて「Azure ポータル サイト」にログインして「関数 アプリ」の機能を使って対応して頂いても大丈夫です。
Azureにログイン
az login コマンドにて、コマンドラインベースでAzureにログインします。
% az login
A web browser has been opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with `az login --use-device-code`.
[
{
"cloudName": "AzureCloud",
"homeTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
...
"name": "メールアドレス",
"type": "user"
}
}
]
Azure 関数 アプリケーションの作成
ここでは、次の記事のスクリプトを参考に、コマンドラインベースで準備および実行しました。
また、こちらの記事も参考にしました。
環境変数の値を設定
参考記事の固定値を「serverless-go」で書き換えて行った感じです。
-
$RANDOM
:0から32767までの乱数を発生させる環境変数。 -
location
:関数をデプロイするサーバーインスタンスの場所。今回は「eastus」(西アメリカ)です。 -
resourceGroup
:リソース グループ名。Azureではリソース グループ単位でプロジェクトや課金を扱います。 -
tag
:リソースやインスタンスに、わかりやすい名前をつけます。 -
storage
:ストレージ アカウント名。Azureにデプロイした関数のファイル群は、ストレージ サービスに保管されるため必要となります。 -
functionApp
:Azureでの関数のアプリケーション名。 -
skuStorage
:ストレージの種類。今回は「Standard_LRS」です。 -
functionsVersion
:Azure Functionsのバージョン。現在は「4」が推奨です。
次の2つは、参考記事にはなく、新しく追加したものです。
-
osType
:OSの種類。今回は「Linux」です。「Windows」も選択できます。 -
runtime
:ランタイムの種類。今回は「custom」です。
% let "randomIdentifier=$RANDOM*$RANDOM"
% location="eastus"
% resourceGroup="serverless-go-rg-$randomIdentifier"
% tag="create-serverless-go-consumption"
% storage="serverlessgo$randomIdentifier"
% functionApp="serverless-go-$randomIdentifier"
% skuStorage="Standard_LRS"
% functionsVersion="4"
% osType="Linux"
% runtime="custom"
以降、生成された $randomIdentifier
の乱数値は「999999999」で表現しています。
リソース グループを作成
az group create コマンドにて、リソース グループを作成します。
% az group create
--name $resourceGroup
--location "$location"
--tags $tag
実行結果。
% echo "Creating $resourceGroup in "$location"..."
Creating serverless-go-rg-999999999 in eastus...
% az group create --name $resourceGroup --location "$location" --tags $tag
{
"id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/serverless-go-rg-999999999",
"location": "eastus",
...
},
"type": "Microsoft.Resources/resourceGroups"
}
ストレージ アカウントを作成
az storage account create コマンドにて、ストレージ アカウントを作成します。
% az storage account create
--name $storage
--location "$location"
--resource-group $resourceGroup
--sku $skuStorage
実行結果。
% echo "Creating $storage"
Creating serverlessgo999999999
% az storage account create --name $storage --location "$location" --resource-group $resourceGroup --sku $skuStorage
{
"accessTier": "Hot",
"allowBlobPublicAccess": true,
...
"tags": {},
"type": "Microsoft.Storage/storageAccounts"
}
Azure 関数 アプリケーションを作成
az functionapp create コマンドにて、Azure 関数 アプリケーションを作成します。
% az functionapp create
--name $functionApp
--storage-account $storage
--consumption-plan-location "$location"
--resource-group $resourceGroup
--functions-version $functionsVersion
--os-type $osType
--runtime $runtime
実行結果。
% echo "Creating $functionApp"
Creating serverless-go-999999999
% az functionapp create --name $functionApp --storage-account $storage --consumption-plan-location "$location" --resource-group $resourceGroup --functions-version $functionsVersion --os-type $osType --runtime $runtime
Resource provider 'Microsoft.Web' used by this operation is not registered. We are registering for you.
Registration succeeded.
Your Linux function app 'serverless-go-999999999', that uses a consumption plan has been successfully created but is not active until content is published using Azure Portal or the Functions Core Tools.
Resource provider 'microsoft.insights' used by this operation is not registered. We are registering for you.
Registration succeeded.
Application Insights "serverless-go-999999999" was created for this Function App. You can visit https://portal.azure.com/#resource/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/serverless-go-rg-999999999/providers/microsoft.insights/components/serverless-go-999999999/overview to view your Application Insights component
{
"availabilityState": "Normal",
"clientAffinityEnabled": false,
...
"usageState": "Normal",
"virtualNetworkSubnetId": null
}
GoでLinux用の実行バイナリーを再生成
GOOS
および GOARCH
にて linux/amd64 を指定して、macOS上でLinux用の実行バイナリーを再生成します。標準機能で簡単にクロスコンパイルができるところが、Goの利点のひとつですよね。
% GOOS=linux GOARCH=amd64 go build server.go
生成された実行バイナリーはLinux用なので、macOS上では実行できません。
% ./server
zsh: exec format error: ./server
ls
コマンドでも、ファイル「server」の更新日時とサイズが変わったことぐらいしか確認できませんね。
% ls -l
-rw-r--r-- 1 luigi staff 67 5 7 15:25 go.mod
drwxr-xr-x 3 luigi staff 96 5 7 15:11 hello
-rw-r--r-- 1 luigi staff 366 5 7 15:18 host.json
-rw-r--r-- 1 luigi staff 117 5 7 15:05 local.settings.json
-rwxr-xr-x 1 luigi staff 6238169 5 7 16:26 server
-rw-r--r-- 1 luigi staff 636 5 7 15:14 server.go
Azureにデプロイ
func azure functionapp publish コマンドにて、Functions プロジェクトをAzureにデプロイします。
関数を修正して再デプロイする際も、本手順を行います。
% func azure functionapp publish $functionApp
Getting site publishing info...
Uploading package...
Uploading 3.4 MB [################################################################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Functions in serverless-go-999999999:
hello - [httpTrigger]
Invoke url: https://serverless-go-999999999.azurewebsites.net/api/hello
関数の確認
提示されたURL "https://serverless-go-999999999.azurewebsites.net/api/hello" に、Web ブラウザーや curl
コマンドでアクセスします。「hello world」の文字が表示されたら成功です。
curl
コマンドの場合、別途、端末画面を立ち上げてアクセスします。
% curl https://serverless-go-999999999.azurewebsites.net/api/hello
hello world
Azure 関数 アプリケーションを削除
az group delete コマンドにて、リソース グループの単位で削除します。削除の実行確認で「y」を入力します。
% az group delete --name $resourceGroup
Are you sure you want to perform this operation? (y/n): y
さいごに
Azure Functions および Custom Handler の情報に関しては、一次ソースである公式ドキュメントサイト「Microsoft Docs」に多く掲載されています。私も参考記事をベースに、コマンド引数などを含めて不明なものが出てきた際には、ひとつずつ調べて理解も進めて行きました。(作業中、ドキュメントの不具合を見つけて報告したりもしました。)
Custom Handler の場合、実際に試した内容や詳細なデプロイ手順となると、インターネット上でも見つけづらかったので、本記事が少しでもお役に立てれば幸いです。
私は関数をGoで作成していますが、RustやDenoなど、Azure Functionsでネイティブ サポートされていないプログラミング言語ユーザーの皆様にも、本記事を参考にぜひ「Custom Handler」にチャレンジして頂きたいです。
今後は、拡張機能を使ったHTTP トリガー以外の起動方法にチャレンジする予定です。上手くいけば、またこちらに記事を投稿させて頂けれたらと思っています。