分散トレース + New Relic でマイクロサービスの稼働状況可視化をはじめました

この記事は New Relic Advent Calendar 2024 の 19 日目の記事です。

こんにちは。株式会社フィックスポイントの KI です。

この記事では、私が所属しているプロダクトデベロップメント部での New Relic の利用、特に分散トレースについて紹介します。
前半では分散トレースを試験環境に実際に導入し、その結果として監視体験がどのように変化したかを紹介します。
後半では、 Go 言語でのメッセージキューを経由するようなトレースの実装においての工夫や、 OpenTelemetry 導入時のおすすめの設定を紹介します。

はじめに

導入した製品のアーキテクチャ概要と、運用の課題

2024 年 11 月より、弊社製品 Kompira AlertHub の一部に分散トレースを導入しました。
Kompira AlertHub はマイクロサービスアーキテクチャを採用しており、複数のサービスによって構成されています。

Kompira AlertHub は非常に簡略化すると、以下のようなサービスです。

  1. 常にサービスの利用者からメッセージを受けつける(複数の監視ツールから上がってくるアラートなど)
  2. メッセージを Kompira AlertHub を構成する複数のサービスで処理する
  3. メッセージを処理した結果、特定の条件を満たす場合 Kompira AlertHub に登録されている通知先に Webhook や Email で通知をする

アーキテクチャ図 A

サービスは上記の「アーキテクチャ図 A」のように実装されています。
※実際にはもう少し複雑ですが、簡略化しています

この図で障害ポイントを考えたとき、下記のように障害を起こす可能性のあるポイントが複数あります。

  • メッセージを受けつける Pod A の障害
  • Pod A と Pod B をつなぐ Message Queue の障害
  • 通知を実行する Pod B の障害

現在、外形監視によって「Pod A→ Message Queue → Pod B の処理が正常に終了しているか」といった監視をしています。
ですが、障害状態であることが判明した後にも、具体的にどの箇所が要因となって障害が発生しているのかの問題の切り分けが必要です。
「熟練のメンバーなら障害対応が可能でも、新規メンバーでは障害対応が難しい」などの課題があります。

New Relic のオブザーバビリティソリューション

前述の運用課題について、オブザーバビリティを実現することが解決になると考えました。

オブザーバビリティとは、以下のような能力です。

簡単に言うと、私たちが考えるソフトウェアシステムの「オブザーバビリティ」とは、 システムがどのような状態になったとしても、それがどんなに斬新で奇妙なものであっても、(中略)事前にデバッグの必要性を定義したり予測したりすることなく、 システムの状態データのあらゆるディメンションやそれらの組み合わせについてアドホックに調査し、よりデバッグが可能であるようにする必要があります。 もし、 新しいコードをデプロイする必要がなく 、どんな斬新で奇妙な状態でも理解できるなら、オブザーバビリティがあると言えます。
オブザーバビリティ・エンジニアリング 1.2

New Relic は公式サイトにも記載がある通り、オブザーバビリティプラットフォームです。

New Relicオブザーバビリティプラットフォーム | New Relic

New Relic をオブザーバビリティバックエンドとして利用するためには、サービスのデータを New Relic にエクスポートする必要がありますが、エクスポートする手段を New Relic APMOpenTelemetry から選択できます。

私たちのサービスでは、以下の理由により OpenTelemetry (以下 OTel ) を選択しました。

  • メッセージキューを利用している
    • OTel を採用すると OTel の Carrier を実装することで柔軟かつ認知負荷の低い実装が可能
  • 既に利用しているライブラリの OTel ラッパーが利用でき、既存実装の変更点の少ない計装が可能
    • go-chi ライブラリに対する OTel ラッパー otelchi が存在する
    • zerolog ライブラリに対する OTel ラッパー otelzerolog が存在する

📖 New Relic と OTel に関しての公式記事は こちら

OpenTelemetry のトレース計装の追加でどう変化したか

上述の通り Kompira AlertHub を構成するサービスについて OTel の導入を決め、まずは分散トレースの計装をサービスに追加することにしました。
トレースの計装はまだ本番環境への適用が完了しておらず、試験環境での変化ですが、下記のような変化が見られました。

