; [/ D% d1 W7 W" _ 原标题:基于 Grafana LGTM 可观测平台的构建
9 J# K: o0 n' e. Z) L' B- g) A p, \: I) ]$ U( F
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
- V' J9 i s/ z x( h 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 ) u/ J, f* D6 s8 u/ _& z
通过本文你将了解: 2 a, z p( b0 R7 {9 h1 y! {" V
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID " u8 h3 c/ H2 h0 l( s; O
如何使用 OTel Collector 进行 metric、trace 收集 " {8 w: ? y D2 L7 V# u. c
如何使用 OTel Collector Contrib 进行日志收集
0 b2 g! Y4 o' B; S: o5 R5 x 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
2 k* ]* C3 ?! ~: P0 s' B- Y8 Y) @ 如何使用 Grafana 制作统一可观测性大盘 # x! h* G6 U$ V, B! y9 t/ S* s
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 ) S% D- j% K- U+ l
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
4 |, Z8 s( F1 t) o3 m1 j+ G 下载并体验样例
' J2 U- l( ]/ {- V; f 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
$ _6 d' E! E# {& I git clone https://github.com/grafanafans/prometheus-exemplar.git % U4 S$ r6 V% I: p8 Z
cd prometheus-exemplar
* j, C1 b! r% j5 E' P 使用 docker-compose 启动样例程序:
) q# Q! E6 k, z% R- m docker-compose up -d , F) A% {- S0 o
这个命令会启动以下程序: 8 G- r! M, [5 C t2 }" U
使用单节点模式分别启动一个 Mimir、Loki、Tempo 2 h; m+ T/ H0 K+ W
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo ) x- m4 T" {% x. v3 M g
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 " h. N' g9 m1 ^6 c
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
. ^# M6 e3 B* j4 K2 j0 N 整个部署架构如下: 4 t0 |" |: [' o" y: u3 e) j% T
9 \8 t. R( e" E/ I, r5 j9 O Y 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: $ b; G5 x" F& `
wrk http://localhost:8080/v1/books
: e' ~: L! z' L0 V wrk http://localhost:8080/v1/books/1 6 T8 ?8 l: U' R& Z5 W E# T, I
最后通过 http://localhost:3000 页面访问对应的看板: 3 z; ?( n: m. ]/ t, r
0 x' K, j7 n* P! ]1 ]1 o
细节说明
- [+ i. E$ A7 M# Y/ S& M- z: B 使用 Promethues Go SDK 导出 metrics
, g" A/ @# Z6 R! q/ x 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
- N& W' b' S0 f. r# W func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", 6 C6 w. u. A& E+ e( J6 G
Help: "Http latency distributions.", # j% m7 H2 ?" N5 ` N9 T
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, 0 T6 V; z+ V ~$ [$ D" r
}, []string{"method", "path", "code"}) ! G; w B) Z* n, N
prometheus.MustRegister(httpDurationsHistogram) ; P5 M9 b1 { ~! _8 f% _
return func(c *gin.Context) <{p> .....
G* s8 _1 o+ W8 z, r observer := httpDurationsHistogram.WithLabelValues(method, url, status) $ y5 c' s. y; ~
observer.Observe(elapsed)
& H1 O+ C! D1 ]* N# a if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), ( K. Q( O/ `3 {* l
})
$ W# L {* T2 u: \- a1 B } / W& y5 P1 l2 O+ `2 I4 C
} ( L$ _$ t3 P5 ~% k- ]9 A) ~7 A
} & `; p8 { ?. d9 h' G$ w7 u6 `
使用 OTLP HTTP 导出 traces 7 @" U" \2 h' U/ j! g
使用 OTel SDK 进行 trace 埋点: 3 I5 Q( w" ^2 N- e3 t
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") * F! ]+ }; x( o
span.SetAttributes(attribute.String("id", id))
5 T. X2 w6 ^! u+ P3 @. f P2 @ defer span.End()
4 |! {: v* z( _ // mysql qury random time duration
$ S" {" o4 h9 C: l+ J# c$ E time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 5 X8 a) w; \; e+ l: ?! }+ I( E
err = db.Where(Book{Id: id}).Find(&item).Error
' f- z5 C' V/ ^) A6 B4 U return / Q @# Y3 z( f* L) O3 M6 l* {
} # B5 ~' b* e. J
使用 OLTP HTTP 进行导出:
* Y& @* E" p% h! g) m7 h3 B func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
( \4 l% r6 X4 c* z* i# c client := otlptracehttp.NewClient( . X. H$ s5 [8 g, r; B% X
otlptracehttp.WithEndpoint(endpoint), + @3 ^8 S H" V$ |: o
otlptracehttp.WithInsecure(), 5 |9 y0 I5 e( E8 c/ o% P
)
+ R+ v% e. x, V3 { exp, err := otlptrace.New(context.Background(), client)
4 @' i8 p g8 I/ t' }" R- T+ _ if err != nil <{p> return err ) ^" E ?' o5 v5 a) L+ j% H0 Y8 W
}
; J$ {; [: J: H tp := tracesdk.NewTracerProvider(
) g8 R, X3 Y) f% w7 m/ I; e3 \ tracesdk.WithBatcher(exp), 8 y& g, l) @7 w" ~( U9 Z& q+ g
tracesdk.WithResource(resource.NewWithAttributes( 0 i! u8 ~0 f$ P, x8 C
semconv.SchemaURL,
, V+ x# S2 t& K' ?& y( E) ~: n semconv.ServiceNameKey.String(serviceName), 9 {5 F4 g0 a( s2 K' `
attribute.String("environment", environment),
# Y0 g) t9 r# ], q$ [, V* E )), 2 o# O2 t( I$ n8 S/ b& R; m. f: ?, M
)
- ~, F z9 R3 r+ ` otel.SetTracerProvider(tp)
6 M+ _ d. ]7 _3 x( a# J: I8 a return nil
, V& I, a3 m1 y } " ]) X# o8 I$ ?2 L
结构化日志
2 s: y0 L3 X4 l0 w 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: ! z, Y8 u, @+ s4 ]2 d) ^. _, Y
cfg := zap.NewProductionConfig()
. E& T. K& ~1 i: [ _ cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} ( q' J' X8 O* z C
logger, _ := cfg.Build() / T7 O3 |, e. o' {1 V
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
' t3 V8 C9 p4 Y% I1 o+ K 使用 OTel Collector 进行 metric、trace 收集 + R9 ~- |# _( W+ |
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 # m7 h# b, A* a Y+ F, I9 d8 k
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: & o- g% @! S0 T: [1 L5 w
receivers: ) `2 a6 f7 Z3 c, }3 E- F9 {$ k9 k
otlp: : M" K1 D* h1 t) l1 F9 R
protocols: " u! u- b; G, L8 X
grpc: ) g- o( Q& d4 C# Y/ H; y) X& X
http: ! }5 ~! `/ v6 O1 t- ~
prometheus: % ?; t8 I/ k* z& G7 a/ d: c
config:
) K4 W b. z. x! s, f scrape_configs: / ]% k) v0 R% o( g; [- @9 a
- job_name: app
& {5 g& n+ z8 d, L, {% x scrape_interval: 10s 1 h+ |, }& J* T
static_configs: ! L$ m% N( p; L B
- targets: [app:8080]
) _1 V) x% c) X, I6 B7 o exporters: . ^2 I. E* C) F8 i9 t2 R! r
otlp:
: Z: {, a3 d# A. _ endpoint: tempo:4317 9 `* S% m. P M( u3 x& J
tls:
! q; J! K: S2 `& Z- R insecure: true
: Z: p5 M+ e C. `5 g- e' N3 J3 \ prometheusremotewrite:
' I; F- b9 b$ k* z2 k& ? endpoint: http://mimir:8080/api/v1/push
2 F8 G! H8 ~+ ]6 k0 q tls: 0 B+ i3 c/ i4 F' ]" B
insecure: true $ W( `& i8 n; o7 P) H& L; w
headers:
3 {& u' P" p* g0 F$ Q6 Q0 s X-Scope-OrgID: demo ( q+ e" W2 B: h6 I- c9 S
processors: ! d1 P7 h2 ~" j* V( ^* `; V2 B
batch:
. Z' u2 z) F F; f service:
" y$ ^+ _2 W p6 y( ?" E$ ^ pipelines:
! |& N: P+ K/ A3 t/ i2 \ traces: ' f9 d6 p$ ]: k1 L3 ]
receivers: [otlp] q/ ?2 w' L( A% j1 ]6 V9 A5 G% d
processors: [batch] 0 E+ U+ O) `' F, b
exporters: [otlp]
3 A6 `2 F( a H& Z2 M metrics: 6 e6 Z {0 Q$ q& M
receivers: [prometheus]
9 d& y& P/ l: w1 Y) B, n8 `2 T processors: [batch]
9 V. C' \* J& i& {4 ~" d exporters: [prometheusremotewrite]
3 Q/ b ^, H! ]. w- m3 O) b 使用 OTel Collector Contrib 进行 log 收集 1 c1 _/ M4 b$ D$ @' o
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: 6 I* d d! [/ ]9 U7 [ y
receivers:
5 |) t1 z7 n K1 b: x filelog: + f- `! G1 r2 M" c a7 R
include: [/var/log/app.log] ; Z$ v, |. a P' J5 W) O+ G
exporters:
& N8 m: X' i8 a* [8 U8 F% I s loki:
9 X8 F6 ^4 Q9 R, |, I endpoint: http://loki:3100/loki/api/v1/push
6 b6 `1 A' c& W$ e0 w" N tenant_id: demo 3 S3 r/ h/ l8 i, z$ f# s& ]6 p6 Q
labels: 9 x) p# n G! c) x. t$ F
attributes: 0 ]) S% Y6 D; f
log.file.name: "filename"
3 o. v2 `* L0 |" T/ Z processors:
. p1 G2 V! o6 |6 r3 D batch: ! i4 z7 ]0 i/ h
service: 2 G* w7 O8 H" u
pipelines: ' [+ N) ^* B* b1 ?3 U) a
logs:
9 U$ s, i" a4 V7 W5 w1 t" I* G receivers: [filelog] 9 e" ]9 q. T/ M( N# \. t* U$ [
processors: [batch]
- R. F2 {: \% `! @' E exporters: [loki] $ o, c' P% _& n3 r
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
* ?4 D! _! U' y" F0 ?- b 总结 / y& n! u6 p; P1 }/ `
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 1 c' O1 L) |) V4 o% Y3 d
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
# }. K) H" S; ?) Y7 C4 B; c% O4 |: h2 T* t$ p
责任编辑: # i2 F- j/ a& ]: W4 a0 n6 ]9 Q B
- E3 I: K- g) n1 W2 [: K' k
/ R: a0 f: q" c! E' v9 i& e- ] y, P. D1 H0 t
$ A- C: v; }9 l |