- バックエンド / リーダー候補
- PdM
- Webエンジニア(シニア)
- 他19件の職種
- 開発
- ビジネス
マイクロサービス共通ライブラリで “Governance through code” を実現する
Photo by Alexander Mils on Unsplash
Wantedly でバックエンドのテックリード的なやつをやってる @izumin5210 です。たまにはバックエンドの話をします。
TL;DR
- 共通ライブラリを導入することで
- 「すべてのマイクロサービスが備えるべき機能を間違いなく提供できる」だけでなく
- 「アーキテクチャ全体を継続的に・小さく改善していく足がかり」にもなった
- 各言語に実装した共通ライブラリのコア機能は「すべてのサービスが」「すべての入出力にフックし」「入力のコンテキストを出力まで伝搬する」であった
マイクロサービス共通ライブラリ"servicex"
Wantedly ではマイクロサービスアーキテクチャを採用しています。Wantedly における Go 導入にまつわる技術背景 などでも言及のあるとおり、Ruby・Go・Python・Node.js(・Rust)と複数の言語が採用されています。そして、"servicex" という共通ライブラリがこれらの言語それぞれに用意されています。servicex の各言語実装はすべて同じ目的を達成するためのもので、その Why / What は以下のように定義されています。
## Why
新しくサービスを作る際に考えることを減らし,本来実現したいドメインに集中できるようにしたい.
## What
Wantedly のすべてのマイクロサービスが備えるべき機能を扱いやすい・統一的な形で提供する.
(wantedly/servicex の README より抜粋)
本記事ではこのマイクロサービス共通ライブラリ "servicex" について、なぜ必要だったのか、そしてそれがあることでどんな良いことがあったかなどを紹介します。
何をしてくれているか
機能ベースでは、大きく2種類に分けられます。
まず、ログやメトリクスなど Observability 関連。マイクロサービスアーキテクチャを採用するとリクエストの流れも複雑になりますし、障害発生時の原因特定などの難易度も上昇します。よって、それぞれのコンポーネントで Metrics, Logs, Traces などを収集しておくことは重要なのです。Wantedly のマイクロサービスでは、サービス開発者が意識せずとも servicex さえ導入しておけば、これらの機能が有効になります。
また、だいたいのマイクロサービスで必要になる便利機能群 というのも実装されています。いくつか挙げてみましょう。
- サービス間通信のデフォルトタイムアウト・認証などの設定
- マイクロサービス間通信で、環境(ローカル, 開発, QA, 本番, ..)を見て URL をいい感じにしてくれる
- BigQuery にイベントを記録できる機能
- RDB に依存しない A/B テスト機構
- ...
ほかにもいくつか機能がありますが、ここでは省略します。
なぜ必要か
「(プロダクト開発者としては)設定を忘れがちだが、忘れたらかなり困る」ものを忘れないようにするためです。特にログやメトリクスはわかりやすいでしょう。普段からメトリクスが取れてないと、問題発生時に気づくのが遅れるかもしれません。また、アクセスログなどは問題に調査・対応するときの最後の砦となることもあります。こういうものはだいたい問題が起きたタイミングで初めて需要が生まれます。しかし、その時点でログが取れてないことに気づいても後の祭りです。
それに、ログやメトリクスの送り先にもちゃんとルールが必要です。マイクロサービスによってログテーブルの命名規則やデータフォーマットが違っていたらちょっとしんどいですよね。
また、一般的なソフトウェア実装プラクティスの話でもあります。同じようなコードを複数のリポジトリで何度も書くのは不毛ですし、間違いも起きるかもしれません。「読み出す環境変数の名前を間違えて正しくメトリクスが取れなかった」みたいなことが起きる可能性もあります。自分は3回くらいやりました。
…と、このように、プロダクト自体の開発以外にも「忘れたら困る」「何回も書きたくない」「間違えたら困る」みたいなコードは意外とたくさん存在します。このような「新しくサービスを作る際に考えないといけないこと」を減らし、本来実現したいドメインに集中できるようにするために、Wantedly のすべてのマイクロサービスが備えるべき機能を扱いやすい・統一的な形で提供するのが "servicex" という共通ライブラリの責務です。
この Why と What を踏まえて、servicex リポジトリの description には "Microservices governance through code." と書いてあります。この文言は書籍 Building Microservices(日本語版: マイクロサービスアーキテクチャ)の2.6章のタイトル "Governance Through Code"(コードを介したガバナンス)から取られています。
コアとなる機能:入出力フックとコンテキスト伝搬
ここまでで「マイクロサービス共通ライブラリ "servicex" とは何のために・何を提供するのか」を紹介してきました。このライブラリを実際に2018年ごろに作り始めてからいままでのあいだに、起きたことを踏まえて、「共通ライブラリがあることで何が起きるか」というのを掘り下げていきます。
さて、前の説で servicex が提供するものに "Observability 関連" を挙げました。具体的には「Metrics, Logs, Traces を収集するための機能」です。これを達成するためには何が必要になるでしょうか。自分は以下の3項目が重要なのではないかと考えています。
- Observability を網羅的に高めるためには、すべての入出力にフックできる必要がある
- 入力と出力を正しく紐付けるためには、入力のコンテキストを出力部まで伝搬している必要がある
- サービスをまたいでログ・メトリクス・トレーシングを関連付けるには、すべてのサービスが上記の性質を満たしている必要がある
入出力 という表現をしましたが、これはサービスが「どんなリクエストを受けて」「どうやってリクエストを飛ばすか」ということです。Wantedly ではサービス間通信に JSON over HTTP だけでなく gRPC を推進しているほか、非同期なやりとりには Cloud Pub/Sub を利用しています。また、当然 RDB などのデータストアを持つサービスも多数存在します。ほかにも Redis や Elasticsearch など、いくつかのミドルウェアを利用しています。これらのサービス間通信の入出力やミドルウェアへの出力にフックし、観察することでリクエスト全体が追跡可能になったり、障害発生時にどこで異常が起きているのかを発見しやすくなるのです。
この「すべてのサービスが」「すべての入出力にフックし」「入力のコンテキストを出力まで伝搬できている」という特性があることで、Wantedly ではサービスの Observability の向上以外にもいくつかの恩恵を受けることができました。
- 事例: Network Observability 向上のための PoC
- (社員向け情報: wantedly/infrastructure#4622)
- サービス間通信のネットワークエラーの発生割合を可視化するために、servicex にデータ収集機構を実装した
- サービスメッシュの導入を念頭に置きつつ、とりあえず PoC として小さく始めるために共通ライブラリを利用した例
- 事例: HTTP Header / gRPC Metadata 伝搬
- (社員向け情報: wantedly/dx#89)
- Feature Flag や開発時の特殊な Request Routing のために、条件を満たす HTTP header / gRPC metadata を後続のサービスに伝搬させる必要があった
- 関連: Istioを使って「Fast, Dependency-Agnostic, Isolated」な開発体験を実現した話
理想的には Sidecar conatiner や Service mesh などアプリ外のレイヤで解決できる/すべき問題も含まれているかもしれませんが、「ぱっと導入して価値検証できる・高速なフィードバックサイクルを回せる」という点において共通ライブラリに優位性がありました。また、アプリケーション中でこれらのコンテキストが取り出せることで、 adhoc なイベントログ収集などでも活用できます。
言語・フレームワークサポート
「すべてのサービスが」「すべての入出力にフックし」「入力のコンテキストを出力まで伝搬できている」を満たすために、Wantedly のバックエンドで利用している Ruby・Go・Python・Node.js に servicex の実装が用意されています。一方で、すべての機能をすべての言語でサポートしていると大変なので、現状メジャーなユースケースのみのサポートとしています。
- フル機能(あらゆる通信方式で入力にも出力にもなれる)のは Go と Ruby のみ
- Python は誰にも依存しない「関数」的なサービスが多いので、 Server として必要な機能のみ存在
- Node.js も基本的には Python と同じだが、SSR server や GraphQL BFF があるため Client になる必要もある
Wantedly では Ruby と Go が最もよく利用されるため、この2つに関してはフル機能の servicex がサポートされています。対して Python や Node.js は限定されたユースケースでのみの利用となっているため、それをカバーする範囲のみのサポートとなっています。
上記の表で ❌ になってたりカバーされてない領域に手を出す場合は、servicex も一緒に作りメンテしていく覚悟が必要になります。ハブになるサービスでコンテキスト伝搬が切れてしまうと、それより後ろのすべてのサービスすべてに影響がでるため、特定のチームだけの問題ではありません。
メンテナンス体制
servicex 全体のデザインに関してオーナーシップを持つ人間が3人と、各言語実装ごとにそれぞれ2〜4人の CODEOWNERS
が設定されています。言語実装のオーナーは各プロダクトドメインから1人ずつ選出されており、コードレビューは GitHub が Round Robin で割り当ててくれるやつにより持ち回りで担当しています。
まとめ
- servicex とは
- Why: 新しくサービスを作る際に考えることを減らし,本来実現したいドメインに集中できるように
- What: Wantedly のすべてのマイクロサービスが備えるべき機能を扱いやすい・統一的な形で提供する
- 具体的に : Observability まわりのセットアップや、あらゆるサービスで共通で使う機能が提供される
- servicex のコア機能
- 「すべてのサービスが」「すべての入出力にフックし」「入力のコンテキストを出力まで伝搬できている」状態を達成する
- これによってインフラレイヤに機能を入れる前の PoC を小さく始めたり、コンテキスト伝搬を活用した便利機能が実現できる
それぞれの言語実装で簡単に使えるようにする工夫などもあって実装の話も楽しいんですが、今回は割愛しました。また別の記事で紹介できればと思います。