Azureで「お問い合わせフォーム付きサイト」をサーバーレスで作ってみた!後編

"Azureで「お問い合わせフォーム付きサイト」をサーバーレスで作ってみた!後編" (2020年6月8日掲載)

ソフトバンク株式会社のエキスパートSE 葉山恵一です。
法人のお客さま向けに各種クラウドの提案、設計、構築を担当しています。

さて、今回はMicrosoft Azure上にお問い合わせフォーム付きWebサイトをサーバーレスで作ってみたという内容で、ポイントとなる箇所や特に法人のユースケースで選定すべきCDN等、お役立ち情報を発信したいと思います。

前後編の2部構成で、この記事は 「後編」 です。前編は、下記からご確認ください。
Azureで「お問い合わせフォーム付きサイト」をサーバーレスで作ってみた!前編

目次

はじめに

「前編」では BLOBストレージを Originとしコンテンツを配置の上、Azure CDN経由の HTTPSでコンテンツを公開しました。
「後編」ではサーバーレスプラットフォームの要とも言える、Azure Functionsを利用してフォームから POSTデータの受取をし、Azure Storageに格納する流れを以下の手順で実装していきます。

  • Azure Functionsに関数アプリをデプロイ
  • Azure Functionsで POSTデータの受け取り・表示
  • Azure Table Storageへのデータ保存

コーポレートサイトのような静的ページに簡単なお問い合わせフォームを実装する作業のシミュレーションとして、十分役立つ内容になっていると思います。

セルフデプロイを試そうとされる全ての方にとって、この記事が有益なものになれば幸いです。
それでは早速作業を開始しましょう。

1.Azure Functionsに関数アプリをデプロイ

サーバーレスプラットフォームの代名詞とも言える Azure Functions。
AWSでは Lambdaというサービス名で類似サービスが展開されていますが、これらに共通するのは、コード配置しいくつかの設定をするだけで、サーバを用意する事も、言語の実行環境を用意する事も必要なく、アプリケーションの実行が可能になるという事です。
これはつまり、煩雑な OSのセキュリティパッチ適用や、言語実行環境の脆弱性対応等、運用上のわずらわしさから開放される事を意味しています。さらに嬉しい事に、この環境上で実行されるアプリケーションは、実行時間や実行回数による完全な従量課金で実行する事が可能です。
一方で完全な従量課金のため、予想よりも多く実行される事によってかえって請求が高額になってしまうようなケースや、初回実行時の実行遅延(コールドスタート)というサーバーレスプラットフォームならではの課題もあります。
こうした問題に対応するため、Azure Functionsでは、Premiumプランや専用プランなどいくつかのプランが用意されています。実に痒い所に手が届いていますね。

今回は手軽な従量課金プランでの構成にチャレンジしてみます。

1.1 関数アプリの新規作成

Azureポータル上部の検索バーに「Functions」と入力し、表示された「関数アプリ」をクリックし、表示された関数アプリのコンソールで「+ 追加」をクリックします。

サブスクリプションやリソースグループは保有する環境にあわせて選択、関数アプリ名はここでは「serverless-form-app」としました。
公開「コード」、ランタイムスタック「Powershell Core」、バージョン「6」、地域は「Japan East」を選択します。
他プラットフォームへの可搬性を考慮すると、OSに依存しない言語の方が好ましいという側面もあるのですが、ポータル上で直接コーディング可能な事等を考慮し、今回は PowerShellでの実装とします。
これらが正しく選択出来たら、 「次: ホスティング」へ進みます。

""

ストレージアカウントはここでは「serverlessformapp」として新規作成します。
このストレージアカウントは後で操作するので、メモしておいてください。
オペレーティングシステムは今回 PowerShellでコードをデプロイするので、Windowsのみ選択可能です。その他言語では Linuxプラットフォームも選択可能ですが、リージョンによってサーバーレスプランが選択出来なくなる事があるため、注意が必要です。
プランは「消費量 (サーバーレス)」を選択し、そのまま「確認および作成」へ進み作成を完了してください。

""

1.2 関数の作成

関数アプリのデプロイが完了したらリソースへ移動し、関数を作成します。左ブレードメニュー「関数」の右側にある「+」をクリックします。

"

HTTPトリガーを選択し、任意の名称(ここでは HttpTrigger1)、承認レベル「Function」を選択し、「作成」をクリックします。

"

作成した関数の「統合」から、トリガーが「HTTP(request)」、出力が「HTTP(Response)」となっている事を確認します。
HTTPメソッドは今回「POST」のみにチェックし、「保存」してください。

""

これで実行に必要な環境の準備が完了しました。

