過去5年間で、当社のチームはGoとKubernetes上で50以上の本番マイクロサービスシステムを出荷してきました。アーキテクチャは「シェルスクリプトを使ったDockerコンテナ」から、現在ではデフォルトで手を伸ばすかなり意見の強いスタックに成熟しました。
サービス境界
マイクロサービスシステムにおける最も重要な決定は、サービス境界をどこに引くかです。これを間違えると、どんな巧妙なインフラもあなたを救うことはできません。これを正しく行うと、他のほとんどの問題は扱いやすくなります。
私たちの経験則:サービスは明確に境界が定められたビジネス能力と1つのデータストアを所有する必要があります。2つのサービスが同じテーブルに書き込む必要がある場合、それらは同じサービスです。
通信パターン
同期的なサービス間呼び出しにはgRPCを使用します。非同期イベントにはKafkaを使用します。内部サービス間ではRESTを意図的に避けています — RESTが悪いからではなく、gRPCがバージョン管理されたスキーマ、コード生成されたクライアント、明確なストリーミングセマンティクスをそのまま提供してくれるからです。
マイクロサービスシステムの一般的な障害モードは、無制限の同期ファンアウトです。サービスAがBを呼び出し、それがCを呼び出し、それがDを呼び出します。Dが遅くなると、依存関係チェーン全体が停止します。
- すべてのgRPC呼び出しに積極的なクライアント側デッドラインを設定し、チェーンを通じて伝播させる。
- 重要でないステップをリクエストパスから取り出してKafkaに乗せ、独立して再試行できる別のコンシューマーで処理する。
- すべてのサービス境界でサーキットブレーカーを使用する。
- TTLが短く許容可能なステイルネスで、読み取りパスを積極的にキャッシュする。
デプロイメントトポロジー
私たちはKubernetesにデプロイし、環境ごとに1つのネームスペース、各マイクロサービスを独自のデプロイメントとしてデプロイします。オペレーターについては保守的です — ほとんどのサービスは標準プリミティブのみを必要とします。
報われる可観測性
重要な3つのストリーム:メトリック、ログ、トレース。すべてのgRPCメソッドとすべてのKafkaコンシューマーをOpenTelemetryで計測します。メトリックはPrometheus、トレースはGrafana Tempo、ログはLokiに流れます。
私たちが行った最も重要な可観測性への投資は、すべてのサービスに、200を返すだけでなく、実際の依存関係チェック(データベースping、Kafkaコンシューマーラグ、下流のgRPC ping)を実行する/healthzエンドポイントを公開させることでした。
優雅な劣化
下流サービスが失敗したときの問題は、「何も起こらなかったかのように動作し続けるにはどうすればよいか」ではなく — それはめったに可能ではありません — 「ユーザーに提示できる最小の優雅な失敗は何か」です。
分散システムは、技術が難しいから難しいのではありません。障害モードが複合し、ほとんどが噛みつかれるまで見えないため、難しいのです。
まとめ
「正しい」マイクロサービスアーキテクチャはありません。あなたの組織、ドメイン、運用成熟度に合うアーキテクチャがあります。境界は技術ではなくビジネスに従う必要があります。
この分野でお困りですか?
本記事に関連する iPlus Solution のサービスをご覧ください。