|
2 T* s5 i2 V, ~7 t0 r
原标题:基于 Grafana LGTM 可观测平台的构建
, t/ z1 L# _, ?7 L5 t& W) M
- O, {, _7 J/ Q# C 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
8 u3 {% i- ?. H+ a2 r: f 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
b& N3 g; k0 E 通过本文你将了解: ( N9 S5 u: [+ { U
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID - `3 f" [8 F, P0 Q1 ~
如何使用 OTel Collector 进行 metric、trace 收集 : p0 a. C4 y" ~. @6 f0 H* h: p
如何使用 OTel Collector Contrib 进行日志收集
' A8 _8 |) m% f2 R7 W 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 + k& U% z; L4 m& G
如何使用 Grafana 制作统一可观测性大盘 4 w, R) n5 b% M! k
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
! z. V- S9 f! c( u* v 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 7 S) g) n0 r5 U0 D- r
下载并体验样例 ' @" x) J1 ^- L* O1 b; v
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
# f9 `" m8 o, B git clone https://github.com/grafanafans/prometheus-exemplar.git
$ b# K6 i$ {% h; v. C% { cd prometheus-exemplar $ x( i' J) x# T- Z/ Y
使用 docker-compose 启动样例程序: }: J# _* \- Q' Y, E- }
docker-compose up -d
/ m6 c- T5 D6 l( D. p. y1 d3 I 这个命令会启动以下程序: 9 J' j. q. s, j# x
使用单节点模式分别启动一个 Mimir、Loki、Tempo
% }3 m3 R7 ^ L4 H. l6 T 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
+ @7 R3 l f5 r* n2 ?+ H; j& t L 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 9 d u0 b! h! _
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
' M$ z U, k3 e6 G' k! u 整个部署架构如下: 4 \) A0 n' f* \+ C& f9 n5 v& r

4 {" m( W* @- H$ V9 }; C 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 4 J& w Y! n3 O3 f& `# Y9 ]" W; y
wrk http://localhost:8080/v1/books # }1 R+ b5 f2 h5 o0 ]* {3 X0 M7 {2 P
wrk http://localhost:8080/v1/books/1 7 G" E7 D2 _1 p! h$ S5 M
最后通过 http://localhost:3000 页面访问对应的看板: $ N3 q; J4 m: @2 c/ z7 `

( I, T' f1 a: U7 x 细节说明 7 h' L, r1 e! @& i. @6 d
使用 Promethues Go SDK 导出 metrics
8 q$ U; j+ z; N; w 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
4 u* I) N" |: [5 p/ y func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", / A: V3 U) m: _5 X0 s
Help: "Http latency distributions.", 6 T6 l7 H2 ~ p. ^
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
. _2 ^' F( P2 y4 x! V }, []string{"method", "path", "code"}) 1 s7 O+ r- g* j( d: R- C- W. w
prometheus.MustRegister(httpDurationsHistogram)
+ ~& t9 F3 s; c return func(c *gin.Context) <{p> .....
+ L" u) F$ Q) O observer := httpDurationsHistogram.WithLabelValues(method, url, status) ' G" x! W6 F2 A7 j d$ o% J" A
observer.Observe(elapsed)
; Y2 G! D" o5 J2 B. u# l( \ if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
b' P3 L1 e4 l1 E0 |) T }) : \ `6 [ i6 C5 w& B
} + I. G9 O- g6 R
} . @) ^ B) k+ r$ u# o
} " w. ?0 _* H( L! t7 C; b, s
使用 OTLP HTTP 导出 traces
6 z9 s. r4 e1 N; N e 使用 OTel SDK 进行 trace 埋点:
, I7 ]' b5 q: Y! S func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
) h; h. \/ V+ J3 O3 }0 z span.SetAttributes(attribute.String("id", id))
' A) t# Q1 S" t- M7 K defer span.End()
/ @0 |, j, X: N) q# S // mysql qury random time duration ! R4 k% S: p- Y3 @% Z" B
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) + A4 ?9 B9 _6 f5 e3 n; K6 \$ {
err = db.Where(Book{Id: id}).Find(&item).Error ' F- ?7 U0 h/ M6 ?* r
return & x0 n) O9 T, C6 P$ {
} & `9 K' @7 t$ t; R+ _9 z2 o
使用 OLTP HTTP 进行导出: 7 r: H8 X( f* |8 @( e" L* r8 ~$ s
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
2 z( ~4 u( w% ?, @/ ? client := otlptracehttp.NewClient(
$ G( s( {& C9 u; h otlptracehttp.WithEndpoint(endpoint),
7 s7 \* Z' ?( o5 H' m4 C% B# V: s+ Z otlptracehttp.WithInsecure(), * G* B# K) p; {( d- ^" w* r8 E- r
)
( n1 t$ e1 C/ ^7 b# \( } exp, err := otlptrace.New(context.Background(), client) 3 l. d) f, F M; X
if err != nil <{p> return err
* `. C0 } L# o" Z# ~ } " G# t9 [% `7 a+ c3 i
tp := tracesdk.NewTracerProvider(
5 S" o" e2 ^7 g) q) N tracesdk.WithBatcher(exp), 6 R) A* ^7 K3 P/ e" e [$ `
tracesdk.WithResource(resource.NewWithAttributes( 9 G& P: Q! y$ D7 E
semconv.SchemaURL, ' e; ]& F# j1 k: Q7 z/ }
semconv.ServiceNameKey.String(serviceName), & Q9 W: `+ d8 E9 S
attribute.String("environment", environment), $ _ g" P4 _$ Y- ^! T8 K
)),
) ^: d- V' {" [% i8 [4 l ) 3 X# v( G! l8 ]3 a; E. t a+ `. k
otel.SetTracerProvider(tp) * X* j* f9 E7 x X3 @
return nil
6 B, t$ f$ ^) u' @# U6 f9 k! ]( e } 5 j1 q" I6 @0 i4 \8 Y; S4 T% X' R
结构化日志 7 J: {1 Z. i5 q& _6 ?- ?* I
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: & s4 [5 L9 Z5 I1 _& U* T: O- {6 j( w# [
cfg := zap.NewProductionConfig() % O; w( d% g9 N! Z- T6 r
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} + Y! B. _* A$ F
logger, _ := cfg.Build() * t- o* v+ Z$ {9 }3 U# P! V
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
4 o& M, c/ K$ X& X' b" Y ?5 g Q 使用 OTel Collector 进行 metric、trace 收集
" N5 p, P2 a! N) `! g9 a 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 7 U) M% u; m: k$ z1 C2 x
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: 0 O2 U) ]8 ?) x' R7 C M
receivers:
& [/ C) p6 f- ]5 I4 } otlp:
$ |" U7 e# L% n2 s% I protocols: + |% P# p, s3 c' U: \
grpc:
- `8 k6 P9 q( K$ x; Y http:
8 {0 o( E, W( ^7 N# b- K8 `2 g* U prometheus: 6 w1 C9 a( p0 U4 O2 n& Z8 ^
config: * K6 v, B! X, E" C- f6 K
scrape_configs:
% t' M. X' a8 T, @ - job_name: app 2 s7 a0 c9 O* v. p3 w; `
scrape_interval: 10s 0 O8 \- J P7 X' r) n& {: T
static_configs:
8 D; E* m0 C% p0 q( Y# {# | - targets: [app:8080] * E. c& v# W, n( n1 U
exporters:
( I6 m9 c2 t3 U; i- f otlp: ' y9 j# a4 z* w/ C$ |
endpoint: tempo:4317
5 b! Z* g& ]+ L8 M. h5 \6 r: i" c tls: 0 S" S R; X% Y5 w- m, g% H
insecure: true
4 {% Z) S/ P. s5 ~: K prometheusremotewrite:
# {! ?5 f$ i, ]6 Q/ l endpoint: http://mimir:8080/api/v1/push
1 \1 D' ^1 B8 w a tls:
' Q5 l' g. b3 V+ f! c2 ~, l- ` insecure: true 3 }) y/ [% B0 z8 z0 P) v
headers: # H/ r' @& T& i/ V$ v
X-Scope-OrgID: demo - M- S6 A# [: V( r0 v8 E; O
processors: : {+ d. P8 X, `
batch:
+ b; b( Z7 g R& V( u+ r5 @ service:
% G! j# L! |' r' i' H# |/ @' X3 Q4 C pipelines: : h6 a9 o7 K- ~* V- u% h: N
traces: * b8 \7 f" \' l6 t' f/ H! l
receivers: [otlp]
0 o& i6 ?$ f' \; \ processors: [batch] 9 L5 A: Q7 w8 F! R% O
exporters: [otlp]
' |9 v m0 l/ E/ g$ C0 q8 E0 K" U metrics: * N" f5 _0 z n. B( G! e$ J
receivers: [prometheus]
7 `/ f, R: H/ l2 r processors: [batch] 8 ?2 V. U3 G( ~! p
exporters: [prometheusremotewrite]
' S3 M- K" b3 E/ q! e( L 使用 OTel Collector Contrib 进行 log 收集
) `9 D7 }/ ~* k% [2 l n. B& t* A" A 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
3 y2 i$ D P( M F; v$ \7 c receivers:
: u2 x: Z" C8 W9 M& M' I' I filelog:
; o Z/ o9 h r8 d+ N( B* N include: [/var/log/app.log]
; h8 e) A" e; S5 b- V exporters:
/ {; |7 O$ L7 ~, e" I loki: & b5 ?1 Z. C7 g/ f( B
endpoint: http://loki:3100/loki/api/v1/push ( C% \& _3 Z& [) \: n/ Z [
tenant_id: demo
! m% _3 ~, ]# G' L labels:
; }1 Z" J, W3 C R Y; z attributes:
6 o6 e" _" |. L0 a+ N5 i6 i log.file.name: "filename"
. W6 ?5 i% x- F) l$ B: i processors: ; v7 j \( t, v% `
batch:
' I7 h. J0 r h: u. h service: 0 ]0 s5 Z3 H) n `- |. q0 H
pipelines:
' q8 ?* H; J5 _. q! L7 G logs:
, G6 [& [3 j( s( Z( D" Q. h receivers: [filelog]
! e# N; S' d$ q6 @ processors: [batch] ( }& g5 o( X2 A" o
exporters: [loki] ) k8 ?, F; d. e' I8 S/ J( V
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 4 }% l0 C* \* l- [0 j7 ?/ E2 E
总结 % a$ C( Y% r/ U7 v* j1 |
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
4 f5 _( B# G" D. X# u 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
% m6 d; S! n) g' F8 N j% ^1 F0 b0 E. v, x7 ~
责任编辑: 3 R# s, T6 U2 C% @
0 V* x, v/ r: t0 a2 h2 O: U
, z1 M9 `4 C* p
" L9 W8 o- T& K" H; p
) Q* I& b9 i, r |