|
: d$ t0 K: K7 ^8 \ d 原标题:基于 Grafana LGTM 可观测平台的构建
: F( F. D+ }7 C# z: p: A6 m% f4 t, G( Y- T" M' b" F' T2 E3 d- N
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 i3 d, F: J. I1 u8 T! p5 k7 W$ G
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
! O2 D0 e+ ]$ d4 l; M, ~ 通过本文你将了解:
N# X0 B. v% }9 Y: v 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID f; J$ P4 s" L8 t' s
如何使用 OTel Collector 进行 metric、trace 收集
* W7 j4 D \" e5 j9 P2 r8 Z 如何使用 OTel Collector Contrib 进行日志收集
H8 k9 X k8 y+ C' k7 F- f n1 a 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
8 {* `: z( A1 q, T% b+ i 如何使用 Grafana 制作统一可观测性大盘 ; u( } G3 X! ?( J
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
Y. R! m ]7 X 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 : M; |( a7 ?, ?* p7 a
下载并体验样例
& l* V( G" C# J1 d3 _5 Y; M7 F) m 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: % r7 K6 Q. [# _
git clone https://github.com/grafanafans/prometheus-exemplar.git
4 S: D) k1 d. R9 n5 B9 s( Z% ] cd prometheus-exemplar
* G0 O) k1 d6 O/ X. R }4 I% ` 使用 docker-compose 启动样例程序: + P1 d, q8 j% y2 C a& C$ S7 Q
docker-compose up -d ) \# W: U" q) x* k
这个命令会启动以下程序:
2 b2 t2 `. [' n' O( w& A 使用单节点模式分别启动一个 Mimir、Loki、Tempo 1 P0 R B3 c9 Q9 r
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo $ s# m0 [7 `+ Z
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
- n" y; e/ s7 @- Y3 U 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
8 x$ J) ]# x Q- q$ N6 q& L 整个部署架构如下: 0 O7 s, z3 V* y) w

+ e' ^3 {0 P+ k" M! B4 h 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
% G" `6 ^! W9 k- L wrk http://localhost:8080/v1/books " y* Z2 F/ u/ V& E! p; j/ ^$ c
wrk http://localhost:8080/v1/books/1
0 i3 N3 J$ r% Y- u9 Y) c 最后通过 http://localhost:3000 页面访问对应的看板: ! J& s! a2 K& U6 M
 1 b4 X+ [. S- W: @) ^
