Azure Functionsが辛すぎて泣きそうになった話

11 min read 3

はじめに

基本的にAWSエンジニアとして仕事していて、Azureは未経験でしたが案件で採用されて触れる事になりました。
Azure Functionsで処理する所があって私に一任という感じだったのですが、まぁAWS Lambdaみたいなもんでしょと(恐らく他の人も)思ってました。
それがこんな事になろうとは…

必要なリソースが多い

まず戸惑ったのがこれです。
関数を書くまでに以下のリソースが必要です。

  • Azure Functionsのインスタンス(という表現が正確か不明ですが。)
    • Lambdaと違って1つのインスタンスに複数の関数を乗せる形になります(1つ1つ作ってもいいですが…)
  • プラン
    • 料金プランの事だと思えば良いかと。なんですが、プランも1つのリソースなんです。。
  • Azure Storage アカウント
    • コードや一部の設定情報などが保存されています。
  • Application Insights
    • 正確に言うとログを格納しないなら不要ですが、基本的に使う事になるでしょう

Lambdaなら権限と関数とトリガーになるものを設定すれば終わりなんですけどね。。
特に謎なのが、結局インスタンス的なものを作るのに、なんで更にプランなるものを作らなきゃいけない仕組みなんでしょうね?Azure VMより分かり辛くなってるのですが。
しかもこのプラン複数種類あります(後述)

とはいえ、とりあえずポータルから画面に沿って作るだけなら難しくはないのですが、この必要リソースの多さが後々響いてきます…

Vnetに接続するなら従量課金プランは使えない

プランは複数種類あります。プランの概要

  • 従量課金プラン
  • App Servieプラン(専用プラン)
  • Premiumプラン
    (他はかなり別物っぽい&よく分かってないので割愛)

プランによって動きが異なります。
そして、ウソでしょと思ったのが、Vnet(仮想ネットワーク)に繋ぐ機能をVnet統合と呼ぶのですが、従量課金プランだと使えません。

なので、Vnetに繋ぐとなったら必然的に月額課金になります。FaaSとは…(ついでに言うとVMと違って停止もできない)
Vnetに接続する必要がない場合であっても、セキュアに使うにはVnet統合が必要になるので(後述)必要なシチュエーションは多いと思います。

ちなみに、従量課金プラン以外はFunctionを削除してもプランを削除しないと課金が続くので注意です。

Multi-AZ非対応

(追記)
これも結構衝撃だったのですが、色々あって漏れてました。(@Y_uuu さんコメントありがとうございました。)

https://docs.microsoft.com/ja-jp/azure/availability-zones/az-region#zone-resilient-services

AzureのAvailability Zones自体が未発展なのであって、Azure Functionsのデメリットではないと言われればそうかも知れませんが、インフラをあまり意識しなくていい事が利点の1つであるFaaSにおいては、余計にこのデメリットは大きいように感じます。

App Service Environmentというサービスを使えば対応可能なようです。
ただ、よく分かってはいませんが、Vnet内にクラスターが作られてその上でApp Serviceを稼働させるようで、結構勝手が変わってきそうです。

App Service Environment

ちなみに、以前少し調べて見送ったのですが、理由はサービスの概要には

受信と送信の両方のアプリケーション ネットワーク トラフィックをきめ細かく制御することができます。

と書いてあるのに、ネットワークの要件には

Outbound
・すべての IP への TCP (ポート 80、443)

と書かれていて、全然きめ細じゃないじゃん!となったからです。値段も高いですしね。

あとで分かったのですが、(Azure Firewallを使えば)きめ細かに制御できるよ!と言いたいんでしょうかね?

https://docs.microsoft.com/ja-jp/azure/app-service/environment/firewall-integration
それAzure Firewallの効果じゃん…

Node.jsとMS系言語しかポータルで編集できない

ポータルでの編集

最終的にはCI/CDを設定する事も多いでしょうし、VSCodeからのデプロイもできますが、それらを使えるかも状況次第。やっぱりポータルから触れないと使い勝手が悪いと感じます。

そうすると大分言語が限られてしまいます。(個人的にはNode.jsでも良いのですが、インフラで分かる人は少ないので今回はちょっとしか触った事ないPowerShellにしました。)

また、作成時に選択するベースOSとプランによっても変わってきます。
とりあえず試してみるなら、WindowsのNode.jsが良いと思います。(分かる人はPowerShellでも)

環境変数が分かり辛い

Azure Functions のアプリケーション設定のリファレンス

  1. ポータル上で設定した内容に応じて自動で値が変わる内部パラメータのようなもの
  2. 直接編集する事でFunctionの動作が変わるパラメータ
  3. コード内で参照したい環境変数

これらが混じってて分かり辛いです。
1とか表示する必要ある…?
UIからの設定で値が変わるけど、設定内容によっては値を直接変更しなきゃダメだったり、関数削除しても一部残留する事もあったりして、なんだかWindowsのレジストリを彷彿とさせますね。

言語によってはストレージのNW制限ができない

