スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

SLI の計測のために Envoy や service mesh を選択しなかった理由

こんにちは。スタディサプリの小中高プロダクト開発部で主にコミュニケーション機能の開発をしている @snowfield702 です。 今回はスタディサプリで SLI の計測に Envoy と service mesh を使うのをやめて Datadog APM を利用することにしたという話をしたいと思います。

また、スタディサプリ小中高プロダクト開発部では、スタディサプリ ブランドのうちの複数のプロダクトを開発しております。 この記事では「スタディサプリ 小学/中学/高校/大学受験講座」を対象に話をしていきたいと思います。

Envoy を導入した経緯

スタディサプリでは主にマイクロサービスの SLI を計測する目的で Envoy が導入されていました。 この時期は service mesh の概念が出始めたばかりということもあり、いきなり service mesh を導入をせずに自分たちで Envoy container を sidecar に定義するという方法を取りました。

参考:SRE NEXT 2020 で「SLO Review」というタイトルで登壇しました #srenext

マイクロサービスが利用可能であるかのSLIを計測する場合、そのマイクロサービス自身が計測した metrics を使うことはできません。 何故なら、そのマイクロサービス自身が死んでしまうと metrics を計測することができなくなってしまうからです。 そのため、SLIを取りたいマイクロサービスに対してリクエストを投げている別のマイクロサービスが metrics の計測を行うようにしております。

リクエストを投げているマイクロサービスは複数あり、それぞれが言語、利用ライブラリ、実装、運用が違っております。 そのため、そのままでは全サービスで同じように SLI の計測を行えるようにすることができません。

そこで、全てのサービスで Envoy を経由してリクエストを投げるようにし、Envoy で計測できる metrics を SLI に利用することにしました。 Envoy の導入はサービスの言語、利用ライブラリ、実装、運用によらず同じように行うことができます。

Envoy を運用していくなかで出た課題

メンテナンスできなくなってしまった

Envoy の導入までは良かったのですが、多くのサービスでアップグレードが行われない状況になってしまいました。 これにはスタディサプリの開発体制が大きく関わってきます。

私たちは現時点で70サービスを運用する大きな組織になっていますが、それに対してSREチームはたった8人しかいません。 そのため、各サービスの Kubernetes manifest と Envoy の運用はサービスを運用しているアプリケーションエンジニアのチームが責任を負い、SREチームはそのフォローをするという体制になっております。

参考:Kubernetes導入で実現したい世界とその先にあるMicroservices

しかし、全てのチームに Kubernetes と Envoy に強い人がいるというわけではなく熟練度はチーム毎にばらつきがあります。 また、開発チームが増えていく中で、SREチームが手厚くフォローを継続することも現実的ではなくなってきていました。

2020年頃 Envoy 1.14.0 のリリースに伴って Envoy の設定ファイルに deprecated の変更が入ったこともあり、各開発チームが Envoy のアップグレードをできていないという状況になってしまいました。

また、通信するマイクロサービスを増やす際にはアプリケーションエンジニアが Envoy の設定ファイルに記述を追加する必要がありますが、これがエンジニアの認知負荷になり生産性を下げることになってしまっていました。

コンテナの起動順の制御にややこしさがある

ワーカーやジョブなど外部からのリクエストを受けずに自動で処理が始まる Pod の場合、Envoy コンテナが起動する前にアプリケーションコンテナの処理が始まると外部通信ができずにエラーになります。 また、同様の理由で Pod が終了する際には Envoy コンテナより先にアプリケーションコンテナを終了させる必要があります。

kubernetes ではコンテナの起動順を制御できる設定値がないため、以下の方法を使ってコンテナの起動順を制御しています。 1. コンテナを定義する順番は Envoy を先にし、アプリケーションコンテナを後にする 2. Envoy コンテナに postStart を定義して、Envoy コンテナが起動するまでアプリケーションコンテナの起動を待たせる。 3. Envoy コンテナに preStop を定義して、アプリケーションコンテナが終了してから Envoy コンテナが終了するようにする。

