11.9K Views
April 08, 24
スライド概要
Rust で OpenTelemetry tracing と一緒に使ってわかった課題 ymgyt · 2024‑04‑10
[speaker] name = "Yamaguchi Yuta" x = "@YAmaguchixt" 自己紹介 [company] name = "FRAIM Inc." blog = "https://zenn.dev/p/fraim"
概要 • Rust に OpenTelemetry を導入して、Traces, Metrics, Logs を取得した • tracing という lib から OpenTelemetry API を 利用した
最初の課題 OpenTelemetry API を直接利用するか lib(tracing)経由で利用するか
OpenTelemetry API を直接利用 するか OpenTelemetry Client Generic Design
OpenTelemetry API を直接利用 するか tracing 経由で OpenTelemetry を利用すること にした • 既に tracing を計装済だった • 利用している lib の多くが tracing で計装され ていた • tracing‑opentelemetry が tokio から提供され ていた
Rust の OpenTelemetry project でも tracing との 関係が OpenTelemetry Tracing API vs Tokio‑ Tracing API for Distributed Tracing で議論されて いる OpenTelemetry API を直接利用 するか
Resource
1 use opentelemetry_sdk::{resource::EnvResourceDetector, Resource};
2 use opentelemetry_semantic_conventions::{
3
resource::{SERVICE_NAME, SERVICE_NAMESPACE, SERVICE_VERSION},
4
SCHEMA_URL,
5 };
6
7 pub fn resource(
8
service_name: impl Into<Cow<'static, str>>,
9
service_version: impl Into<Cow<'static, str>>,
10) -> Resource {
11
Resource::from_schema_url(
12
[
13
(SERVICE_NAME, service_name.into()),
14
(SERVICE_VERSION, service_version.into()),
15
(SERVICE_NAMESPACE, "foo".into()),
16
]
17
.into_iter()
18
.map(|(key, value)| KeyValue::new(key, value)),
19
SCHEMA_URL, // https://opentelemetry.io/schemas/1.21.0
20
)
21
.merge(&Resource::from_detectors(
22
Duration::from_millis(200),
23
// Detect "OTEL_RESOURCE_ATTRIBUTES" environment variables
24
vec![Box::new(EnvResourceDetector::new())],
25
))
26}
#[tracing::instrument(skip_all, fields(%url))]
async fn fetch_feed(&self, url: Url) -> Result<Feed,Error>
{ /* ... */ }
Traces
関数に Span を設定するには #[tracing::instrument] annotation を
利用する
fields()に値を指定すると Span の attribute になる
Traces Span attributes に url が記録される
Traces async fn run() { // ... info!( "enduser.id"= user_id, "operation" = operation, ); // ... 関数の中で行った logging(info や error)は Span の Events として記録される
Traces Span の Events に記録される
Traces の課題 Context propagation では OpenTelemetry の仕組 みを利用するので、OpenTelemetry の Context や Baggage を意識する必要がある。 (Application が OpenTelemetry をまったく意識 しなくていいことにならない) • inject 時は tracing::Span ‑> opentelemetry::Context ‑> http::Header • extract 時は http::Header ‑> opentelemetry::Context ‑> tracing::Span
OpenTelemetry の trace は 1 layer(plugin)という 位置づけなので、sampling しない場合でも tracing 側で respect されない Traces の課題 あくまで tracing 側の機構で span の sampling を制御する必要がある RUST_LOG=app=info, lib_b[request{path="foo"}]=trace (一方で、アプリケーションは info、lib_b の request span の path が"foo"は trace といった制 御ができる)
Metrics 3 つの選択肢があった • Prometheus Metrics • OpenTelemetry Metrics API • tracing‑opentelemetry MetricsLayer
Prometheus Metrics Metrics Application からは prometheus 形式の metrics を export して OpenTelemetry collector で OpenTelemetry 形式に変換する
Prometheus Metrics Metrics Application からは prometheus 形式の metrics を export して OpenTelemetry collector で OpenTelemetry 形式に変換する => Signal を関連づける重要性が謳われていたの で採用しなかった
OpenTelemetry Metrics API
Metrics
fn main() {
let meter = opentelemetry::global::meter("foo");
let counter = meter.u64_counter("counter").init();
counter.add(
10,
&[opentelemetry::KeyValue::new("key","value")],
);
}
OpenTelemetry Metrics API
Metrics
fn main() {
let meter = opentelemetry::global::meter("foo");
let counter = meter.u64_counter("counter").init();
counter.add(
10,
&[opentelemetry::KeyValue::new("key","value")],
);
}
=> traces との一貫性を重視して、metrics も tracing 経由で
操作することにした
tracing‑opentelemetry MetricsLayer Metrics info!( monotonic_counter.http.server.request = 1, http.response.status.code = status ); tracing では、info!の logging は Event の dispatch になる layer 側で、monotonic_counter prefix がついた event を受け 取ったら、attribute(http.response.status.code)を付与して、 counter metrics を生成する
Metrics の課題 Prometheus/Mimir を利用している場合 OpenTelemetry ‑> Prometheus の変換を意識す る必要がある
例えば、OpenTelemetry の UpDownCounter は Prometheus の Gauge になる Metrics の課題 > If the aggregation temporality is cumulative and the sum is non‑monotonic, it MUST be converted to a Prometheus Gauge. https://opentelemetry.io/docs/specs/otel/ compatibility/prometheus_and_openmetrics/# sums
Prometheus との互換性に関しては Prometheus の Our commitment to OpenTelemetry というブ ログで互換性向上の取り組みが紹介されていた • Metrics の課題 http.server.request.duration ‑> http_server_request_duration の変換をなくす • Native support for resource attributes(prometheus は metrics attributes と resource を flat な label にする) • OTLP の support
Metrics の課題 Elastic stack(elasticsearch, kibana)を使っていた 際も OpneTelemetry の resource や attributes が 変換されてしまっていたが Announcing the Elastic Common Schema(ECS) and OpenTelemetry Semantic Convention Convergence で、両者の統合が発表されていた > The goal is to achieve convergence of ECS and OTel Semantic Conventions into a single open schema that is maintained by OpenTelemetry
Event の field(key,value)だけで表現できない metrics も Metrics の課題 value には primitive 型(i64,f64,bool,..)や fmt::Display 等の文字列表現しか利用できない => Histogram で boundaries を指定したい場合 に対応できない
use opentelemetry_sdk::metrics::{Instrument, Stream,View};
fn view() -> impl View {
|instrument: &Instrument| -> Option<Stream> {
match instrument.name.as_ref() {
"graphql.duration" => Some(
Stream::new()
.name(instrument.name.clone())
.aggregation(
Metrics の課題
opentelemetry_sdk::metrics::Aggregation::ExplicitBucketHistogram {
boundaries: vec![
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1.0, 2.5,
5.0, 7.5, 10.0,
],
record_min_max: false,
},
)
.unit(Unit::new("s")),
),
_ => {
None
}
}
}
}
Metrics の View という仕組みで、SDK 初期化時 に変換処理を追加することで対処した Metrics の課題 • Metrics 生成箇所と SDK 初期化処理で code が 別れてしまった • 名前(Instrument.name)で一致させているので 型/compile 時の保証がない
Logs Logs の収集には 2 つの方法を利用した • Kubernetes 環境では stdout に出力したのち、 node に配置した collector から filelog receiver で収集 • opentelemetry‑appender‑tracing
Logs Logs の spec OpenTelemetry Logging では Traces や metrics のように新しい API を提供す るのではなく Logs Bridge API を用意して、言語ごとの既存の logging ecosystem に組み込んでもらう方針が説 明されている > Our approach with logs is somewhat different. For OpenTelemetry to be successful in logging space we need to support existing legacy of logs and logging libraries
Logs この方針があってか、traces,metrics では tracing が提供している tracing‑opentelemetry で opentelemetry と連携していたが opentelemetry‑appender‑tracing は opentelemetry‑rust が提供している
Logs opentelemetry‑appender‑tracing を利用すると info!("message")といったこれまでの logging が そのまま OTLP で collector に送られる
Logs の課題 #[tracing::instrument] async fn foo() { info!("message"); } とした場合 • foo Span の event に"message"が記録される • Log の record としても"message"が記録される
tracing(tracing_subscriber::FmtLayer)では、 http{method=POST path=/graphql request_id=Fsg3zhkIS4}: service{query="foo"}: db{connection=1} "message" Logs の課題 のように log に span の情報を埋め込んでくれる ので Log 側も trace の情報を一部保持すること になる
tracing(tracing_subscriber::FmtLayer)では、 http{method=POST path=/graphql request_id=Fsg3zhkIS4}: service{query="foo"}: db{connection=1} "message" Logs の課題 のように log に span の情報を埋め込んでくれる ので Log 側も trace の情報を一部保持すること になる trace は sampling されたり log とは lifecycle が 違うので log にどの程度 context 情報を付与す るか悩ましい
趣味で作っているツールの backend api でも OpenTelemetry を利用しました 本スライドで話した内容の実際のコード、 collector の設定 file、grafana dashboard を公開 しています 実際のコード
まとめ • Rust で tracing を利用して OpenTelemetry の 各 signal を導入できた • tracing を通じて OpenTelemetry API を利用す ることのメリットもある一方で課題もわかっ た
リンク • https://github.com/tokio‑rs • https://github.com/tokio‑rs/tracing‑ opentelemetry • https://github.com/open‑telemetry/ opentelemetry‑rust/discussions/1032 • OpenTelemetry Tracing API vs Tokio‑Tracing API for Distributed Tracing • OtelLayer の compose