サービスごとの処理時間が閲覧できるようになった

トレースデータがあると、複数のサービスを経由する一連の処理の中で、特定のサービスでの処理にかかった時間を簡単に求めることができます。
たとえば、あるトレースについて、サービスごとの処理時間は NRQL では以下のようなクエリで求めることができます。

SELECT (max(timestamp + duration.ms) - min(timestamp)) / 1000 as 'sec' FROM Span WHERE service.namespace = '{{ service_namespace }}' FACET trace.id, service.name LIMIT MAX

※タイムスタンプが 13 桁のエポックミリ秒で記録されていたため、 timestamp を 1000 で割って sec に変換しています
https://docs.newrelic.com/jp/docs/logs/ui-data/timestamp-support/

ヒストグラムで集計する場合は以下のようなクエリで求めることができます。

SELECT histogram(sec) FROM (
    SELECT (max(timestamp + duration.ms) - min(timestamp)) / 1000 as 'sec' FROM Span WHERE service.namespace = '{{ service_namespace }}' FACET trace.id, service.name LIMIT MAX
) FACET service.name

サービスが行っている処理の理解がしやすくなった

今回、サービス内で以下のような箇所にのみトレースを計装しました。

  • メッセージキューに enqueue / dequeue している関数
  • http ハンドラ

これだけでも、以下のようなことがトレースデータを閲覧するだけで分かるようになりました。

🤔「この外部エンドポイントを叩くとどのサービスをどんな順番で処理がされてるんだろう…?」
→「トレースデータによって、どのサービスがどの順番で処理しているか分かる!」
→「Span をちゃんと計装したら、どの処理がどのファイルで実装されているかも分かるので、コードも追いやすい」

🤔「構成図ではこのサービス間は HTTP リクエストで連携しているけど、実際にどんなエンドポイントを叩いているんだろう…?」
→「http ハンドラにもトレースの計装を追加したことで、実際に叩いているエンドポイントが分かる!」

変化のまとめ

トレースの計装があることで

  • 処理の全体図が分かる
  • 実際の処理に紐づいた稼働状況の詳細が分かる

といった、新規メンバーがサービス理解をするのに非常に頼もしい変化がありました。

OpenTelemetry の計装時の工夫

このセクションでは、実際にサービスコードに OTel の計装を追加するにあたり、工夫した点や、おすすめの実装を紹介します。

  1. Go 言語でのメッセージキューを経由するようなトレースの実装においての工夫
  2. New Relic の APM & Services の利用が便利になる OTel Resource Attribute の紹介

Go 言語でのメッセージキューを経由するようなトレースの実装においての工夫

アーキテクチャ図 A」のように、製品内でメッセージキューを利用しています。
メッセージキューを経由した場合でもトレースを維持するには、トレースコンテキストをメッセージに含める必要があります。

OTel において、トレースコンテキストの伝搬は Propagators API を利用して行いますが、現在この実装として TextMap Propagator が存在します。

The Propagators API currently defines one Propagator type:

つまり TextMap Propagator は何かというと、Map に対してトレースコンテキストを注入したり、抽出ができるものです。
TextMap Propagator を利用してキューのメッセージにトレースコンテキストを含めると、キューのメッセージでもトレースコンテキストの伝搬ができるようになります。

そこで製品に OTel トレースを導入するにあたり、以下のような Carrier を実装しました。
※ OpenTelemetry トレースを導入したサービスは Go で実装されているため、Go での実装です

メッセージキューとして Azure Service Bus Queue を利用しているため、関数名などに AZServicebus を含んでいますが、 // type of azsb.Message.ApplicationProperties というコメントの箇所をお使いのメッセージキューにあわせて変更いただければ他のメッセージキューでも利用いただけると思います。

package tracer

import (
    "strings"
)

func getWithPrefix(prefix, key string, p map[string]any) string {
    value := p[prefix+key]
    str, ok := value.(string)
    if !ok {
        return ""
    } else {
        return str
    }
}

func keysWithPrefix(prefix string, p map[string]any) []string {
    var keys []string

    for k := range p {
        if strings.HasPrefix(k, prefix) {
            keys = append(keys, strings.TrimPrefix(k, prefix))
        }
    }
    return keys
}