このような複雑な設定を入れていることが、アプリケーションエンジニアが k8s manifet を運用することの負担になってしまっていました。

Kustomize で Rollout の設定を上書きする際に、コンテナが複数あることで複雑になってしまう

スタディサプリでは k8s manifest を以下のように運用しております。

  • canary release するために k8s Rollout を使って Pod をデプロイする
  • k8s Rollout で設定する Memory や CPU を kustomize で環境毎に変更する

Kustomize の patchesStrategicMerge で k8s Rollout に定義されている Memory と CPU を上書きする際に、spec.template.spec.containers 配下の設定が全て上書きされてしまうという問題があり、patchesStrategicMerge ではなく patchesJson6902 を使っていました。

patchesJson6902 で設定を上書きする場合、上書き対象の設定値のパスを指定する必要があります。 spec.template.spec.containers の中は配列になっているため、以下のようにパスに配列番号を指定することになります。

# 0番目のコンテナの resources を上書きするという内容
- op: replace
  path: /spec/template/spec/containers/0/resources
  value:
    limits:
      memory: 825Mi
    requests:
      cpu: 100m
      memory: 825Mi

このように patchesJson6902 を使う場合は配列番号が出てくるため、Envoy コンテナとアプリケーションコンテナの2つがある場合はその順序を意識する必要があります。 しかし、上記のyamlだけではどちらのコンテナを上書きする設定なのかが読み取れず、エンジニアが混乱するということが起きていました。

なお、patchesStrategicMerge で Rollout をうまく上書きできないという問題は Kustomize v4.5.5 で解消しております。 当時 Envoy を廃止する方向性に決めた要因の1つだったのですが、現在この問題は起きなくなっております。

参考:https://argo-rollouts.readthedocs.io/en/latest/features/kustomize/

service mesh の導入検討

service mesh はサービス間通信を管理するためのインフラストラクチャ層の実装で、サービス間通信における信頼性と可観測性の観点でメリットがあります。 SLI を計測するのに必要な機能 (ここでは Envoy) を全てのマイクロサービスに対して一括で挿入することも可能な技術です。

service mesh を導入し Envoy の運用をSREチームでまとめて行えるようにすることで課題を解決しようとしました。

スタディサプリでは AWS EKS を基盤にしているため、service mesh には AWS App Mesh の利用を前提として検討しました。 しかし、検証を進めていく中でいくつかの課題があることに気がつきました。

kubernetes manifest の運用

App Mesh を導入するとアプリケーションエンジニアが Envoy を運用する必要がなくなるため、認知負荷や運用コストが減ることを期待していました。 しかし、App Mesh を使うには代わりに VirtualNode, VirtualRouter, VirtualService などの k8s リソース をアプリケーションエンジニアが運用する必要があるということが分かりました。

バージョンアップはインフラストラクチャ層で行われるため、アプリケーションエンジニアが意識することは減ります。 しかし、リクエスト先サービスとの繋ぎこみなどアプリケーションエンジニアがやることは残ってしまい、期待していたよりも認知負荷や運用コストが減らないということが分かりました。

AWS リソースを作れなかった際にアプリケーションエンジニアが気付けない

App Mesh では VirtualNode, VirtualRouter, VirtualService などの k8s リソース をデプロイすることで、それらに対応した AWS App Mesh のリソースが作られるようになっております。

k8s リソースと AWS App Mesh リソースのそれぞれでバリデーションがあるのですが、AWS App Mesh のバリデーションの方がより細かく設定されています。 そのため、k8s リソースのデプロイは成功したにも関わらず、AWS App Mesh のリソースが作られないということが起きてしまいます。

k8s リソースの内容をAWS App Mesh に反映する仕組みはインフラストラクチャ層にあるため、そこでエラーが出ていてもアプリケーションエンジニアが気づくのは難しいです。 そのため、アプリケーションエンジニアからするとデプロイは成功したのに設定が反映されないということが起きてしまいます。

App Mesh のリソース作成上限

