ほりひログ

所属組織の製品 (Azure とか) に関連する内容が多めだけど、個人の見解であって、所属組織を代表する公式情報ではないです。

Azure Functions の custom handlers で Deno を動かした

f:id:horihiro:20200320151642p:plain

Azure Functions に custom handlers がやってきたので、(決まりに従えば) お好みのランタイムで Azure Functions が使えるようになりました。

例によって Deno で試してみたところ、ぽつぽつハマったので、まとめたいと思います。

Deno on Azure Functions

今回、Deno を使った Azure Functions で、HTTP トリガーの関数を一つ作ってみました。

ファイル構成

こんな感じです。

D:\home\site\wwwroot (or /home/site/wwwroot)
  ├── deno.exe (or deno)
  ├── host.json
  ├── HttpTrigger1
  │   └── function.json
  ├── proxies.json
  └── server.ts

Deno は実行ファイルひとつで動くので、構築が楽チンです。
Windows の場合は、deno.exeLinux の場合は deno を一緒にデプロイします。

前のエントリーに書いた通り、基本的には HTTP サーバーが構築できれば、どの言語でも動作するはずで、今回は server.ts がその Web サーバーです。
関数の実装も全てを任せましたので、関数 HttpTrigger1 の下にあるのは、function.json だけです。

server.ts

server.ts の中身はこれだけです。

import { serve } from "https://deno.land/std@v0.36.0/http/server.ts";

const port = parseInt(Deno.env().FUNCTIONS_HTTPWORKER_PORT);
const s = serve({ port });

console.log(`listening at port ${port} ...`);

for await (const req of s) {
  console.log(req.url);
  if (/^\/HttpTrigger1\??/.test(req.url)) {
    req.respond({ body: "This is HttpTrigger1\n" });
    continue;
  }
  req.respond({ body: "Hello Deno\n" });
}

Deno の Web サーバーのサンプル、そのものですが、待ち受けポートは、公式ドキュメントに従って、環境変数 FUNCTIONS_HTTPWORKER_PORT を使って設定しています。

/HttpTrigger1 にリクエストが来たら 「This is HttpTrigger1」を返すだけです。
Azure Functions の HTTP トリガーの場合、?code=xxxxx といった URL クエリーが付くことが多いので、その辺をすこーしだけ考慮して正規表現にしています。

その他のパスへのリクエストは、「Hello Deno」を返します。
Function Host から Web サーバーの / に、時折 ping が来ているようなので、即 200 OK を返せるようにした方がいいと思います。

ハマりポイントその 1 ~ host.json

最初にハマったのは、host.json の書き方でした。

まずは NG な host.json から。

{
  :
  "httpWorker": {
    "description": {
      "defaultExecutablePath": "deno.exe",
      "defaultWorkerPath": "server.ts",
      "arguments": [ "run", "--allow-env", "--allow-net"]
    }
  }
}

一件、公式ドキュメントの例と同じに見えますが、これがうまく動かない。
deno.exe というファイルがない と言われます。

いろいろ試した結果、server.ts を入れている defaultWorkerPath に文字列を入れると (空文字列は OK ) 、deno.exe が見つからないようでした。
ドキュメントの例は、環境変数 PATH に node のフルパスが含まれているので問題ないのでしょう。多分。

結果、OK な host.json がこちらです。

{
  :
  "httpWorker": {
    "description": {
      "defaultExecutablePath": "deno.exe",
      "arguments": [ "run", "--allow-env", "--allow-net", "server.ts"]
    }
  }
}

server.ts を defaultWorkerPath ではなく、引数に入れてみました。
この書き方だと、D:\home\site\wwwrootdeno.exe (Linux の場合、/home/site/wwwrootdeno) を見つけてくれます*1

ハマりポイントその 2 ~ ホスティング プラン

公式ドキュメントの Restrictions の項目には、Linux の従量課金プランでは動作しない旨が記載されています。

docs.microsoft.com

Custom handlers are not supported in Linux consumption plans

上の制限は、Azure Functions 単独のものですが、Azure Functions (というか、App Service で)で Deno を使うと、もう少し制限があるのを忘れていました。

Windows 版 Deno は、起動時に OS のユーザー プロファイルをロードしようとしますが、通常 App Service 上ではこれができません。 github.com # まだ治っていませんね。。。

こちらに記載されている アプリケーション設定に WEBSITE_LOAD_USER_PROFILE を追加することで回避できますが、これを有効にするには Basic プラン以上の App Service プランが必要です。

blog.shibayan.jp

まとめるとこんな感じでしょうか。

Comsumption ElasticPremium Free/Shared Basic/Standard/Premium
Windows ×*2 ×*2
Linux ×*3

まとめ

なんだかんだで、Deno を使って Azure Functions の関数を書くことができました。
ただ、すべての処理を server.ts に入れると、コードの可読性が悪くなりそうなので、できれば従来のように、関数単位でファイルを分離したいところです。

次はその辺りを試してみようかと。
express 的なフレームワークがあれば、できそうです。

# 逆に、関数と Web サーバー コードを統合できるので、ステートフルな関数も書き放題説。。。

*1:確認がてらに issue あげました

*2:deno と App Service の相性問題

*3:Azure Functions の制限