前編 につづいて、書籍 マイクロサービスアーキテクチャ のまとめです。
要点
8章.監視
- 
システムを複数のサービスに分割することで、個々のサービスの状態やサービス間のネットワークなど、監視すべき項目が増える。手動による監視では間に合わなくなるので、情報を自動的に収集し、運用監視に適したビューを提供する必要がある。
 - 
マイクロサービスでは、サービスのスケールによって監視対象のサーバなどが動的に増減する。監視の仕組みではそのような動的な監視対象の変化に自動的に追随できる必要がある。
 - 
監視はシステム全体で統合的に行う必要があるので、個々のサービスの独自ルールにするのではなく、システム全体でルールやツール、ライブラリなどを統一しておく。
 - 
ログやメトリックなどの収集した情報には、サービス開発者が必要なときにすぐにアクセスできるようにしておく。(誰かに依頼しないと入手できない、では遅い)
 - 
マイクロサービスでは、基本的にイベントベースの疎結合な連携を目指すので、イベントの監視が重要になる。イベントの監視機能を備えた汎用的なイベントルーティングシステムがあれば、システム全体のアーキテクチャを簡素化できる。
 
ログの監視
- 
複数のサーバに分散するログを収集する仕組みが必要。
例えば Logstash でログを収集して ElasticSearch に蓄積し、Kibana で運用に必要なビューやグラフを表示できる。 - 
マイクロサービスによるシステムでは、ひとつのイベント(ユーザ操作や外部システムとの連携など)を基点に複数のサービスが連鎖的(しかも非同期に)に動作するため、あるイベントに関連する複数のログを紐付けて見ることができるようにする必要がある。
イベントの最初の入り口となるサービスで一意な 相関ID を発番して後続のサービスにも引き回し、各サービスでは相関IDをログに含めて出力する。 
メトリックの監視
- 
必要に応じてサービスをスケールさせるためにメトリクスを継続的に監視する必要がある。(CPU負荷やメモリ消費量、サービスの応答時間 など)
 - 
複数のサーバに分散して稼働しているサービスの場合、それらのサーバ全体の(=サービスとしての)メトリックと、個々のサーバのメトリックも見れるようにしておく。
 - 
大量のメトリックの収集では、直近の情報は細かく、古い情報は荒く記録するようにすることで、メトリックの保存量を節約する。
 - 
メトリック監視でも、Graphite のようなビジュアライズツールの利用を検討する。
 - 
インフラやミドルウェアのメトリック監視だけに頼るのではなく、サービス自身のプログラム内でメトリックを取得することでよりきめ細かで実用的なメトリックを収集すべき。
DropWizard Metrics のように、メトリックを収集するためのライブラリを利用できる。 
システム全体の監視
- システムの健全性を把握するには、個々のサービスやサーバのメトリック(低水準メトリック)からシステムが健全かどうかを判断よりも、**システム全体に対してテスト用のトランザクションを送信することで健全性を判断する(セマンティック監視)**ほうが、より優れた指標を入手できる。
 
連鎖的障害の検知
- 
個々のサービス自身の健全性だけではなく、下流サービスも含めた健全性を監視して連鎖的障害が発生したら検知する必要がある。
- 下流サービスと リクエスト/レスポンス で連携する場合は、下流サービスから応答でその健全性を知ることができる。
 - 下流サービスと イベントベース で連携する場合は、そもそも下流サービスを認知していないので、イベントを配信するキューの状態(メッセージが滞留していないかなど)から健全性を判断する必要がありそう。
 
 - 
リクエスト/レスポンス で連鎖的障害に対処するには、下流サービス呼び出しにサーキットブレーカー(下流サービス呼び出しが一定数失敗したら、復旧するまで処理しない。詳細は 11章)を仕込む。
 
9章.セキュリティ
ユーザの認証・認可
主にユーザインターフェースを通してアクセスするユーザを認証・認可する必要がある。
- 
複数のサービスでユーザを認証・認可するためにシングルサインオンの仕組みが必要。
 - 
