マイクロサービスアーキテクチャのざっくりまとめ

目次

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

上記の書籍をもとに学んだことをまとめていく。 まずマイクロサービスについての定義だが、

マイクロサービスは、協調して動作する小規模で自律的なサービスです。

とのことである。 かなり曖昧であるが、サービス群に対してどこからがマイクロなサービスのような具体的な線引くのは難しい。 本書で説明しているマイクロサービスの特徴としては

  • 一つの機能に専念していること
  • 独立したデプロイが可能なこと

の2点が担保されていることがマイクロサービスの条件となっているようである。

マイクロサービスを用いる利点

技術異質性

モノリシックなサービスに対し、技術選択の自由度が格段に上がる。 railsを使用して全てを管理しているアプリケーションにおいてrubyとJS以外のプログラミング言語を導入するのは容易ではないが、 適切にマイクロサービスに分割し機械学習分野に定評のあるPythonを推測部分に用いたり、 課金周りの処理には信頼と安定のJavaを使用したりなどといった選択が可能になる。

回復性(Resilience)

システムのあるコンポーネントに障害が発生しても、その障害が連鎖しなければ、 問題を分離してシステムの残りの部分は機能し続けることができる。 モノリシックサービスでは一つの障害が全体に波及しやすい。

スケーリング

モノリシックサービスと違い、処理の重いサービスに集中的にリソースを割くということが可能になる。 そのため、一般的に柔軟なスケーリングが可能となりコストを制御することが容易になる。

デプロイの容易性

大きなモノリシックサービスの軽微な変更に対してデプロイを行おうとすると、 変更量と釣り合わない長いビルド時間やリリース時間と戦う羽目になる。 また、リリースの失敗やバグ、障害の発生があると一気に全体に影響が出るため高頻度のリリースに精神的な障壁が生まれる。 サービスを分割した場合上記の懸念事項が軽減される。

組織面の一致

1チームはピザ2枚で足りる人数が良い。 そのような小規模チームで巨大なモノリシックサービスを見切るのは至難の技である。 一つのサービスと一つのチームを結びつけられるように分割することで、効率よくマネジメントが可能になる。

合成可能性

分割されたサービスを再利用することが容易になる。 例えば、シューズストアサービスが参照していたユーザサービスを 新たに開発するブックストアサービスも参照するようにするといったことが簡単にできる。

マイクロサービスを用いるデメリット

明確に書籍には記述されていないがざっと思いつくものをまとめる。

  • 結局コストパフォーマンスが悪くなることがありそう
  • 技術の自由度からチーム間の異動の際に学習コストが上がりそう
  • インタフェースが増えるのでその分事故も起きやすそう
  • 理想のマイクロサービスの状態を全員が共有できていないとすぐにどこかで密結合が起こりそう

サービスのモデル化

優れたサービス

疎結合

あるサービスを変更しても別のサービスを変更する必要がない状態。

高凝集性

関連するビジネスロジックが一つのサービスに収まっている状態。 一つのロジックの振る舞いを変更したい時に、複数サービスにまたがって変更する必要がない状態。

境界

ビジネス的境界

大きなビジネスロジックを分解し、一つのビジネスロジックに着目した際外部との通信が必要な部分と必要のない部分に分割する。

技術的境界

分割の第一条件はビジネス境界であることを念頭に置く。技術的に分割できる部分は性能的な問題等を鑑みて分けても良い。

共有データベースは絶対にダメ

疎結合性と凝集性の真逆をいくプラクティスであるのでいかなる代償を払っても避けるべきである。

オーケストレーションとコレオグラフィ

通信モードにおいて、リクエスト/レスポンスとイベントベースの二つが存在する。 サービス間の関係性として リクエスト/レスポンスを用いたオーケストレーションと イベントベースの通信を用いたコレオグラフィがある。 一般に、コレオグラフィ手法に向かう傾向が強いシステムの方が疎結合で柔軟性がある。 リクエスト/レスポンスを使用する際にも非同期にするように心がければ、疎結合性がある程度担保できる。

オーケストレーション

オーケストラの指揮者のように中枢部のサービスに頼ってプロセスを推進する。 指揮者サービスが各マイクロサービスとリクエスト/レスポンスを行い、 データを集めて最終的なレスポンスを形成するというようなモデルがオーケストレーションである。 メリットとしてはモデルが非常に分かりやすいこと、問題の特定が容易なこと。 デメリットは、指揮者サービスが中央監督機関になりすぎること。 中央集約しすぎるとほとんどのロジックをその部分で行うことが効率的となり、 結果的にモノリシックなサービスが出来上がってしまう方向に引力が働く可能性がある。