すでになかなか辛いのですが、ここからが本番です。

言語によってベースのOSが異なります。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-scale#operating-systemruntime

そしてLinuxの場合、Azure Storage アカウントのNW制限が行えません。
また、Vnet統合が前提となるので従量課金プランも不可です。

まず、Azure Storageのある仕様について話さなきゃいけないのですが、
Azure Storage アカウントを作ると、データにフルアクセスできる共有のキーが強制的に発行されて無効化も制限もできません。ksg

これがよろしくない事は認識しているようで、比較的最近共有キーでの認証を禁止してAzure AD認証のみ許可する事ができるようにはなりました。(プレビュー)
ただ、ユーザが接続したり、作ったアプリから接続する分にはAzure AD認証でいけるのですが、多くのAzureサービスは共有キーを使うようで完全に使用しないという事はできません。
Azure Functionsもその1つです。

で、この共有キーさえ入手できれば誰か分からない人がどこからでも繋げるという事になってしまい、それでは困るのでせめてストレージ自体にNW制限を掛けましょうという対策が考えられます。

しかし、ストレージのNW制限を掛ける場合、ベースOSがWindowsじゃないとダメというのがこの問題です。
Azure Storage アカウントがそういった仕様である以上、結構使うんじゃないかなぁと思うのですが、言語によってはそれすらできないとは、酷くないですか。。
※もっと言うと私が構築始めた時点ではPremiumプランしか対応していなかったです。
過去のドキュメントを見るとそうなっています。

現在、この機能は、Windows Premium プランでのみ機能します。

共有キーを定期的にローテする事で、リスクを低減する事はできます。(本来はこれも合わせてやった方が良いんでしょうね)
ただ、キーの情報はAzure Functionsのパラメータに直接格納されているので、そのローテ方法は別途考える必要がありますけどね。

ちなみに、Function作成時点ではNW制限できないので、一旦無制限で作成したのちに設定変更する必要があってこれもまた分り辛い。
(あと、私が試した分には制限を掛けた状態でApp Servieプランで作ると、作る事はできるが動作しないというバグっぽい動作になりました。本来は作成時点でエラーになる)

セキュアを考えると一段と複雑化する

もうかなり分り辛いのですが、まだこれからです。

上記のAzure Storage アカウント制限、サービスエンドポイントとプライベートエンドポイントどちらを利用するかが選べます。
ただ、なぜかVnet統合とサービスエンドポイントポリシーが共存できない制約があります。
ドキュメントには記載が見当たりませんでしたが、Microsoftの担当者から聞きましたし、試すとエラーになります。
サービスエンドポイントエラー

ちょっと話がずれるのですが、Azureは○○と××の機能は共存できないという事がちょくちょくあります。(そしてドキュメントに書いてあるとは限らない)
これとこれを組み合わせれば実現できそうだなという考え方が通用せず、作った後に想定外の事象が発覚することもざらで困ります。。

元の話に戻ると、前述の制限があるため、サービスエンドポイントのどのリソースにでも接続できてしまう仕様を許容しない場合、プライベートエンドポイントを使用する事になります。
プライベートエンドポイント自体ちょっと分り辛いサービスで(プライベートDNSゾーンを作成して使用する事になる)、単にFunctionをセキュアにするだけで使うハメになるのは不本意です。

なお、Premiumプランですと、最低2つのエンドポイントが必要です。

https://docs.microsoft.com/ja-jp/azure/azure-functions/storage-considerations

ちなみにこのドキュメントにもAzure Storage アカウントのNW制限のドキュメントにも何番ポートで通信するか書いてませんが、Azure Filesは445ポートです。(それ以外は443の認識)
また、Durable Functionsを使わない限り2つで良さそうに見えますが、使用するトリガーによってはQueue Storageのエンドポイントが必要です。

上記にマネージドID認証を組み合わせると、NSGを全開放する必要がある

ラスボスです。意味不明すぎて心が折れました。
(私が認識している範囲でしかないのでもっとあるかも)

本来Vnet統合を使う場合、送信先をNSGで制御可能です。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-networking-options#regional-virtual-network-integration

統合サブネットに配置された NSG を使って送信トラフィックをブロックできます。

前述のAzure Storage アカウントのNW制限を行う際、「WEBSITE_VNET_ROUTE_ALL=1」というパラメータを設定しました。
加えて、これはグローバルIP向け通信をVnetに向ける場合にも必要です。デフォルトではプライベートIP向け通信のみがVnetに向きます。
Vnet統合
※これはApp Serviceの図ですが、ほぼ同じと思って良いかと。

なので、セキュリティポリシーで無制限なインターネットアクセスは許容しないという場合も「WEBSITE_VNET_ROUTE_ALL=1」が必要です。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-networking-options#regional-virtual-network-integration

既定では、アプリでは RFC1918 トラフィックのみが VNet にルーティングされます。

