はじめに

タイマーやキュー、ファイルのアップロードのようなイベントをきっかけにアプリケーションを実行できるAzure Functionsと、WebJobsという2つのサービスを使って、イベント駆動アプリケーションの作り方や双方の違いを2回に分けて見ています。前回のAzure Functionsに続き、今回はWebJobsについて説明し、アプリケーションを作成していきます。

検証環境
・Windows 10 Pro
・Firefox 54
・Microsoft Visual Studio Community 2017

WebJobsとは

WebJobsは、Azure App Serviceの機能のひとつとして用意されているイベント駆動のアプリケーションのプラットフォームです。Functionsが登場するまでは、Azureでイベント駆動アプリケーションの作成に特化したサービスといえばWebJobsでした。

FunctionsはWebJobsの後継とも言われており、トリガーとして対応している他のAzureサービスの数も圧倒的に増えています。しかしながらFunctionsとWebJobsは大きく異なる特徴を持ったサービス同士ですので、一概にFunctionsを使えばいいというわけではありません。

Azure Functionsとの違い

Azure FunctionsもWebJobsも、ともにイベント駆動のアプリケーションを処理するサービスという位置づけは同じです。大きな違いとして以下の3点が挙げられます。

(1)サーバの有無
Functionsはサーバを意識させないサービス(サーバレス)であるのに対し、WebJobsはApp Serviceというサーバ(厳密には異なりますが)を意識したサービスとなっています。

例えば、高負荷時のスケールアウトをFunctionsは自動的に行いますが、WebJobsの場合は手動でスケールアウトするか、オートスケールのルールを決めて初めてオートスケールできるようになります。

これはどちらが優れているということではなく、それぞれに特徴があるということです。Functionsのようにあらかじめオートスケールが組み込まれていることで急な大量アクセスに耐えられる反面、利用料金の推測が難しいといった面がありますし、WebJobsのように手動や自分でオートスケールの設定をすることが手間である反面、スケールタイミングや価格のコントロールが容易であるといったプラスの面もあったりします。これらはどんなアプリケーションを動かしたいかによって評価がわかれるポイントだと思います。

(2)料金体系
Functionsは従量課金、つまり使った分だけお金を支払うプランがあります。これによってクラウドサービス上でコードを実行するまでのハードルが下がります。ちょっとしたツール的なプログラムやアプリとアプリの間でデータの形を変換するようなつなぎのアプリケーションのように、わざわざ時間と大きなリソースを用意してまでサーバを立てるほどではない機能があるときに重宝するのではないでしょうか。

一方WebJobsは、App Service上で実行される機能のため料金体系もApp Serviceに依存します。App Serviceではプラン(BasicやStandard、Premium等)、インスタンスサイズ、インスタンス数といった利用可能なリソースをあらかじめ決定し、それに対して利用時間分の課金が発生します。

リソースを確保してその中で稼働する仕組みのため、バッチアプリケーションのように定期的かつ長時間にかけてある程度コンピュータリソースを消費するアプリケーションの場合でも利用料金を一定に保つことができます。 なお、App Serviceプランの価格については、App Serviceの価格ページを参照してください。

(3)アプリケーションの規模
おおむね、Functionsは小規模で、WebJobsは中・大規模なアプリケーションを作成するのに向いています。

Functionsはサーバの構築が不要ですし、一つの関数から始めることができるため、小さく始めることができます。WebJobsはVisual Studio等のツールを使って省力化することはできるものの、ソリューション、プロジェクト、クラスの順に作成していき、やっと関数を記述することができます。

ただし、規模が大きくなった場合には、Functionsの手軽さはあだになります。クラスの作成も可能なものの、あくまで関数がエンドポイントとなるため、処理が複雑になってくると関数が肥大化していきます。リファクタリングによって関数を分割し、トリガーとバインドで関数間を連携することができますが、旧来よりあるクラスのメソッドによる連携の方がコードの構成管理がシンプルになってきます。この場合はWebJobsの方が効率的に開発を行うことができるかもしれません。

このようにFunctionsとWebJobsは同じイベント駆動アプリケーションのサービスでありながら、相対する特徴をもっていますので、作成するアプリケーションの特性によってサービスを使い分けることが大切です。

WebJobsでタイマー起動の関数を作る

ここからは、サンプルアプリの作成を通して、WebJobsでのアプリケーションの作り方やApp Service上での動かし方を学んでいきます。まずはWebJobsでタイマー起動の関数を作ってみましょう。

