こんにちは、開発基盤の Taiki です。今回は、マイクロサービスで必須のコンポーネントとなりつつあるサービスメッシュについて、クックパッドで構築・運用して得られた知見についてご紹介できればと思います。
サービスメッシュそのものについては以下の記事や発表、チュートリアルで全体感をつかめると思います:
- https://speakerdeck.com/taiki45/observability-service-mesh-and-microservices
- https://buoyant.io/2017/04/25/whats-a-service-mesh-and-why-do-i-need-one/
- https://blog.envoyproxy.io/service-mesh-data-plane-vs-control-plane-2774e720f7fc
- https://istio.io/docs/setup/kubernetes/quick-start.html
- https://www.youtube.com/playlist?list=PLj6h78yzYM2P-3-xqvmWaZbbI1sW-ulZb
目的
クックパッドでは主に障害対応やキャパシティプランニング、Fault Isolation に関わる設定値の管理といった、運用面での課題を解決すべくサービスメッシュを導入しました。具体的には:
1つ目については、どのサービスとどのサービスが通信していて、あるサービスの障害がどこに伝播するのか、ということを規模の拡大とともに把握しづらくなってるという問題がありました。どことどこが繋がっているかの情報を一元管理することでこの問題は解決できるはず、と考えました。
2つ目については (1) をさらに掘ったもので、あるサービスと別のサービスの通信の状況がわからないという課題でした。例えば RPS やレスポンスタイム、成功・失敗ステータスの数、タイムアウトやサーキットブレーカーの発動状況などなど。あるバックエンドサービスを2つ以上のサービスが参照しているケースでは、アクセス元のサービス毎のメトリクスではないため、バックエンドサービス側のプロキシーやロードバランサーのメトリクスでは解像度が不十分でした。
3つ目については、「Fault Isolation がうまく設定できていない」という課題でした。当時はそれぞれのアプリケーションでライブラリを利用して、タイムアウト・リトライ・サーキットブレーカーの設定を行っていましたが、どんな設定になっているかはアプリケーションコードを別個に見る必要があり、一覧性がなく状況把握や改善がしづらい状況でした。また Fault Isolation に関する設定は継続的に改善していくものなので、テスト可能であったほうが良く、そのような基盤を求めていました。
さらにもっと進んだ課題解決として、gRPC インフラの構築、分散トレーシング周りの処理の委譲、トラフィックコントロールによるデプロイ手法の多様化、認証認可ゲートウェイなどのような機能もスコープに入れて構築しています。このあたりについては後述します。
現状
クックパッドでのサービスメッシュは data-plane として Envoy を採用し、control-plane は自作、という構成を選択をしました。すでにサービスメッシュとして実装されている Istio を導入することも当初は検討したのですが、クックパッドではほぼ全てのアプリケーションが AWS の ECS というコンテナ管理サービスの上で動作している関係上 Kubernetes との連携メリットが得られないことと、当初実現したいことと Istio のソフトウェア自体の複雑さを考慮して、小さく始められる自作 control-plane という道を選びました。
今回実装したサービスメッシュの control-plane 部分はいくつかのコンポーネントによって構成されています。各コンポーネントの役割と動作の流れを説明します:
- サービスメッシュの設定を中央管理するリポジトリ
- kumonos*3という gem を用いて上記の設定ファイルから Envoy xDS API*4用のレスポンス JSON を生成
- 生成したレスポンス JSON を Amazon S3 上に配置して Envoy から xDS API として利用する
中央のリポジトリで設定を管理している理由は、
- 変更履歴を理由付きで管理して後から追えるようにしておきたい
- 設定の変更を SRE 等の組織横断的なチームもレビューできるようにしておきたい
という2点です。
ロードバランシングについては、基本的には Internal ELB に任せるという方式で設計したのですが、gRPC アプリケーション用のインフラ整備も途中から要件に入った*5ので、SDS (Service Discovery Service) API を用意して client-side load balancing できるようにしています*6。app コンテナに対するヘルスチェックを行い SDS API に接続先情報を登録する side-car コンテナを ECS タスク内にデプロイしています。
Image may be NSFW.
Clik here to view.
メトリクス周りは次のように構成しています:
- メトリクスは全て Prometheus に保存する
- dog_statsd sink*7を使用してタグ付きメトリクスを ECS コンテナホスト(EC2 インスタンス)上の statsd_exporter*8に送信
- 固定タグ設定*9を利用してノード区別のためにアプリケーション名をタグに含めています
- Prometheus からは EC2 SD*10を利用して statsd_exporter のメトリクスを pull
- ポート管理のために exporter_proxy*11を間に置いています
- Grafana、Vizceral*12で可視化
ECS, Docker を利用せずに EC2 インスタンス上で直接アプリケーションプロセス動かしている場合は、Envoy プロセスも直接インスタンス内のデーモンとして動かしていますが、構成としてはほぼ同じです。直接 Prometheus から Envoy に対して pull を設定していないのは理由があり、まだ Envoy の Prometheus 互換エンドポイントからは histogram メトリクスを引き出せないからです*13。これは今後改善される予定なのでその時は stasd_exporter を廃止する予定です。
Image may be NSFW.
Clik here to view.
Grafana 上ではサービス毎に各 upstream の RPS やタイムアウト発生等の状況が見れるダッシュボードと Envoy 全体のダッシュボードを用意しています。サービス×サービスの粒度のダッシュボードも用意する予定です。
サービス毎のダッシュボード
Image may be NSFW.
Clik here to view.
Upstream 障害時のサーキットブレーカー関連のメトリクス
Image may be NSFW.
Clik here to view.
Envoy 全体のダッシュボード
Image may be NSFW.
Clik here to view.
サービス構成は Netflix が開発している Vizceral を利用して可視化しています。実装には promviz*14と promviz-front*15を fork して開発したのもの*16を利用しています。まだ一部のサービスにのみ導入しているので現在表示されているノード数は少なめですが、次のようなダッシュボードが見れるようにしています:
リージョン毎のサービス構成図と RPS、エラーレート
Image may be NSFW.
Clik here to view.
特定のサービスの downstream/upstream
Image may be NSFW.
Clik here to view.
またサービスメッシュのサブシステムとして、開発者の手元から staging 環境の gRPC サーバーアプリケーションにアクセスするためのゲートウェイをデプロイしています*17。これは hako-console という社内のアプリケーションを管理しているソフトウェア*18と SDS API と Envoy を組み合わせて構築しています。
- Gateway app (Envoy) が gateway controller に xDS API リクエストを送る
- Gateway controller は hako-console から staging 環境かつ gRPC アプリケーションの一覧を取得して、それを基に Route Discovery Service/Cluster Discovery Service API レスポンスを返す
- Gateway app はレスポンスを基に SDS API から実際の接続先を取得する
- 開発者の手元からは AWS ELB の Network Load Balancer を参照し Gateway app がルーティングを行う
Image may be NSFW.
Clik here to view.
効果
サービスメッシュの導入で最も顕著だったのが一時的な障害の影響を抑えることができた点です。トラフィックの多いサービス同士の連携部分が複数あり、今までそれらでは1時間に5,6件ほどのネットワーク起因の trivial なエラーが恒常的に発生していた*19のですが、それらがサービスメッシュによる適切なリトライ設定によって1週間に1件出るか出ないか程度に下がりました。
モニタリングの面では様々なメトリクスが見れるようになってきましたが、一部のサービスのみに導入していることと導入から日が浅く本格的な活用には至ってないので今後の活用を期待しています。管理の面では、サービス同士の繋がりが可視化されると大変わかりやすくなったので、全サービスへ導入することで見落としや考慮漏れを防いでいきたいと考えています。
今後の展開
v2 API への移行、Istio への移行
設計当初の状況と、S3 を配信バックエンドに使いたいという要求から xDS API は v1 を使用してきましたが、v1 API は非推奨になっているのでこれを v2 へ移行する予定です。同時に control-plane を Istio へ移行することも検討しています。また、仮に control-plane を自作するとしたら、今のところの考えでは go-control-plane*20を使用して LSD/RDS/CDS/EDS API*21を作成することになると思います。
Reverse proxy の置き換え
今までクックパッドでは reverse proxy として NGINX を活用してきましたが、内部実装の知識の差や gRPC 対応、取得メトリクスの豊富さを考慮して reverse proxy や edge proxy を NGINX から Envoy に置き換えることを検討しています。
トラフィックコントロール
Client-side load balancing への置き換えと reverse proxy の置き換えを達成すると、Envoy を操作してトラフィックを自在に変更できるようになるので、canary deployment や traffic shifting、request shadowing を実現できるようになる予定です。
Fault injection
適切に管理された環境でディレイや失敗を意図的に注入して、実際のサービス群が適切に連携するかテストする仕組みです。Envoy に各種機能があります*22。
分散トレーシングを data-plane 層で行う
クックパッドでは分散トレーシングシステムとして AWS X-Ray を利用しています*23。現在はライブラリとして分散トレーシングの機能を実装していますが、これを data-plane に移してネットワーク層で実現することを予定しています。
認証認可ゲートウェイ
これはユーザーリクエストを受ける最もフロントのサーバーでのみ認証認可処理を行い、以降のサーバーではその結果を引き回し利用するものです。以前はライブラリとして不完全に実装していましたが、これも data-plane に移すことで out of process モデルの利点を享受することができます。
終わりに
以上、クックパッドでのサービスメッシュの現状と今後について紹介しました。すでに多くの機能を手軽に実現できるようになっており、今後さらにサービスメッシュの層でできることが増えていくので、マイクロサービス各位に大変おすすめです。
*1:https://blog.twitter.com/engineering/en_us/a/2013/observability-at-twitter.html
*2:https://medium.com/@copyconstruct/monitoring-and-observability-8417d1952e1c
*3:https://github.com/taiki45/kumonos
*4:https://github.com/envoyproxy/data-plane-api/blob/5ea10b04a950260e1af0572aa244846b6599a38f/API_OVERVIEW.md
*5:gRPC アプリケーションはすでに本仕組みを利用して本番環境で稼働しています
*6:単純に Internal ELB (NLB or TCP mode CLB) を使った server-side load balancing ではバランシングの偏りからパフォーマンス面で不利であり、さらに取得できるメトリクスの面でも十分ではないと判断しました
*7:https://www.envoyproxy.io/docs/envoy/v1.6.0/api-v2/config/metrics/v2/stats.proto#config-metrics-v2-dogstatsdsink最初は自前拡張として実装したのですが後ほどパッチを送りました: https://github.com/envoyproxy/envoy/pull/2158
*8:https://github.com/prometheus/statsd_exporter
*9:https://www.envoyproxy.io/docs/envoy/v1.6.0/api-v2/config/metrics/v2/stats.proto#config-metrics-v2-statsconfigこちらも実装しました: https://github.com/envoyproxy/envoy/pull/2357
*10:https://prometheus.io/docs/prometheus/latest/configuration/configuration/
*11:https://github.com/rrreeeyyy/exporter_proxy
*12:https://medium.com/netflix-techblog/vizceral-open-source-acc0c32113fe
*13:https://github.com/envoyproxy/envoy/issues/1947
*14:https://github.com/nghialv/promviz
*15:https://github.com/mjhd-devlion/promviz-front
*16:NGINX で配信したりクックパッド内のサービス構成に合わせる都合上
*17:client-side load balancing を用いたアクセスを想定している関係で接続先の解決を行うコンポーネントが必要
*18:http://techlife.cookpad.com/entry/2018/04/02/140846
*19:リトライを設定している箇所もあったのだが
*20:https://github.com/envoyproxy/go-control-plane
*21:https://github.com/envoyproxy/data-plane-api/blob/5ea10b04a950260e1af0572aa244846b6599a38f/API_OVERVIEW.md#apis
*22:https://www.envoyproxy.io/docs/envoy/v1.6.0/configuration/http_filters/fault_filter.html