なんですが、「WEBSITE_VNET_ROUTE_ALL=1」を設定するとマネージドID認証の通信もVnetを向き、そのIPアドレスは不定です。
「AzureActiveDirectory」のサービスタグをNSGで許可すれば良いでしょと思いましたがエラーになる。
それでは足りないそうです。以下のURLへの接続が必要で、サービスタグではカバーできないとのこと。

login.microsoftonline.com
management.azure.com
aadcdn.msftauth.net
dc.services.visualstudio.com

※なお、AWSだと対象サービスのエンドポイントに接続できれば良いのですが、Azureだと同様のドキュメントは無さそうです。毎回サポートに聞くしかないんですかね。。

では、どうしたら良いかと聞いたところ…
NSG全開放ですよw
どちらもセキュリティ上必要なのに、組み合わせるとなぜガバガバになるのか。コレガワカラナイ

まだ解決していないのですが、別用途でAzure Firewallを立てていたのでグローバルIP向け通信をそちらに向けて必要なURLのみを許可する事で制限しようとしています。
あと詳細は分かってないですが、.Netでコードを書けばこういった事にはならないはずとMicrosoftの人が言ってたらしいです。

そんな事ある?w
仮にそうだとしたら、.Net以外はこういうセキュリティ上の懸念がありますってドキュメントに分かるように書いといて欲しいですね。そしたら最初からAzure Functions使わない方が良いのではって話もできたでしょう。
(前述の方式も私から尋ねたもので、明確に実現できるかはMicrosoftの人も知らなかったですし。)

感想

FaaSって手間が掛からなくて処理の実装に集中できるのがメリットでは?あと処理課金。
Azure FunctionsはFaaSと思わずに接した方が良いかも。こんなはずじゃなかったが減らせると思う。

少なくともセキュアを求められる環境で、ちょっとした処理を動かすのにAzure Functionsを使うのは一度立ち止まった方が良さそう。
結局月額課金になるんだし、Azure VMで良いのでは?

セキュアを求められないなら、使い勝手はLambdaに劣るとはいえそこまで難しい事にはならないでしょう。
また、多数の関数を動かすなら構築が難しくてもペイできるかもしれません。(そもそもこのサービスで多数の関数を動かすのは正気の沙汰じゃないかもだけど)

Azureの知識が結構ないと荷が重いと思いますね。Azure未経験で触るようなサービスじゃなかった。色々な要素を理解する必要があります。

私的には今回の事で、もう二度とAzure触るかという気持ちになりました。

補足

Zennのトレンドや、はてブのホットエントリーに載って、結構な人に見ていただいて嬉しいです。
これで少しでも事故の減少に役立てたら良いなと思います。

意図が伝わってないかもと思うコメントも見かけたので補足しておきます。

  • RBACでコントロールするのがAzureの考え方だから、NW分離しようとすると辛くなるのでは?
    境界型の考え方だからそうなるんだというような事ですね。
    もっともだと思いますが、Storage アカウントの共有キーがあるのでそうも言ってられないと思います。
    Azureさん自身が作った共有キーの存在により、RBACでの制御に頼れないから面倒な事になってると捉えています。
    また、RBACで制御できるのはあくまで内部リソースに対しての操作という認識です。
    (IT統制的な?意味での)外部向け通信の制御には使えないのでは。AzureにRBACで外部向け通信を制御できるソリューションがあるならそれを利用すれば良いのかも知れませんが。

  • べスプラに沿ってないから辛いのでは?
    あるなら是非知りたいのですが、この件についてべスプラは存在しないのではと思ってます。
    サポートの方やMicrosoftの担当の方と結構やりとりしていますが、そういった話はありません。むしろあちらも明確な情報持ってなくて苦労してるように見えてます。

  • VPC Lambdaでもエンドポイントに疎通できる必要があるのは同じでは?
    そうです。ですが、Lambdaの場合IAMロールを使うためにSGを全開放しなければならないという事にはなりません。
    また、通信先のエンドポイントは対象サービスのエンドポイントを確認して、VPCエンドポイントの対応有無を調べ、有るならVPCエンドポイントを設定、無いならProxyサーバを通せばよいです。
    Azureの場合、そもそもどこに通信するかサポートに聞かないと分かりませんし、サービス毎のURLではないようでどういった条件で各URLに通信するのかよく分かりません。
    初期構築も大変ですし、コードや設定の変更が発生した際よく分からない条件でよく分からない通信が発生して動かなくなる可能性も考えられます。

おまけ

他にいけてないなと思ったところ

  • Blobトリガーが普通のイベント起動じゃない
  • Functionに紐づいているAzure Storage アカウントが分かり辛い
    • プランやApplication Insightsはポータル見れば普通に分かるのですが…
    • 「AzureWebJobsStorage」という環境変数の値で分かります。
  • 関数を削除するとゴミが残る
    • ドキュメントに記載がないですが、関数を1度でも無効化すると「関数名.Disabled」というパラメータが設定されて、関数を削除しても残留します。(ので別途手動で削除)
  • プランの種類を後から変更できない。また、App Servieプランは単体で作成できるが、従量課金プランとPremiumプランは単体では作成できない。