コレオグラフィ

コレオグラフィはオーケストレーションとは違い中枢サービスがない。 代わりに入り口となっているサービスがイベントを発行し、 それらをサブスクライブしている各マイクロサービスがイベントをトリガーにプロセスを開始する。 一般的に問題特定やエラーハンドリングを行うことがオーケストレーションよりも難しくなる。

RESTとRPCのトレードオフ

リクエスト/レスポンス方式の通信の代表的なものとしてREST(REpresentational State Transfer)とRPC(Remote Process Call)がある。 これら二つのトレードオフについて考える。

REST

RESTの考え方とHTTPプロトコルは非常に相性が良いため、ここでは主にHTTPにおけるRESTについて考える。 HTTPリクエストを送ることによってリモートサーバに処理を依頼する。 ハイパーメディアコントロールにより、リソースにアクセスして次の操作を決定することが容易となり、サーバとクライアントの分離が進む。 欠点としては性能面や複雑さ、開発工数の増大などがある。

RPC

クライアントが単なる関数呼び出しを行うようにリモートサーバにリクエストを送れる手法である。 いくつかの問題として、クライアントサイドとサーバサイドの技術選択に制限がかかることや、 リモート呼び出しを抽象化しすぎた結果リモートサーバへのリクエストと気づかずに修正を入れてしまい事故につながるといったことがある。 最新のProtocol BuffersやThriftのような機構はこのようなデメリットを軽減している。

非同期イベントベース連携

イベントベースの手法としてはメッセージキューを使用するものが代表的だろう。 HTTPを使用してAtomのような手法を使うことも可能である。 メッセージキューのインフラを整えるために追加の工数が大量に必要になるが、 完成した暁には大いなる疎結合性が得られる。

クライアントライブラリ

クライアントライブラリを使用する場合はクライアントサイドでアップグレードのタイミングを管理できるようにする。

バージョニング

バージョニングが必要な場合はセマンティックバージョニングを使用するのが良い。 2.1.5のように MAJOR.MINOR.PATCH と数字に意味を持たせる。 これによりアップグレードした際に破壊的な変更が行われているかどうかを察知することが可能となる。

その他の方式としては新旧のバージョンを共存させることが挙げられる。 どちらの方式も選べるような状態にし、旧バージョンを誰も参照しなくなった際に削除する。

前者は頻繁なアップデートを行うインターフェースに、後者はそれほど頻繁でないものに使用することができると考えられる。

ユーザインタフェース

合成レイヤと考え、レイヤと接続する各サービスがそのレイヤに対して責任を持つのが好ましい。 BFF(Backends for Frontends)を使用することでサービス呼び出しの管理が容易になるが、 BFFの層で中間ロジックを入れると複雑さが増すので注意が必要。

デプロイ

相互依存するマイクロサービスにおいてデプロイを適切に行うことは非常に重要である。 本章ではデプロイに関するテクニックや技術について言及する。

CI/CD

継続的インテグレーション

CI(Continuous Integration)は、新たにチェックインされたコードが既存コードと適切に統合されるようにする。 実際にCIを行っているかどうかの質問として

  • 1日に一度はメインラインにチェックインしているか
  • 変更を検証するテストスイートがあるか
  • ビルドが壊れた時に、素rを修正するのがチームの最優先事項か

というのがある。

CIとマイクロサービスのマッピング

CIを考える際に

  • ソースコードリポジトリ
  • CIビルドジョブ
  • ビルド生成物

の3つについてマッピングを考える必要がある。 最終的な生成物が3つ出来上がると考えた時に、CIビルドジョブまで3つに分割するか、ソースコードリポジトリまで3つに分割するかで 3パターン考えることができます。 一般にCIビルドジョブを一気にやる手法は考えることが少ないことをメリットとして捉えられるかもしれないが無駄捉えられるかもしれないが CIビルドジョブを分割し、ソースコードリポジトリを分割しない場合はリポジトリが一つなので、 チェックイン/チェックアウトのプロセスが容易になる。 しかし、独立すべき複数のサービスを同時にレビューする習慣がつくため、密結合なコードに気付きづらくなるという欠点は抱えている。 ソースコードリポジトリまで分割した場合、リポジトリの所有権がチーム単位で明確になり、独立したレビューを行うことができるが、 リポジトリをまたがる変更が難しくなるというデメリットがある。

継続的デリバリ

