ほりひログ

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

Azure Functions Linux 従量課金プランでファイルの実行パーミッションを維持する方法

本エントリーは Microsoft Azure Tech Advent Calendar 2021 9 日目の記事です。

当初 Container Apps 関連を書こうとして、↓ のように「Container Apps で何か書くぞ!」と登録してましたが、最近 Azure Functions でハマってしまい、開発チームとやり取りする中で挙動が見えてきたものの、割とややこしい動きのようなので、それについて書くことにします。

f:id:horihiro:20211205172232p:plain

この実行パーミッションに関する似たような issue がいろんなところで上がるので、需要はありそうだと、勝手に思ってます。

ちなみに書く予定だっ Container Apps の話は、下記サンプルの解説です。
まずは devconatiner なりで動かしてみるのが手っ取り早いと思います。

github.com

Azure Functions Linux 従量課金プランでのプロセス起動

さて本題。

Azure Functions の Linux 従量課金プランは、Windows のようなサンドボックス制限 (Azure Functions の下回りにある App Service 基盤に由来) もなく、かつ、使った分だけの課金なので、割と好んで使っています。
Linux なので関数処理から別プロセスを起動する場合、そのプロセスの実行ファイルに実行パーミッションがついている必要があります。

この外部プロセスを起動するユースケースをいくつか紹介します。

ユースケース 1) Headless Browser

Web ページから PDF を作成する機能が欲しい場合、Azure Functions でも App Service でも、Windows だと上述のサンドボックス制限によって OS の画面描画系 API が使えないので Linux でやるのが近道です。

下記のブログは、Puppeteer や Playwright といった Chromium ベースのHeadless (画面がない)Browser を使って、Web ページのスクリーンショットを撮る例です。

anthonychu.ca

これらの Chromium ベースの Headless Browser を使う場合、内部で chrome のプロセスを起動しているので、chrome に実行パーミッションが割り当てられている必要があります。

ユースケース 2) カスタム ハンドラー

Azure Functions で既定でサポートされていないプログラミング言語を使いたい場合に使用するのがカスタム ハンドラーです。

docs.microsoft.com

このカスタム ハンドラーを使う場合、お好みのプログラミング言語で関数の実行ロジックを含めた HTTP サーバーを作成し、デプロイ パッケージに含めて (+ちょっとした設定する) デプロイします。

この時、Azure Functions のホスト プロセスがカスタム ハンドラーの HTTP サーバーを起動するので、HTTP サーバーを起動するコマンドにも実行パーミッションが必要になります。

デプロイ パッケージ内の実行パーミッションを有効にする設定

というわけで、上記のユースケースで使えるように、実行パーミッションを有効にする設定ですが、細かい説明は抜きにしたい人向けに表にまとめました*1
デプロイ方法や対象のファイルの使い方 *2 によって対応方法が違っていてややこしいです。
※ 2021 年 12 月時点の状況です。

カスタム ハンドラー 一般的な実行ファイル
ZipDeploy WEBSITE_MOUNT_ENABLED01 リモート ビルド … 2
Run from Package - … 3 v3: WEBSITE_MOUNT_ENABLED1
v4: -
4
補足) Linux 従量課金プランでの ZipDeploy

ZipDeploy は、Kudu(Lite) の API /api/zipdeploy に直接 zip パッケージを HTTP POST する、Azure Functions を含む App Service のデプロイ方法です。

docs.microsoft.com

Linux 従量課金プランの場合は少し特殊で、ZipDeploy を使ってデプロイすると、アプリケーション設定 AzureWebJobsStorage で指定された Storage Account 内の scm-release というコンテナーの中に scm-latest-<Function App 名>.zip というファイルで保存されます。
このファイル、拡張子 は .zip となっていますが、中身は SquashFS *3 というディスク イメージです。
なので、squashfs-tools を入れた Linux や WSL 上にダウンロードして、

mount -t squashfs <squashfs ファイル パス> <マウント パス>

のようにマウントすると中身が確認できます*4

実際に ZipDeploy をしてできた SquashFS イメージをマウントして中身を覗いてみると、このイメージ内のファイルには実行パーミッションがないことが確認できます。
HTTP POST された zip パッケージの展開、もしくは、その後の SquashFS イメージの作成の際に外れてしまっているのかもしれません。

(補足ここまで)