2.Azure FunctionsでPOSTデータの受け取り・表示

ここまでの作業で、サーバーレスプラットフォームを利用する準備が整いました。早速コードをデプロイし、Webフォームとのつなぎ込みをしてみましょう。
個人的に Azure Functionsが優れていると感じたポイントは、この後2.2で説明する通り、関数そのものにエンドポイントが提供される事です。
AWS Lambdaの場合、API Gatewayサービスと組み合わせて API Gatewayサービスのエンドポイント越しに関数をキックする必要があります。
Azure Functionsでも API Gateway同等の機能を利用するためには、プロキシ機能を活用する必要があるのですが、今回のケースのように簡単なテストをしたい時等、 関数に直接エンドポイントが提供される事は非常に手軽で、Azure Functionsをより身近に感じられるポイントだと思います。

2.1 PowerShellコードの展開

まずは自動作成された「run.ps1」を今回の Webフォームを受け取るための PowerShellコードに置き換えてみます。
関数アプリブレードから作成した関数「HttpTrigger1」をクリックし、「run.ps1」を表示します。

""

表示されたサンプルコードを以下のコードに置き換えます。
今回は Webフォームから application/x-www-form-urlencodedで送信された POSTリクエストを受け取り、これを JSONデータとして単純に Webレスポンスするだけのアプリケーションを作成しました。

1.  using namespace System.Net
2. 
3. # Input bindings are passed in via param block.
4. param($Request, $TriggerMetadata)
5. 
6. # Write to the Azure Functions log stream.
7. Write-Host "PowerShell HTTP trigger function processed a request."
8. 
9. # Interact with query parameters or the body of the request.
10.    $Request_Array = $Request.Body.Split("&")
11.    $Request_Hash = @{};
12.    foreach($line in $Request_Array){
13.       $line = [System.Web.HttpUtility]::UrlDecode(($line),[System.Text.Encoding]::UTF8)
14.       $line_split = ($line.Split("="))
15.       $Request_Hash.($line_split[0]) = $line_split[1]
16.    }
17.    
18.    $Response_Hash = [PSObject]@{
19.       PartitionKey = $Request_Hash.field2
20.       RowKey = get-date -Format "yyyy-MM-dd HH:mm:ss.ms"
21.       name = $Request_Hash.field1
22.       email = $Request_Hash.field2
23.       telephone = ($Request_Hash.tel_no_1 + "-" + $Request_Hash.tel_no_2 + "-" + $Request_Hash.tel_no_3)
24.       category = $Request_Hash.field4
25.       inquiry = $Request_Hash.field5
26.    }
27.    
28.    # Associate values to output bindings by calling 'Push-OutputBinding'.
29.    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
30.       headers    = @{'content-type' = 'text/json' }
31.       StatusCode = [HttpStatusCode]::OK
32.       Body = $Response_Hash | ConvertTo-Json
33.    })

コードの置き換えが完了したら、保存をクリックしてください。

2.2 関数のURL取得とフォームへの埋め込み

作成した関数のエンドポイントを取得します。先程保存した「run.ps1」が表示されている状態で、「</> 関数の URL の取得」をクリックし、ポップアップしたウィンドウ上で「コピー」をクリックします。

""

前編で作成済みの「index.html」Formタグの9行目<form>タグの actionへ取得した関数のURLをペーストします。method="post"となっている点も念のため確認してください。

""

編集した index.htmlを BLOBストレージ上に再度アップロードします。前編で作成した Webコンテナーへ同名の「index.html」のままアップロードします。
「ファイルが既に存在する場合は上書きする」をチェックし、「アップロード」をクリックしてください。

""

2.3 CDNのキャッシュクリア

実際に index.htmlに Webブラウザからアクセスしてみると、なかなか差し替えた「index.html」に切り替わらないはずです。 これは CDNのエッジがキャッシュしている影響なので、キャッシュのクリアを実施しましょう。

前編で立ち上げ済の CDNエンドポイントの概要コンソールから「消去」をクリックします。

""

今回はエッジキャッシュを全て削除して問題ないので、「すべて消去」をチェックし「消去」をクリックします。

""

操作後、しばらく経ってから Webブラウザで対象ページを更新すると、最新の index.htmlが読み込まれている事を確認できるはずです。 ページのソースを表示し、9行目の formタグの action先が関数のエンドポイントになっている事を確認してください。

2.4 関数の動作確認-JSONデータのレスポンス

ここまでの作業が完了したらいよいよフォームからデータを送信してみます。Webブラウザ上のフォームへダミーデータを入力し、送信ボタンをクリックします。