継続的デリバリ(CD: COntinuous Delivery)とはチェックインするたびに本番環境への準備状況に関する フィードバックを常に得られ、さらに各チェックインを全てリリース候補として扱う手法である。 準備状況についてはビルドパイプラインによってステップに分けられて実行され、その成否において状況が判断できる。

成果物

リリースまで考えた際にソースコードの成果物として出力されるものの種類とそれらの特徴について考える。

プラットフォーム固有の成果物

Javaの場合はjarファイルとwarファイル、Pythonの場合はEggのようなプログラミング言語に対する成果物が生成される。 しかし、それを稼働させるためにはそれぞれの成果物を実行するための環境を整える必要がある。 環境を整えるためにはchefのような自動構成管理ツールが役に立つ。

OS成果物

Red HatにおけるRPMやUbuntuにおけるdebのようなOS固有の成果物を生成する手法もある。 この場合、プラットフォーム固有の環境構築に依存する必要がなくなる。 しかし、OS固有の成果物の生成は一般に難易度が高いことや複数のOSにデプロイする場合には逆に手間がかかる可能性があるなどの欠点もある。

カスタムイメージの成果物

Puppet, Chef, Ansibleといった自動構成管理ツールの欠点は構成処理に時間がかかることである。 そのため、予め仮想マシンイメージを生成物とすることで各サーバへのデプロイ時間を軽減することができる。 また、イミュータブルサーバの概念を実現するのにも役に立つ。 この概念は端的にいうと、稼働中のサーバ郡は常に同じ状態にさせるために各々に対する変更は行わないようにするといったものである。 イメージを作成することで、SSH接続を不可にしてサーバを稼働させることが可能になり、概念としてのイミュータブルサーバを より現実的なものとすることができる。

サービス構成

ホストとサービスをどのようにマッピングするかを考える。

1ホストに複数のサービス

スケールが難しい。 管理するホストが一つなのが楽。

ホストごとに一つのサービス

スケールアウトが容易。管理するホストが複数になるので監視等のコストがかかる。

物理と仮想化

仮想化に対するキーワードを記載する。 最新の仮想化ではオーバーヘッドがかなり軽減され、進めるにつれて柔軟性や再現性が上がるという恩恵を得られる。

  • Docker
  • Linuxコンテナ
  • Vagrand
  • ハイパーバイザ

テスト

テストの種類

Maricの4象限

Marickの4象限の派生系というものがある。 図を書くのが面倒なので、言葉で記載すが、4象限なので軸が二つあり、それぞれ ビジネス <-> 技術 , プログラミングのサポートの大小 である。

  • ビジネス、プログラミングのサポート小: 探索的テスト
  • ビジネス、プログラミングのサポート大: 受け入れテスト
  • 技術、プログラミングのサポート小 : 性質テスト
  • 技術、プログラミングのサポート大 : 単体テスト
テストのスコープ

テストのスコープについては単体テストが最も狭く、End to Endテストが最も広い。 テストのスコープが大きくなるにつれてコードに対する自信が向上する。 テストのスコープが小さくなるにつれて実行時間が短くなり、分離性が向上する。

単体テスト

機能が適切かどうかに関して高速なフィードバックを得られることが単体テストの目的である。 コードのリファクタリングを支援するために非常に重要になる。

サービステスト

1マイクロサービスのみを対象としたテスト。 可動部がマイクロサービス単体に限定されているためテストの脆弱性が小さくなる。

End to Endテスト

サービス全体を対象としたテスト。 テストの脆弱性は大きくなり問題箇所の特定も難しくなるが、成功した際に最も大きな自信が得られる。 その他にテストの責任者が曖昧になりやすいという組織上の欠点があったり、実行時間が大きくなりやすかったりとくつかの問題点を抱えている。

コンシューマ駆動テスト

コンシューマ/クライアントがプロデューサ/サーバの試験を行う。 これにより確実にインタフェースの整合性が担保でき、End to Endテストよりも遥かに高速に試験が行える。 コンシューマ駆動テストを助けるツールとしてPactというものがある。

スモークテスト

新たにデプロイしたソフトウェアに対して実行し、そのデプロイが正常に機能していることを確認する。 これを行うためにデプロイとリリースを分離する必要がある。

ブルーグリーンデプロイメント
必要なサーバの倍量を準備しておき、半分に分割、片方にのみリクエストが流れる状態にする。 リクエストが来ていない側のサーバに新しいバージョンを行い、スモークテストを実施する。 スモークテストが成功したら、新しいバージョンにリクエストを流す。

