O`REILLY発行の マイクロサービスアーキテクチャ を読んだのでその内容を軽くまとめる。

以下の記事を参考にした。

書籍「マイクロサービスアーキテクチャ」まとめ(前編)

書籍「マイクロサービスアーキテクチャ」まとめ(後編)

要点

1章 マイクロサービス

マイクロサービスとは

マイクロサービスとは, 協調して動作する小規模で自律的なサービス。DDD(ドメイン駆動設計), CI・CD(継続的インテグレーション・デリバリ), インフラ仮想化, 自動化, アジャイル開発といったさまざまな分野から生まれたものでありこれらを総合的に実践する必要がある。

以下がマイクロサービスの利点である。

  • 技術異質性…サービスごとに異なる技術を使うことができる
  • 回復性(レジリエンス)…障害のあるコンポーネントを切り離せる
  • スケーリング…必要なサービスのみスケーリング
  • デプロイの容易性…必要なサービスのみデプロイ
  • 組織面の一致…アーキテクチャと組織を一致させる
  • 合成可能性…再利用することができる
  • 交換可能にするための最適化…2週間程度で作り直せる

マイクロサービス以外の分解テクニック

  • 共有ライブラリ
  • モジュール

2章 進化的アーキテクト

マイクロサービスのアーキテクトは建築士(アーキテクト)というより都市計画家(アーバンプランナー)であり, あらゆる不測の事態に備えて計画するのではなく, 可能性が低いことを必要以上に指定したい衝動を避けて, 変化を許容するように計画する。

以下が進化的アーキテクトの主な責務とされている。

  • ビジョン…システムが顧客や組織の要件を満たすのを助けるシステムの技術ビジョンを, 明確に伝えるようにする
  • 共感…顧客や同僚に対する自分の判断の影響を理解する
  • 協調…できるだけ多くの仲間や同僚と関わり, ビジョンの定義, 改良, 実行に役立てる
  • 適応性…顧客や組織の要求により技術ビジョンを変更する
  • 自律性…チームに対して標準化と自律性の実現との間の適切なバランスを見出す
  • ガバナンス…実装しているシステムを技術ビジョンに合わせる

3章 サービスのモデル化方法

以下が優れたマイクロサービスを設計するのに必要な性質

  • 疎結合性…連携するサービスに関して必要最低限のことだけしか把握しないようにする
  • 高凝縮性…関連する振る舞いを1箇所にまとめておく

4章 統合

データベースの共有によるサービスの連携は, かんたんではあるが疎結合性と高凝縮性の両方を失うことになり, マイクロサービスを採用する意味がなくなる。

コレオグラフィ

サービス間の通信プロトコルとしては, リクエスト/レスポンスとイベントベースがある。前者では, HTTP + RESTをデフォルトの選択肢とする。後者では, あるサービスがイベントを発行して他のサービスが受信するための基板が必要になる。

複数のサービスによって一つの処理が構成される場合の制御方法としては, オーケストレーションとコレオグラフィがある。前者は基点となるサービスが他のサービスをリクエスト/レスポンスで呼び出し, 処理全体のフローを制御する。コレオグラフィでは, 基点となるサービスがイベントベースでイベントを発行して, 他のサービスがそれを受信して個々に処理を行う。

マイクロサービスの原則を満たすためには, コレオグラフィによる連携を模索すべき。

そのためには, 一連のイベントの最初にユニークな相関IDを発番して, イベントに乗せて伝搬させなければならない。

コードの統合

マイクロサービスをまたいでコードを共有しないほうが, 個々のサービスの独立性を維持できる。

公開したサービスの仕様を変更する場合は, 可能な限り下位互換性を保ち, 呼び出し元への破壊的変更を回避する。破壊的変更を伴う場合は, 一時的に新旧バージョンを並行運用して呼び出しも元が新バージョンに移行する期間を持たせる。

ユーザーインタフェースとの統合

API合成

JSONデータを返す複数のサービスを呼び出してフロントで画面を生成

UI合成

サービスがUI部品を返して, フロントでそれらを組み合わせて画面を生成

BFF (Backend For Frontend)

サーバー側に, 複数のサービス呼び出しを画面向けにまとめるゲートウェイを用意する

レガシーシステムとの統合

既存システムと統合する場合は, その既存システムへの呼び出しをマイクロサービスでラップし, 徐々に既存システムを新しいサービスに置き換えていく(ストラングラー, 絞め殺しパターン)ことでビックバンな変更を避けてマイクロサービスを導入できる。

5章 モノリスの分割

一気にマイクロサービス化するのではなく, 影響の小さい機能から徐々にマイクロサービスに分割していく。

データベースの分割

モノリシックなシステムを分割するには, コードの分割だけでなく, RDBも分割する必要がある。

  • 境界づけられたコンテキストをまたぐテーブル共有はせず, API呼び出し経由でデータをやり取りするようにする。これによって外部キーによるデータ関連やトランザクション整合性を保証できなくなり, 結果整合性になる。
  • 共有静的データも境界づけられたコンテキストをまたいで共有することはしない。同じテーブルを重複して持つ, コード化してそれぞれ取り込む, 独立したサービスにするなどの方法がある。
  • まずスキーマを分割してその後サービスを分割する。
  • 分散トランザクションは正しく実行するのが困難。なるべく結果整合性の概念に頼ることができるかを考える。

レポート

サービスを分割することで, 複数のコンテキストの情報を集約する方法を考えなければならない。以下の方法が考えられる。

  • 必要に応じてサービスに情報をバッチ的にまとめて取得するAPIを設ける。
  • サービスからの変更イベントをサブスクライブしてレポートデータベースにデータを投入する。

6章 デプロイ

モジュール

  • マイクロサービスのCIでは, 個々のサービスを独立してビルド・デプロイできるように, サービスごとにコードリポジトリやビルドジョブを構成するのが良い。
  • 手動で実施する受入れテストもステージの一つとして管理するべき。

インフラ

  • モジュールが稼働するために必要なインフラやミドルウェアも自動的に構成できるようにする必要がある。
  • CI・CDによって構成され稼働しているサーバは, 原則として手作業で変更してはいけない。

設定項目

  • 環境依存の情報はモジュールには含めず設定ファイルなどで別管理できるようにする。

サービスとサーバのマッピング

  • マイクロサービスでは, 1台のサーバに複数のサービスを同居させるべきではない。1台のサーバに1つのマイクロサービスをデプロイする。

デプロイツール

  • モジュール, バージョン, 環境の3つのパラメータを受け取って実行できるツールが望ましい。
  • 環境の作成もコード化して自動的に作成されるようにする。

7章 テスト

  • マイクロサービスの利点を得るには, テストを自動化してサービスを迅速かつ効率的に検証できるようにすることが不可欠。

単体テスト

1つの機能やメソッド呼び出しを検査するためのテスト。TDD (Test-Driven Design) の副作用として生成される。高速にフィードバックを得ることができる。

サービステスト

個々のサービスを対象とするテスト。呼び出しのユーザーインタフェースは迂回して, 下流のサービスはスタブorモック化する。

エンドツーエンドテスト

システム全体に対して実行するテスト。フィードバックサイクル時間が増え, 問題のある機能を判別するのが難しい。また, 可動部の数が増えてテストが脆弱になる。

CDC (Consumer-Driven Contract) テスト

利用者から見た挙動を保証することに焦点を絞ってテストすることで, 比較的早いサイクルで利用者に影響する破壊的変更を検知することができる。

本番環境でのテスト

スモークテスト

本番環境にデプロイされたシステムが正しく稼働しているかを確認するテスト。

ブルーグリーンデプロイメント

本番環境上に現行バージョンと並行して新バージョンをデプロイ(トラフィックは現行バージョンで処理)してテストし、問題がなければトラフィックを次バージョンに向ける方法。

カナリアリリース

本番環境上に現行バージョンと並行して新バージョンをデプロイし、トラフィックの一部または全部(のコピー)を新バージョンに向けてテストする方法。

非機能テスト

性能テストは大量のデータや負荷状態を作り出すことが必要なため頻繁には実施できない。日々のサイクルではその一部のケースを実施し、全ケースの実施は週次などで行う。

8章 監視

  • システムを複数のサービスに分割することで, 個々のサービスの状態やサービス間のネットワークなど, 監視すべき項目が増える。手動による監視では間に合わなくなるので, 情報を自動的に収集し, 運用監視に適したビューを提供する必要がある。
  • ログやメトリックなどの収集した情報には、サービス開発者が必要なときにすぐにアクセスできるようにしておく。(誰かに依頼しないと入手できない、では遅い)
  • マイクロサービスでは、基本的にイベントベースの疎結合な連携を目指すので、イベントの監視が重要になる。

ログの監視

  • マイクロサービスによるシステムでは、ひとつのイベントを基点に複数のサービスが連鎖的に動作するため、あるイベントに関連する複数のログを紐付けて見ることができるようにする必要がある。そこで, 最初のイベントが一意な相関IDを発番して後続イベントに乗せて伝搬する必要がある。

メトリックの監視

  • サービスをスケールさせるためにメトリクスを継続的に監視する必要がある。
  • 複数のサーバに分散して稼働しているサービスの場合、それらのサーバ全体のサービスとしてのメトリックと、個々のサーバのメトリックも見れるようにしておく。

システム全体の監視

  • システムの健全性を把握するには、個々のサービスやサーバのメトリック(低水準メトリック)からシステムが健全かどうかを判断よりも、システム全体に対してテスト用のトランザクションを送信することで健全性を判断する(セマンティック監視)ほうが、より優れた指標を入手できる。

連鎖的障害の検知

  • 個々のサービス自身の健全性だけではなく、下流サービスも含めた健全性を監視して連鎖的障害が発生したら検知する必要がある。
    • リクエスト/レスポンスで連携する場合は, 下流サービスから応答で健全性を知る。
    • イベントベースで連携する場合は, イベントを配信するキューの状態から健全性を判断する
  • 連鎖的障害に対処するためには, サーキットブレーカーを仕込む。

9章 セキュリティ

ユーザの認証・認可

  • 複数のサービスでユーザを認証・認可するためにシングルサインオンの仕組みが必要
  • 個々のサービスでユーザの認証・認可を実装するのではなく, ユーザーインタフェースとサービスの間に入って認証・認可を透過的に行うゲートウェイを置く

外部システムの認証・認可

自システム内の他のサービスを呼び出す場合に認証・認可をどう扱うか検討が必要

  • ベーシック認証
  • SAML/OpenIDConnect
  • 証明書
  • HMAC (Hash-based Message Auth Code)
  • APIキー

10章 コンウェイの法則とシステム設計

システムを設計するあらゆる組織は, 必ずその組織のコミュニケーション構造に倣った構造を持つ設計を生み出す

コンウェイの法則

チーム

  • 各サービスはひとつのチームによって所有され, チームはそのサービスについての要件・設計・実装・テスト・運用の全てに権限と責任を負う

メンバー

  • マイクロサービスでは, モノリシックなシステムと比較して, 広範囲への考慮や新しい技術への適応に関してメンバーにより高いスキルが求められる

11章 大規模なマイクロサービス

障害への対策

  • そもそもどれだけ対策をしても障害を完全になくすことはできないので, 費用対効果の薄い障害防止策にリソースを費やすよりも, いかに簡単に障害から復旧させられるかを考えたほうが, 有効な障害対策になる
  • 非機能的な要件がどこまで許容できるかを把握し, やりすぎないように必要十分な対策を講じるようにする
観点内容
応答時間ユーザーから見たシステムの応答時間はどのくらいに収まるべきか
可用性システムの停止はどれくらい許容されるのか
耐久性ある程度のデータ欠損は許容されるのか, データはどれくらいの期間保全されなければいけないのか
  • マイクロサービスにおいては, あるサービスが停止してもシステム全体は稼働し続けることができるように, 安全に機能低下させてそこから回復させることを考える必要がある
  • あるサービスで障害が発生した場合に, それが他のサービスにまで波及しないようにすることが重要
    • 特に応答遅延は下流のサービスに波及していって対応困難になるので, 遅延しながら稼働し続けるよりは, 停止(機能低下)させたほうが良い
  • 障害を意図的に起こすことで, 十分な障害対策ができているかを確認するとともに, チームが障害に対する学習の機会を得ることができる (アンチフラジャイルな組織)
    • Chaos Monkey…Netflixが公開した, 人工的に障害を発生させるツール
  • サービスの振る舞いをできるだけ冪等にしておくことで, 障害発生時にどこまで処理されたかを考えずに単純に再処理すれば良くなる

スケーリング

  • 書き込みのスケーリングでは、シャーディング(データの格納先を分散)する方法がある。
  • サービスごとにデータベースのスキーマを分割するだけでなく, サービスごとにデータベースインスタンス自体を別にするほうが望ましい
  • CQRS (Command-Query Responsibility Segregation: コマンドクエリ責任分離) パターンを使うことで, データベースのスケーリングに柔軟に対処することができる
    • CRUD操作に対処するモデルとは乖離しているので適切に機能させるのが難しい

キャッシング

  • サービス間の通信プロトコルとしてHTTPを採用している場合は、HTTP自体のキャッシング仕様を利用する
    • cache-controlとExpires…キャッシュする期間や日時を指定
    • ETag…リソースのバージョンを管理する
  • 更新処理では, データの変更要求をキャッシュしておいて, まとめて下流サービスに流す (ライトビハインドキャッシュ) ことで更新処理をバッチ化して最適化できることがある
  • データの新鮮度が多少古くてもサービスが稼働し続けることの方が価値がある場合は, キャッシュを使うことで下流サービスが停止していても情報を提供することができる
  • キャッシュする場所が増えればデータの鮮度がわかりづらくなるので, キャッシュする場所はできるだけ少なく(1箇所だけが望ましい)する
    • キャッシュポイズニング…しばらくの間陳腐化したデータを提供すること

データの整合性

分散システムでは, 整合性/可用性/分断耐性 の3つの特性のうち, 2つしか同時に満たすことはできない

CAP定理
特性内容犠牲にする場合
整合性
Consistency
ある時点において, 複数のノードが同じ結果を返すAPシステム。結果整合性によって可用性と分断耐性を満たす。一時的に整合性が取れていないデータが見えることを許容しなければならない
可用性
Availability
あるノードで障害が発生しても他のノードは稼働し続けるCPシステム。データの整合性を保証するために, 応答時間低下やデータが不整合な場合はエラーとなることを許容しなければならない。
分断耐性
Partition tolerance
一部ノード間で通信障害が発生してもシステム全体は稼働し続けるCAシステム。マイクロサービスアーキテクチャではあり得ない。
  • 分散システムにおいてデータの整合性を保ことは困難なので, APシステムになることが多い。

サービスの検知

  • DNS…単純に、エンドポイントのドメイン名に命名ルール(https://サービス名環境.ベンダー/ など)を設ける。
  • サービスリポジトリ…ZooKeeperConsulNetflix Eureka などの、サービスリポジトリツールを利用する。

サービスのドキュメント化

  • JSONベースの RESTful APIであれば、Swagger などを使ってドキュメント化する。

12章 まとめ

マイクロサービスの原則

  • ビジネス概念に沿ったモデル化 (技術的観点ではない)
  • 自動化の文化の採用
  • 内部実装詳細の隠蔽
  • すべての分散化
  • 独立したデプロイ
  • 障害の分離 (連続した障害が発生しない)
  • 高度な観測性

ドメインに対する理解が低い場合は, 一旦モノリシックに作って徐々にサービス化していく

感想

この本自体はマイクロサービスアーキテクチャの要約というよりも, 筆者の経験から基づくtipsだったのでまとめにくかったが, マイクロサービス化しようとする開発者の視点で書かれていて面白かった。DDDとも関わりが深いので今度はiDDD本を読んでみたい。