MGDXにおけるKubernetesとマイクロサービス運用の現在地
はじめに
私たちのチームではKubernetesを採用してマイクロサービスアーキテクチャでシステムを運用しています。
今回はマイクロサービス運用について現状の運用・運用上のメリット・デメリットといった内容を共有します。
私たちも手探りの中で運用しているので同じようにマイクロサービスを運用されている方々の参考になれば幸いです。
現状の運用
ブランチ戦略としてGitHub Flowを採用し、デプロイメントには Argo CD と Argo Rolloutsを使用してCanaryデプロイを実施
私たちのチームでは開発フローにGitHub Flowを採用しています。
開発者は機能ごとにブランチを作成し、コードレビューを経てメインブランチ(develop
)にマージします。
特定ブランチへのマージがトリガーとなり、CI/CDパイプラインが実行されます。
Argo CDがGitリポジトリの変更を検知し、Kubernetes上のアプリケーション状態をGitで定義された状態に自動的に同期します。
マイクロサービス数とバッチ処理の構成
現在、私たちのプロダクトは数十個規模のマイクロサービスで構成されています。
それぞれのサービスは特定のドメインや機能(例: 薬局管理、予約管理、通知サービスなど)を担当するように設計されています。
また日次の集計や非同期処理を行うバッチ・ワーカーも複数稼働しています。
これらのバッチジョブはKubernetesのCronJobリソースや先述のArgo Workflowsを用いてスケジューリングされ、定期実行を行っています。
サービスディレクトリ構成
コードベースの管理にはモノレポ(一つのリポジトリで全サービスを管理)戦略を取っています。
ディレクトリ構成はドメインごとに整理しています。具体的には、リポジトリ内にドメイン別のフォルダを作成し、その配下にAPIサーバー用とワーカー用のコードを配置しています(例: services/pharmacy/pharmacy
,services/clinic/conselor
のように分類)。
この構成により、あるドメインに関する実装がまとめて管理されるため、関連するコードを横断的に把握しやすくなっています。
新しいサービスを追加するときもディレクトリを追加して、k8s manifestを配置するのみで完結するようになっています。
例: サービスディレクトリ一覧
services
├── bff
│ ├── admin
│ ├── patient
│ └── pharmacy
├── clinic
│ ├── clinic
│ └── counselor
├── patient
│ ├── patient
│ └── phr
├── pharmacy
│ ├── counselor
│ ├── messenger
│ ├── pharmacy
│ └── streamer
└── platform
├── chatter
├── deliverer
├── gifter
├── notifier
├── nsips
└── payer
サービス間通信はgRPC、認証管理にFirebase Authenticationを活用
サービス間の呼び出しにはgRPCを採用(一部REST)し、相互の通信インターフェースを厳密に定義しています。
各サービス間のAPIはProtocol Buffersで定義された .proto
ファイルとして管理しており、それに基づいてサーバー・クライアントのコードを自動生成しています。
また通信自体もバイナリプロトコルで効率的に行われるため、サービス間通信のオーバーヘッドを抑えられているはずです。
認証・認可の基盤としてはFirebase Authenticationを利用しています。
権限情報をCustom Claims(カスタムクレーム)としてトークンに埋め込み、各サービスでそのクレームを参照して権限チェックを実施しています。
例えばマイクロサービスにはシステムロール・患者さんのアカウントにはユーザーロールのようなカスタムクレームを付与しています。
運用のメリット
現在の運用方式には、以下のようなメリットがあります。
- サービス拡張がしやすい
- gRPCによりインターフェース管理が容易
- サービスごとのドメインロジックの把握がしやすい
サービス拡張がしやすい
Kubernetes上でマイクロサービスを動かしているため、新しいサービスやバッチ処理を追加することが容易です。
モノレポのディレクトリ構成により新サービスの雛形を素早く用意でき、Argo Workflowsによる共通のデプロイパイプラインで自動的にデプロイまで完了できます。
gRPCによりインターフェース管理が容易
gRPCを利用していることでサービス間のインターフェース仕様が.protoスキーマとして明文化され、一元管理されています。
プロトコル定義が単一のソースとなるため変更箇所が追いやすく、チーム内でAPIの契約内容を共有しやすいです。
サービスごとのドメインロジックの把握がしやすい
各サービスがそれぞれ独立したドメインモデル(データスキーマやビジネスロジック)を持つように設計・実装されています。
例えば、オンライン処方箋予約に関するデータ構造やロジックはservices/pharmacy/counselor
に集約されており、そのサービスのコードとドキュメントを読めば当該ドメインの理解が深まります。
また新メンバーも特定のサービス単位で知識を習得しやすく、全体を一度に理解しなくても段階的にキャッチアップできるメリットがありそうです。
直面している課題
一方で、運用を続ける中で次第に以下のような課題も顕在化しています
- システムのドメイン把握むずかしすぎ問題
- NodeやIstioなどのバージョンアップ対応しんどすぎ問題
- 特定マイクロサービス負荷集中問題
システムのドメイン把握むずかしすぎ問題
マイクロサービスの数と機能が増えるにつれ、チームメンバー全員がシステム全体を把握することが難しくなっています。
サービスごとに異なる技術スタックやドメイン知識が要求される場合もあり、全容を理解しようとすると開発者の認知負荷が高まります。
現状チームでは歴の長いメンバー数名で分散してシステムレビューを担当することで安全性を担保していますが、初期開発メンバーがすでにチームを去っているという背景もあり管理が大変になってきているという現状があります。
NodeやIstioなどのバージョンアップ対応しんどすぎ問題
基盤技術のアップグレード対応も大きな課題です。
サービスメッシュとして導入しているIstioのメジャーアップデートでは、コントロールプレーンおよびサイドカーの更新をクラスタ全体で調整する必要があり、慎重に進めないと一部サービスで通信エラーが発生するリスクがあります。
これらのアップグレード作業中は一時的なサービス再起動が避けられず、ダウンタイムの発生が懸念されます。
現在はアップグレードのたびにユーザー数が少ない時間帯にメンテナンス時間を確保するなどして対応していますが、サービス数が増えるほど頻繁に何らかのアップデートが必要となっており運用コストがやや高い状況です。
特定マイクロサービス負荷集中問題
特定のサービスに負荷が集中してしまうということも問題となっています。
例えば私たちのサービスでは処方箋予約が大きな関心ごとであるため、負荷が集中する傾向にあります。
以前チームメンバーがマイクロサービス間の関係性を整理したところ、特定サービスに10個以上のサービスから参照されているという事例もありました。
終わりに
いかがだったでしょうか?
マイクロサービスを運用しているチームではあるあるな内容もいくつかあったのではないでしょうか
私たちのZenn Publicationではマイクロサービスなどを中心とした様々な技術情報を今後も共有していきます。
皆様の感想、意見共有お待ちしておりますので今後ともチェックしていただけますと幸いです。
今回はお読みいただいてありがとうございました。
Discussion