オライリーのこちらの本を元に、マイクロサービスについてザックリとまとめてみます。
マイクロサービスの利点
- 技術異質性
複数の連携するサービスからなるシステムでは、サービスごとに異なる技術を使う選択ができる。
- 回復性
あるサービスに障害が発生しても、障害が連鎖しなければ残りの部分は機能し続けることができる。
- スケーリング
分割したサービスごとにスケールすることができる。
- デプロイの容易性
1つのサービスへの変更を、残りのシステムとは独立してデプロイができる。
- 組織面の一致
個々のサービスに対して小規模なチームを割り当てることにより、チームの生産性を最適化できる。
- 合成可能性
個々のサービスを再利用することができる。
- 交換可能にするための最適化
さらに優れたシステムや実装に置き換えるコストが低くなる。
マイクロサービス作成における2つの考え方
疎結合
マイクロサービスの本質は、システムの他の部分を変更する必要なしに、あるサービスを変更してデプロイできること。これを実現するようにサービスを設計する。
高凝集性
関連する振る舞いは一緒にし、関連のない振る舞いは別の場所に配置すること。このために「境界」を探す必要がある。『エリック・エヴァンスのドメイン駆動設計』は、サービスの理にかなった境界の検出に役立つ。また、『実践ドメイン駆動設計』は、この手法の実用性を理解するのに役立つ。
マイクロサービス間の対話
あるマイクロサービスが別のマイクロサービスと対話する方法を考える。データベースを共有することはいかなる代償を払っても避けるべし。
同期と非同期
サービスの連携において、通信を同期にすべきか非同期にすべきか。同期通信の方が単純だが、時間のかかるジョブでは非現実的になる。 リクエスト/レスポンス形式は同期通信に適しており、 イベントベースの連携は非同期通信に適している。前者の実装のアーキテクチャは オーケストレーション、後者は コレオグラフィと呼ばれる。一般に非同期(コレオグラフィシステム)の方が複雑ではあるが、疎結合で柔軟性があり、変更を受け入れることがわかっている。
REST
同期通信においては、RPCやRESTといった技術を採用することになる。RPCにはいくつかの潜在的な落とし穴がある。RESTの形式についてはリチャードソン成熟度モデルに目を通すこと。
レベル1は、分割と統治を使い、大きなサービスのエンドポイントを複数のリソースに分割することによって、複雑さを処理するという問題に取り組んでいる。
レベル2は、HTTP verbの標準セットを導入し、不必要な変形を取り除き、似たような状況を同じように処理している。
レベル3は、発見性を導入して、プロトコルをもっと自己説明する方法を提供している。
HTTP上のRESTはサービス間対話のための理にかなったデフォルトの選択肢になる。詳しくは『REST in Practice』を読むと良い。
非同期イベントベース連携
非同期イベントベース連携に関しては、以下の2つのことを考慮する必要がある。
- マイクロサービスがイベントを発行する方法
- コンシューマがそのイベントが発生したことを検出する方法
RabbitMQなどのメッセージブローカーが両方の問題に対処する。
非同期アーキテクチャは複雑なので、相関IDを用いてプロセス境界を超えたリクエストの追跡を行うべきである。また、『Enterprise Integration Patterns』では様々なプログラミングパターンに関する詳細が説明されておりおすすめ。
バージョニング
セマンティックバージョニングを利用して、MAJOR.MINOR.PATCH
という形でバージョン番号を付与する。MAJOR番号の増加は、後方互換性の無い変更が行われたことを意味し、MINORは後方互換性のある新機能の追加、PATCHの変更は既存機能にバグ修正が行われたことを示す。
フロントエンド向けのバックエンド(BFF)
デバイスの種類ごとにコンテンツを変えなければならない問題に対しては、モバイルバックエンドやWebサイトバックエンドなど、それぞれに対してフロントエンド向けのバックエンド(BFF: Backends For Frontnds)を作成することができる。
マイクロサービスへの分割における問題点
データベース
- 外部キー関係を削除する。
- 国コードなどの共有静的データは多くの場合データベースではなく、構成ファイルかコードに直接追加するのが良い。
- 分割する方法が間違っていないか、テーブルを分割できないか検討する。
スキーマ分離に役立つデータベースリファクタリングに関するより詳細な議論は『データベース・リファクタリング』を読むと良い。
トランザクション
マイクロサービスにおいて、データベースを分割することで単一トランザクションが提供する安全性が失われる。この場合の対処として以下のような方法が考えられる。
1. 後でリトライする
結果整合性を担保する。
2. 操作全体の中止
操作に失敗した際、その逆の操作となる補正トランザクションを発行する。補正トランザクションに失敗したらどうするか?補正トランザクションをリトライするか、後でこの非一貫性を取り除けるようにしなければならない。補正トランザクションが複数ある場合は?
3. 分散トランザクション
トランザクションマネージャと呼ばれる全体管理プロセスを使って、基盤となるシステムが実行する様々なトランザクションをオーケストレーションする。分散トランザクションを処理する最も一般的なアルゴリズムは2フェーズ(2相)コミットを使うことである。
これらの解決策は全て複雑である。そもそもトランザクションが本当に必要かどうか。結果整合性に頼ることはできないか検討した方が良い。
マイクロサービスのデプロイ
マイクロサービスごとに1つのCIビルドを持ち、本番環境にデプロイする前に迅速に変更して検証できると良い。そのために、サービスごとに独自のソースコードリポジトリを持ち、各リポジトリが個々のCIビルドにマッチングされると良いだろう。
『継続的デリバリー』に概説されているように、継続的デリバリ(CD: Continuous Deelivery)は、チェックインするたびに本番環境への準備状況に関するフィードバックを得られ、さらに各チェックインを全てリリース候補として扱う。例えば次のようにビルドパイプラインを構築する。
- コンパイルと高速テスト
- 低速テスト
- UAT (User Acceptance Testing)
- 性能テスト
- 本番環境
デプロイを簡潔にし、技術固有の成果物に関する問題を回避するために、基盤となるOSにネイティブの成果物を作成するという方法がある。
LinuxではFPMパッケージマネージャがLinux OSパッケージ作成のための抽象概念を提供する。
Packerは、イメージの構築を大幅に簡素化するために設計されたツールで、好みの構成スクリプトを使用して、同じ構成で様々なプラットフォーム向けのイメージを作成できる。AWSの本番環境デプロイ向けのイメージと、対応するローカル開発/テスト向けのVagrantイメージを全て同じ構成から作成できる。
Docker
Dockerは、軽量コンテナ上に構築されたプラットフォーム。Dockerでは、アプリを作成してデプロイする。複数のマシン上の複数のDockerインスタンスにわたるサービス管理に役立つツールとして、GoogleのKubernetesやCoreOSのクラスタ技術が有効で。Deisは、Docker上にHerokuのようなPaaSを提供しようとしている。
デプロイは、以下のようにできると最適。
deploy artifact=catalog environment=local version=local
テストステージが開始したら、CIステージが次のコマンドを実行する。
deploy artifact=catalog environment=ci version=b456
一方、QAチームはカタログサービスの最新バージョンを統合テスト環境に渡して探索的テストを実行する。
deploy artifact=catalog environment=integrated_qa version=latest
このために最もよく使用するツールはFabricである。FabricをBotoなどのAWSクライアントライブラリと組み合わせると、大規模なAWS環境を完全に自動化できる。
環境定義に関しては自分でyamlを作っても良いが、Terraformが最適なツールかもしれない。
デプロイは以下のような方法がある。
- ブルーグリーンデプロイメント
- カナリアリリース
マイクロサービスにおけるテスト
『実践アジャイルテスト』では、テストを以下の4つに分類する。
- 受け入れテスト(適切なものを構築したか)
- 単体テスト(適切に構築したか)
- 探索的テスト(どのようにシステムを分割できるか。)
- 性質テスト(応答時間、スケーラビリティ、性能、セキュリティ)
『Succeeding with Agile』では、テストピラミッドと呼ぶモデルの概念として、必要な自動テストの種類が説明されている。自動テストを単体、サービス、UIに分割している。
単体テスト
単体テストでは、1つの機能やメソッド呼び出しを検査する。
サービステスト
サービステストでは個々のサービスの機能をテストする。1つのサービスを単独でテストをするのは、テストの分離性を高めて、問題の検出と修正を早めるためである。この分離を実現するために、外部のコラボレータを全てスタブ化し、サービス自体だけをスコープにする必要がある。
サービステストスイートは、下流コラボレータ用のスタブサービスを起動し、スタブサービスに接続するようにテスト対象のサービスを構成する必要がある。そして、実世界のサービスを模倣してレスポンスを返すようにスタブを構成する必要がある。
この際、スタブを使うかモックを使うかという問題がある。多くの場合、モックよりもスタブを使う。これに関しては『実践テスト駆動開発』を参照すると良い。
スタブサービスの構築は自分でやることが多いが、mountebankというスタブ/モックサーバを利用すると大量の作業が省かれる。
UIテスト(エンドツーエンドテスト)
エンドツーエンドテストは、システム全体に対して実行するテストである。エンドツーエンドにおいて適切にテストを実行することはとても難しい。
テストスコープが大きくなることで可動部が増え、テスト対象の機能が壊れていること以外の問題が生じることが増える。可動部が増えると、逸脱の常態化と呼ばれる、間違っていることに慣れすぎてそれを正常だと思い始めてしまう。Eradicating Non-Determinism in Testsの中で、Martin Fowlerは、信頼できないテストがあったら、テストスイートから排除してしまう手法を推奨している。
性能テスト
アプリケーションの性能をテストする手段を持つことが、モノリシックシステムの場合よりもはるかに重大になる。性能テストには時間がかかるため、一部を毎日実行し、大部分は毎週実行するようにするのが一般的なプラクティスである。さらに、目標を持つことが非常に重要で、結果に基づいてビルドをレッドかグリーンに振り分けると良い。
マイクロサービスの監視
単一サービス・単一サーバーの監視
まず、ホスト自体を監視するべきである。CPU、メモリなどは全て有益。Nagiosや、New Relicのようなホスト型のサービスを使うことができる。次に、サーバー自体のログにアクセスする必要がある。最後に、アプリケーション自体を監視する。最低でもサービスの応答時間を監視する。さらに進めたければ、報告されているエラー数を追跡すると良い。
単一サービス・複数サーバーの監視
問題を分離できる必要がある。全てのホストにわたるメトリックと、個々のホストでのメトリックを知る必要がある。Nagiosではこのようなホストをグループ化できる。ログに関しては、ホスト数が少数の場合はSSHマルチプレ草のようなツールを使用できる。応答時間の追跡のようなタスクでは、ロードバランサのレベルでそれを追跡することで集約情報を入手出来る。
複数サービス・複数サーバー
ログからアプリケーションメトリックにいたるまで、できるだけ多くの情報を集約して中央に集約する。Logstashを利用すると、複数のログファイル形式を解析し、さらなる調査のために下流システムに送ることができる。Kibanaは、ログを閲覧するためのElasticsearchに支えられたシステムである。Kibanaを使って集約ログを閲覧することができる。
メタデータとメトリックを関連づけるシステムの1つとしてGraphiteがある。リアルタイムでメトリックを送信でき、グラフを表示し、何が起こっているかを確認できる。また、直近の10分間はホストのCPUを10秒ごとに記録し、直近の数年間は1つのデータを30分ごとに記録するといった方法で、データ量が増えすぎないようにしている。
Linuxボックスにcollectdをインストールし、Graphiteでcollectdを指定するとわかるように、利用しているOSは大量のメトリックを生成する。同様にNginxやVarnishといったサブシステムは、応答時間やキャッシュヒット率といった有益な情報を公開する。サービスの基本的なメトリックは自分で公開するのが良い。最低でも、Webサービスでは応答時間やエラー率といったメトリックを公開するべきだ。
相関ID
多くのサービスが対話して特定のエンドユーザーに機能を提供している場合、1つの呼び出しから複数の下流サービスの呼び出しが発生する。最初の呼び出しの際にGUIDを生成し、後続の全ての呼び出しに渡すことでシステム内でそのイベントを追跡できるようになる。この相関IDは、HTTPヘッダで伝搬させると良い。
サーブによってログの形式がバラバラにならないように、メトリックの標準名のリストがあると良い。
利用者の考慮
ログは、調べる人の種類によって以下のことを検討するべきである。
- すぐに知りたいのか
- あとで必要になるのか
- どのようにデータを利用したいのか
量的情報のグラフィカル表示の微妙な差異に関する議論は『Information Dashboard Design』を読むと良い。
運用メトリックとビジネスメトリックは、両方とも同様のイベントに分解されるため、このようなイベントの収集、集約、格納に使うシステムを統一してレポートに利用できると単純なアーキテクチャになる。Riemannはイベントの高度な集約とルーティングを可能にするイベントサーバーである。SuroはNetflixのデータパイプラインであり、同様の領域で昨日する。このようなデータをリアルタイム分析のStormやオフラインバッチ処理のHadoop、ログ分析のKibanaといった様々なシステムに送ることができる。
詳細な調査についてはLightweight Systems for Realtime Monitoringを読むと良い。
あとがき
以上、マイクロサービスの基本的な部分についてザックリとまとめてみました。
マイクロサービスのメリットが最大化されるのはある程度大きなサービスであることは間違いないと思いますが、僕らの会社のようなスタートアップでも活かせるサービスはあると思っています。実際、1つの開発中のサービスでマイクロサービスを採用しています。
やっていく中でスタートアップにはスタートアップ流のマイクロサービスの作り方があるな、と見えてきた部分もあるので、それはそれでまた近いうちに共有できればと思います。