|
3 r4 D& E* P/ f0 b 原标题:基于 Grafana LGTM 可观测平台的构建
0 P2 j$ @ Y5 z2 t/ k$ e; C6 J9 v ^$ N& }
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
/ n: N7 f2 x' E 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
3 H9 l7 v4 z1 q. u 通过本文你将了解: & g# Y: O+ s2 M/ {
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
9 M/ ?! J) K* c 如何使用 OTel Collector 进行 metric、trace 收集 * |/ p0 k0 }/ e5 d# x; c: d
如何使用 OTel Collector Contrib 进行日志收集
5 S: A% c7 W2 @' D 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 / q: E0 D4 b# y0 V( x! v
如何使用 Grafana 制作统一可观测性大盘 - Z3 `( G V, M1 u" V& h
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
/ Z. ]2 c, Q+ ` 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 - \6 ]: I# x( L0 f1 A6 {+ E9 P
下载并体验样例
8 w! k' d% w- w 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
$ f& P' ~+ O% E' p! m, e: I7 z4 m git clone https://github.com/grafanafans/prometheus-exemplar.git 4 g: z1 Z$ M9 f$ u, P4 o
cd prometheus-exemplar
" x( L8 o' ?! z: f 使用 docker-compose 启动样例程序: $ b1 @4 I5 i% L3 L* U& e
docker-compose up -d + Z( E- f6 ` |' {! z7 N. g
这个命令会启动以下程序:
2 m6 Q+ [. m* w" g' @; D2 e) a 使用单节点模式分别启动一个 Mimir、Loki、Tempo
4 f( b' |, Y9 Q) m 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo * H! h- Y# I8 {6 P
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
4 V- p( g9 ~; e4 |9 w% l; V 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
$ J& C1 |* g* o6 w6 u2 @* c: Y 整个部署架构如下: H; i( D& r2 L( [: J# S

2 v9 ?( h* V5 x. e x5 V1 l: c. I 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
, |2 H; f: |3 d. Q& y% U wrk http://localhost:8080/v1/books / b* B: v- r) j0 u
wrk http://localhost:8080/v1/books/1 $ | }: T2 G& r( c, P
最后通过 http://localhost:3000 页面访问对应的看板:
/ E' f9 K" G% e: Z7 H6 x  " n4 h/ p2 O7 S3 A, z
细节说明
X; c. w1 {) n+ ~0 N/ V 使用 Promethues Go SDK 导出 metrics
, {. U# C. N. X0 h) j 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: , {; ^* o3 Q0 N7 M$ M
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
; |, z" O) ]" d( c Help: "Http latency distributions.", 6 ?, }: Y1 e+ n
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
+ ^' [8 }9 b \. c4 ?: V1 X1 i }, []string{"method", "path", "code"}) % [; i; H( V' |, \* p( G/ R
prometheus.MustRegister(httpDurationsHistogram)
' ?' O& n' R! Y& I. g return func(c *gin.Context) <{p> ..... & j- P2 ^1 }2 e j
observer := httpDurationsHistogram.WithLabelValues(method, url, status) + V9 [: ]+ _4 X6 d9 m+ X
observer.Observe(elapsed)
; {) ^) m) F- n- c/ v. O/ B if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
% w0 X, w& D" Y- s0 j })
& \8 n& \$ `! q" } } ! d+ c3 A: H! G: Y2 R, W& m
}
! {" o$ Y5 K1 \' `# \4 C" w; z( ~8 _1 ] }
8 W) P3 G8 o3 a9 j8 H- y* Y. K, M4 N 使用 OTLP HTTP 导出 traces 6 H+ @( d) k5 F% I+ g; n, p3 `
使用 OTel SDK 进行 trace 埋点:
7 D. w! U8 d- V+ c, k2 y$ k# w func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
' z& v1 y" [2 Y# ~/ N span.SetAttributes(attribute.String("id", id)) 7 F, O+ q* W1 x- D0 I
defer span.End() 7 a5 X1 l& |/ J6 M) E" m0 T
// mysql qury random time duration
N* [- W' q' ?+ u9 c time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
5 _# S, @ h X+ J err = db.Where(Book{Id: id}).Find(&item).Error % R$ l7 b. d) L7 h
return
7 d# w8 W2 H; O' J! m) X* _ }
0 r) q9 U1 T1 G2 G+ J" b 使用 OLTP HTTP 进行导出: 8 Y; z1 y7 ] V' I. U {
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name - ?4 ?" n- d1 j7 _% N' p, g3 v
client := otlptracehttp.NewClient( 0 N7 T9 a1 a9 r$ D% [! ~
otlptracehttp.WithEndpoint(endpoint), $ O0 L: b7 k6 K1 k
otlptracehttp.WithInsecure(),
9 o# ~% S1 Z5 O, O4 I )
1 g7 i3 N5 N! W' N5 e5 B0 @, f; E3 k exp, err := otlptrace.New(context.Background(), client)
% _, P D" n( r6 U if err != nil <{p> return err
* m7 `7 |: P' O }
6 [8 n7 [2 Z6 G8 ^0 [ tp := tracesdk.NewTracerProvider( D3 l( h/ W" O4 e* ^* K
tracesdk.WithBatcher(exp), ; M3 { W) R$ l1 X# P1 X( J0 N
tracesdk.WithResource(resource.NewWithAttributes( 1 u$ V! x; T7 r/ o
semconv.SchemaURL, & m% p) F/ O: [8 y. t: t, b
semconv.ServiceNameKey.String(serviceName), - w- U" Y) `1 F) q% B' @ {0 T
attribute.String("environment", environment),
+ g6 N8 l) ^) ? Q& p- i )),
) z: Z4 ]2 t1 f ) % s# O, N4 g+ C7 e
otel.SetTracerProvider(tp)
& u6 U$ u; s5 C5 a2 y4 V return nil 7 O3 N6 X U0 x6 n
} " D/ H/ k0 M; q. D) s
结构化日志 8 E# H/ F8 l x6 c- j" g) d6 h& f
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
! r5 D f7 b- E$ l* [ cfg := zap.NewProductionConfig()
3 C9 U8 M( Y/ C( d/ v5 y G cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} , V. q) T1 {7 |. k
logger, _ := cfg.Build()
1 i' l- w/ c0 c; U. z logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) 6 \0 {2 c& N- ~/ {2 `/ w
使用 OTel Collector 进行 metric、trace 收集
8 u u: D# Z: s# z% X7 n 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 # z c/ Q4 ^1 a0 i7 a: U) |* S
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: $ [; n" E& V( C
receivers: ( V; r: O @, d% X/ U0 d
otlp: 6 ` c/ n- |1 ? }
protocols:
) ^7 Z: u% Y) E4 }$ t$ ` grpc:
- m% c( x: s2 }' G+ e) |/ C* J; R http:
- n' h* P" l2 k* o prometheus: - B8 f" H9 ~/ ^# {. S- g! f
config:
( J; z4 o) c& n. B A a; u scrape_configs: + h, O3 b- p4 s* `
- job_name: app
" g3 @2 F0 p. T7 r6 [$ x scrape_interval: 10s
2 s5 @9 f* `& l& F1 v3 ~* [" A4 N1 ^ static_configs:
( V3 h9 s# y/ l' i( k2 [7 I - targets: [app:8080] - S9 b N, n) M0 G8 s% Y
exporters:
! q, a' @1 i: U# x otlp:
: |: M0 b2 r9 C7 h2 X endpoint: tempo:4317 : T, S% y6 Q& H& d* c" V- @3 L
tls: 2 f3 f3 B& w, ]2 r i
insecure: true
8 L/ ^& i# r2 {" g3 o. h2 b prometheusremotewrite: . u& f& h# [/ g: G K$ z, q, d4 z! S
endpoint: http://mimir:8080/api/v1/push
( n% F, [' l! K/ j2 H0 `' O5 n3 q- j% s7 g tls:
: K( m, j6 A$ A& X insecure: true 5 @ F, W) U+ y }' E" n. P B( M2 R
headers: - s5 B8 n6 w" b
X-Scope-OrgID: demo
, S! ]% z3 V- t% i processors:
( C2 `9 o1 w/ y$ N9 z" h! _; O batch:
3 j& H: P) J5 E* Q( a( c7 c service:
, y3 C. E, ^1 ?2 Z7 K" \1 i- K pipelines:
5 O+ p& q. F* @" y0 _ traces:
L2 D+ Y6 j7 ` receivers: [otlp]
( M# d0 R1 o! @- k+ d6 R7 p processors: [batch] * r; g; ?/ [% _
exporters: [otlp] 0 N" ]" O$ x0 d" g' G3 _" m
metrics:
) [4 Y* q) m( a- N receivers: [prometheus] + R# M7 l. S1 p. A9 a# i
processors: [batch]
3 k. x! r8 t% C; j+ k$ y exporters: [prometheusremotewrite] ) q3 M9 z9 ^; f" I! N
使用 OTel Collector Contrib 进行 log 收集
5 d8 g# D' s. f! V' s 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
/ k( K: ~% _0 M7 L+ f receivers:
( N$ U6 w) a( B0 c# g5 u* \ filelog:
' ~& M2 O! E& j- s# @ include: [/var/log/app.log]
4 K u/ G- R0 `1 W' j exporters: - c5 N6 T3 e5 g: \! p. {
loki:
1 R$ c, [- h3 Q! \1 K6 j endpoint: http://loki:3100/loki/api/v1/push & w1 t+ k& k) R' [
tenant_id: demo
( p" \; N" k3 ]6 ^4 a/ e: C. V labels: 6 o2 n5 }$ W+ o, [0 e7 |0 ?' h
attributes: 7 O3 ~, |! E4 M" T0 c2 z& E
log.file.name: "filename" $ D. I1 Q w% W+ ?1 M1 E+ U
processors: ' [: G9 x- e) a8 I+ R. ^7 J* y: W. e
batch: $ n6 p$ Z+ {$ k8 N) K; o
service:
, V# o' `) F( T4 P5 l. i* C) s pipelines:
# q; O/ i$ Z( g' ~ z logs: + A' b$ Y7 w) B" U+ H
receivers: [filelog]
& c0 j% ?3 q5 P1 j) a processors: [batch]
' J5 U, N6 b7 H4 I, f exporters: [loki]
3 h+ Y5 q- Z8 r7 H; M 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
* n. R! Y: ~' ]& L 总结 2 o K0 c( h% B5 t4 u
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 4 G; J6 y" q8 J) p$ q4 v
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 4 r8 Y/ P w7 D2 \; q' U
: g/ Z5 V |. `( \ 责任编辑:
2 {: c+ k* R6 w4 @3 G' f
' l! s8 l8 h3 \# M0 {
) G" b( V8 _6 o! J1 z8 g; `+ B/ a" Z6 k
2 k) A+ l/ `/ Q3 o9 K8 } |