すると以下のように、JSONデータがレスポンスされた事が確認できるはずです。

関数のログを見て、エラーが発生していない事も確認してください。

""

このレスポンスデータはデプロイした「run.ps1」の 37-41行目で表現されています。
HTTPヘッダを ’content-type' = 'text/json’ として、$Response_Hash PSObjectを出力しています。

34. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
35.       headers    = @{'content-type' = 'text/json' }
36.       StatusCode = [HttpStatusCode]::OK
37.       Body = $Response_Hash | ConvertTo-Json
38.    })

Push-OutputBinding コマンドレットで指定される ‘-Name Response’は、関数を構成するファイル「function.json」で定義されています。

""

これは 1.2 関数の作成 で確認した関数の統合の出力「HTTP(Response)」が指定されたタイミングで自動生成されたものです。

ここまでの操作で、フォームデータの受け取りを実行できるようになりました。しかし、これではただオウム返しをしているに過ぎません。 次章では POSTデータをデータベースへ保管する事に挑戦してみます。

3.Azure Table Storageへのデータ保存

レコード保管用のデータべースとして思い浮かべる代表的なエンジンは、SQL Serverや MySQL・ PostgreSQL等の RDBMSだと思います。Azure SQL Database サーバーレス等のサービス提供もありますが、今回の用途にはやや冗長です。
今回は記録用の簡易的な KVSで十分に用が足りますので、ストレージアカウント上でサーバーレス提供されている NoSQL 「Azure Table Storage」を利用してみたいと思います。

3.1 Azure Table Storageの作成

1.1 関数アプリの新規作成でメモをした、新規作成済みのストレージアカウントを開き、ブレードから「テーブル」を選択、「+ テーブル」をクリックし「テーブルの追加」ポップアップを開きます。
今回はテーブル名を「inquiry」とし、「OK」をクリックして Azure Table Storageを作成します。

""

たったこれだけの手順でテーブルストレージの作成が完了しました。
念のため、ブレードから「アクセス キー」を開き Key1, Key2それぞれの「キー」と「接続文字列」を控えておいてください。

""

3.2 Azure Table Storageへの出力追加

HttpTrigger1関数に戻り、統合から 「+ 新しい出力」をクリックし、Azure Table Storageを選択の上、「選択」をクリックします。

""

テーブル名を作成済みのテーブル名「inquiry」とし、「保存」をクリックします。
このときストレージアカウントへの接続用キーは自動指定されているはずですが、念のため 3.1 Azure Table Storageの作成 で控えた内容と突合しておきます。

"関数アプリの新規作成"

作成が完了したら再度「function.json」を開き、Azure Table Storageへの出力設定が追加されている事を確認します。

""

これで Azure Table Storageへ出力するための設定準備が完了しました。 早速コードを改修し、出力を試してみましょう。

3.3 関数の動作確認-Azure Table Storageへの書き込み

run.ps1の末尾に以下のコードを追加します。

39. # Inquiry table update
40.    Push-OutputBinding -Name outputTable -Value ($Response_Hash | ConvertTo-Json)

このコードは、先程 HTTPレスポンスとして利用していた PSObject $Response_Hashを新たに追加した「-Name outputTable」へバインドするというだけのシンプルなものです。
再度先程と同様にテストデータを送信してみます。

3.1で作成したストレージアカウントへ移動し、「Storage Explorer (プレビュー)」をクリック、inquiryテーブルを開きます。
画像のようにレコードが追加されている事が確認できるはずです。

""

いかがでしたでしょうか。
ここまでの内容で、お問い合わせフォームからのデータを受けて HTTPレスポンスし、データベースへの登録処理をする所まで実装出来ました。
本格的な運用にあたっては、入力項目のバリデート処理や、お問い合わせ受信時の管理者向け通知、データベースの定期的なメンテナンス等、いくつかの考慮事項がありますが、動的な Webページをこれほど簡単に構成できる事でサーバーレースアーキテクチャの魅力を感じていただけたのではないでしょうか。
オンプレミス環境からクラウドリフトをするコンテンツがある際は、単純なサーバ構築で置き換えるだけではなく、このようにクラウドサービスを活用する事で、運用の手間やランニングコストを大きくセーブする事が可能になります。
ぜひパブリッククラウドを賢く安くご利用ください。

f:id:aq-sb-03:20200121114118p:plain

関連リンク

Microsoft Azure

Microsoft Azureは、Microsoftが提供するパブリッククラウドプラットフォームです。 コンピューティングからデータ保存、アプリケーションなどのリソースを、必要な時に必要な量だけ従量課金で利用することができます。