スタディサプリでは github で pull request を作ると、自動的に検証用の環境 (k8s Namespace) が作られるようになっています。 そのため、App Mesh を導入した場合 pull request の数だけ AWS App Mesh のリソースが作られることになってしまいます。 AWS のリソース作成数には上限が決められており、上限に引っかかってしまった場合に pull request で環境を作れなくなってしまうという課題がありました。

App Mesh で取得できる metrics が不便である

App Mesh を導入する主目的は Datadog で SLI を計測できるようにすることですが、そのためには各HTTPリクエストの情報をリクエスト先サービス毎に分けて計測できる必要があります。 そのため、HTTPリクエスト情報の metrics にはリクエスト先サービス名のタグがついている必要があります。

App Mesh で取得できる metrics にはリクエスト先サービス名だけのタグはなく、1つのタグにさまざまな情報が含まれてしまいます。 また、datadog でタグの絞り込みにワイルドカードを使う場合、前方一致か後方一致のどちらかでしか絞り込みが行えません。 そのため、リクエスト先サービス名の前後両方に他の情報がついてしまっているタグでは上手く絞り込みができません。

この問題は App Mesh で自動挿入される Envoy の設定ファイルを変更することで、Datadog に送信する metrics のタグを分解するという方法で解決することができます。 しかし、Envoy の設定ファイルだけを差し替えるということはできず、差し替える場合は独自の Envoy image を作る必要があります。 自前で App Mesh で使われる Envoy image を作成し保守するとなると SRE チームの負荷になってしまうという問題が残ってしまいました。

これらを踏まえた判断

このまま導入をしてもうまく運用していくことは難しく、Envoy を運用保守していた時より改善はされないと判断をして App Mesh の導入を見送りました。

Datadog APM の登場

今回の件とは別のところで、アプリケーションの監視や調査に Datadog APM を利用したいという話が持ち上がり、ほとんどのマイクロサービスに Datadog APM が導入されることになりました。 そこで、SLI に使う metrics は Datadog APM で計測できるものを使うようにすることで Envoy の利用を廃止できるのではと考えました。

Datadog APM は Envoy と違って各アプリケーションにライブラリを導入する必要があるため、Envoy のように全てのチームで導入方法や運用を一般化することはできません。 しかし、ほぼ全てのチームで Datadog APM を利用するモチベーションが高く、一般化しなくても自然と導入が進み、問題なく運用されるようになりました。

また、Datadog APM のライブラリで計測できる metrisc は私たちが利用している全ての言語で同じなため、SLI の計算方法は一般化することができます。 そのため、全てのチームで同じように SLI の運用を行うことができています。 こうして各開発チームを Envoy の運用から解放し、それでいて SLI の計測を行えるという世界を実現することができました。

Datadog APM の課題

Datadog APM はアプリケーション毎にライブラリを導入して利用する必要があるため、公式のライブラリが未対応の新しい言語などで利用することは難しいです。 Datadog APM は多くの言語に対応しておりますが、今後の言語選定に制約をかけることになってしまいました。

参考:Add the Datadog Tracing Library

より技術が発展し私達にとって使い勝手の良い service mesh が登場したら、改めて service mesh の導入を検討していけたらと考えています。

得られた学び

service mesh の思想はとても良いものですが、技術的な成熟度という観点で私たちの開発体制には合っていないと考え、今回は採用には至りませんでした。 一方、Datadog APM を使う方法は言語や環境に依存するにも関わらず、私たちの開発体制にマッチし問題を解決してくれるという結果になりました。 技術選定をする際には単に技術が良いものであるかどうかだけではなく、導入する組織やチームの事情を理解した上でマッチしているかどうか考えることが大切です。

また、プロダクトを開発している @snowfield702 と SRE の @int128, @44smkn が一緒にプロジェクトを進めることで、プロダクト開発とSREそれぞれの面で感じている課題をお互いに共有しながらうまく解決に持っていくことができました。 課題解決には多方面からの視点が必要になってくるため、チーム横断でプロジェクトを進めることができるというのはとても重要になります。