BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル マイクロサービスに失敗する7つの方法

マイクロサービスに失敗する7つの方法

キーポイント

  • Microservices are a means, not a goal
  • Being distributed does not guarantee being decoupled
  • Contract tests are an important part of any microservices architecture
  • Decomposition needs to happen in the front-end, back-end, and integration layer, as well as in the business logic
  • If a business does not have the ability to release microservices quickly and independently, many of the benefits of microservices will be lost

原文(投稿日:2022/02/14)へのリンク

 

昨年11月のQCon Plus、私は、マイクロサービスがうまくいかない理由について講演しました私はIBMに籍を置くコンサルタントで、ビジネスのクラウドネイティブ化を支援する仕事もしています。これらの問題は私の経験に基づくものです — そして、残念なことに、現場で何度も目にするものでもあります。

まず問題なのは、問題が何であるのかさえ分かっていない場合がある、ということです。私たちはマイクロサービスを導入すべきだと思ってはいますが、マイクロサービスを導入する理由の定義には、これまで十分な時間を費やしてきませんでした。

解決しようとしている問題は何なのか?今、何に苦労しているのか?マイクロサービスを導入することで何がよくなるのか?これには人としてごく自然な、特に私たち技術者に顕著な、ある性向があります。ソリューションに飛びつきたい。目新しいものを取り入れたい。解決すべき問題を把握するということは、非常に重要であるにも関わらず、悲しいかな、問題の解決そのものよりもずっと楽しくないことなのです。

"ソリューションに飛びつく"というこの自然な性向を、さらに悪化させているのがコンテナです。なぜならコンテナは、ソリューションを素晴らしいものにする、魔法に近いテクノロジだからです。極めて軽量。極めてポータブル。コンテナはとても多くのものを、とてもよくしてくれます。そこで、次のように考えるのです。"せっかくコンテナがあるのだから、アプリケーションをひとつのコンテナだけで運用するのは、まったくもってコンテナ機能の無駄遣いだ。できるだけ多くのコンテナでアプリケーションを実行するべきだ!" — 残念なことに、"コンテナが足りない"という問題提起が理由ではないのです。

CV駆動開発

次の問題は、CV駆動開発(CV-driven development)です。自分のCV(Curriculum Vitae、職務経歴書)を見ます。すると、"マイクロサービス"に関する部分が大きく空いていることに気付きます。これでは都合が悪いので、"自社のスタックを再構築して解決しよう"、と考えるのです — 読者の皆さんは、"Holly、それは皮肉が過ぎるよ"、と思うかも知れません。"自分の履歴書の都合でアーキテクチャを決めるやつがどこにいるんだ?" それが実は ... いるのです。

Red Hatは先日、コンテナベース開発を導入したおもな動機に関する調査を行いました。その結果、最も多かったものは"キャリアアップ"でした。"キャリアアップ"は、CV駆動開発の体のよい言い換えに過ぎません。  

現在のマイクロサービスは"新正統派"と言ってもいいものですから、経歴書にマイクロサービスが欠けているのは大きな問題なのです。大量退職時代の一員でなくても、現在は求職中でなくても、脱落はしたくないのが人情です。そう思って周りを見回せば、他の人たちはみな、マイクロサービスを実践しているようです。彼らがマイクロサービスをやっているのならば、自分がマイクロサービスをやっていないのは何が悪いのか、と考えるのは自然なことでしょう。私はこれを"マイクロサービス羨望(microservices envy)"と呼んでいます。

マイクロサービスは目標ではない

マイクロサービスは羨望するようなものではありませんから、マイクロサービス羨望は問題行為です。私の仲間のコンサルタントのひとりは、自身の経験則から、クライアントがNetflixmの話題を続けた上でマイクロサービスを要求する場合、その契約には問題があると判断する、と言っています。そういったクライアントは、ほぼ間違いなく、マイクロサービスに移行する正当な理由を持っていないのです。会話がもう少し深くなって、結合やコヒージョンといったことが話題に上れば、そのクライアントは適切な立ち位置にいると判断できます。 