個々のサービスでユーザの認証・認可を実装するのではなく、ユーザインターフェースとサービスの間に入って認証・認可を透過的に行うゲートウェイを置く。
- ゲートウェイでは、ユーザがそのサービスの呼び出すことができるかどうかの判断までとし、それ以上の制御は個々のサービスに任せる。
 
 
外部システムの認証・認可
サービスを呼び出す外部システムが信頼でき、許可される呼び出しかどうかを認証・認可する必要がある。
| 方式 | 説明 | デメリット | 
|---|---|---|
| 境界内のすべてを許可 | 同じネットワーク境界内であれば、認証・認可しない。 | ネットワーク内に侵入されると無抵抗になる。 | 
| ベーシック認証 | HTTPSベーシック認証で認証・認可する。 | 呼び出し元のアカウント管理が必要。SSL化に伴い証明書管理が面倒、およびレスポンスのキャッシュが困難。 | 
| SAML/OpenID Connect | SAML や OpenID Connect などの標準的なシングルサインオンプロトコルを使って認証・認可する。 | 呼び出し元のアカウント管理が必要。実装が面倒。 | 
| 証明書 | クライアント証明書を使って認証・認可する。 | 証明書管理が面倒。 | 
| HMAC(Hash-based Message Auth Code) | あらかじめ交換しておいた鍵を使ってハッシュ化したリクエストを一緒に送信し、呼び出されたサービスでリクエストが信頼されるものであることを確認する。 | サービス間での鍵交換・管理が面倒。実装が面倒。 | 
| APIキー | 許可した呼び出し元にAPIキーを発行し、そのキーで認証・認可する。 | 
自システム内での下流サービス呼び出しの認証・認可
ユーザインターフェースや外部システムから呼び出されたサービスが、さらに自システム内の他のサービスを呼び出す場合に認証・認可をどう扱うか検討が必要。
- 
同じネットワーク境界内にあることを前提に 境界内のすべてを許可 することも可能。
- 下流サービス呼び出し時に、誰のために要求しているのか、もとの要求者の情報を下流サービスに連携しても良い。
 
 - 
下流サービスで同等の認証・認可を行うことにはデメリットもあるので、ケースバイケースで必要な認証・認可機構を組み込む。
 
情報の保護
- 
従来のシステムと同様に、必要に応じて適切なデータ暗号化を行う。
 - 
機密情報はログにそのまま出力しない。
 - 
そもそも必要のない機密情報を保持しない。
 
10章.コンウェイの法則とシステム設計
システムを設計するあらゆる組織は、必ずその組織のコミュニケーション構造に倣った構造を持つ設計を生み出す。
- より良いマイクロサービス(をベースとしたシステム)を生み出すには、組織を含めたチームビルディングも重要になる。
 
チーム
- 
各サービスはひとつのチームによって所有され、チームはそのサービスについての要件・設計・実装・テスト・運用のすべてに権限と責任を負う。
こうすることでチームに強い当事者意識を持たせ、自律性が向上し、結果的にサービスのデリバリサイクルが速くなる。 - 
マイクロサービスはDDDの境界づけられたコンテキスト によってビジネス視点で分割されるので、チームがサービスを所有すれば自然とビジネス視点・顧客視点を持つことができるようになり、より多くの価値ある機能をデリバリできるようになる。
 
メンバー
- 
マイクロサービスでは、モノリシックなシステムと比較して、メンバーにより高いスキル(広範囲への考慮 や 新しい技術への適応 など)が求められる。
 - 
メンバーのスキルを踏まえて、マイクロサービス化を戦略的に進める必要がある。
メンバーが、サービスを開発するチームの一員として負わなければならない責任とその理由を理解し、自律的な成長を促すことが重要。 
11章.大規模なマイクロサービス
障害への対策
- 
そもそも、どれだけ対策をしても障害を完全になくすことはできないので、費用対効果の薄い障害防止策にリソースを費やすよりも、いかに簡単に障害から復旧させられるかを考えたほうが、有効な障害対策になる。
 - 
非機能的な要件がどこまで許容できるかを把握し、やりすぎないように必要十分な対策を講じるようにする。
 
