: V, U, Z3 h. j; |6 @2 p2 N 原标题:基于 Grafana LGTM 可观测平台的构建 0 o$ b# R6 p2 g z% N
/ N! G& k8 w# {4 i9 a+ O+ ` 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 9 `) g7 G& [5 R0 Q/ W( T
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
+ J1 I6 s) P% u3 N3 ^# | 通过本文你将了解: ( Q! }2 T/ M# F$ K3 @9 J3 I5 \+ P
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
+ m$ D2 @( v5 Y( Z- f% @+ ] 如何使用 OTel Collector 进行 metric、trace 收集 ! u- ^% B! z) {' `; }9 y2 M' A! F& g
如何使用 OTel Collector Contrib 进行日志收集
* Y |; d( n4 d+ J0 H7 T 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 1 L* D% b6 p1 ^
如何使用 Grafana 制作统一可观测性大盘
0 E; w! J @& d4 c8 n, D& A 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
( F" f6 N" {5 B# I$ o$ L- y 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
' M" m/ L! v. Q9 f- f! w. T 下载并体验样例 , ]3 {% ^3 r( z8 H& n- y
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
# D1 x- q8 j) h% i3 L" ] git clone https://github.com/grafanafans/prometheus-exemplar.git ! [6 W, Y( E# m' A9 k! ~9 r- X. y
cd prometheus-exemplar ( u7 @5 v1 Y0 M
使用 docker-compose 启动样例程序: 0 t0 W. h7 t: y/ g. g0 r! {2 n
docker-compose up -d
; u5 s' Z) K! D. r% u5 f- U 这个命令会启动以下程序:
& P2 R% ?! x8 y4 @( O0 N" z/ A 使用单节点模式分别启动一个 Mimir、Loki、Tempo 4 q- g& z5 [. C, _4 x
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo 1 m% V. ~% ^) `/ v
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
+ j; K: k1 d4 |/ t4 B 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 3 F0 z c( S! ]4 U% z. [ F0 A
整个部署架构如下:
+ }2 ^- V6 ]2 y. C  1 Z6 N5 M& j8 x$ ~% R- @9 c! c
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 1 }+ z2 h2 {% D5 C
wrk http://localhost:8080/v1/books 8 `2 |. ]1 f" n* C' l' {1 i
wrk http://localhost:8080/v1/books/1 . s$ ~3 G/ d4 }! l2 u7 d7 |' t
最后通过 http://localhost:3000 页面访问对应的看板: `) J- U: v% o2 y( R) P
 , u9 j# `, \" K
细节说明
3 G) G: L: W6 A5 L! b8 C% O+ X 使用 Promethues Go SDK 导出 metrics
4 o( Y g! z, Y$ l6 \1 n 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
2 W; H8 f6 t1 \% A func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
7 e+ i0 z, N" n7 l Help: "Http latency distributions.",
3 ^/ S3 b% q$ s* h; R3 x6 r, H. \( i/ _% G Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
$ a0 j+ M7 \! g" v }, []string{"method", "path", "code"})
& ~8 K" s5 u, O5 Z/ q& h prometheus.MustRegister(httpDurationsHistogram) & m( V+ N1 p* W& F6 x, Z% k4 _
return func(c *gin.Context) <{p> .....
, Y- E8 N2 O, G3 ?4 M" J; v observer := httpDurationsHistogram.WithLabelValues(method, url, status)
/ Q5 X z6 [8 | observer.Observe(elapsed)
8 j% B' ^1 b6 j* A6 \ if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
! t) ^8 ]" ]5 j })
% j" a0 V0 W5 G5 ~- I# l3 }- L }
, b F6 M" T1 V' S2 e% e }
) z( g/ v' [& U |6 c0 \ } ! G- F( c4 C* l, e
使用 OTLP HTTP 导出 traces ( u$ O) k/ b& u# K" O+ I; X
使用 OTel SDK 进行 trace 埋点:
1 R a& Y& P4 I, g# I/ R func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") 6 Q E# @, v; d
span.SetAttributes(attribute.String("id", id)) / ~. F/ I. \% i! T q( e
defer span.End()
4 h8 s0 |1 Z3 V/ L# P$ d // mysql qury random time duration 3 ]$ C6 _, Z4 g9 b$ K. Y
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
3 D% z0 h$ s2 }5 e; r( M5 k& j err = db.Where(Book{Id: id}).Find(&item).Error
' B7 w; S: a: Q9 n& c& ^7 |# b4 ] return
5 X; {1 s3 K" g( ]4 S. `/ z7 p } * ]- K4 B* p; Z m. k) }* V
使用 OLTP HTTP 进行导出: 7 {6 p6 g+ W# @& x* E
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name % V2 \% b; l ?6 W6 a, f6 y3 y
client := otlptracehttp.NewClient( ; c7 M. E9 Z: ~. l. Q/ ^
otlptracehttp.WithEndpoint(endpoint),
( c5 ?8 v# Q0 G6 C5 B6 _ otlptracehttp.WithInsecure(), 9 B) ]. A8 s& u9 Z' }% J; v ~) }
)
R4 r( s- w7 `% W& @- [ {* R exp, err := otlptrace.New(context.Background(), client)
' m# [8 a# H: g$ G+ b% C6 j if err != nil <{p> return err
8 ?* J: q! c2 @7 D" e }
* @5 W/ f Z3 g6 }2 ^( d tp := tracesdk.NewTracerProvider( 6 N7 L, E6 r/ ?; }
tracesdk.WithBatcher(exp), ; R; f( Q6 G4 Q: w% \7 K
tracesdk.WithResource(resource.NewWithAttributes(
2 p( i; x$ Y% ~* j$ o( X8 p semconv.SchemaURL, # Z( c1 W8 l4 a: j
semconv.ServiceNameKey.String(serviceName), - N# i5 B+ p- m. w2 x2 E$ q
attribute.String("environment", environment),
& |5 p; C7 t- u6 ?9 u7 V$ A )), $ A2 D( @) J$ \5 s g( V/ i
)
5 o0 ]7 [2 W9 X, S otel.SetTracerProvider(tp) A; p U: M3 ^5 q {' ]2 m
return nil 4 r; h5 x7 D$ q" f. L o. u
}
6 c+ l" h: ?9 R/ ?0 A* C1 n 结构化日志 ' Z! ]; ]' O3 i
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: 5 n5 @ X$ p3 s" D9 S6 B5 f, W
cfg := zap.NewProductionConfig()
4 C* `3 [7 j( m. M5 d cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} 7 J& ]) K% J! q+ A3 S% t
logger, _ := cfg.Build()
# y7 r% y. n$ z" a logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
% x8 _# G7 K, }3 D# P& O 使用 OTel Collector 进行 metric、trace 收集 8 B8 Q! ~; h1 n+ F
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 7 }8 x9 ]4 |; c- M$ S0 r
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
# ?6 q) a9 m% E8 X0 w, W* X# k$ } receivers: - {, y, d) ]4 \6 f
otlp: 5 i7 x( J$ D6 W' F" T
protocols: # q. d9 _1 G5 a
grpc: + A; b! J0 G, ^
http: & e9 W6 h, {! F) G
prometheus:
, \2 d, N. U; A% ^* N8 X' K5 P config:
1 [2 O( d( v2 X8 Z3 t scrape_configs:
% V, w) e, q B s' _( P - job_name: app & ~* d9 z2 N9 y6 T
scrape_interval: 10s 9 Q0 _2 t! W' v( L( j
static_configs: 2 o/ x2 b! U0 S. T( O, G% ]$ q1 I
- targets: [app:8080] " I' D8 w Q+ r
exporters: 9 i/ r" z% P0 ^8 l0 \
otlp: ! l [( E) R4 ]2 P
endpoint: tempo:4317
4 A9 g9 c$ ]5 G) B. i. E h tls:
' u% [: ]+ }2 f( E4 D, o insecure: true
/ j N2 S- i5 L& [( ]" n+ U5 k; s prometheusremotewrite: + M9 d& q; y, S) e( @8 A; _
endpoint: http://mimir:8080/api/v1/push / g% l' }/ u* ^3 u! C
tls: 4 G8 a2 D! e$ c( v6 |+ c
insecure: true / e7 U7 _/ Z+ `+ B
headers:
8 O" e. P: n* M y6 R3 E4 o X-Scope-OrgID: demo
( h2 q" E0 X' E4 ?! _ t$ A processors:
( p3 z+ H9 T+ Q batch: " M4 L( Z/ I( M, w- e/ d
service: 2 @4 D, v3 |: T! z& `
pipelines: - G: Z: b4 \' v7 n. c
traces: 2 J; ]6 L h. p% _. ~/ ~, `- O3 X
receivers: [otlp]
. ?' f$ Q, d5 g! e( P/ W processors: [batch]
9 O) ^5 d, I" v& z exporters: [otlp] 8 F j. k$ O& P2 Q5 c9 }' a2 O
metrics: / V' `9 p( ~- J6 G, {, i
receivers: [prometheus]
+ C1 U" f5 M) ~' E1 L processors: [batch] ' b: V* J2 d( O9 i1 _9 c4 u
exporters: [prometheusremotewrite]
4 W5 B1 q, s8 w7 t 使用 OTel Collector Contrib 进行 log 收集 $ F, Y* `' w+ \
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
3 c: g, [9 M3 {+ I3 \# g receivers: 9 j& @$ w+ @, p0 u; c- I
filelog: * `% i; {/ u# {8 O
include: [/var/log/app.log] : q5 L& s' N5 l( C& J/ y
exporters:
/ t8 i) F1 ]# Y6 b& `$ y/ L loki:
7 N( l# D4 k8 J2 j endpoint: http://loki:3100/loki/api/v1/push & e; O+ B9 K A0 z! z
tenant_id: demo 6 L4 F- f$ j" b! e8 v
labels: 1 k' v9 U; U8 e
attributes:
' b7 `6 B4 [; F2 j4 Y H log.file.name: "filename" 4 Q4 h' R3 P+ O- b5 f% t
processors:
1 c7 a. \* N3 ^- i& |6 K, R batch:
) c W7 a* |0 h. X service: " t5 C7 m$ l& m0 n5 ^) |) h
pipelines:
% N! w$ g5 a: D' ~; \: A' u a' a logs: 3 L- ?% K0 B0 w. ^" g3 D
receivers: [filelog]
! |8 S& I! J. w5 T* K$ s processors: [batch]
& ], h' g5 I6 S& D* N exporters: [loki] : |8 L3 ^( [5 G1 I5 F6 r8 `
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 9 M, F. k1 n- v- h; }
总结
+ u+ D$ L% d! l7 z5 G* ]* ` 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
8 g& f& w! Z/ E' g& r 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
, L& S% T3 T! r0 _) f+ J) h. W8 L* `" h
责任编辑: * S) E2 L+ p- z4 Q a% f. G1 z
9 w% z: S- X" U9 ~" ^" z
0 \6 a% I- m1 X0 \ S% F$ L) c* w" I- c! O
- _. _* _! m, G, J
|