细节说明
( L9 @' F, t( B) _- {, j1 l 使用 Promethues Go SDK 导出 metrics
! W' g* I$ k; X0 j/ N 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
" Z0 I: @8 m* I+ `/ I/ n) u. T func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", . r+ ]: ]: |4 E) B' L
Help: "Http latency distributions.",
0 \- z4 Q$ l0 i/ [ Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
1 P5 o. j: }% d8 p }, []string{"method", "path", "code"})
. u v5 P: J/ H ^; P8 L prometheus.MustRegister(httpDurationsHistogram) * Q* d! R _; y! A( w/ n$ o
return func(c *gin.Context) <{p> ..... 8 z6 V; j$ T+ K+ |& m+ j
observer := httpDurationsHistogram.WithLabelValues(method, url, status)
9 | w1 }" @6 F7 I; m observer.Observe(elapsed) ; a/ u0 s% f% {( j' c
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), # F. d1 y& `4 N* {
})
: w1 J4 o p1 x5 l: w! b: a3 V } 5 J2 V J4 [! D: s: z9 ~& }
} ) Z3 a( z; `, E- S6 @
}
& T, g6 \/ s, y& e 使用 OTLP HTTP 导出 traces
1 d3 u5 G, T7 o 使用 OTel SDK 进行 trace 埋点: 6 @0 _2 U3 p6 b# [6 b, \
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") 3 N; p, k: @" s" I, \/ m3 U; x9 O
span.SetAttributes(attribute.String("id", id)) ' e& E: g! n, J
defer span.End()
: @( w& |2 S8 s q/ C. J# K, B // mysql qury random time duration 0 H& U0 u$ R, ^+ l# m5 w
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) ' e1 V+ D% E9 y
err = db.Where(Book{Id: id}).Find(&item).Error
) b1 j$ F- B/ Z3 m) j- Q return + ?. c, s* E' f' J, r' u
} . M" K1 e3 c, t3 K# Z8 L/ @
使用 OLTP HTTP 进行导出:
( n" F: p& d' ~) W8 `2 n func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
9 E, w+ M& X1 ]! E- r client := otlptracehttp.NewClient(
' ]2 }" O* G; _( ]8 w otlptracehttp.WithEndpoint(endpoint), $ R2 ]: s% }% E0 Y3 I
otlptracehttp.WithInsecure(),
% Z3 H( S2 ^+ X$ h. i# J7 L6 D ) ' O4 d, W Y: R1 P. Z
exp, err := otlptrace.New(context.Background(), client)
3 `7 N) ~0 B9 [/ X( f1 k$ }: H if err != nil <{p> return err
( A/ V8 W0 l7 X0 f } ; o; s. J8 J* Y1 C2 O
tp := tracesdk.NewTracerProvider( . m7 v. f% p' ?8 [ J4 P
tracesdk.WithBatcher(exp),
8 J$ ]- b4 j( y6 |4 _& Q$ \! D* G tracesdk.WithResource(resource.NewWithAttributes(
4 f. z! C- i& g6 {. C) e' I semconv.SchemaURL,
- R, _" J9 |# C9 K1 C1 N6 y semconv.ServiceNameKey.String(serviceName),
2 k# G& V p+ [- N2 r attribute.String("environment", environment), 1 E0 [6 P- t' H* X+ }# m( N
)),
1 I8 b9 Q. u5 ], Z' R )
/ r' `& B3 N3 N+ b* k otel.SetTracerProvider(tp)
0 c8 c7 {0 o3 U- S& J5 j return nil
! M: N+ {& u' U. L }
5 V7 v- |2 C" Q, X! L 结构化日志 2 d9 ?% r" V/ T3 r* ` ?( C c9 }
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: - x, d& g3 ~! j7 q7 u4 B# w
cfg := zap.NewProductionConfig()
8 j0 F! R4 ?) a% @% u6 B cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
6 H; u" G5 d5 R% q+ l( r- f logger, _ := cfg.Build() : P, [# [9 k ^4 W
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
. O+ A7 ~2 Q+ U# r- h8 b 使用 OTel Collector 进行 metric、trace 收集
" S) C( _9 Y9 W6 a" t3 q% U% N, z 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
7 J$ x6 }" T+ h/ Q0 ^/ @ 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: ; _ i3 S4 V, [
receivers:
9 o! L* B( s0 x* m- i# y {0 P otlp: * g1 t8 o* ^* Y' c3 x+ G( [
protocols: 1 A6 ?; n3 c( V* A3 c6 w, a
grpc:
. c, p% \' v. E3 Z- l- W1 }+ N1 K http:
- k; l# K# R! h# \& N4 ~9 B6 s. p prometheus: 0 A V' k# m/ n2 u# w4 v3 @
config: & V4 U# x g9 a Q. @
scrape_configs:
: I+ v: V1 l+ ^/ \) |4 t, M3 u - job_name: app 2 W# `6 a# @ r5 q( A
scrape_interval: 10s
) X" I9 o$ u" R+ P3 G. f% r8 k static_configs:
' L2 J$ U" ~1 r1 U. l; c' O J - targets: [app:8080] $ x) n2 }. v% H: W: C
exporters: 9 j- K/ D/ [: z
otlp:
! K. N: a" b6 b8 Q3 i" ?& O" \# C/ Z endpoint: tempo:4317
# j& l9 `; _/ V7 M% f$ x2 H9 z/ n# } tls: $ X6 T9 A& `2 S
insecure: true
0 y+ B/ b& z5 q5 n3 k, _- n prometheusremotewrite:
; W4 O2 w6 w- P0 _+ W6 ^ E0 Y endpoint: http://mimir:8080/api/v1/push
% M, t; `3 B3 {) e/ z tls:
1 ~* a- }# k4 R* A insecure: true
$ R1 }- n" m; r% Q! h3 O+ y headers: 7 \& z$ g) Q( Y! K% O' w9 Q
X-Scope-OrgID: demo
. x3 P' X' A( d: k' | processors:
5 y. h! u! c4 _/ U# a5 N' y3 z3 Y batch:
/ G$ c: l2 P% {7 V9 M service: 4 V1 g' f9 L7 ]5 }1 I& r( B8 d
pipelines: ) ^ q0 i7 j& ?; a$ ^8 T0 I) F7 W
traces: 5 }4 w% h/ w; R. H% O5 E7 K
receivers: [otlp] * s p) B( w2 O0 e
processors: [batch] - X- M( O0 }3 |# O- A3 V
exporters: [otlp] + ?2 U2 }2 m1 U: j& h C8 ~
metrics:
# M+ c/ A/ {8 o/ [/ C9 P receivers: [prometheus]
0 O( u5 s' R/ C3 d3 k processors: [batch] 2 i8 x/ P4 V, v. j+ b9 u
exporters: [prometheusremotewrite] 4 Q, n. v0 m: ^8 [* l. O5 R/ f
使用 OTel Collector Contrib 进行 log 收集
9 I+ j( [3 z( Y& y, @6 U2 v& i 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
0 L" O n% l6 q" P+ U receivers:
1 ?* R$ b2 z& C( R5 ^ J9 c8 ~3 L filelog: $ {& x$ [$ H, l1 t& d5 J% j
include: [/var/log/app.log] 3 P6 @9 G4 `' G5 l% L/ }- a7 o
exporters: 1 C {5 K7 l$ M& V3 u: d
loki:
3 ^: \# U( s% u endpoint: http://loki:3100/loki/api/v1/push - ]0 y4 l$ z7 Z) ~0 Y8 M
tenant_id: demo & u+ z7 k* d0 p! J
labels: % o w+ h) l0 w
attributes: / z+ {+ t9 `$ g( R8 b: ^8 T; d
log.file.name: "filename"
m: n) e" V) P7 r" l processors:
# g3 P+ V! D+ ] batch:
( F- K9 M% ~1 w service: $ W2 J4 r6 `2 ^
pipelines:
( W+ y9 Q4 ~9 g3 T logs:
7 Z8 S: A3 E& l' D receivers: [filelog] ; u- |# ~: l0 d* y* f! P5 B
processors: [batch] X* }6 ]4 Y. d3 W/ u( I
exporters: [loki] 0 _: U7 [& m$ G$ R8 b+ I( K
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
7 [/ y# F' u: q% Y, @7 } 总结
z8 a7 M7 [# N 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 - y: Y: m A2 w4 ~2 t7 T
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 " a/ S5 p, J! c0 T3 M7 {0 D
# i7 _8 D1 [+ k0 B0 q. C! n 责任编辑: R# U) p) v8 s# Z1 F0 B
6 f% O8 O- `& Y& P+ m
; V) D2 y9 }: p* [$ V& L" P8 r% Y$ S
3 Q& ^ F( f: D! s+ Y3 |
|