カナリアリリース
本番環境の一部に新しいバージョンをリリースする。 様々な観点から新しいバージョンに問題ないことを確認し、新しいバージョンの割合を最終的に100%にする。

機能横断テスト

非機能要件と呼ばれる通常の機能のように実装できないシステムの特性を表す用語があるが、その用語と同様のものとして機能横断要件というものを定義する。 多くの機能横断要件は本番環境のみでしか検証することができず、4象限で大別すると性質テストに当てはまる。 これらに関しては極力自動化できるようにするが不可能な部分は手動になり、臨機応変な対応が求められる。 多くの場合機能横断要件に関して検討されるのが遅いので、早めに考えるよう提案されている。

性能テスト

上記の機能横断要件の一部性能面を満たしているか確認するテストである。 性能テスト環境は本番環境と近しいものを用意する必要があり、努力が必要な場合が多いがボトルネックの特定など有意義な結果が得られる。

監視

サービスについてのアドバイス

  • 最低でもインバウンド応答時間を追跡する。続いてエラー率、アプリケーションレベルのメトリクス。
  • 全ての下流のレスポンスの健全性を追跡する。最低でも下流呼び出しの応答時間を追跡し、可能ならエラー率も追跡する。
  • メトリクスを収集する方法と場所を標準化する。
  • 可能なら標準的な場所に標準形式でログを記録する。
  • 基板となるOSを監視し、不正なプロセスを探し出して、キャパシティプランニングを実行できるようにする。

システムについてのアドバイス

  • CPU使用率のようなホストレベルメトリクスはアプリケーションレベルのメトリクスと一緒に集約する。
  • メトリクス格納ツールを使用し、システムレベルまたはサービスレベルで集約、掘り下げができるようにする。
  • メトリクス格納ツールでシステムの傾向を把握するのに十分な期間のデータを保持できるようにする。
  • ログの集約と格納用にクエリ可能なツールを一つは導入する。
  • 相関IDの標準化を検討する。
  • 対応が必要な条件を理解し、アラートやダッシュボードを作成する。

セキュリティ

マイクロサービスを利用するとモノリシックサービスと比較して強固なセキュリティを築ける可能性が得られ、 セキュリティの手法に関する自由度も上がる。 SAMLやSSOなどの統合的な認証方法についてはさらに学ぶ必要があるが、その他は基本的なセキュリティ法則を遵守する。

マイクロサービスにおける留意点

指標

  • 応答時間/遅延
  • 可溶性
  • データの耐久性/損失性

システム障害への対処法

  • タイムアウト
  • サーキットブレーカー
  • 隔壁
  • 分離

CAP定理

整合性(Consistency)、可用性(availability)、分断耐性(partition tolerance)の中で、 故障モードでは二つを得る。三つ全てを保証することはできない。 分断耐性を犠牲にすることはできないため、APシステムかCPシステムかで故障モードを考える必要がある。 ただし、どちらか一方に振り切る必要はない。

サービスレジストリ

  • DNS
  • ZooKeeper
  • Consul
  • Eureka
  • 自作

サービスの文書化

  • Swagger
  • HALとHALブラウザ

マイクロサービスの原則

ビジネス概念に沿ったモデル化

ビジネスで境界づけられたコンテキストを技術概念に基づいたインタフェースよりも優先する。

自動化の文化の採用

管理するものが多く、複雑なマイクロサービスにおいて、

  • 自動テストは必須
  • CI/CDは必須
  • カスタムイメージ/環境定義/イミュータブルサーバは推奨

内部実装詳細の隠蔽

境界づけられたコンテキストをモデル化し、共有すべきモデルと隠すべきモデルを調べる。 サービスはデータベースを隠蔽し、データポンプや、イベントデータポンプを用いて レポートのために複数のサービスからのデータを集約する。

全ての分散化

チームにサービスを所有させる。 オーケストレーションではなくコレオグラフィとダムミドルウェアを選び、高度なエンドポイントを持たせる。

独立したデプロイ

破壊的な変更が必要な時でもバージョン付けされたエンドポイントを共存させてコンシューマに対して後方互換性を持たせるべきである。 ホストごとに一つのサービスを持つようにするとブルーグリーンやカナリアといったリリーステクニックを使用できる。 コンシューマ駆動契約を用いて、破壊的変更が起こる前にそれについて察知する。 コンシューマが自ら更新のタイミングを決められるようにする。

障害の分離

リモート呼び出しをローカル呼び出しのように扱ってはいけない。 障害がどこでも起こりうることを念頭に対処法を施す。

高度な観測

セマンティック監視を利用して、ログと統計データは集約する。