envoyのrequest_mirror_policiesを用いて本番環境のリクエストでテストする構成
TL;DR
リリース前のアプリケーションが本番環境のリクエストをミラーリングして受け取り、デプロイに対する信頼性を高める構成を作りたい!
それをenvoyのrequest_mirror_policiesという設定で実現を試みている話になります。
はじめに
はじめまして。システム開発部の笹島です。
私は開発チームの中でも、キュビナで生徒が解答するための教材を登録・提供するシステムを開発するチーム(CSD[Contents Solution Development] チーム)に属しています。
チームについては弊社EMが執筆した記事がありますので興味があればご参照ください。(以下の記事CUEBと呼ばれているチームが教材提供の機能も管轄となり、CSDと名称変更しました。)
今回は、キュビナに教材の情報を提供するマイクロサービスのAPIにリクエストをミラーリングする構成を取り入れようとしている内容について執筆いたしました。また、ここでご紹介する構成は現在絶賛構築中であり、本番環境での実現には至っていないことをご承知おきください(近々導入される見込みです。)
この記事はこんな方におすすめ
- マイクロサービスの信頼性担保の取り組みの例を知りたい方
- envoyのrequest_mirror_policiesについて概要を知りたい方
リクエストのミラーリングを検討するに至った経緯
教材の情報を提供するための機能は今までキュビナのバックエンド内に内包されており、いわゆるモジュラモノリスの状態でした。
しかし教材情報の提供には様々な仕組みが必要となっており、この機能に引きずられて他の機能にも制約を強いる必要がありました。(詳細は本題と関係が薄いので割愛します。特定のライブラリに依存しておりその運用が大変だったくらいの認識を持っていただければと思います。)
そこで教材情報を提供する機能を、content-apiというマイクロサービスとして切り出す決定をしました。この構成自体はすでに完成されており、無事にマイクロサービスとしてリリースされている状態になっています。
しかし、切り出したマイクロサービスのリリースフローは確立されておらず、このままではいわゆる分散モノリスとなってしまう恐れがありました。
今まではキュビナ全体でステージング検証・負荷試験などのQA業務を設けてリリース判断に至るというフローでしたが、content-apiをマイクロサービスとして切り離した以上、content-api単体でのデプロイフローを確立する必要があります。
切り分けたサービス同士でデプロイをまとめて行わなければならない状態は、分散モノリスと呼ばれるアンチパターンとされています。
そこでデプロイ可能性について定義を進め、その中で信頼性を担保する施策としてリクエストのミラーリングを実現しようと考えました。
そもそもリクエストのミラーリングとは?
本番環境で受けたリクエストを別のホストへ転送する仕組みです。
転送元はミラーリングしたリクエストのレスポンスを待たないので、仮にミラーリング先でレイテンシが悪化しているなど異常が発生しても影響を受けずにサービスを継続できます。
同様の仕組みではgoreplayが有名だと思います。
こちらは転送数をrps単位で限定できたり、独自のmiddlewareを組み込む仕組みがありますので、envoyのrequest_mirror_policiesよりも柔軟な設定ができます。
リクエストのミラーリングによる恩恵は、本番稼働しているサービスに対するAPIのリクエストをリリース前の状態のサービスへ転送することで、次回のリリースに対する信頼性を高めることができるという点にあります。
自動テストで機能面のチェックはある程度担保できますが、CI上では外部接続などがテストダブルであることが多いと思います。自動テストは通っていても、本番リリースするとデータベースに想定と異なるデータが登録されていて、本番ではエラーになってしまったという経験はないでしょうか。
また、OSSのバージョンアップの予期せぬ影響で、本番稼働させると突如としてCPUやメモリが高騰してしまうなどのトラブルは経験されたでしょうか。悲しいことに私はどちらとも経験ありです。
そういった「本番ならでは」の事象を事前に検証できる環境というのは、エンジニアの「デプロイに対する安心感」を上げてくれるものになります。
私が以前携わったプロダクトでもgoreplayを利用しており、確かな効果を実感しておりました。
request_mirror_policiesとは?
公式ドキュメントはこちらになります。
envoyにミラーリング用のcluster設定を行い、 route
に request_mirror_policies
の設定を追加するだけで簡単に設定可能です。
先述のgoreplayのように細かな設定はできません(と私は認識しています)が、簡易にやりたいことを実現できます。
だいぶ省略していますが、現状のcontent-apiでは以下のように request_mirror_policies
を設定しています。
routes:
route:
cluster: content-api
request_mirror_policies:
- cluster: content-api-mirroring
runtime_fraction:
default_value:
numerator: 5
denominator: HUNDRED
これで本番環境の5%のリクエストがミラーリング用のアプリケーションへ転送されるようになりました。
envoy + request_mirror_policies 設定の理由
以下の理由から、content-apiは request_mirror_policies
を採用することとしました。
- 元々マイクロサービスへ切り出す上で gRPC + envoy の構成としていた
- キュビナに表示すべき問題情報をレスポンスするためのAPIであり、データの読み込みに特化している(書き込みを考慮する必要がない)
- 上述の通り
request_mirror_policies
の設定はかなり簡易で実現可能
元よりenvoy等を利用せずにリクエストを受ける + gRPCではなくRestAPIとかであれば、goreplayの利用を検討しておりましたが、環境的にこの方法が一番低コストだと判断しました。
設定の追加に対するpros/cons
prosは上述の通り、ミラーリング用アプリケーションの監視を行うことで、エンジニアがデプロイに対して自信を持つことができます。デプロイに対する心理的ハードルは低ければ低いほどよいと考えているので、このメリットはかなり魅力的です。
一方でconsとしては以下が挙げられます。
- ミラーリング用のアプリケーションのコスト・運用負荷
- ミラーリング用のアプリケーションが本番と同じDBをreadしているため、5%転送すると5%データベースへの負荷も増える
- %単位での転送なので、高負荷時ほど影響が大きい
テスト用のアプリケーションがわずかでも本番に影響を与える構成は好ましくないと考える方も多いと思います。特に参照するデータベースについてはリードレプリカを活用する方法などの手段もありますので、今後も状況に応じて設定は見直していく方針です。
また、今回は副作用のないAPIであるため全てのエンドポイントへのリクエストを転送していますが、INSERTなどの更新を本番DBへ2重に行ってしまうと問題があるため、その辺りを考慮する必要があるAPIだとエンドポイントの制限などが必要になってきます。
終わりに
簡単ですが、弊社で取り組んでいる信頼性担保の一つとして取り入れたリクエストのミラーリングについて記述いたしました。
冒頭でお伝えした通り、その他の開発が並行しており現状ではまだ本番導入には至っておりません。
これから運用するにあたって細かい問題点が出てくるかもしれませんが、都度改善してデプロイ可能性に寄与できる形を実現したいと思います。
Discussion