| 観点 | 内容 | 
|---|---|
| 応答時間 | ユーザから見たシステムの応答時間はどのくらいに収まるべきか。 | 
| 可用性 | システムの停止はどれくらい許容されるのか。 | 
| データの耐久性 | ある程度のデータ欠損は許容されるのか、データはどれくらいの期間保全されなければならないのか。 | 
- 
マイクロサービスにおいては、あるサービスが停止してもシステム全体は稼働し続けることができるように、安全に機能低下させてそこから回復させることを考える必要がある。(どのように機能低下させるかはビジネス的な判断になることが多い)
 - 
あるサービスで障害が発生した場合に、それが他のサービスにまで波及しないようにすることが重要。(主にリクエスト/レスポンスによるサービス連携で)
特に応答の遅延は下流のサービスに波及していって対応が困難になることがあるので、遅延しながら稼働し続けるよりは、停止(機能低下)させたほうが良い。 
| 対策 | 説明 | 備考 | 
|---|---|---|
| タイムアウト | 下流サービスの遅延の影響を受けないようにするため、すべての下流サービス呼び出しにタイムアウトを設ける。 | |
| サーキットブレーカー | 下流サービスの呼び出しが一定回数失敗したらその呼び出しを停止(キューイングしておくか、機能低下させるか)する。下流サービスが復旧したかをチェックし、復旧したら自動的に再開する。 | Hystrix・・・タイムアウトやサーキットブレーカー機能を持つJavaライブラリ | 
| 隔壁 | ある下流サービスとの連携が他の下流サービスとの連携に影響しないように、接続プールなどのリソースを別にしておく。 | 
- 
障害を意図的に起こすことで、十分な障害対策ができているかを確認するとともに、チームが障害に対する学習の機会を得ることができる。(アンチフラジャイルな組織)
- Chaos Monkey・・・Netflixが公開した、人工的に障害を発生させるツール。
 
 - 
サービスの振る舞いをできるだけ**冪等にしておく**ことで、障害発生時にどこまで処理されたかを考えずに単純に再処理すれば良くなる。
 
スケーリング
システムのスケーリング
- 
スケールアップ によって手っ取り早くシステムの性能を増強できるが、単一障害点を避けてシステムの回復性を高める意味では効果的ではない。
 - 
マイクロサービスアーキテクチャの利点を活かすためには、サービスごとにサーバを別にし、かつ同じサービスを複数のサーバで同時稼働させることが望ましい。
 - 
最初から大掛かりな仕掛けを構築するのではなく、システムの成長に合わせて適切な増強手段(必要であればサービスの書き直し)を講じていくほうが良い。
 - 
サービスの応答時間のしきい値や、サーバの障害の検知によって自動的にスケーリング(オートスケーリング)させることを検討する。
 
データベースのスケーリング
- 
一般的にサービスのほとんどのデータベースアクセスは読み取りがほとんど。
読み取りのスケーリングでは、DBMSのレプリカ機能を利用できる。- ただし、レプリカの前にキャッシングを先に導入したほうが効果的。
 
 - 
書き込みのスケーリングでは、シャーディング(データの格納先を分散)する方法がある。
- ただし、格納先を跨いだ処理は扱いにくくなる。
 
 - 
サービスごとにデータベースのスキーマを(同じインスタンス上で)分割するだけではそのインスタンスが単一障害点になってしまうので、サービスごとにデータベースインスタンス自体を別にするほうが望ましい。
 - 
DDDの CQRSパターン(Command-Query Responsibility Segregation)を使うことで、データベースのスケーリングに柔軟に対処することができる。
- Command でデータベースの変更を同期的にも非同期的にも扱える。
 - データベースに限らず、要件に適した他のデータストアを組み合わせることもできる。
 
 
キャッシング
システム全体の性能を最適化するためにキャッシュを活用する。
| キャッシュする場所 | 説明 | 備考 | 
|---|---|---|
| クライアント | サービスの利用側でデータをキャッシュする。利用側が自身でキャッシュをリフレッシュするタイミングを判断し、必要に応じてサーバを呼び出す。 | |
| プロキシ | クライアント-サーバ間に置くプロキシでデータをキャッシュする。クライアントからの要求に透過的にキャッシュを返し、サーバ側のデータが変更されたらキャッシュをリフレッシュする。 | Squid、Varnishなど | 
| サーバ | サービスの提供側でデータをキャッシュする。変更頻度の低いデータをメモリ上にキャッシュしておく。 | Memcachedなど | 
- 
サービス間の通信プロトコルとしてHTTPを採用している場合は、HTTP自体のキャッシング仕様を利用する。
 - 