// NOTE:
// AZServicebusMessageCarrier のメソッド経由で
// map の値を操作すると強制的に prefix が付与されるので trace context propagation 以外の用途では利用しないこと
type AZServicebusMessageCarrier struct {
    metadata map[string]any // type of azsb.Message.ApplicationProperties
}

func (c *AZServicebusMessageCarrier) Get(key string) string {
    return getWithPrefix("otel-", key, c.metadata)
}

func (c *AZServicebusMessageCarrier) Set(key string, value string) {
    c.metadata["otel-"+key] = value
}

func (c *AZServicebusMessageCarrier) Keys() []string {
    return keysWithPrefix("otel-", c.metadata)
}

// NOTE:
// OpenTelemetry の Propagator(に含まれるCarrier) を Azure Service Bus のため独自定義する
// NewAZServicebusMessageCarrier() の返り値を azsb.Message.ApplicationProperties にセットすることで
// Azure Service Bus に trace context を埋め込む
func NewAZServicebusMessageCarrier(p map[string]any) *AZServicebusMessageCarrier {
    return &AZServicebusMessageCarrier{metadata: p}
}

この実装の工夫ポイントとして、この Carrier を利用した際、トレースコンテキストに otel- prefix を付与するようにしたことが挙げられます。
Azure Service Bus Queue の ApplicationProperties には、トレースコンテキスト以外のメタデータを格納したいケースがあるかもしれません。
万が一でも他のメタデータを格納した際に衝突しないよう、prefix を付与しました。

補足:
carrier の計装については以下の記事にとても助けられました。ありがとうございます!
https://ymtdzzz.dev/post/opentelemetry-async-tracing-with-custom-propagator/ https://zenn.dev/google_cloud_jp/articles/20230626-pubsub-trace

New Relic の APM & Services の利用が便利になる OTel Resource Attribute の紹介

OTel Resource の Attribute は様々なものがありますが、Service Attributeservice.namespace を指定しておくと、New Relic での体験が良かったので紹介です。

New Relic の OTel のテレメトリを表示する画面で APM & Services 画面があります。

ここには様々な環境のサービスが全て並ぶかと思います。

この状態では複数の環境に OTel 計装をデプロイした際に、目的のサービスを探すのが大変です。
ですが、 Attribute で service.namespace を指定しておくと、画面上部の 「+」 から service.namespace でのフィルタができ、特定の namespace のサービスのみ表示することが出来ます。

画面上部の「 Save view」より、フィルタ状態に名前をつけて保存することも可能です。

このように活用できるので、service.namespace を指定しておくのをオススメします。

さいごに

分散トレースの導入にあたっては New Relic 社のテクニカルサポートも利用しました。
APM 画面の仕様の問い合わせや、 細かい疑問の解消など、お世話になりました。
ありがとうございました!

追記: 2025-01-22

New Relic Advent Calendar 2024 のナイストライ賞をいただきました!

Qiita Advent Calendar 2024 プレゼントカレンダー 結果発表! - Qiita Blog

Fixpoint で初の技術合宿を実施しました!

こんにちは。株式会社フィックスポイントのきむらです。

先日、社内の有志メンバーで開発合宿をしてきました!

今まで合宿イベントをしたことはなかったのですが、社内メンバーの一人が発起人となって企画を立ち上げ、実現に至りました🥳

今回はその合宿の様子をお届けします✨

はじめに

そもそもなぜ今回合宿をすることになったのでしょうか?

実は業務中にこんなツールがあったら便利だなというアイデアがあっても、なかなかまとまった時間をツールの開発にとれないといった思いがありました。

具体的に今回のターゲットになったのは、弊社製品の独自言語であるジョブフローを、エディタ上で手軽に整形するツールです。

そこで、2泊3日の合宿でさながら文化祭のように一気に作っちゃおう!となりました。

その結果、普段はリモートで対面する機会のない遠方のメンバーも楽しめる合宿企画ができあがりました🥳

開発風景

今回の合宿は、静岡県伊豆半島の山の中に位置するコテージ1棟を貸し切って行いました!