以下に、それぞれのデプロイ方法 × 実行ファイルに関する設定について解説します。

1. ZipDeploy × カスタム ハンドラー

カスタム ハンドラーの場合、host.json にどの実行ファイルを使うかが記載されています。
この host.json 内の記載を頼りに Azure Functions のホスト プロセスは起動時にカスタム ハンドラー ファイルに実行パーミッションを追加しようとします。

ただ Linux 従量課金プランの場合、上述の SquashFS イメージをマウントしており、これは読み取り専用のため、実行パーミッションの追加に失敗します。

このケースでは、アプリケーション設定 WEBSITE_MOUNT_ENABLED0 を設定することで、SquashFS ファイルをマウントする代わりに展開するので、実行パーミッションの追加が可能になります。
このアプリケーション設定がない場合、もしくは 1 に設定されている場合は、実行パーミッションは追加できません。

# このアプリケーション設定 WEBSITE_MOUNT_ENABLEDソースコード*5 以外にほとんど出てこないので、挙動を理解するのが大変です。

2. ZipDeploy × 一般的な実行可能ファイル

一般的な実行ファイルについては、カスタム ハンドラーのように、どのファイルの実行パーミッションを有効にしたらいいのかを判断するものがないので、読み書き可能だとしても、一律に実行パーミッションを追加していません。

この場合、ZipDeploy する際にリモート ビルド (Kudu 上で npm install や ビルドなどを行うオプション) を指定すると、実行パーミッションを維持できます。

リモート ビルドは、

  • アプリケーション設定 SCM_DO_BUILD_DURING_DEPLOYMENTtrue にする
    もしくは
  • --build remote オプション付きの func コマンドでデプロイする

のいずれかで有効にできます。

ユースケース 1 のブログではこの方法を使っています。

3. Run From Package × カスタム ハンドラー

Run From Package は、アプリケーション設定 WEBSITE_RUN_FROM_PACKAGE に設定された URL から zip パッケージを取ってきてマウントするデプロイ方法です。

docs.microsoft.com

Run From Package を使った場合、カスタム ハンドラーの実行パーミッションはローカルで zip パッケージを作成した時のものが引き継がれるはずなので、まずは zip パッケージ作成前に実行パーミッションが付いているか確認しするのがよいでしょう。

もしどうしても WSL も Linux も使えない場合は Windows で zip パッケージを作ることになりますが、Windows のファイル システムだと通常は Linux のファイル パーミッションを付けられません。
その場合、Azure Functions Core Tools に同梱されている gozip というツールを使うと、Windows からでも指定のファイルに実行パーミッションを付けたまま zip パッケージを作ることができます。

こんな感じで使います。

gozip -base-dir <基点になるディレクトリ> ^
      -output <出力 zip パッケージ> ^
      -input-file <パッケージ対象ファイルのリスト ファイル名> ^
      -set-executable <実行パーミッションを付けるファイル>

# Go 言語はオプションが - (ハイフン) 一つで始まるんですね。

4. Run From Package × 一般的な実行可能ファイル

Function のランタイム バージョンで挙動が違うようです。

  • v3 ランタイム
    ZipDeploy × カスタム ハンドラー で出てきたアプリケーション設定 WEBSITE_MOUNT_ENABLED を、このケースでは 1 にする必要があります。
    ない場合、もしくは 0 に設定されている場合は、実行パーミッションは引き継がれません。
  • v4 ランタイム
    こちらも Run From Package × カスタム ハンドラー と同様、追加設定なしで、実行パーミッションが引き継がれます。

ランタイムのバージョン設定は、アプリケーション設定 FUNCTIONS_EXTENSION_VERSION~3~4 と指定してください。

まとめ

今回は、Linux 従量課金プランを使用した場合の、実行パーミッションに関する設定をまとめてみました。
ドキュメントにない設定が多いので、ランタイムのアップデートで変わる可能性はありますが、カスタム ハンドラーや Puppeteer/Playwright などの外部コマンドを実行するモジュールを使用する場合は、思い出していただければ。

*1:デプロイ前のローカルファイルシステム上で、実行パーミッションが有効になっていることが大前提です

*2:カスタム ハンドラーなのか、その他一般的なファイルなのか

*3:SquashFS - WikiPedia

*4:SquashFSをマウントするまで

*5:Azure Functions Core Tools, Azure Functions Host