マイクロサービスへの移行で目指すべきものが、マイクロサービスそれ自体であってはなりません。マイクロサービスは、ビジネス上の目標やレジリエンシ、あるいはそれに類する高度な目標を達成するための手段なのです。しかも、唯一の手段ですらありません — 手段のひとつに過ぎないのです。 

分散型モノリス

"今ここにあるのは、マイクロサービスなのか、それとも何百というGitリポジトリに分散したモノリスなのか"、という問いは重要です。そして、残念なことに、後者であることが少なくないのです。これは分散型モノリスという、ろくでもない代物です。メリットが何もありません。モノリシックなものよりもエラーを起こしやすいのです。単一の開発環境にすべてが含まれる従来型のモノリスであれば、コンパイル時のチェック、IDEによるリファクタリングのサポート、といったメリットを享受できます。ひとつのプロセスですべてを行うので、関数の実行も保証されています。分散コンピューティングの誤謬やサービスディスカバリについて考えたり、コールしようとしている相手が存在しなくなった場合に対処したりする必要はありません。すべてが安全です。このようなモノリスの安全性を取り払って、結合だけを残すならば、そこにあるのは、クラウドネイティブなスパゲッティです。

分散と分解は等価ではない

数年前に私は、あるトラブルプロジェクトの支援に呼ばれました。私が赴任した時、チームが最初に説明してくれたことのひとつは、"ひとつのマイクロサービスを変更すると、別のひとつに障害が生じる"、というものでした。マイクロサービスに何が期待できるかを少しでも知識として持っていれば、これは起こるはずのないことだ、と分かるでしょう。マイクロサービスは独立していて、互いに分離しているはずなのです。しかし分解は、システムを分散化すれば無条件に得られるものではありません。"分散(Distributed)"と"分解(Decoupled)"は、どちらもDで始まりますが、同じではないのです。

高度に分散化されたシステムが、分散化で生じるすべての苦痛を抱えつつ、依然として絡み合って結合している、ということは十分にあり得ます。このケースはまさにこれでした。コードベースを調べ始めると、それぞれのリポジトリで同じコードを何度も目にすることになりました。アプリケーションのオブジェクトモデルはかなり複雑なものでした。20程度のクラスがあって、その中のいくつかは70のフィールドを持っていました。複雑なスキーマだと言えます。 