Visual StudioからWebJobsプロジェクトの作成

FunctionsがWebブラウザ上でコーディングできるのに対し、WebJobsはIDEを用いたコーディングが基本となります。特に、Visual Studioを使用すると、WebJobs用プロジェクトの作成からAzure上へのモジュールのデプロイまで一貫して行うことができます。本記事でもVisual Studio 2017を使用します。

Visual Studioを開き、[ファイル] - [新規作成] - [プロジェクト]と進みます。プロジェクト新規作成ダイアログが表示されたら、左側メニューから[インストール済み] - [テンプレート] - [Visual C#] - [Cloud]と辿っていきます。(あるいは、検索ボックスに「webjob」と入力しても構いません)「Azure WebJob (.NET Framework)」を選択し、任意のプロジェクト名とソリューション名を入力して[OK]ボタンをクリックします。これでWebJobs用のプロジェクトが作成されます。

WebJobs用プロジェクトの作成

ソリューションエクスプローラーからプロジェクトを確認すると、2つのクラス(.csファイル)が作成されているかと思います。

ProgramクラスはWebJobsプログラムの起動クラスに当たります。WebJobs全体に関わる設定は、このクラスのMainメソッド内で行います。

Functionsクラスは実行する関数の処理を記述していくクラスになります。なお、関数を記述するクラスはこのFunctionsクラスでなければならないという訳ではなく、処理の種類や目的によって複数のクラスに分けたり、クラス名を変えたりしても構いません。

プロジェクト作成直後のソリューションエクスプローラー画面

タイマー起動関数の作成

まずは、前回作成したのと同じようなタイマー起動の関数を、WebJobsでも作成してみましょう。

拡張ライブラリのインストール

WebJobsでタイマートリガーの関数を作成する場合、WebJobsの拡張ライブラリが必要となるので、.NETフレームワークのパッケージ管理ツールであるNuGetを使ってライブラリをインストールします。

[プロジェクト] - [NuGetパッケージの管理]からNuGetの画面を表示し、検索ボックスに「WebJobs.Extensions」と入力して下さい。候補の中から「Microsoft.Azure.Webjobs.Extensions」を選択してプロジェクトにインストールします。

NuGetからWebJobs拡張ライブラリをインストール

Program.csの修正

次にProgram.csのMainメソッドで、タイマートリガーを使うための設定を追加します。Mainメソッドを、以下のように修正して下さい。

Mainメソッドの修正(Program.cs)

 public static void Main()
 {
     var config = new JobHostConfiguration();

     if (config.IsDevelopment)
     {
         config.UseDevelopmentSettings();
     }

     // この行を追加(タイマートリガーを使用することを宣言する設定)
     ★config.UseTimers();★

     var host = new JobHost(config);

     // The following code ensures that the WebJob will be running continuously
     host.RunAndBlock();
 }

Functions.csにタイマートリガーの関数を追加

続いてFunctions.csを修正して、タイマートリガーの関数を作成します。プロジェクト作成後の状態では、FunctionsクラスにProcessQueueMessageというメソッドがあります。このメソッドはキューに入ったメッセージをトリガーにして起動する関数となっていますが、今回はこのメソッドは削除して新たにタイマートリガー用のメソッドを作成していきます。

TimerFunctionメソッドを追加(Functions.cs)

 public class Functions
 {
     public static void TimerFunction([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo)
     {
         Console.WriteLine($"Timer job fired! at {DateTime.Now}");
     }

     ★// 以下のメソッドは削除する★
     // This function will get triggered/executed when a new message is written
     // on an Azure Queue called queue.
     public static void ProcessQueueMessage([QueueTrigger("queue")] string message, TextWriter log)
     {
         log.WriteLine(message);
     }
 }

TimerTrigger属性にはcron式でこのメソッドが起動するタイミングを設定します。前回のAzure Functionsで説明したものと同じ記述ルールとなっており、「秒 分 時 日 月 曜日」で定義されていて、年を定義する項目は存在しません。

このように、WebJobsではトリガーやバインドの設定を、メソッドの仮引数に対する属性として指定していきます。

AzureへのWebJobsのデプロイ

ここまでコードの修正ができたら、AzureのApp Service上にWebJobsをデプロイしてみましょう。デプロイは、Visual Studioから行います。

ソリューションエクスプローラーからプロジェクトを右クリックし、[Azure WebJobとして発行する] (あるいは [Publish as Azure WebJob...] )を選択します。「Azure Web ジョブの追加」というダイアログが表示されますので、「Webジョブ名」を入力し(プロジェクト名のままでも構いません)、「Webジョブ」実行モードを連続実行にしてOKボタンをクリックします。連続実行モードにすることでWebJobsが常に起動状態となり、タイマートリガー関数のような繰り返し呼び出される関数を実行できるようになります。

WebJobsの新規追加画面

次のダイアログが表示されたら「Microsoft Azure App Service」を発行先として選択します。

WebJobsの発行先選択

次の画面でデプロイ先のApp Serviceを決定します。App Serviceの環境が無い場合は[新規作成]ボタンからApp Serviceを新しく作成します。

App Serviceの選択画面

新規作成画面では、名前、サブスクリプション、リソースグループ、App Service プランを入力します。リソースグループとApp Service プランも既存のものが無い場合はここで新規作成することができます。

App Serviceを新規作成する

[作成]ボタンをクリックしてApp Serviceを作成します。作成が完了すると、デプロイの手順へと戻ります。接続先情報としてApp Serviceのサーバー名やサイト名が表示された画面が表示されます。入力されている情報はそのままで[次へ]ボタンをクリックします。

接続先の確認

設定画面では、デプロイするWebJobsの構成を「Release」と「Debug」から選択します。本番相当の環境であれば「Release」を、リモートデバッグ等を行う場合は「Debug」を選択します。今回は「Debug」を選択して[次へ]をクリックします。

構成の選択

最後にプレビュー画面が表示されます。[プレビューの開始]ボタンをクリックすると、デプロイ前にモジュールのビルド行ってコンパイルエラー等がないかをチェックします。 [発行]ボタンをクリックして、WebJobsをApp Serviceにデプロイしましょう。

プレビュー画面

デプロイが開始されると、Visual Studioのコンソールにデプロイのログが表示されます。ログの最後に「Web App は正常に発行されました」と表示されればデプロイは成功です。

WebJobsデプロイ後のVisual Studioのコンソール

App Serviceの設定を変更する

デプロイされたWebJobsをAzureポータルから確認したいと思います。Azureポータルにログインし、App Serviceの画面を開き、Webジョブというメニューを選択します。

Visual Studioで作成、デプロイしたWebJobsがこの一覧に表示されているかと思います。また、画面の上部に警告文が表示されています。これは、タイマートリガーのWebJobsを作成した場合はApp Serviceの設定で「常時接続」をオンにしなければならないということを知らせてくれています。

App ServiceがホストしているWebJobs一覧

この警告文をクリックすると、「アプリケーション設定」画面が表示されます。この中の「常時接続」の項目がオフになっているはずですので、オンにして下さい。

常時接続の有効化/p>

また、WebJobsを使うにあたって必要な設定をもう1つ、この画面で行います。WebJobsを実行した際のログ情報を保存するためのストレージを指定します。画面をスクロールすると「接続文字列」というセクションがありますので、2つの接続文字列を登録します。

名前に「AzureWebJobsDashboard」と「AzureWebJobsStorage」、値にはストレージアカウントの接続文字列を、接続の種類(プルダウン)は「カスタム」を選択して下さい。なお、ストレージアカウントの接続文字列については本連載の第2回目を参照してください。

接続文字列の設定

最後に画面上部の「保存」をクリックして設定を保存します。

WebJobsダッシュボードから関数の実行確認

再度App Serviceのメニューから「Web ジョブ」へ移動します。デプロイしたWebJobsを選択すると、「ログ」のボタンが有効化されるのでクリックします。

WebJobsを選択して「ログ」をクリック

クリックすると新しいウィンドウでWebJobsダッシュボードが表示されます。この画面ではWebJobsのこれまでの実行履歴を確認することができます。 今回作成したタイマートリガーの関数が、5分間隔で実行されていることが確認できるかと思います。

タイマートリガー関数の実行結果

キューメッセージをテーブルストレージに追加してみる

今度は、キューから受け取ったメッセージをテーブルストレージに保存する関数を作ってみましょう。

Tableエンティティの作成

テーブルストレージに登録するデータを表すエンティティクラスを作成します。今回はキューに追加されるメッセージとしてログメッセージを想定し、LogEntityというクラスを作成します。 その際、TableEntityクラスを継承することを忘れないようにしましょう。

LogEntityクラスを作成

 using Microsoft.WindowsAzure.Storage.Table;

 namespace HajimeteWebJob
 {
     public class LogEntity: TableEntity
     {
         public string Message { get; set; }
     }
 }

キュートリガー関数の作成

キューストレージにメッセージが追加されると起動し、そのメッセージをテーブルストレージに保存するQueueToTableFunctionメソッドをFunctions.csに追加します。

QueueToTableFunctionメソッドの追加(Functions.cs)

 public static void QueueToTableFunction(
     [QueueTrigger("log-message-queue")] string queueMessage, // 関数のトリガーとなるキューストレージ
     [Table("logs")] CloudTable table) // データの登録先となるテーブルストレージ
 {
     // テーブルに追加するエンティティを作成する
     var log = new LogEntity
     {
         PartitionKey = "LOG",
         RowKey = Guid.NewGuid().ToString(),
         Message = queueMessage
     };

     // 挿入オペレーションを作成して実行する
     TableOperation insertOperation = TableOperation.Insert(log);
     table.Execute(insertOperation);
 }

引数queueMessageに付与されたQueueTrigger属性には、トリガーとなるキューストレージ名を指定します。キューストレージにメッセージがエンキューされるとQueueTriggerがメッセージをデキューしてstring型でメソッドにデータを引き渡します。

引数tableに付与されたTable属性には、データの保存先となるテーブルストレージ名を指定します。CloudTable型はテーブルストレージを表すオブジェクトになります。

メソッド内の処理では、テーブルストレージに登録するデータを表すLogEntityオブジェクトを作成します。今回はMessageプロパティにキューストレージから取得したメッセージを代入するようにしています。その後テーブルストレージへの挿入オペレーションを作成し、データを1件テーブルストレージに登録しています。

次に、この関数で使用するキューストレージとテーブルストレージをAzure Storageに作成していきます。

キューストレージの作成

キューストレージに「log-message-queue」という名前のキューを作成します。この名前は、先ほどQueueTrigger属性で指定したものと同じにします。

Visual StudioからCloud Explorerを開き、[サブスクリプション(図では従量課金)]-[Storage Accounts]-[作成したストレージアカウント]-[Queues]の順に進みます。 Queuesを右クリックして「キューの作成」を選択し、「log-message-queue」と入力してエンターキーを入力するとキューが作成されます。

キューの作成

テーブルストレージの作成

上記のキューストレージの作成と同様の手順でテーブルストレージに、logsテーブルを作成します。Queuesと同じ階層にあるTablesを右クリックして「テーブルの作成」を選択し、「logs」と入力してエンターキーを押すとテーブルが作成されます。

テーブルの作成

WebJobsのデプロイ

キューとテーブルの作成が完了したら、関数を追加したWebJobsをAzureにデプロイしましょう。ソリューションエクスプローラーからプロジェクトを右クリックし、[Azure WebJobとして発行する]を選択します。

既に一度デプロイをしていて、デプロイ情報が保存されているのでいきなりプレビューのステップまでスキップしてくれます。[発行]ボタンをクリックして、WebJobsをApp Serviceにデプロイしましょう。

WebJobsデプロイのプレビュー画面

Visual Studioのコンソールに「Web App は正常に発行されました」と表示されればデプロイは成功です。

WebJobsデプロイ後のVisual Studioのコンソール

キューにメッセージを追加して関数を起動させる

先程作成したlog-message-queueキューにメッセージを追加して、logsテーブルにデータが追加されるか確認してみましょう。Cloud Explorerからlog-message-queueキューをダブルクリックしてキューの画面を表示します。画面の右上にある「メッセージの追加」ボタンをクリックして何かメッセージを入力し、OKボタンをクリックしてメッセージを追加してみましょう。

キューへのメッセージ追加

キューにメッセージが追加されたかと思います。しばらくしてから「メッセージの追加」ボタンの左隣にある「更新」ボタンをクリックするとメッセージがキューから消えます。今度はCloud Explorerから「logs」テーブルをダブルクリックします。画面の右上にある三角の「実行」ボタンをクリックします。テーブルのMessage列に、先程キューに追加したメッセージの入ったデータが1件存在していれば成功です。

キュートリガー関数起動後のテーブル

まとめ

イベント駆動アプリを作成できるFunctionsとWebJobsという2つのサービスですが、その特徴とアプリの作り方や実行の仕方が大きく違うことが分かったかと思います。それぞれに特徴や使い所がありますので、要件にあったサービスを選択してください。

次回は、これらのアプリケーションのログやリソース情報を分析できるApplication Insightsを紹介する予定です。

WINGSプロジェクト 秋葉龍一著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中