自然豊かな場所で、天候にも恵まれ穏やかな日差しの中3日間を過ごしました✨

リビングのテレビに画面を映してみんなで作業
気分転換に屋根付きのテラスに移動

勉強会

弊社では毎月オンラインで社内勉強会を開催しています。 このブログでも過去の勉強会の様子を紹介していますね。

今回の合宿では現地メンバーが発表テーマを持ち寄り、よりラフな雰囲気で勉強会を行いました!

他のメンバーが発表した内容に触発されて、突発的に新たな発表が生まれたりと、対面ならではの盛り上がりを見せました。

BBQ

合宿といえばおいしいご飯! 夜は外のテラスでBBQをしました。

料理が得意な我がエンジニア部門の部長がコテージのキッチンでアボカドと卵を使ったソースを作ってくれ、とても美味しかったです🍽️

最終成果物

今回の合宿で開発したツールは、その後社内の定例会議でエンジニアの皆さんに紹介しました。

今までコードを整形するためには少しだけ操作が必要だったのですが、今回のツールによりエディタ上で保存するだけで自動で整形されます🎉

少しの手間ですが、その手間がゼロになる自動化は、やはりエンジニアのストレスを下げてくれますね。

終わりに

今回初の試みだった合宿は、無事成功に終わりました!

弊社では、社内で実際に自動化したい業務を募集し、新人に解決してもらうというユニークな新人研修があります。

困っている社員にヒアリングをし、要件定義、設計から実装まで一通り経験できるうえ、 実際に社内に導入されて皆に使ってもらえる楽しい研修です。

ただ、どうしても新人研修用には難易度や開発期間が合っていないネタは後回しにされがち……

今回の合宿はそういった要望とも非常に相性のいいものとなりました。

ぜひ今後も第2回、第3回と継続して開催していきたいですね。

職場でのコミュニケーションについて考えよう

こんにちは。株式会社フィックスポイントのよしだです。

今回はエンジニアの技術に関わる発表というより、一般的な社会人スキルについての発表がありました!

社会人として職場で円滑な人間関係を構築するには、適切なコミュニケーションが必要不可欠ですよね。

最近社内でもホットな話題でしたので、いつもと毛色は違いますが取り上げさせていただきます✨

続きを読む

2023年度新入社員技術研修レポート

こんにちは。株式会社フィックスポイントのよしだです。

今回は、2023年度の新入社員技術研修をご紹介します!

フィックスポイントでは、新入社員のエンジニアに対して毎年部署配属の前に2ヶ月ほど研修を行っています。

本記事では、研修の終盤に行う個人開発タスクに焦点を当てて取り上げたいと思います!

全体的な研修の内容や体制については、前年度の記事に詳しく記載しておりますので、気になる方は見てみてくださいね😊

blog.fixpoint.co.jp

続きを読む

テスト…どう書く??2

こんにちは。株式会社フィックスポイントのよしだです。

今回は、前回に引き続き新入社員が勉強したテスト内容と実際に行ったテストについてご紹介します。

前回の記事は下の通りですので、興味のある方は前編から見てみてくださいね!

blog.fixpoint.co.jp

それでは早速、テストのお勉強の続きから内容に入っていきましょう♪

続きを読む

テスト…どう書く??

こんにちは。株式会社フィックスポイントのよしだです。

今回は、新入社員のエンジニアが初めてテストを書くにあたり勉強した内容について発表がありました。

学習過程から実際のテストに至るまで盛りだくさんの内容でしたので、二部構成にてお送りします!

第一弾では、テストを行うにあたっての前提のお話から単体テストの紹介までをご紹介します。

下記の本から勉強したとのことですので、是非併せて参考にしてみてくださいね😊

bookplus.nikkei.com

続きを読む

ダイエット必勝法

こんにちは。株式会社フィックスポイントのよしだです。

今回は少し毛色を変えて、健康に仕事を続けるための発表がありましたのでご紹介します!

勉強会では技術に関する内容以外にも社内で共有したい情報について発表することもできます😊

私生活にかなり役立つような発表でしたので、ぜひご一読ください!

続きを読む