マイクロサービス開発の原則のひとつは、DRY(Don't Repeat Yourself)を忘れて、結合の元になる共通ライブラリを回避することなのですが、このケースでは、集中的なオブジェクトライブラリへの結合を避けるために、それぞれのマイクロサービスがコードのオブジェクトモデルをカット・アンド・ペーストでコピーしていたのです。しかし、ドメインスキーマは共有したままだったので、結合は依然として残っていました。オブジェクトコードを複製しても結合は解消しません。単にコンパイル時のチェックがなくなるだけです。フィールドの名称が変われば、すべてが壊れてしまいます。しかもそれが、実行時まで発生しないのです。

この悲しい物語は、マイクロサービスにおけるドメイン駆動設計原則の重要性を物語っています。目標とする理想は、各マイクロサービスがひとつのドメインにきちんとマップされることです。この副次的効果として、マイクロサービスのインターフェースは小さなものになります。これはまた、設計が正しいことの証明でもあります。ドメインバウンダリではなく、技術的なバウンダリに沿って分割を行った場合、私がここで見たような状況に陥ることになります。すなわち、それぞれのマイクロサービスが、巨大で不安定なインターフェースを持つことになるのです。最後に待っているのは、断片化したスパゲティ状態のカオスです。

火星探査機"Mars Climate Orbiter"の物語

マイクロサービスというよりも、技術的には宇宙船ですが、Mars Climate Orbiterの例は、分散と分解の違いをよく表しています。NASAが1998年に打ち上げたMars Climate Orbiterのミッションは、火星の気候を研究することでした。残念ながらOrbiterは火星を周回できず、火星に墜落してしまいました。NASAの原因調査によって、問題は、2つの異なる制御チームが開発した制御システムの関係に起因するものであることが分かりました。飛行時間の大半においては、探査機本体に搭載されたシステムが操縦を行っていたのですが、Orbiterが地球の視野に入る数日間については、フロリダの監視制御システムがコース修正を送信していました。つまり、システムとしては分散していて、その一部が宇宙空間にあった、ということです。しかしながら、2つのシステムのドメインは事実上同じで、どちらもエンジン推力計算を処理していました。この2チームの間で、インターフェースをどのようにするかの意思疎通が十分でなく、結果として異なる単位を使用してたのです。宇宙区間の側がメトリック単位を使用したのに対して、地球側はインチ単位を使用しました — そして悲劇が起きました。このケースでは、システムは非常に分散していたが、分散したことが役には立たなかった、と言って差し支えないでしょう。

コンシューマ駆動コントラクトテスト

このような、些細なコミュニケーションに起因する問題は、複数のチームが関与すれば常に起こり得るものです。幸いにも、よい軽減化手段があります。それがコンシューマ駆動コントラクト(CDC)テストです。IDEが型チェックを支援できないシステムでは、インテグレーションテストを行う必要がありますが、私たちとしては、本格的な結合テストは最小限に留めたいところです。統合テストは難しく、実行コストが高価で、本質的に結合が起こしやすいものだからです。マイクロサービス開発に投資している身であれば、テスト時には過去に戻って大規模な統合モノリスを作る、ということはしたくありません。では"実際に機能するものを作っている"という自信を得るには、どうしたらいいのでしょうか? 

一般的なソリューションはモックですが、モックにはそれ自体の問題があります。モックをセットアップするためには、インターフェースをどのようなものにするのか、提供側(producing)チームと利用側(consuming)チームが開発当初から打合せを行う必要があります。2チームが合意すれば、コンシューマは自分たちの持ち場に戻って、提供側チームがコードについて説明したことに対する、自分たちの理解に基づいてモックを書きます。その理解が正しければ理想的なのですが、問題なのは、コンシューマ側がモックを書くことによって、コンシューマ側の想定がそこに含まれることです。自分たちのものではないコードの見かけや振る舞いを知る、という意味で言えば、コンシューマはモック作者として適任ではないのかも知れません。

幸運にも理解が正しければ、ユニットテストはすべて合格して、統合に至っても合格し続けます。すべてが良好です。残念ながら、いつもうまくいくとは限りません。現実の実装が、コンシューマ側の理解とは違うものになる可能性があるのです。提供側が考えを変えたのかも知れませんし、どこかの誰かが誤った想定をしたのかも知れません。この場合でもテストはパスしますが、実際のサービスに対して本当に統合した時、失敗することになります。ここでの問題は、モックの動作が実際のサービスに対して検証されていないことにあります。提供側チームはおそらく、作成されたモックをまったく見ないでしょう。

これより望ましい方法は、コンシューマ主導でコントラクトテストを行うことです。コントラクトテストのメリットは、モックとは違って、提供側と利用側の双方が関与する点にあります。コンシューマにとって、コントラクトテストは手軽なモックにもなります。

その一方で、提供側にとっては、コントラクトテストは便利な機能テストの役割を果たします。単にOpenAPIで構文チェックを行うよりも、より深い検証を行うことができるのです。コントラクトテストは事実上、セマンティクス(意味)とビヘイビア(振る舞い)の両方をチェックします。これによって提供側チームは、機能テストを記述する手間を省略することができます。

すべてが適合して機能すれば、コントラクトテストはすべてパスします。コントラクトテストは簡単で手軽に実行できるので、実装に対する自信を手早く深めることが可能になります。提供側チームに何らかの問題があればテストが失敗するので、互換性を損なう変更が統合環境に漏れ出す前に、早い段階で警告を受けることができます。APIが変更されれば、コントラクトの新バージョンが両方のチーム(あるいは接続ブローカ)にロールアウトされます。 

世の中にはいくつかのコントラクトテストシステムがあります。Springエコシステムにいるのであれば、Spring Contractが非常によく機能するのですが、もっと多言語な環境にいるのであれば、ほぼすべての言語バインディングを備えたPactをお勧めします。

エンタープライズ・ヘアボール

言うまでもないことですが、すべてのテストを完了して、ビジネスロジック層に分離したマイクロサービスのセットを美しく配置したとしても、それで成功が保証される訳ではありません。私たちのシステムには、本当にクリーンなマイクロサービスアーキテクチャを描き上げた時点では考えてもみなかった、その他の要素がまだたくさんあるのです。ビジネスロジックに集中するあまり、フロントエンドやバックエンド、すべての接合層(glue)のことを忘れているかも知れません。エンタープライズアーキテクチャでは、特に接合層にその可能性が高く、厄介な問題を起こします。当社のアーキテクトのひとりは、これを"エンタープライズ・ヘアボール(Hairball、毛玉)"と呼んでいます。 

ビジネス層の機能分解にのみ労力を集中させていると、きれいに分離されたマイクロサービスの集団が、モノリシックなフロントエンドとモノリシックなデータベースに挟まれる、という構造になることが多々あります。後になってこのようなシステムを修正するのは至難の業です。とは言いながら、業界としては、データベースの分離化によって個々のマイクロサービスへのマッピングは改善されていますし、マイクロフロントエンドの開発も進んできているのですが、 

まだ完了には至っていません。システムがある程度の規模であれば、統合層が存在すると思います。これはメッセージングや、あるいはその他の、複雑なシステムを互いに引き付ける統合ソリューションです。アーキテクチャの他の部分が最新化された後でも、この統合層に関しては、相変わらずモノリシックで柔軟性のないものであることが少なくありません。チーム自体にも、大きな負担がかかっている — 私の同僚の表現では"パニック・サンドイッチ" — かも知れないのです。統合層はモノリシックなので、変更には慎重なスケジュールが必要です。これが他のメンバをブロックすることになります。

このことは、特に統合チームに対して、多くのフラストレーションを生み出すことになります。ハードに働いているにも関わらず外部からは、反応が悪く作業が遅い、と見られるかも知れません。結合を解決するためには、モジュラ統合パターン(modular integration pattern)の採用が必要です。 

統合層、データベース層、フロントエンドを分離しなければ、何が起こるのでしょうか?ほぼ確実なのは、私たちの望むマイクロサービスは実現しないだろう、ということです。ヘアボールのあちこちを結ぶ依存関係が、各部分の速やかな変更の妨げになります。ビジネス層マイクロサービスの独立的なデプロイは不可能になり、デプロイのペースが非連続的なものになることは間違いありません。 

リリースへの抵抗

このシナリオに心当たりのある方はどれくらいでしょうか。あなたは熱心に働いて、素晴らしいものを作っています。ユーザが喜ぶのは分かっていますが、まだ彼らの手元には届いていません。価値は棚上げされたまま、素晴らしい作品はまだリリースできないのです。マイクロサービスアーキテクチャであったとしても、リリースボードは変わらず存在します。他のマイクロサービスもすべて、同時にリリースしなくてはなりません。テストは一緒にしなくてはなりませんし、ビッグバッチ以外でそれを行うのはコストが高すぎるからです。リリースチェックリストの記入にさえ、お金がかかります。業務部門はリリースに臆病になっています。というのも、過去に手抜きリリースでひどい目にあっているからです。リリースチェックリスト、リリースボード、そしてシングルスレッドテストやその他のリリースに関わるおまじないは、どれも認知されているリスクを軽減するための試みです。リリースの期限は組織全体で決まっているので、その期限までに機能を詰め込まなくてはなりません。それは当然、リリースをよりリスキーなものにします。誰かがどこかで、必要以上に結合したマイクロサービス間のすべての依存関係を、スプレッドシートで追いかけているのです。そしてもちろん、月の満ち欠けもちょうどよい頃でなくてはなりません。私たちがマイクロサービスを選択したのは、このような結果を得るためではないのです!このような善意によるプロセスのすべてが、ユーザに価値を届ける上での足かせになるだけでなく、実際にリスクを増大させる場合も少なくないのです。 

オートメーション

なぜ、こんなことになるのでしょう?一般的に、私たちがリリースに尻込みするのは、リリースには山のような手作業が付き物であるためです。特に、私たちに本当の自信を与えてくれるテストは自動化されていないため、アプリケーションが機能するかどうかを判断するのにさえ、多くの作業を行う必要があります。クライアントのところへ行った時に、"私たちのテストは自動化されていません"と聞かされるのは、"今のところ、コードが機能するかどうか、まったく分かりませんが、大丈夫だと思います。前回、手作業でQAを行ったときには動いていましたから、多分、今でも動くでしょう"、と言われたも同然です。これは悲惨な状況と言えます。 

心配ならば、自動化しましょう — 気にかけるべきなのは品質なのです。特に、アーキテクチャがスパゲッティ状態に向かって突進中で、結合が忍び込んでいるようであれば、問題が発生する可能性があります。スパゲッティの解消は難しい作業なので、せめて問題が発生した場合にできる限り速やかに検出できるような、超高速フィードバックの場所でありたいと思うのです。スパゲッティになるなら、せめてテスト済スパゲッティを目指しましょう。

リリースサイクル

手作業テストは、リリースに関わる手作業プロセスのほんの一部です。規制やコンプライアンスの厳格な産業では、手作業によるコンプライアンス業務が山積みになっているのが通常です。コンプライアンスは大いに尊重しなければなりません — ですから、自動化すべきです。 

これらすべての手作業プロセスと、それにともなう作業速度の低下が本当に意味するのは、デプロイ先がクラウドであっても、クラウドの恩恵を得られていない、ということです。クラウドを、クラウドではないように使っているのです。皮肉なのは、優れたアイデアであり、安全を保証してくれるものとして使ってきたクラウドが、現実には私たちを傷付けている、という事実です。クラウドでは、古いタイプのガバナンスは通用しません。私たちが望むようなビジネス成果には到達できず、クラウドのビジネスメリットの多くを失うことになります。

ビジネスがクラウドのメリットを本当に活かしきれているかどうかは、リリースサイクルを見れば一目瞭然です。数年前、私の同僚が、ある大手レガシー銀行に売り込みをしたことがありました。その銀行は、フィンテックや新たな銀行の挑戦によって、利益を奪われている状況でした。勝負に負ける理由は分かっていました — 行動が遅く、彼らに追いつけないのです。彼らは私たちのところへ来て、自分たちが大規模なCOBOL資産を抱えており、それがスローダウンの原因だ、と説明しました。(確かにそのとおりでしょう。) さらに彼らは、COBOLをすべて捨て去ってマイクロサービスに移行しなければならない、他はみなそうしているからだ、と言ったのです。加えて、自分たちがリリースボードを立ち上げるのは年に2回のみだ、と言いました。これを聞いた時点で、同僚は意気消沈しました。リリースボードが年2回だということは、リリースのケイデンスは6か月ということになります。マイクロサービスが個別にデプロイ可能な状態になったとしても、それは変わりません。これではアジリティの実現は不可能です。

この銀行に本当に必要なのは、技術的な支援ではありません。リスクに関する考え方、運用の方法を変える必要があります。リリース計画にも完全にオーバーホールしなくてはなりませんし、自動化の必要な部分もたくさんあります。足枷となっているのはCOBOLではなく、継続的デリバリの理念が欠如していることなのです。 

"分解したい"というのが一般的なクライアントの要望ですが、分解(decompose)の意味はひとつではありません。アプリケーションの分解を望んだとしても、それでモジュラリティが保証される訳ではなく、単に混乱を拡散するだけになることもあるのです。リリースボードや時代遅れのプロセスのように、足止めをするような外部的制約があるならば、それらを解決しない限り、アプリケーションを分解しても意味はないのです。

作者について

この記事に星をつける

おすすめ度
スタイル

BT