こんにちは、サイオステクノロジー技術部 武井です。今回は、なかなかわかりにくいマイクロサービスの概念をわかりやすく説明し、さらにAzureで提供されるマイクロサービスフレームワーク「Azure Service Fabric」を使って、LINE風なチャットアプリを作ってみたいと思います。
※本記事をベースにJapan Azure User Group 8周年イベントで登壇させて頂きました!!
マイクロサービスとは?
昨今、様々なところで、マイクロサービスという言葉を聞くと思います。なんだか、バズワード化している感じもしますが、マイクロサービスとは、一言で言ってしまえば、今まで、一つの大きなアプリケーション(プロセス)で動かしていた複数のサービスを、1プロセスあたり1サービスに分けてしまいましょうということになります。
マイクロサービスは、今後アプリケーションを設計する上で非常に重要な概念と言えます。特にクラウド上の基盤でアプリケーション開発・運用する際、マイクロサービスの特徴である「コンポーネント化」や「分散データ管理」は、非常に有効に機能します。
しかしながら、マイクロサービスは、そのアーキテクチャの複雑さゆえ、学習コストは決して小さいものではないこともあり、マイクロサービスの採用に踏み切ることに抵抗を持たれる方も少なからずいらっしゃいます。
本記事では、そういった方のために、可能な限り平易な言葉による解説、親しみやすい具体例、実践的なコンテンツをご提供することで、マイクロサービスの採用を検討している方の助力となればと考えております。
では、さっそく具体的な例を上げて、更に詳しく説明します。説明をわかりやすくするために、マイクロサービスではないアプリケーション(ここではモノリシックなアプリケーションと呼びます)と、マイクロサービスなアプリケーションを比較してみます。
モノリシックなアプリケーション
まず従来型のモノリシックなアプリケーションがどういったものかを説明していきます。モノリシックなアプリケーションの代表的なものは、下図のような構成です。
Javaで作成された掲示板のWebアプリケーションを例にあげております。Apache Tomcatの上に、単一のJavaプロセスが稼働しており、その中には、メッセージやユーザー情報を管理するサービスクラス、画面を表示するJSPが内包されています。データストアは、単一のデータベースの中に複数のテーブルが入っており、Webアプリケーションはそのテーブルにアクセスすることで、情報を取得、更新します。
マイクロサービスなアプリケーション
先のモノリシックなアプリケーションをマイクロサービスに置き換えると、以下のような構成になります。
違いを見ると一目瞭然なのですが、マイクロサービスなアプリケーションは、1プロセスごとに1サービスが稼働しており、そのサービスが稼働するために必要な他の全てのもの(ここではメッセージ表示UIなどのUIコンポーネント)も内包します。
また、サービスがアクセスするデータストアは、サービス単位で全て独立しており、一つのデータストアをサービス間で共有することは原則としてありえません。
サービス間の通信は、RPC(リモートプロシージャーコール)もしくはメッセージキューサービスを用いることが多くあります。上記の例はメッセージキューサービスです。あるサービスは、相手のサービスに送りたいデータをメッセージとして、キューに登録します。相手のサービスはキューからのそのメッセージを取得することで、サービス間通信を実現します。
この例では、一つの仮想マシンやコンテナあたりに、1プロセス配備されていますが、必ずしもこのような構成にする必要はありません。以下のような構成でも問題はありません。
また、上記例では、Javaをベースにしていましたが、PythonでもC#でも構いません。さらにサービスごとに異なる技術を使ってもよいとになっています。あるサービスでは、開発言語C#、データストアMySQL、一方のサービスでは、開発言語Python、データストアPostgreSQLといった構成もありです。こちらについては、後述します。
さて、一見すると、モノリシックなアプリケーションと比べて、かなり複雑な構成ですよね。いろいろな課題が想像できます。サービス間の通信もモノリシックなアプリケーションと比べるとメッセージキューサービスを用いてるがゆえに手順が煩雑ですし、データの結合方法など、複雑がゆえに色々と考慮しなければならないことがあります。そのような課題を抱えてまでも、何故にマイクロサービスというアーキテクチャを検討する必要があるのかを、以降の章で説明していきたいと思います。
マイクロサービスのメリット
メリットその1:機能追加・変更による影響が少ない
マイクロサービスを用いると、機能追加・変更をした場合の、他の機能への影響を少なくすることができます。
先の掲示板のアプリケーションで、何らかの機能を追加するため、ソースコードを修正したとします。モノリシックなアプリケーションの場合、その修正による影響を調査するコストは想像に難くないと思います。ソースコードの構造を理解し、修正したメソッドや関数を呼び出している場所を特定し・・・なんてすごい手間ですよね。
マイクロサービスであれば、サービス間の通信に利用するインターフェースさえ変更がなければ、他のサービスへの影響を一切気にかける必要はありません。先の例では、サービス間通信にメッセージキューサービスを使っていますが、その場合、サービス同士がやり取りするメッセージのフォーマットさえ変更がなければ、どれだけサービスの中身を改修しても、他のサービスへの影響はありません。
メリットその2:スケールアウトしやすい
マイクロサービスは、スケールアウトのコストを非常に少なくすることができます。例えば、先の掲示板サービスの例で、掲示板に投稿されたメッセージを閲覧するユーザーが爆発的に増加し、ロードバランサーで負荷分散しなければならなくなったとしましょう。
モノリシックはアプリケーションですと、以下のような構成を取ります。
上記の例を見れば分かる通り、メッセージ管理サービス、メッセージ表示JSPが2つに増え、ロードバランサーで分散されているので目的は達成できました。しかし、本来、負荷の増えていないユーザー情報管理サービスや、アクセスログ管理サービスまで負荷分散を行っています。これの欠点は、必要のないサービスまでスケールアウトすることにより、余分なサーバーリソースを消費してしまうことになります。
マイクロサービスですと、以下のような構成になります。
ご覧の通り、モノリシックなアプリケーションをスケールアウトした場合と違い、メッセージ管理サービスを含む仮想マシンのみをスケールアウトさせています。メッセージ管理サービスだけを稼働させる仮想マシンのリソースは、モノリシックなアプリケーションを稼働させるためのリソースより少なくてすみますので、スケールアウトした場合も全体的に消費するリソースは少なくすることが可能です。
メリットその3:障害に強い
マイクロサービスは障害に強いです( ´∀`)bグッ!正確に言いますと、障害に強いのではなく、障害が発生することを前提に設計を行い、あるサービスに障害が発生しても、その障害の影響が他のサービスに波及しません。
こちらも今までと同様に、まず、モノリシックなアプリケーションにおいて、アクセスログの処理に障害が出たとしましょう。
掲示板アプリケーションでは、ユーザーがメッセージを書き込んだと同時に、アクセスログ管理サービスを呼び出し、アクセスログを記録します。しかしながら、アクセスログ管理サービスは障害が発生しているので、メッセージの書き込み中にエラーが発生し、メッセージの書き込みが失敗してしまいます。
しかしながら、マイクロサービスでは、以下のようになります。
冒頭でもご説明したように、サービス同士の通信はメッセージキューサービスを使っていますので、非同期であり疎結合です。エンドユーザーがメッセージを登録すると、メッセージ管理サービスが呼び出され、メッセージキューサービスにメッセージが配信され、エンドユーザーには処理が成功したように見えます。しかしながら、アクセスログ管理サービスは障害が起こっているため、アクセスログを記録することができません。ただ、キューにはメッセージが登録されているので、サービスが復旧次第、アクセスログを登録することができます。
このような仕組みにより、障害の影響を最小限に抑えることが可能です。
メリットその4:デプロイによる影響が少ない
例えば、アクセスログの処理を高速化するために、アクセスログ管理サービスを修正したとします。モノリシックなアプリケーションでは、一つのサービスを修正するだけでも、アプリケーション全体をデプロイする必要があるので、全てのサービスを停止する必要があります。つまり、ユーザーに直接関係のないアクセスログの処理を修正・デプロイするだけで、掲示板のメッセージを閲覧・登録といったサービスも停止する必要はあり、そのユーザー影響は甚大です。
しかしながら、マイクロサービスは、今までに挙げてきた例からもわかりますように、アクセスログの処理を修正した場合は、アクセスログ管理サービスだけデプロイすればよいのです。また、「メリット3:障害に強い」の例でも挙げたように、アクセスログ管理サービスが停止していても、他のサービスは通常通り稼動可能なので、ユーザー影響は全くありません。
メリットその5:サービスごとに異なる技術を使うことが出来る
先述しましたように、マイクロサービスでは、サービス間の通信はメッセージキューサービスなどを使うことによって、疎結合にします。やり取りするメッセージのフォーマットさえ変更がなければ、サービス間の影響は全くないので、各サービスごとで異なる技術を使っても構いません。つまり、言い換えれば、各サービスが提供する機能に適したアーキテクチャを選択出来るということです。
先程の掲示板アプリケーションで、以下のような構成を取ることも可能です。
メッセージ管理サービスは、SQL Serverを用いているため、同じマイクロソフト製で相性が良いと思われるC#、ユーザー情報サービスは、単なるマスタメンテナンス画面であるため、scaffoldが利用可能なPHPのフレームワークCakePHPを採用し、データベースはPHPと相性がよいMySQL、アクセスログ管理サービスは、ログのフォーマットが将来的に変更する可能性が高いため、定型的なスキーマ構造を持たないDocumentDB、そしてJSONと相性がよいnode.jsを使う・・・といった形で、それぞれのマイクロサービスが提供する機能に適したアーキテクチャを選択することが可能になります。
ただしデメリットも・・・
いいことずくめのマイクロサービスですが、デメリットもあります。
モノリシックなアプリケーションに比べて、アーキテクチャが複雑になりがちです。モノリシックなアプリケーションで保有していた機能を細かいサービスに分けるがために、どうしてもサービス間通信や、サービス間のデータの結合、トランザクション処理が複雑になりがちです。サービス間通信は、RPCやメッセージキューサービスを用いなければなりませんし、トランザクション処理も単一のデータベースのように簡単にロールバックできません。
また、性能面にも懸念が出てくる場合があります。モノリシックなアプリケーションは、サービス間の通信はインメモリコールになりますが、マイクロサービスではサービス間通信にRPCやメッセージキューサービスを用いるため、リモートコール(ネットワーク越しの通信)になります。もちろん、インメモリコールの方が、リモートコールに比べて、レスポンス面で軍配が上がります。全体的なアプリケーションの性能要件をよく見極めなければいけません。
メリットでもあった、サービスごとに異なるアーキテクチャを適用することができるというのも、一方ではデメリットの側面もあります。先程の例のように、サービスごとにC#、PHP、node.jsというアーキテクチャを用いるということは、それだけ、その技術を有するエンジニアが必要ということになります。モノリシックなアプリケーションでは、単一の言語で作成されるので、その言語に精通しているエンジニアだけ集めれてばよかったのに、マイクロサービス化したら、エンジニアのリソース確保が困難になるという可能性もあります。
マイクロサービスは、モノリシックなアプリケーションより構成が複雑なゆえに、その設計に必要な技術は、モノリシックなアプリケーションのそれより高度なものが必要になります。様々なアーキテクチャに関する知識に精通している必要がありますし、サービスの粒度を適切に分割するための技術(Domain-Driven Designなど)も必要になります。
こういったデメリットもありますので、マイクロサービスを適用する際は、そのトレードオフをよく考える必要があります。
マイクロサービスフレームワーク「Azure Service Fabric」
さて、ここまでさんざん偉そうにウンチクを述べさせていただきましたが、実際にマイクロサービスなアプリケーションを作るための実践的コンテンツのご説明をさせて頂きたいと思います。
実は、Microsoft Azureのサービスで、マイクロサービスなアプリケーションを(比較的)簡単に構築することが出来るフレームワーク「Azure Service Fabric」というものが提供されています。
Azure Service Fabricは、ものすごく平たく言ってしまえば、Azure Service Fabricが規定する通りのディレクトリ構成や定義ファイルの書式に従ってコーディングして、Azure Service Fabricが適用する専用のデプロイコマンドを叩くだけで、Azure上の基盤にマイクロサービスが展開されるといったものです。
Azure Service Fabricでマイクロサービスが稼働するまでのおおざっぱな流れ
先の説明は、あまりにもざっくりとしすぎたので、本章で、具体的な例も交えて説明します。(図が細かいので、クリックして拡大して見てください)
上記の図は、Azure Service Fabricにて、マイクロサービスが稼働するまでの、とってもおおざっぱな流れを図にしたものであり、細かい手順は後ほどご説明しますが、まずは、本章でイメージを掴んで頂ければと幸いです。ちなみに、Azure Service Fabricは2018年7月9日現在、JavaとC#をサポートしており、上記の図の例は、Javaになりますが、多分C#もさほど変わりないと思っております。
さて、早速、上記の図を元にして、詳細に説明致します。上記の図の項番((1)・・・(6))と、以下の説明の項番は対応しております。
(1) Azure Service Fabricクラスターの構築
AzureポータルからAzure Service Fabricが稼働する環境を構築します。この環境のことを、以降、Azure Service Fabricクラスターと呼称します。Azureポータルからは、数クリックで簡単にAzure Service Fabricクラスターを構築することができます。
Azure Service Fabricクラスターの実態は、Azure Load BalancerでバランシングされたVirtual Machineの集合体です。Azure Service Fabricクラスターを作成すると、Azure Load Balancer、Virtual Machine Scale Setsが作成され、その中にオートスケール対象のVirtual MachineがAzureポータルで指定された数だけ、作成・展開され、後の工程で、各Virtual Machineにマイクロサービスを展開することとなります。
Azure Service Fabricとかマイクロサービスとか聞くと、なんかすごいことやってそうな雰囲気ですが、それらを構成する要素は、意外にもレガシーな技術なのです。
(2) マイクロサービスを構成するソースコードの作成
マイクロサービスを構成するソースコードを作成します。一番、肝心な部分ですし、エンジニアの腕がなる部分ですね。もちろん、Azure Service Fabricはフレームワークですので、ソースコード作成時には、フレームワークのルールに乗っ取る必要があります。実装の詳細については、後ほど説明しますが、ここで大事なのは、ソースコードを配置するディレクトリです。
Azure Service Fabricでは、サービス単位に以下のディレクトリを作成し、ビルドの成果物や設定ファイルを配置することが決まりとなっております。
Service ├── Code ├── Config └── Data
それぞれのディレクトリの詳細は以下のとおりです。
Code・・・アプリケーションの実行ファイルを格納します。
Config・・・アプリケーションの設定ファイルを格納します。
Data・・・アプリケーションで使用するデータを格納します。
もっとも重要で利用頻度が高いのはCodeというディレクトリです。ここでは、Javaで作成したコードをjarにビルドしたものを格納します。
(3) 起動スクリプトの作成
(2)で説明したCodeディレクトリには同時にentryPoint.shというファイルも作成する必要があります。このファイル、実際は、雛形作成ツールYeomanやEclipseなどのIDEプラグインで自動生成されることが多いのですが、非常に重要なファイルです。これは、(3)で作成した実行プログラム(Javaの場合はjar)を起動するためのスクリプトです。実際に中身を見てみましょう。
#!/bin/bash BASEDIR=$(dirname $0) cd $BASEDIR export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/lib echo $LD_LIBRARY_PATH java -Djava.library.path=$LD_LIBRARY_PATH -jar App.jar
なんともないbashのシェルスクリプトですが、最後の行にご注目ください。(2)で配置したjarで実行する一文があります。これが実はキモでありまして、それは、このあと(5)ご説明致します。
(4) 実行ファイルのAzure Service Fabricクラスターへのデプロイ
Azure Service Fabric専用のコマンドsfctlで、(2)(3)で作成した実行ファイルをAzure Service Fabircクラスターへアップロードします。このコマンドを一発叩くと、指定したインスタンス数分、各ノードにマイクロサービスを構成する実行ファイルがアップロードされます。例えばノードが5つあり、インスタンス数を3としてアップロードすると、5ノードの中からランダムに3つのノードの実行ファイルがアップロードされます。
(5) マイクロサービスの起動
(4)で各ノードにアップロードされたマイクロサービスは、entryPoint.shが内部的に起動されます。先程ご説明したようにentryPoint.shには最後の行に実行ファイル(App.jar)を起動する一文が書かれておりまして、これが実行されることにより、マイクロサービスを構成するプロセスが起動することになるわけです。
(6) ユーザーのアクセス
ユーザー向けのエンドポイントは、Azure Load Balancerで提供されています。Azure Load Balancerに来たリクエストは、各ノードに配置されいてるマイクロサービスに分散されます。
このような仕組みでAzure Service Fabricでマイクロサービスが稼働するわけです。
Azure Service Fabricが提供するマイクロサービスの種別
Azure Service Fabricでは様々な種別のマイクロサービスを作成することはできますが、本記事では以下の2つの代表的なマイクロサービスをご説明します。この後に紹介する実践的コンテンツ(チャットアプリ)でもこの2つのサービスを使います。
- Stateless Reliable Service
- Stateful Reliable Service
その前にStateful及びStatelessの説明をさせて頂きます。
Statefulであるとは?
Statefulとはサーバーが状態を持つ構成のことをいいます。Statefulの代表格は、認証にセッションとCookieを使った実装です。セッションを使った実装は以下のようなイメージになります。
アプリケーションサーバーはセッションを発行し、セッションIDとそれに紐づく値(ユーザー名など)を保存します。それと同時にアプリケーションサーバーはユーザーにセッションIDのCookieを発行します。次回のリクエストからは、ユーザーはHTTPリクエストにセッションIDが格納されたCookieをリクエストし、アプリケーションサーバー側では、そのCookieにあるセッションIDから、アプリケーションサーバー側にあるユーザー情報を取得して、アクセスしてきたユーザーを識別します。
しかし、これだと不都合が生じる時があります。アプリケーションサーバーの負荷が高くなり、サーバーの台数を増やす(スケールアウト)したときは、以下のような構成になります。
どちらのアプリケーションサーバーにアクセスしたときも同じセッション情報を取得できるようにしなければなりません。そのためには、セッション情報をアプリケーションサーバー間で共有する仕組みが必要になります。セッション情報をデータベースに格納したり、セッション情報をサーバー間でレプリケーションしたりと手法は色々ありますが、どれも大掛かりな構成となり非常に手間がかかります。これでは簡単にスケールアウトできません。これがステートレスでないことのデメリットです。
Statelessであるとは?
一方で、Statelessとは、サーバーが状態を持たない構成のことをいいます。
認証にJSON Web Token(詳細はこちら)を用いた例です。アプリケーションサーバー側から発行されたJSON Web TokenをクライアントのWebストレージに保存して、リクエストごとにAuthorizationヘッダにJSON Web Tokenを乗せています。JSON Web Tokenはその性質上、サーバー側に情報を持たなくてよい(JSON Web Tokenの中に情報がある)ので、インフラ構成がシンプルになります。
APIを用いたシステムの場合、APIはその汎用性を保つため、非常に細かい機能に分割され、様々なシステムから利用することが予想されます。そのため、負荷も高くなり、必要に応じてスケールアウトすることが必然とされますが、ステートレスであるということはスケールアウトしやすいということになります。
これがステートレスであるということのメリットです。
Stateless Reliable Serviceとは?
先程ご説明したStatelessな構成を提供するためのサービスです。一般的な用途は、Web アプリケーションの静的コンテンツやAPI を公開するフロントエンド サービスです。こちらは特に何も難しいことはありません。
Stateful Reliable Serviceとは?
Statefulな構成を提供するためのサービスです。こちらがちょっと特殊なのですが、先程もご説明したようにStatefulはサーバー側で状態を持つ、すなわちサーバー側でデータを持つことを前提とした構成です。
しかしながら、Azure Service FabricはVirtual Machine Scale SetsとAzure Load Balancerによって構成された複数ノードが高度に分散して稼働します。サーバー側で状態を持ってしまったら、全てのデータをノード間で同期しなければなりませんが、実はAzure Service Fabricはこの同期処理を裏でやってくれるのです。
具体的には、Azure Service Fabricが提供するReliableHashMapというクラスを用いてHashMapを作成すると、開発者が意識することなく、Azure Service Fabricクラスター側でHashMapの内容が各ノード間で同期される仕組みとなっています。
LINE風なチャットアプリを作ってみよう
では、前置きが非常に長くなりましたが、早速、実践的なコンテンツを作成してみましょう。さきほどご紹介したStateful Reliable Service及びStateless Reliable Serviceを駆使した、LINE風(?)なチャットアプリをAzure Service Fabricで作成してみたいと思います。
チャットアプリの仕様
チャットアプリの画面は以下のようになっております。
画面下部にテキストボックスがあります。チャットに発言したい場合は、「名前入れてね」の部分に発言者の名前を入れて、「発言内容入れてね」の部分にメッセージを入れて「発言する」をクリックします。
「更新する」のボタンをクリックしない限り、相手の最新の発言が表示されません。
なんともレガシーな仕様ですみません。イマドキならWebSocketあたりでリアルタイムのチャットが出来るようにするんでしょうけど、今回はAzure Service Fabricの説明がメインなので、あまりチャットアプリの仕様を複雑にすると、本質から外れてしまうような気がしたので、こんな簡単な作りにさせて頂きました。
認証とかユーザー登録とかいった仕組みもありません。
画面に表示されるユーザーのアイコンは、ユーザー名に紐付いております。ntakeiの場合はlisaの場合はそれ以外の場合はのアイコンが表示されるようになっております。
システム構成
チャットアプリのシステム構成は、下図のとおりになります。
ChatWebとChatDataの2つのマイクロサービスから構成されており、それぞれの役割は以下のとおりです。
ChatWeb (Stateless Reliable Service)
- HTMLや画像などの静的コンテンツを表示するためのHTTPインターフェース
- メッセージを投稿・閲覧するためのWeb APIのHTTPインターフェース
ChatData (Stateful Reliable Service)
- メッセージをReliableHashMapに登録・取得するためのRPCサービスを提供
アプリケーションの動作シーケンスは以下のとおりです。
- ユーザーが特定のURLにアクセスすると、そのリクエストをHTTP経由でChatWebサービスが受け、ユーザーに画面のHTMLをレスポンスとして返す。
- ユーザーは、チャットのメッセージ登録・取得要求をHTTPの経由でChatWebサービスに要求する。
- ChatWebサービスは、RPC経由でChatDataサービスをコールし、ReliableHashMapにメッセージを登録・取得を行う。
- Azure Service Fabricが、ReliableHashMapに登録されたデータをノード間でレプリケーションする。
開発環境の構築
開発環境を構築します。Azure Service Fabricでは、2018年7月9日現在、JavaとC#をサポートしていますが、今回はJavaで作成してみます。マイクロソフト的には、Javaはマイノリティかもしれませんが、多分、世界的にみれば、Javaのユーザーの方が多いと思いますので、Javaで作ってみます。というか、私自身C#があまり得意でないというのも大きな理由のひとつなのかもしれません。
OSはMac Sierra、IDEはEclipse Neonを想定しております。今回の開発では、Azure Service FabricのEclipseプラグインを使いますが、こちらWindowsには対応していないようです(2018年7月9日現在)。
では、早速開発環境を構築してみましょう。Eclipse Neonはインストール済みであるとします。
Azure Service FabricのEclipseプラグインをインストールします。このプラグインを使うと、Azure Service Fabricのプロジェクトの雛形の作成や、クラスターへのデプロイなどをコマンドいらずのGUIでできちゃいます。Eclipse上部のメニューにて、「Help」→「Install New Software…」の順にクリックします。
「Add…」をクリックします。
「Location」のところに、「https://dl.microsoft.com/eclipse」と入力して、OKをクリックします。
「Azure ServiceFabric Plugin for Eclipse」にチェックして「Next」をクリックします。
後はウィザード通りに従えば、プラグインのインストールは完了です。
次は、Azure Service Fabricクラスターに対する各種操作(ビルドなど)を行うsfctlコマンドをインストールします。sfctlコマンドの動作にはPython3が必要になりますので、インストールします。
# brew install python3
次にsfctlコマンドをインストールします。
# pip3 install sfctl
これでsfctlコマンドのインストールは完了です。
次に、Azure Service FabricのビルドにはGradleが必要なので、これをインストールします。
Gradleは、sdkmanというパッケージマネージャーを使ってインストールします。まず、そのsdkmanをインストールします。
# curl -s https://get.sdkman.io | bash ... All done! Please open a new terminal, or run the following in the existing one: source "/path/to/.sdkman/bin/sdkman-init.sh" Then issue the following command: sdk help Enjoy!!!
上記のように表示されればOKです。上記の/path/toは、コマンドを実行したユーザーのホームディレクトリと読み替えてください。
# source "/path/to/.sdkman/bin/sdkman-init.sh"
Gradleをインストールします。
# sdk install gradle
これでGradleのインストールは完了です。しかしながら、ちょっと小細工が必要になります。後にインストールするAzure Service FabricのEclipseプラグインは、なぜか内部的にGradleのパスが、/usr/local/bin/gradleに固定になっているようなのです。なので、実際にGradleがインストールされているパスへのシンボリックリンクを作成する必要があります。私の環境では、Gradleは/Users/ntakei/.sdkman/candidates/gradle/current/bin/gradleにインストールされていましたので、以下のようにシンボリックリンクを作成しました。
# ln -s /Users/ntakei/.sdkman/candidates/gradle/current/bin/gradle /usr/local/bin/gradle
プロジェクトの構成
本章では、今回作成するチャットアプリの、Eclipseでのプロジェクト構成を説明します。これ以降の章より、具体的な手順を説明しますが、その手順はかなりのステップ数に及ぶこととなります。なので、途中で自分が何をやっているのか迷子にならないように、まず、手順の概略を最初に示します。全体の流れをおさえた上で、詳細に踏み込むというわけです。
まず、前提知識として、Azure Service Fabricの、Eclipseプラグインによるビルドでは、ビルドツールとしてGradleを用います。Gradleの基本的な知識を事前におさえておく必要がありますので、Gradleに自信のない方は、まず本ブログの「多分わかりやすいGradle入門」をご覧になってください。今回のアプリケーション作成に必要なGradleの知識が一通り記載されています。
さて、またまた前置きが長くなってしまいました。今回作成するプロジェクトは以下のような構成になります。
Chatという親プロジェクトのサブプロジェクトして、3つのプロジェクト「ChatWeb」「ChatData」「ChatRPC」があります。ChatWeb、ChatDataは「システム構成」のところで説明したChatWebサービス、ChatDataサービスに該当します。ChatRPCはChatWeb、ChatDataの両方から参照されるプロジェクトで、ChatDataへのRPCでやり取りされるデータ、及びRPCのインターフェースを定義しています。
ビルドの流れ
これ以降は、ビルドの大まかな手順を示していきます。ビルドツールGradleにて、以下のような手順でビルドが行われます。まず、ChatWebプロジェクトからビルドします。
ChatWeb.jarが出来上がると、次はChatWebサービスを稼働させるために必要なライブラリのコピーを行います。
これでChatWebサービスのビルドは完了です。ChatDataサービスにおいても、ほぼ同様のことを行います。ChatDataプロジェクトをビルドし、ChatData.jarを作成します。
ChatData.jarが出来上がると、次はChatDataサービスを稼働させるために必要なライブラリのコピーを行います。
最後に、Azure Service FabricのEclipseプラグインを使って、Azure Service Fabricクラスターにデプロイします。
プロジェクトの構成、ビルドの大まかな流れをご説明しました。これより、詳細な手順に入ります。
Chatプロジェクトの作成
今回作成しようとするアプリケーションのソースコードは、以下のGitHubに登録しております。これから行うプロジェクトの作成で、ソースコードはもちろんのこと画像ファイル等いくつか必要なファイルがありますので、以下のGitHubをあらかじめCloneしておいてください。
https://github.com/noriyukitakei/asf-sample-chat
では、まずはじめにChatプロジェクトを作成します。以下のような構成のプロジェクトを作成することをゴールとします。
Eclipseを起動し、「File」→「Other…」の順にクリックします。
「Service Fabric」→「Service Fabric Project」の順にクリックします。
「Project Name」のところに「Chat」と入力して、「Next >」をクリックしてください。
「Stateless Service」を選択して、「Enter Service Name」の部分に「ChatWeb」と入力して、「Finish」をクリックしてください。
下図のようにChatApplication/ChatWebPkg/Code/wwwroot/imgというディレクトリを作成し、3つの画像ファイルを置いてください。画像ファイルは先程のGitHubにあがっております。この画像ファイルは、ユーザー名と紐付いており、ユーザー名ntakeiで発言すると、ntakei.pngがアイコンとして表示されます。ntakei、lisa以外のユーザーはother.pngが表示されます。
wwwrootディレクトリ配下に、以下の内容のファイル(ファイル名はindex.html)を作成してください。このファイルはチャットの画面を表示するためのHTMLになります。
HTTPのリスニングポートの設定をします。Chat/ChatApplication/ChatWebPkg/ServiceManifest.xmlを開き、以下のように修正します。<Resources>〜<Resources>の部分を追加します。後に作成するHttpCommunicationListener.javaでこの設定を利用します。ServiceManifest.xmlというファイルはサービスごとに存在し、サービスの全体的な挙動を決定するための設定ファイルになります。
Chat/build.gradleを以下のように修正します。
Chat/setting.gradleを以下のように修正します。
ChatWebプロジェクトの作成
Stateless Reliable ServiceであるChatWebサービスのプロジェクトを作成します。プロジェクト自体は、Chatプロジェクトのサブプロジェクトとして既に出来ているので、ここではいくつかのファイルを追加・修正します。以下のような構成のプロジェクトを作成することをゴールとします。
ChatWebサービスがWebサーバーとしてリスニングするためひ必要な構成ファイルHttpCommunicationListener.javaを以下の内容で作成します。
先程作成したHttpCommunicationListenerをリスナーとして登録するため、Chat/ChatWeb/src/statelessservice/ChatWeb.javaを以下のように修正します。
build.gradleを以下のように修正します。
ここでHttpCommunicationListenerに「Access restriction: The type ‘HttpServer’ is not API」というエラーが出ている場合には以下の対処を行ってください。
「Eclipse」→「Preferences…」の順にクリックします。
「Java」→「Compiler」→「Errors/Warnings」→「Deprecated and restricted API」の順にクリックして、「Forbidden reference(access rules)」を「Info」に変更して「OK」をクリックしてください。エラーが解消されるはずです。
ChatRPCプロジェクトの作成
ChatWeb⇔ChatData間でRPC通信をするために必要なJavaBeans及びインターフェースを定義します。以下のような構成のプロジェクトを作成することをゴールとします。
「File」→「New」→「Other…」の順にクリックします。
「General」→「Folder」の順にクリックして、「Next >」をクリックします。
「Enter or select the parent folder」で「Chat」を選択して、「Folder name」に「ChatRPC/src/rpcmethods」を入力して、「Finish」をクリックしてください。
ChatWeb⇔ChatData間でRPC通信をする際に、ユーザーやメッセージの情報を格納するJava Beansを作成します。Chat/ChatRPC/src/rpcmethods/ChatMessage.javaを以下の内容で作成してください。
ChatWeb⇔ChatData間でRPC通信をする際のメソッドのインターフェスを作成します。Chat/ChatRPC/src/rpcmethods/ChatRPC.javaを以下の内容で作成してください。
build.gradleを以下の内容で修正してください。
ChatDataプロジェクトの作成
RPCでリスニングし、メッセージの取得、登録を行うStateful Reliable Serviceのプロジェクトを作成します。以下のような構成のプロジェクトを作成することをゴールとします。
一番上にあるChatというプロジェクトを右クリックして、「Service Fabric」→「Add Service Fabric Service」の順にクリックします。
「Stateful Service」をチェックして、「Enter Service Name」の部分に「ChatData」と入力して、「Add Service」をクリックしてください。
上記の手順でChatDataというStateful Serviceが作成されるはずなんですが、わたしの環境では一発で作成されませんでした。サービスの入れ物だけ出来て中身が空っぽなんです。そこで、何をしたかといいますと、全く別のService Fabricのプロジェクトを作って、そこで適当なStateful Serviceを作成して(この場合はきちんと成功します)、そのプロジェクトを消して、Chatプロジェクトに戻って、もう一度、Stateful Serviceを作成したらうまくいきました。うーん(゜゜)なんでなんでしょうね。よくわからなかったです。バグですか?
ささ、気を取り直して、先に進みます。Chat/ChatData/src/statefulservice/ChatData.javaを以下のように修正します。先程ChatRPCプロジェクトで定義したChatRPCインターフェースをimplementsしたRPC通信の実装クラスを作成します。
Chat/ChatData/build.gradleを以下のように修正します。
さて、これで全てのプロジェクトを作成し終わったのですが、おそらくコンパイルエラーが多数出ていると思います。これは、Gradleの設定ファイルで設定した各プロジェクト間の依存関係が反映されていないからです。もっと正確に言いますと、ChatWebプロジェクトとChatDataプロジェクトがChatRPCプロジェクトを参照出来ていませんし、build.gradleのdependenciesで定義した依存ライブラリをダウンロード出来ていないからなのです。ここで、Gradleの設定ファイルをプロジェクトに反映させる処理を行います。
Chatプロジェクトを右クリックして、「Gradle」→「Refresh Gradle Project」の順にクリックします。すると、コンパイルエラーがキレイサッパリなくなるはずです。
ローカルクラスターへのデプロイ
さて、これで準備は整ったので、Azureにデプロイしてみましょうと思いますが、いきなりAzureに上げる前にローカルクラスターにデプロイします。
Azure上にService Fabric Clusterを作成すると、最低限5台のVirtual Machineを起動しなければいけないので、非常にお金がかかってしまいます。開発の段階で、こんなにVirtual Machine起動していたら、いくらお金があっても足りません。
そこで、ローカルクラスターの出番です。ローカルクラスターとはDockerイメージで提供されたServic Fabricのクラスターです。Dockerがインストールされていれば、どこでも使えます。まずはローカルクラスターで試しに動かしてみて、問題なければAzure上のクラスターにデプロイするというのがベストです。
では、ローカルクラスターをインストールしてみましょう。Dockerはインストール済みであることを前提とします。
以下のDockerfileを作成します。
FROM microsoft/service-fabric-onebox WORKDIR /home/ClusterDeployer RUN ./setup.sh #Generate the local RUN locale-gen en_US.UTF-8 #Set environment variables ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en ENV LC_ALL=en_US.UTF-8 EXPOSE 19080 19000 80 443 #Start SSH before running the cluster CMD /etc/init.d/ssh start && ./run.sh
次にビルドしましょう
# docker -t build .
Dockerイメージが出来上がります。次に以下のコマンドでDockerイメージからコンテナを生成します。
# docker run --name sftestcluster -d -p 19080:19080 -p 19000:19000 -p 25100-25200:25100-25200 -p 8080:8080 -p 8001:8001 mysfcluster
ローカルクラスターが立ち上がるまでには、ちょっと時間がかかります。しばらくしてからhttps://localhost:19080/にアクセスしてください。以下の画面(以降、Service Fabric Exploreと呼びます)が表示されれば成功です。
ローカルクラスターの準備が整ったところで、アプリケーションをローカルクラスターにデプロイしてみましょう。
まずは、アプリケーションをビルドします。Chatプロジェクトを右クリックして、「Service Fabric」→「Rebuild Application」の順にクリックします。
ビルドが成功すると、Eclispeのコンソールに以下のように表示されるはずです。
BUILD SUCCESSFUL in 1s 11 actionable tasks: 2 executed, 9 up-to-date
ビルドが成功していたら、いよいよローカルクラスターにデプロイしてみましょう。
Chatプロジェクトを右クリックして、「Service Fabric」→「Publish Application…」の順にクリックします。
「PublishProfiles/Local.json」を選択して、「Publish」をクリックしてください。
デプロイに成功するとService Fabric Exploerの「Application」の表示が以下のようになります。(失敗するとApplicationの緑色の丸が黄色になったり赤になったりします)
では、チャットにアクセスしてみましょう。https://localhost:8080/にアクセスしてください。
キタ━━━━(゚∀゚)━━━━!!
デプロイ成功ですヮ(゚д゚)ォ!
ちゃんと動きます。
Azure Service Fabricクラスターへのデプロイ
いよいよこれが最後の手順です。Azure上のクラスター(Azure Service Fabricクラスター)にデプロイしてみましょう。
その前にAzure Service Fabricクラスターを使いには、公的な証明書機関で署名された証明書が必要になりますので、取得してください。この証明書は、Service Fabric Exploreへのアクセスに利用するクライアント証明書や、Service Fabricのノード間の認証に利用されます。
そして、この証明書をAzure Service Fabricクラスターに適用するためには、Azureの証明書管理サービス「キーコンテナ」を使うのが一番です。
では、取得した証明書をキーコンテナに登録する手順を説明致します。
Azureポータルにアクセスして「すべてのサービス」とクリックしてください。テキストボックスが表示されますので、以下のように「キー」と入力すると「キーコンテナ」が表示されますので、クリックしてください。
「追加」をクリックしてください。
「名前」に任意の名称、「サブスクリプション」「リソースグループ」「場所」は環境に適したもの、それ以外は以下の図のように入力して、「作成」をクリックしてください。
しばらくすると一覧に表示されるようになりますので、クリックしてください。
「証明書」→「生成/インポート」の順にクリックしてください。
「証明書の作成方法」は「インポート」、「証明書の名前」は任意の名称、「証明書ファイルのアップロード」はpfx形式(鍵と証明書が一つになったもの)の証明書を選択、「パスワード」はアップロードした証明書に設定されているパスワードを入力してください。最後に「作成」をクリックしてください。
これで証明書の登録は完了です。
次に後ほどの工程で使う情報を今のうちにメモしておきます。インポートが完了した証明書をクリックしてください。
最新のバージョンの証明書をクリックしてください。
次の画面で表示される「X.509 SHA-1 拇印」「シークレット識別子」をメモっておいてください。
次に、Service Fabricのクラスターを作成します。Azureポータルにアクセスして「すべてのサービス」とクリックしてください。テキストボックスが表示されますので、以下のように「fabric」と入力すると「Service Fabricクラスター」が表示されますので、クリックしてください。
「追加」をクリックします。
「クラスター名」には任意の名称、「オペレーティングシステム」にはUbuntuServer、「ユーザー名」「パスワード」は任意のもの、「サブスクリプション」「リソースグループ」「場所」は環境に応じて必要なものを入力して、最後に「OK」をクリックします。
「ノードタイプ名」には任意の名称、「持続性層」はブロンズ、「仮想マシンのサイズ」は任意のもの(小さすぎるとクラスターが起動しないこともあります)、「VMスケールセットの初期容量」は5、「カスタムエンドポイント」は8080と入力して「OK」をクリックします。カスタムエンドポイントは、アプリケーションのエンドポイントのポートです。チャットのアプリケーションは8080でアクセスします。ここでカスタムエンドポイントを設定しておくと、Azure Load Balancer側で8080でリッスンして、各ノードにリクエストを分散してくれます。
「必要な設定の構成」をクリックします。
先程作成した「sf-cert」をクリックしてください。
以下のようなエラーが発生します。「sf-certのアクセスポリシーを編集する」をクリックします。
「展開に対してAzure Virtual Machineへのアクセスを有効にする」にチェックを入れて、「保存」をクリックしてください。
もう一度「sf-cert」をクリックしてください。そして「カスタム」にチェックしてください。
以下の画面が表示されます。証明書URLには先程メモした「X.509 SHA-1 拇印」、証明書の拇印には先程メモした「シークレット識別子」を入力して「OK」をクリックします。
設定に問題なければ「作成」のボタンが表示されますので「OK」をクリックします。
しばらくすると、Service Fabricクラスターが出来上がります。結構時間がかかります。
Service Fabricクラスターが完了したらデプロイしてみましょう。その前に、設定ファイルに修正が必要になります。ローカルクラスターにはサクッとデプロイ出来ましたが、Azure上のクラスターにはセキュリティ上、証明書本体、証明書の拇印、証明書の秘密鍵の設定が必要になります。
Chat/ChatApplication/ApplicationManifest.xmlを以下のように編集します。<Certificates>〜</Certificates>の部分を追加しました。X509FindValueの値は、先程メモした「X.509 SHA-1 拇印」を入力してください。
Chat/PublisherProfiles/Cloud.jsonを以下のように修正してください。
{ "ClusterConnectionParameters": { "ConnectionIPOrURL": "sf-chat.japaneast.cloudapp.azure.com", "ConnectionPort": "19080", "ClientKey": "/path/to/server.key", "ClientCert": "/path/to/server.crt" } }
上記のパラメーターについて説明します。ConnectionIPOrURLはAzure Service Fabricのトップ画面(以下参照)に表示される「クライアントの接続エンドポイント」のホスト名の部分です。
ConnectionPortは19080、ClientKeyは、先程キーコンテナに登録した証明書の秘密鍵で、x509形式であり、パスワードで保護されてない必要があります。ClientCertはx509形式の証明書です。
では、早速デプロイしてみましょう。
Chatプロジェクトを右クリックして、「Service Fabric」→「Publish Application…」の順にクリックします。
「Publish Profiles/Cloud.json」 を選択して、「Publish」をクリックしましょう。
https://sf-chat.japaneast.cloudapp.azure.com:8080/にアクセスして、ローカルクラスターのときと同様にチャットの画面が表示されればOKです。
キタ━━━━(゚∀゚)━━━━!!
最後に
いかがでしょうか?ちょっと長くなりましたが、マイクロサービスの概要、またAzure Service Fabricを用いたマイクロサービスの実現方法がご理解頂けたかと思います。
マイクロサービスは、あくまで目的ではなく、設計方法の一つです。モノリスが適しているシステムもあれば、マイクロサービスが適しているシステムもあります。大切なのはシステム設計をする際、モノリシックな観点に加えて、マイクロサービスな視点も持ち、そのシステムに適した設計を行うことだと思います。