|
7 k# q3 G7 D: x& B
原标题:基于 Grafana LGTM 可观测平台的构建 8 I) x' ?4 Y! a! _( d( D
% ^" `1 X9 x) E4 U4 t% X/ e 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
6 b- _! ]+ G9 U5 E% Q 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 ! E; A( S1 @' x2 T
通过本文你将了解: $ Y: n- V2 |0 R+ T8 k0 p
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID ' O) X }: R/ \* o4 z
如何使用 OTel Collector 进行 metric、trace 收集
8 p6 m5 b( x( \ 如何使用 OTel Collector Contrib 进行日志收集 ' T! Q7 ]6 ^# m: t3 a6 v
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
S7 P- K Z3 }* ~( U8 C 如何使用 Grafana 制作统一可观测性大盘
8 w2 Y s) B/ d1 G. I' V 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
% K4 R/ y$ m+ @& J( C 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 , V6 |9 b' x* {) h
下载并体验样例 5 C& m m- _+ {) N/ u) `/ V+ H
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: ; I7 j: D! I, i) m* h5 E8 ]
git clone https://github.com/grafanafans/prometheus-exemplar.git 2 p: k" Q8 o* C: s5 P1 e
cd prometheus-exemplar
# Z& _8 h# d9 H4 ~# n- @( {( | 使用 docker-compose 启动样例程序:
% `( B, S8 O1 U3 B# B2 d docker-compose up -d ! D z9 C% `, U: y/ T9 U
这个命令会启动以下程序:
) @! f( R8 w8 B3 ^% u% x 使用单节点模式分别启动一个 Mimir、Loki、Tempo
1 j8 Z% `7 T. S i3 |$ B u 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
& E1 V/ G& F8 e. Q1 f+ w 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 3 T2 S e2 y- D8 q
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
: b5 o3 `8 d% ]. h' y: D2 [ 整个部署架构如下: 7 F2 o5 @" ^2 {+ g$ c
 7 K" ^; j) U* `& S1 m j6 R2 s
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
$ M2 C* V2 h( v( g: e wrk http://localhost:8080/v1/books
6 z' D( L v+ k4 B6 q3 b/ I3 r7 w wrk http://localhost:8080/v1/books/1 % y& E4 Z% j) f) j: x* s
最后通过 http://localhost:3000 页面访问对应的看板:
0 a6 x% t% X3 C/ B$ ? 
7 J4 A$ n& a! e 细节说明 8 r. ~7 u( X8 l
使用 Promethues Go SDK 导出 metrics 2 ]: r1 i& h9 g. D+ S5 X
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
$ a a+ G0 ^, h, ~9 D func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", " B3 d0 Z; K- a$ r
Help: "Http latency distributions.",
+ O8 U3 d" \+ @6 {. d' J Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
* K* O5 h, X1 s+ j) ?6 m }, []string{"method", "path", "code"})
2 l. P& u9 L& |" ]5 ~ k prometheus.MustRegister(httpDurationsHistogram)
+ ]* F# W5 Z" m9 W6 A* j9 n. o, F return func(c *gin.Context) <{p> ..... Y, q/ i3 U: K7 w$ K% c" {
observer := httpDurationsHistogram.WithLabelValues(method, url, status) / x2 R% B( H/ }$ ~) \" D
observer.Observe(elapsed)
7 [/ N I+ ^! p( E7 G if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
$ n; k3 g. R$ D# h% m, l }) g+ ], a- y( _7 w/ U5 p: M/ |( Z
} . P' k$ i/ t A: I# P0 \7 o
} ) |# o2 d6 a6 S
}
3 b4 m9 r6 q- E8 { 使用 OTLP HTTP 导出 traces
7 n5 P2 E+ z) K; p& t2 N* _5 e! h 使用 OTel SDK 进行 trace 埋点: : P0 f( Q. j: n- j6 _
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
/ v" V1 a) m4 m! P9 `2 T7 u span.SetAttributes(attribute.String("id", id)) : _7 Q7 K2 e' \# S: `
defer span.End() ' f; I V$ t- l, O& b/ n' O
// mysql qury random time duration
N6 A O% ]; ? time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) ( B, z7 M- }) Y. H! Y
err = db.Where(Book{Id: id}).Find(&item).Error
4 B" j* x# W3 Q5 P) f$ Y. m4 z return
% V+ o- [5 E6 W) T5 {8 J+ C8 [ }
# d3 ]" l$ D3 s' `3 U' U# I 使用 OLTP HTTP 进行导出: 4 q! k" `# x$ A( O' Y0 H, I& v
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name v3 `9 L& H( [, [& F# ^' f" T/ i
client := otlptracehttp.NewClient( 3 I# B- n3 W) a) |+ q3 Y
otlptracehttp.WithEndpoint(endpoint),
! v8 b5 V0 O7 g& r9 ^0 e) e+ L: T: ` otlptracehttp.WithInsecure(),
8 k( ~( y( K; e4 u5 e! |+ u2 ]* y$ C/ T$ P ) ) D- W* X m- c# n# P
exp, err := otlptrace.New(context.Background(), client)
' y/ m5 b% z" y9 `; x7 K if err != nil <{p> return err
( a' j; N7 s# v, [. H4 Z } - @5 s; H. k8 P8 }1 a8 }
tp := tracesdk.NewTracerProvider(
& D% |. N! C6 j$ f1 e tracesdk.WithBatcher(exp),
5 U+ F0 l! w# k( W tracesdk.WithResource(resource.NewWithAttributes( & a# }6 n# J7 ^, q0 _; J9 H
semconv.SchemaURL, : ?6 E5 Y, {& \* a
semconv.ServiceNameKey.String(serviceName), 9 Z9 l6 C1 T9 V5 D0 ]! z
attribute.String("environment", environment), 2 \; B2 I; L$ R4 E
)),
- I) i( v0 x) m )
# L9 U2 d% w. m4 S otel.SetTracerProvider(tp) , J4 r' ^. H6 }2 h2 D
return nil
: f: Z0 P. y \. |/ u& ` } 6 C, t+ Q0 E4 t: _* T
结构化日志
* K# ^2 Q$ P* s# N5 u 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
# h b( g7 R, H cfg := zap.NewProductionConfig()
( y" c: t% v$ z1 n* C cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
' X! h: d" E! h logger, _ := cfg.Build() * t' @3 h0 t F! `
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) ^# t. E$ `: `" j0 T$ L
使用 OTel Collector 进行 metric、trace 收集
; h( ~ Y; u6 C& X4 Q# L8 E 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 9 u$ o: K" c T, R4 R$ |+ k
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: . k2 w$ S( O+ a$ k3 X' \) G( r
receivers:
/ j2 `4 Y* k1 o( ]3 m1 D1 h* |1 P otlp: + {2 K) T& r# h r6 }0 F/ T
protocols: 5 G" q/ P( d5 R# D
grpc: + O/ d& v6 y$ U' `! [/ k
http: * Q/ f3 x( W* }6 F
prometheus: 5 f9 l) X& I/ q: A K
config: & r. V+ T, h# Y, ]
scrape_configs:
! H% `) J) ?6 l9 E: j6 E* } - job_name: app 7 O0 a: k% P. t4 r
scrape_interval: 10s + x# k0 b/ j3 X g" ?! _5 [
static_configs: ( Q' [; ^3 _, J3 o
- targets: [app:8080]
" n. ]0 @. \) u: }9 e exporters: ' M( I S) h9 c" q
otlp:
% f2 Y% ]7 D& @8 R. o }0 i endpoint: tempo:4317
! K6 I$ V# V2 k* N! Q6 Q tls:
6 `4 l6 |& m7 t+ q1 o( \ insecure: true * i5 |( ]/ y# K3 Z, q7 v
prometheusremotewrite: + e7 ~1 C4 r& Q9 \6 o
endpoint: http://mimir:8080/api/v1/push
: W$ i1 X: M% c& w1 [( w+ s! o$ l tls:
- d5 X% c) n) z5 q! J0 `: R9 R insecure: true ; g. s! W: l% { `6 k2 Z
headers: , q: g+ P6 e& K0 F) y
X-Scope-OrgID: demo ) H/ y1 O4 I, G0 R. k5 \
processors:
[# b$ d. B9 Z2 U' B/ H- F batch:
9 B. Y, z, h6 Y* M3 v6 e1 G# P0 U service:
1 y! _ E% }* Z3 L5 f8 I pipelines:
" p2 [* u/ w% ]3 u1 m; {( j" g; s traces:
w( C! _( ], v5 P( K receivers: [otlp]
( `; A% A4 j4 P processors: [batch]
* a2 f. ~4 @( E) i5 ?" y& w5 G exporters: [otlp] / ?. t" I7 V- l
metrics:
0 T/ m- b- k9 W- n receivers: [prometheus] . ]2 E/ O9 F$ ~) k! e `5 B5 b
processors: [batch] # q0 g* o+ z5 H
exporters: [prometheusremotewrite] % T r# U* ?) O) i5 g: {2 f
使用 OTel Collector Contrib 进行 log 收集 & l! M8 a: J) c1 _% q! L- V
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
& s$ Y6 g/ H1 u6 o% d receivers: 4 n' H% E i8 _8 P
filelog:
# z, d/ H0 q' @# U4 I, g include: [/var/log/app.log]
; \* v; _) l8 l9 L- d0 l exporters: # U# m& Y" c) Y0 p
loki: " a, t4 t1 j: p2 {4 i* x2 S6 z+ m
endpoint: http://loki:3100/loki/api/v1/push ! ~' L( ?& b7 k: m6 ^3 E
tenant_id: demo
0 h' A0 m8 m- s3 J* a labels:
' f7 x f4 v- u; z1 q4 ?! { attributes:
& _7 g6 L0 [% h( R/ J4 `: J log.file.name: "filename"
6 l2 s# M4 X& @3 ^1 Z processors:
! Y* M' G' Z3 R% O$ x8 B batch:
/ Y l, p, f9 Z$ G service:
. T9 z& W6 z3 v/ \& k, S/ G pipelines: 0 P `% B' g. P- ^( f7 M4 s; ^
logs:
+ f& a2 d$ U$ s9 t% z& I" I7 D! P receivers: [filelog] , u0 U7 l6 I3 g \' _) X
processors: [batch]
. m& K6 m- l1 l7 M9 E C# | exporters: [loki] ; \9 z/ v4 s& _% {
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 ( O" E6 T) f! t
总结 ) w4 k+ k6 |# } R9 `8 Y
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
1 H; S( _6 s# R9 d 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
5 I' D+ C+ _1 i' r8 B3 {9 N
F3 { w* e$ k r. K1 e1 m 责任编辑:
; K2 k5 ?' r. S6 n, b
2 w. _; R2 ^+ [2 x7 |, D! t4 q! z! f: X, {( L' u) f" S
4 `5 D0 {1 P+ k! D
5 W0 a# [, H& B9 Q
|