|
; P" v/ v7 e) W" j1 Y! b5 T! v0 M
原标题:基于 Grafana LGTM 可观测平台的构建 0 [5 w$ E* u$ j! q4 Q# K
[9 j; N) D# S b" Q5 F+ m) R
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 : u7 V7 T& K1 E+ z4 [9 i& b& f
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 ) g6 d* i8 b) i
通过本文你将了解: $ B3 t* z2 X6 e8 R3 w! T
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID 9 e# M% f2 U- B6 P
如何使用 OTel Collector 进行 metric、trace 收集
$ }0 B3 B3 v6 D$ w# T7 c& u* C. ] 如何使用 OTel Collector Contrib 进行日志收集 & ^3 f5 R9 F+ ]; ~# y" w
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 1 F" l$ s1 f- ], L' e
如何使用 Grafana 制作统一可观测性大盘 % n0 M' U1 ]" O/ |4 t( [+ \. e+ q
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
4 _, H( ^" h* O5 F8 \ 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
% Q1 [0 m& Q& W, D9 a$ Q 下载并体验样例
0 K1 K. e: c, U8 t4 Y" j7 l 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: # R$ h" N( H( Z) J) b
git clone https://github.com/grafanafans/prometheus-exemplar.git 5 W3 [& V# M; i
cd prometheus-exemplar # {+ `3 D% F$ h' v" z
使用 docker-compose 启动样例程序: : V) B, z m6 j, _
docker-compose up -d * z7 U& `5 u; m1 f% F
这个命令会启动以下程序: * n! r7 g! V& z' `1 m
使用单节点模式分别启动一个 Mimir、Loki、Tempo * K. K' a$ W7 n3 b
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
' A( N2 [/ S; H: s) E 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 $ `4 v& }8 m+ I n+ v& I
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 % `! m) W3 w9 v) t
整个部署架构如下: 1 a6 L- V! f3 y0 [- z9 N
 $ Y' c+ M7 \8 ` r: b6 Q
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: ( X2 |) t$ s P8 d
wrk http://localhost:8080/v1/books 5 V+ X8 q! c. P) ~ h( \
wrk http://localhost:8080/v1/books/1 # H9 h$ V5 b" \' m, z6 ?, j4 l
最后通过 http://localhost:3000 页面访问对应的看板: 6 t: P R% Q/ U( O4 X2 a% C# l% i

: A# `- e* _9 K4 m p$ y5 W 细节说明 * h( U" b+ z( X J% _( x3 r( [
使用 Promethues Go SDK 导出 metrics
/ p, j+ g; m$ A+ Q 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: ! N9 U7 z$ p* T4 d2 Q8 U
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", 5 j0 S( |9 t! c! O
Help: "Http latency distributions.", - p$ {4 X* u! J0 H& o
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
% i1 R8 F+ ?) v. l }, []string{"method", "path", "code"}) 7 i1 F) g; k) B) N2 y
prometheus.MustRegister(httpDurationsHistogram) ) ?6 c5 D" q; i5 [+ `; {! d
return func(c *gin.Context) <{p> .....
' R$ B5 K6 u' |% V; Q& Z" i observer := httpDurationsHistogram.WithLabelValues(method, url, status) 3 N) ?& `0 [+ Y/ ?8 E7 v4 j1 `0 t1 e# ]
observer.Observe(elapsed) 6 [$ I8 f, j3 p# P( r+ N, W
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), G0 _" ?% d- F. c
})
7 U7 J- `( ~+ y: X- J } % _4 T& y+ `4 u6 c' i
}
$ I( S# a- D. K/ w" U$ b8 E, l } + ~: D o0 X/ d4 a1 ^# B
使用 OTLP HTTP 导出 traces V( S8 n. L4 @, ~! f$ c; {& t
使用 OTel SDK 进行 trace 埋点: * d5 r. {* o: J8 [1 @
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") 1 I8 j+ J) x! y0 J0 ]5 S
span.SetAttributes(attribute.String("id", id)) 8 Z+ \/ f' M) n4 R
defer span.End()
$ i: }$ ~- T# } v; T // mysql qury random time duration
) A8 L0 n# _+ \# d time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 5 m- l9 L6 k! h' n% ]
err = db.Where(Book{Id: id}).Find(&item).Error # K0 Z% Z. e% Q! l3 b# T
return
1 @+ t) a g' `! n" L }
% n( Z; C$ n+ v! I2 ?1 d 使用 OLTP HTTP 进行导出: $ g$ m" Y# r* D
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 3 L- U2 O3 ]( l0 X4 w
client := otlptracehttp.NewClient( " c4 R, y) P; D( \
otlptracehttp.WithEndpoint(endpoint), 3 Q+ ~2 |$ c, P, d- B/ s
otlptracehttp.WithInsecure(),
' ^% {8 W2 G/ E )
* ?* k6 {3 ?4 v+ M# l0 u exp, err := otlptrace.New(context.Background(), client) 6 ?( {$ ?" ^4 U% `+ x0 d+ R
if err != nil <{p> return err
; O) j0 K5 v+ \4 N% C } $ F4 C$ [- Q; j, G5 v# H1 n
tp := tracesdk.NewTracerProvider(
; x: b$ S7 `- T% R8 J- I- x tracesdk.WithBatcher(exp), 8 m8 d+ y1 _2 h: D w
tracesdk.WithResource(resource.NewWithAttributes(
$ {, A9 {# l; s6 } semconv.SchemaURL, 2 ~+ {; n: }. `+ I% h, Q
semconv.ServiceNameKey.String(serviceName),
7 x6 n0 o8 u8 H5 t3 N5 z attribute.String("environment", environment),
3 s8 S6 w5 v. U& U )),
% i1 [6 y5 A: |, A. x )
. w' W: s0 C0 Q, \8 I8 Z otel.SetTracerProvider(tp) ' A5 ^; T5 I9 r
return nil . ~& Z' @9 }! \5 Y7 }) ~
}
7 z$ O! \. r6 P \ 结构化日志 s6 i2 z, Z3 k+ k) I! E! F
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
8 r0 |' C: ]5 B* t& I' D+ P5 j cfg := zap.NewProductionConfig() 3 o5 L V# e0 V+ j- Y
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} 0 f9 N& L5 O) |
logger, _ := cfg.Build()
1 P$ M- K# K) _0 M# \/ ^% ^# I) H logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) $ \9 K- s6 ^4 U y
使用 OTel Collector 进行 metric、trace 收集
) @. r/ d$ C s- i* K& m+ a 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 0 m0 H" o' O: q+ b6 n) |
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: ( E" i {+ ~3 f; r6 m
receivers: 5 j# |. o' w% M# `' z1 V; a5 u; |
otlp:
- A. U5 l2 I4 I' H protocols: 9 b7 Y2 j+ }: K, _$ {/ N# Q
grpc:
6 I! y0 v- a3 P2 S, S5 H Y& O http:
- U+ n. w2 c" E3 L prometheus:
1 d, [- v# M2 @0 d& l; p/ i config:
, D* |9 C% _) }8 l. I% i. |" ` scrape_configs:
% B1 H: v+ }9 v6 c - job_name: app
' T: C5 C2 J& l3 q$ C) B. o# F scrape_interval: 10s
9 Y; _! l( A/ a2 @7 {( U static_configs: 2 M2 j" c+ x$ n9 u$ e5 ]
- targets: [app:8080] $ Y9 o! {' a; i5 i" G6 l
exporters:
2 w2 x. J) o. v. [7 X5 P otlp:
7 [& c2 r0 y8 u3 v% P0 B endpoint: tempo:4317
3 h9 ^/ K4 ?) @2 m' b; X tls:
- T: x R5 \7 n; P. c insecure: true
" G; A! e1 m- Z, o f$ b prometheusremotewrite: 6 j h1 H" O+ C3 h6 E
endpoint: http://mimir:8080/api/v1/push , y$ ?# W% M- Y3 }1 N9 r
tls:
+ I6 j& u7 h7 `0 S insecure: true
" p- W6 [( |& N/ W# N headers: : q8 g5 i: O* ^$ K
X-Scope-OrgID: demo 3 u( g( G$ _3 w! q
processors:
1 i7 R: J+ b- q, X# S batch: ) |1 ?6 m$ G; Z1 t7 o4 K
service:
( G$ y S8 c, }- C4 I5 f; C pipelines: 5 R# j7 B6 K$ m6 a: T5 g
traces:
% a) S3 _4 F* j. M/ g* G) Z receivers: [otlp] ' i6 q5 e+ b) ~8 v' L c' X
processors: [batch] . `* Y- n$ g k$ ]" a0 W) n
exporters: [otlp] . g9 P6 {/ K0 j5 D6 i8 R
metrics: 5 E1 m; a1 k6 w3 Z3 p
receivers: [prometheus] 7 A' O$ P; ^1 v$ {
processors: [batch]
5 H& T" `; l# S2 x2 @$ A exporters: [prometheusremotewrite]
, ~9 C( w' K! f2 B5 h1 |* R 使用 OTel Collector Contrib 进行 log 收集 # Z/ a1 z! v# p+ Y9 z: a% e# h. ]
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
- y% g( g5 H3 r) i3 \ receivers:
* a% T7 P4 n+ X filelog:
6 `. ?. @0 ]) L# I% H5 d+ v include: [/var/log/app.log] ( x1 T2 Y' u0 T
exporters:
/ A6 }4 g$ e" U9 F" i. C loki: % v- s6 ?; d9 U0 |" V% a2 U8 r5 @
endpoint: http://loki:3100/loki/api/v1/push ^* {# F% d( ~: p# v
tenant_id: demo
, O' {% R+ B$ r- b* L9 q: A+ ]2 ] labels: & Y, h+ L' T/ a8 X$ I; D7 ]* s
attributes:
7 m5 g# O' B* A5 ?% Q log.file.name: "filename"
9 r0 c; }5 `. N4 W { E, i' p processors: + j' w0 Z9 n* |/ I9 u6 e* s+ E5 ~2 C
batch:
8 j) d; k$ b6 q# O service:
+ V) Z n% H" H pipelines:
1 _' n" Y- B$ ?6 A: A logs: - D+ h; ~# H# P; u3 ?( \
receivers: [filelog] . O. I" o! W) X! z+ N
processors: [batch]
! i; z8 o' O& f6 W+ J$ L5 x exporters: [loki]
- |: P! y8 C) [ x 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
6 K, I) Q4 p9 l9 E: d4 p J/ Y2 G 总结
, U$ `1 l; N s4 E" `) [6 ] 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
0 N9 s2 o, R. ~* b: h 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 2 ?0 j; N% @1 y7 i. \& F
6 S, H/ z! w# k7 T. _
责任编辑:
# c! L7 j& o8 w, N0 C
* p n0 S6 ^, x8 N0 @, d* d! `! v
+ j$ N- Q. P% e: x' _& e. f
@! _3 { i! P: G. N |