マイクロサービスに対する過大評価は、組織構造やチームの大きさ、サービスのサイズ、修正よりも書き換えと破棄、ユニットテストの回避といった、多くの極端な考え方に端を発しています。私の経験から言えば、これらのほとんどは間違いや非現実的、あるいは少なくとも一般的には適用不能であることが証明されています。今日残っている原則やプラクティスの大部分は、非常に一般的でゆるやかな定義であるがために、現実的にはあまり意味を持たないまま、長らく事実とされてきたものばかりです。
Kubernetesが生まれる数年前から採用されているマイクロサービスは、現在も分散システムにおける最もポピュラーなアーキテクチャスタイルです。しかしKubernetesとクラウドネイティブのムーブメントは、アプリケーション設計と大規模開発を、ある面で再定義しつつあります。この記事では、元々のマイクロサービスの考え方にいくつかの疑問を呈するとともに、Kubernetes時代である現在においては、かつてのような強い立場にはない、という事実を確認したいと思います。
監視可能なだけでなく、自動化可能なサービス
監視可能性は、マイクロサービスの初期段階から重要な原則でした。これは分散システム一般に当てはまるものですが、今日では(特にKubernetesにおいては)その大部分がプラットフォームレベルで最初から用意されています(プロセスのヘルスチェック、CPUおよびメモリ消費など)。アプリケーションに必要な最低限の要件は、JSON形式でコンソールにログを出力することです。それをもとにプラットフォームが、リソース消費量の把握、要求の追跡、あらゆる種類のメトリクスやエラー率の収集などを、サービスレベルでの開発をさほど必要とせずに実現してくれます。
クラウドネイティブなプラットフォームでは、監視可能性だけでは不十分です。それよりも基本的な前提条件が、ヘルスチェックの実装、シグナルへの対応、リソース使用量の定義などによって、マイクロサービスを自動化可能(automatable)にすることです。そうすることで、ほとんどのアプリケーションをコンテナに収めて実行することが可能になるのです。しかしながら、クラウドネイティブなプラットフォームを使って、効果的に自動化およびオーケストレーションが可能なコンテナアプリケーションを開発するには、従わなくてはならない一定のルールがあります。これらの原則やパターンに従うことによって、成果物としてのコンテナが、多くのコンテナオーケストレーションエンジンの下で善良なクラウドネイティブ市民として振る舞うことが保証され、スケジュールやスケール変更、監視を自動化形式で実施することが可能になるのです。
サービスに何が起こっているかを監視するだけではなく、プラットフォームが異常を検出し、事前に定義したとおりに対処してくれれば、私たちにとってはより好都合です。サービスインスタンスへのトラフィックの停止と再開、スケールアップとスケールダウン、あるいは別の正常なホストへのサービスの移動、エラーになった要求の再実行など、方法は何でも構いません。サービスが自動化可能ならば、すべての是正操作が自動で行なわれます。監視や対応をしなくても、望ましい状態を単に記述すればよいのです。サービスは監視可能であるべきですが、プラットフォームによって人の介入なしに修正可能であることも必要です。
スマートなプラットフォームとスマートなサービス、適切な責任
SOAからマイクロサービスの世界へと移行する過程においては、サービス間通信に関しても、“スマートエンドポイントとダムパイプ”の概念という、もうひとつの抜本的な変化がありました。マイクロサービスの世界では、サービスは集中型のスマートなルーティング層の存在に頼るのではなく、プラットフォームレベルで機能を持つスマートエンドポイントに依存しています。これを実現したのは、従来のESBのすべての機能を各マイクロサービスに組み込んだことと、ビジネスロジック的要素を持たない軽量なプロトコルに移行したことです。
この方法は、(Hystrixなどのライブラリを使用した)信頼性の低いネットワーク層の上にサービス間通信を実装する場合には、現在でも一般的ではありますが 、Kubernetes時代である現在では、サービスメッシュ技術に取って代わられようとしています。興味深いのは、サービスメッシュが従来のESBよりもはるかにスマートであることです。メッシュは動的ルーティングやサービスディスカバリ、レイテンシに基づくロードバランシング、応答タイプ、メトリクスや分散トレース、リトライ、タイムアウトなどを実行する能力を備えています。
サービスメッシュがESBと違うのは、単一の集中的なルーティング層ではなく、各マイクロサービスが独自のルータ – 新たな集中型の管理層を使用してプロキシロジックを実行する、サイドカーコンテナを備えている点です。さらに重要なのは、パイプ(プラットフォームとサービスメッシュ)がビジネスロジックを保持していないことです。パイプはあくまでもインフラストラクチャであって、ビジネスロジックに関する責任はサービスが留保します。下の図は、ESBでの経験から学んだマイクロサービスが、動的で信頼性の低いクラウド環境に適合するまでの進化の過程を示しています。
(画像をクリックして拡大)
SOA、MSA、CNAの比較
サービスの他の面に目を移すと、クラウドネイティブがエンドポイントやサービス間通信以外にも影響を与えていることが分かります。Kubernetesプラットフォーム(と付加的なテクノロジ)は、リソース管理やスケジューリング、デプロイメント、構成管理、スケーリング、サービス間通信といった処理を行なう能力も備えています。従来のような“スマートなプロキシとダムエンドポイント”というよりも、スマートなプラットフォームとスマートなサービスによる適切な責任分担、と表現した方がよいでしょう。エンドポイントだけではなく、ビジネス機能に重点を置き、サービスのインフラストラクチャ面をすべて自動化した、完全なプラットフォームなのです。
障害のための設計、リカバリのための設計
本質的にインフラストラクチャとネットワークの信頼性が低いクラウドネイティブ環境で動作するマイクロサービスには、障害のための設計が不可欠です。疑問の余地はありません。しかしながら、プラットフォームによって検出され、処理される障害の数が増えると、マイクロサービス内で可能な障害対策の余地は少なくなってきます。従ってサービス側では、複数のディメンションからの冪等性を実装することによるリカバリ設計を検討してください。
コンテナ技術、コンテナオーケストレータ、サービスメッシュによって、無限ループ – CPU共有、メモリリークとOOM – ヘルスチェック、ディスク占有 – クォータ、fork爆弾 – プロセス制限、隔壁によるプロセスアイソレーション – メモリ制限、レイテンシとレスポンスに基づくサービスディスカバリ、リトライ、タイムアウト、自動スケーリングなど、多くの障害を検出し、リカバリすることが可能になります。サービスが数ミリ秒動作し、たったひとつのリクエストを処理するようなサーバレスモデルへの移行では、ガベージコレクションやスレッドプール、リソースリークといった概念は、当然ながら意味を持たなくなっていきます ...
これら以上のものがプラットフォームによって処理されるのですから、サービスは開始と停止を繰り返す、ハーメチックなブラックボックスと考えるべきです – 再起動に対して、サービスに冪等性を持たせてください。サービスはスケールアップ、スケールダウンを繰り返します – ステートレスにすることで、安全なスケーリングを可能にしましょう。着信する要求の多くがタイムアウトするものと仮定して、エンドポイントに冪等性を持たせてください。発信する要求の多くが一時的にフェールし、プラットフォームがリトライすることを想定して、冪等なサービスを使用するようにしましょう。
クラウドネイティブ環境での自動化に適合するために、サービスには以下のことが必要です。
- 再起動に対して冪等であること(サービスは何度も強制終了され、起動される可能性があります)。
- スケールアップおよびダウンに対して冪等であること(サービスはマルチインスタンスに自動スケールされる可能性があります)。
- 冪等なサービスプロデューサ(他のサービスがコールをリトライする可能性があります)。
- 冪等なサービスコンシューマ(サービスないしメッシュが発信コールをリトライする可能性があります)。
このような動作が複数回実行された場合でも、サービスが常に同じように振る舞うことができれば、障害時にも人が介在することなく、プラットフォームによるサービスの復旧が可能になります。
最後になりますが、プラットフォームの提供するリカバリは、すべてが部分最適化である点に注意が必要です。Christian Posta氏がいみじくも語ったように、分散システムにおいても、アプリケーションの安全性と正確性はやはりアプリケーションの責任なのです。全体論として安全なシステムを設計するには、ビジネスプロセス全体(複数のサービスを対象とする場合もあります)を考える必要があります。
ハイブリッドな開発責任
マイクロサービスの原則はますます多く実装されて、Kubernetesやその代替的なプロジェクトによって機能として提供されるようになっています。その結果、開発者は、ビジネス機能を実装するためにプログラム言語に精通すると同時に、クラウドネイティブなテクノロジにも同じように習熟して、機能の完全な実装を行いながら、インフラストラクチャレベルの非機能要件にも対処しなくてはならなくなりました。
ビジネス要件とインフラストラクチャの境界線(運用およびクロスファンクショナルな要件、あるいはシステムの品質属性)は常に曖昧で、どちらか一方を選択して、もう一方は他の誰かがやってくれる、という期待はできません。例えば、サービスメッシュ層にリトライロジックを実装するのであれば、サービス内のビジネスロジック層やデータベース層で使用されるサービスは冪等性を持つ必要があります。サービスメッシュのレベルでタイムアウトを使用する場合には、サービス内のサービスコンシューマのタイムアウトを同期させなくてはなりません。サービスの繰り返し実行を実装するのであれば、テンポラリな実行を行うようにKubernetesのジョブを設定する必要があります。
将来的には、サービス機能の一部をビジネスロジックとしてサービス内に実装すれば、その他はプラットフォームの機能として提供されるようになるでしょう。タスクに適したツールを使用することによって責務が適切に分担される一方で、テクノロジの普及が全体的な複雑性を大幅に増加させることになります。ビジネスロジックの観点からは単純なサービスの実装であっても、すべての層に責務が分散するため、分散テクノロジスタックを十分に理解する必要が生じるのです。
Kubernetesが数千のノード、数万のポッド、毎秒数百万トランザクションにスケールアップできることは、すでに実証されています。では、スケールダウンはどうでしょう?私自身、“クラウドネイティブ”による複雑性の導入を正当化するようなアプリケーションのサイズ、複雑性、あるいは臨界性のしきい値がどれ程であるかについては、いまだ掴みかねている状況です。
結論
マイクロサービスのムーブメントが、DockerやKubernetesといったコンテナテクノロジの普及に大いに寄与したという点には、非常に興味深いものがあります。当初は、マイクロサービスのプラクティスがこれらのテクノロジを推進する役割を果たしていましたが、現在では逆にKubernetesが、マイクロサービスのアーキテクチャ的な原則とプラクティスを定義しています。
最近の例では、ナノサービスのアンチパターンとしてではなく、有効なマイクロサービスプリミティブとして関数モデルが受け入れられるようになっています。私たちは今や、中小規模のユースケースに対する実用性や妥当性について十分な疑問を持つこともなく、ある面では不注意に、かつ熱狂的にクラウドネイティブテクノロジに飛びついている状態にあります。
KubernetesにはESBとマイクロサービスからの知見が数多く活かされており、まさに究極の分散システムプラットフォームとなっています。アーキテクチャのスタイルを定義するのは、何よりもテクノロジです。それがよいのか悪いのかは、時間だけが教えてくれるでしょう。
著者について
Bilgin Ibryam (@bibryam)はRed Hatのプリンシパルアーキテクトで、ASFのメンバ兼コミッタでもあります。オープンソースのエバンジェリストでブロガ、“Camel Design Patterns”や“Kubernetes Patterns”といった書籍の著書でもあります。日常の業務では、クラウドネイティブなソリューションの構築を成功させるべく、メンタや、コーディングと開発者の指導などにあたっています。現在は、アプリケーション統合、分散システム、メッセージング、マイクロサービス、DevOps、クラウドネイティブ全般に関わる課題などを中心に活動しています。氏についてはTwitterやLinkedIn、あるいはブログを通じて知ることができます。