freeeの開発情報ポータルサイト

envoyにおける分散トレーシングの導入でオブザーバビリティを向上させた話

はじめまして、2023/6からfreeeのEnabling SREチームに所属している大木竜勝(ryu)です。 この記事はfreee Developers Advent Calendar 2023 - Adventar 3日目です。 この記事ではenvoyの分散トレーシングを用いてオブザーバビリティの向上を実現した取り組みについての記事になります。

背景

freeeではAmazon Web Services (AWS) Elastic Kuerbentes Service (EKS) クラスタ上にアプリケーションが載っておりその構成としてはenvoyが前段に存在し、後段に各アプリケーションPodが存在する構成となっています。

freeeにおける基本的なPod構成

上記の構成においてクラスタの単位はfreee会計やfreee人事労務といったプロダクト毎にクラスタが分かれている構成となっています。 そのため、他プロダクトとの連携でアプリケーションPodがクラスタの別れたPodにリクエストをおくるとApp Pod => LB => envoy => App Podという経路でリクエストが届くようになっています。

リクエストが複数クラスタを通る際の概要図

また、freeeではDatadogにトレース情報を送信することでサービス間の関係性を把握したり処理のボトルネックを把握していますがこれまでのfreeeではアプリケーションPodのみがトレース情報を送信しておりenvoyはトレース情報を出していませんでした。

アプリケーションPodのTrace情報をDatadogに送信する際の概要図

そのためPodにリクエストが届かない際や届くのに時間がかかっている際にLBとenvoyどちらが原因なのか切り分けが非常に難しいという問題がありました。

リクエストがPodに到達する前にタイムアウト等で失敗した際に調査が難しいことの図

そこでenvoyからもトレース情報を送るようにすることでオブザーバビリティを向上させる必要がありました。

どのようにトレーシングを導入したか

設定は非常に簡単でenvoyのConfigurationファイルに以下のようなfilterとclusterの設定を追加するだけでした。

        - filters:
          - name: envoy.filters.network.http_connection_manager
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
              generate_request_id: true
              request_id_extension:
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.request_id.uuid.v3.UuidRequestIdConfig
                  use_request_id_for_trace_sampling: false
              tracing:
                provider:
                  name: envoy.tracers.datadog
                  typed_config:
                    "@type": type.googleapis.com/envoy.config.trace.v3.DatadogConfig
                    collector_cluster: datadog_agent
                    service_name: xxx-app-envoy
~~~
      - name: datadog_agent
        connect_timeout: 1s
        type: STRICT_DNS
        lb_policy: ROUND_ROBIN
        load_assignment:
          cluster_name: datadog_agent
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: datadog.datadog.svc.cluster.local
                    port_value: 8126

このような設定をデプロイすることにより簡単にenvoyのトレース情報を見ることが出来るようになりました。

envoyのトレース情報を出すことに成功

気をつけた点

今回の設定を行う上で気をつけた点としては以下の2点です

  1. トレース情報を出すことによってenvoyのパフォーマンスの劣化が起きないか
  2. kubernetes環境でenvoyからdatadog agentにトレース情報を送る際の設定方法について

トレース情報を出すことによってenvoyのパフォーマンスの劣化が起きないか

今回envoyの分散トレーシングを導入する上でパフォーマンスに悪化がないかは特に気になるポイントでした。 そのため以下のようなことに注意しながら導入を進めていくことで大きな問題なくデプロイする事ができました

  1. staging環境でメトリクスを確認しパフォーマンスに影響がないことを確認する
    1. envoyのレスポンスタイムに変化がないか
    2. CPUやメモリの使用率に変化がないか
  2. 比較的小さめのプロダクトから導入する
  3. 影響の少ない時間にデプロイを行うようにし、問題があった場合もすぐに切り戻せるようにする

kubernetes環境でenvoyからdatadog agentにトレース情報を送る際の設定方法について

envoyのConfigurationファイルはDatadogの公式Docを参考に設定を進めていましたが、datadog agentのアドレスとして何を設定すべきかについて悩んでいました。
具体的には以下の2つの選択肢があり得ると考えていました。

  1. Datadog Agentがnodeに開けているportであるhostportにリクエストを送るようにする
  2. Datadog AgentのClusterIP Serviceに対してリクエストを送るようにする

当初、Datadog Agentがhostportを用意していること等から1番の方が良いのかと考えておりそのためにenvoyが存在しているNodeのhostipをenvoyのConfigurationファイルに設定する必要がありました。 しかしながら、freeeではenvoyのConfigurationファイルはConfigMapで管理しておりDownward APIによるPodへの環境変数の導入では実現できそうにありませんでした。 代替案を探すためにも改めてkubernetesの古いissueなども見てみたのですがConfigMapにはDownward APIから値を受け取って値を代入することは出来ず、init containerを使ってenvoyのConfigurationファイルを修正する必要がありそうでした。

init containerを使う手法は個人的にはあまりシンプルではなく設定がConfigMapで完結しなくなってしまうので出来れば避けたいと考えました。 そこで、Datadogのサポートに1番と2番の方法どちらが良いのかまた1番の方法のほうが良い場合どのように設定するのがベストプラクティスなのかを問い合わました。 数日のうちに返ってきた回答としては2番の手法でClusterIP Serviceを利用すべきという回答を頂きinit containerを作成する手戻りなどなくスムーズにトレース情報を取得する事ができました。

まとめ

SREチーム内課題に感じていたenvoy周りのオブザーバビリティの向上を実現した方法とその際に気をつけた点について簡単にですが紹介しました。この設定は現状いくつかのプロダクトに導入されている段階ですべてのプロダクトにはまだ入っていないので今後スピード感を持って横展開していきオブザーバビリティの高い状況を作っていきたいと考えています!

明日のfreee Developers Advent Calender 2023は、freee 会計のエンジニア jaxx さんの記事になります!

最後に私事ですが私がfreeeに転職して来てからちょうど半年ほどで分からないことも多い中、Enabling SREのメンバー・他SREチームの方々・プロダクトチームの皆様には多くのサポートを頂くことで実現できたかなと思っています。本当にありがとうございました。