更新処理では、データの変更要求をキャッシュしておいてまとめて下流サービスに流す(ライトビ ハインド キャッシュ)ことで更新処理をバッチ化して最適化できることがある。
また、キャッシュを永続化しておけば、下流サービスが停止から復帰したあとで永続化しておいたキャッシュから変更要求を送るような使い方もできる。 - 
データの新鮮度が多少古くてもサービスが稼働し続けることのほうが価値がある場合は、キャッシュを使うことで下流サービスが停止していても情報を提供することができる。
 - 
キャッシュする場所が増えればデータの鮮度がわかりづらくなるので、キャッシュする場所はできるだけ少なく(1箇所だけが望ましい)する。
 
データの整合性
分散システムでは、整合性/可用性/分断耐性 の3つの特性のうち、2つしか同時に満たすことはできない。
| CAP定理の特性 | 説明 | 犠牲にする場合 | 
|---|---|---|
| 整合性(Consistency) | ある時点において、複数のノードが同じ結果を返す。 | APシステム。結果整合性によって可用性と分断耐性を満たす。一時的に整合性が取れていないデータが見えてしまうことを許容しなければならない。 | 
| 可用性(Availability) | あるノードで障害が発生しても、他のノードは稼働し続ける。 | CPシステム。データの整合性を保証するために、応答時間低下やデータが不整合な場合はエラーとなることを許容しなければならない。 | 
| 分断耐性(Partition tolerance) | 一部ノード間で通信障害が発生しても、システム全体は稼働し続ける。 | *CAシステム。*マイクロサービスアーキテクチャではありえない。 | 
- 
システム全体が CAP定理の APシステム または CAシステム のいずれかでなければならないわけではなく、サービスの要件に応じて使い分ける。
 - 
分散システムにおいてデータの整合性を保つことは困難であり、実際には結果整合性で運用できることが大半なので、APシステムが適切な選択になることが多い。
 
サービスの公開
サービスの検知
どのようなサービスが公開されていて、どのエンドポイントでアクセスすれば良いのかを検知したい場合がある。
| 方法 | 説明 | 
|---|---|
| DNS | 単純に、エンドポイントのドメイン名に命名ルール(https://サービス名-環境.ベンダー/ など)を設ける。 | 
| サービスリポジトリ | ZooKeeper、Consul、Netflix Eureka などの、サービスリポジトリツールを利用する。 | 
| 自作 | 自作(AWSであれば、タグを活用するなど)する。 | 
サービスのドキュメント化
サービスが公開するAPI仕様をドキュメント化する。
- JSONベースの RESTful APIであれば、Swagger などを使ってドキュメント化する。
 
人間味のあるレジストリ
- 実際に稼働しているサービスから情報(どんなサービスがあるのか、稼働状況はどうなのか、APIの仕様は、など)を自動的に収集して、人間が見やすい形にまとめることができれば、マイクロサービスを運用・管理する助けになる。
 
12章.まとめ
- 
マイクロサービスはどうあるべきか、と言う原則を理解する。
 - 
サービスは、技術的観点ではなく、ビジネス的観点(DDDの境界づけられたコンテキスト)で分割する。
 - 
テスト、デプロイなどは積極的に自動化する。
 - 
サービスは内部実装の詳細を隠蔽し、特定の技術要素に依存しないAPIを公開する。
 - 
サービスはチームに所有させる。
 - 
サービスを単独で本番デプロイできるようにする。
 - 
システムの可用性を得るには、連鎖的障害が発生しないような設計・実装が必要になる。
 - 
CAP定理の 整合性 と 可用性 のどちらを犠牲にするのが適切かを検討する。
 - 
セマンティック監視で、システム全体の状態を監視する。
 - 
ドメインに対する理解度が低い場合は、いきなりマイクロサービス化せずにいったんモノリシックに作って、徐々にサービス化していく。
 - 
ビジネスや技術の進歩にあわせて、サービスを徐々に進化(書き直し)させていく。
 
以上です。