|
" a7 n# v# w* Y" F' z 原标题:基于 Grafana LGTM 可观测平台的构建 9 L! f4 W0 q" z; H- Q6 f. E
) c: s* c( ]+ O- p. S) ~ 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 $ `8 R* c* [. S3 G/ k3 F8 X, f
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
* e0 H8 o* K. B4 |/ k# n1 J+ I1 C 通过本文你将了解:
2 P& e% ^& F. P3 G( q* r" y 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID ! ~$ d" ~& Q, C @8 T4 D- T& k- H
如何使用 OTel Collector 进行 metric、trace 收集 , g3 R; e l1 M0 ]
如何使用 OTel Collector Contrib 进行日志收集 ; A4 ?9 v( c& t$ a: G$ w. N0 U
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
* X1 O8 }# [ c- n' R- x# f 如何使用 Grafana 制作统一可观测性大盘 " o" u& {2 `- g1 a/ ?
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
' O! r( d) S7 E# |: ~0 v2 O 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
& O2 r: O+ I' F9 X8 I 下载并体验样例
# {7 W$ h# I- P! s" H4 \ 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: ) \4 R. g8 p7 O7 T
git clone https://github.com/grafanafans/prometheus-exemplar.git
! u% u e9 r8 z- f% ` cd prometheus-exemplar
- k/ s4 O9 p6 K5 P/ b W0 x 使用 docker-compose 启动样例程序:
6 ?; f, E, C3 V3 h) P" K docker-compose up -d
8 W, @! L# M8 m! l5 L* o 这个命令会启动以下程序:
3 k; q& K* g( c8 a 使用单节点模式分别启动一个 Mimir、Loki、Tempo
; u6 K5 u. {: p& g1 J 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
& w" M& k: s$ B! w& \ 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
+ \! u8 b; o) Z& d" q: M0 o 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
2 P6 e$ Z( p& \- C9 n: D( d) Z0 c( } 整个部署架构如下: ; e! z5 p" V3 n
 ( v1 g1 E) ` y5 U
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
! a# E+ r x8 r: ^0 p8 G3 l2 l* z wrk http://localhost:8080/v1/books - R5 F) q6 {: h/ I; Z
wrk http://localhost:8080/v1/books/1 4 h. d9 \6 i3 b& D
最后通过 http://localhost:3000 页面访问对应的看板:
- e! t+ T8 \0 P  2 o2 i1 N2 R2 c; A2 [' Z6 z
细节说明 : k( @* d4 ^) L5 J* }1 F
使用 Promethues Go SDK 导出 metrics
! Y- X9 ]) _ f( T: J 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
! @& g5 W1 H/ O) Q6 ]/ Z- _$ V func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
8 p( ~+ |# ~( G Help: "Http latency distributions.", & ^- {8 E3 _# B2 ]( P* E9 L: `
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, 3 z' K. o% e& e
}, []string{"method", "path", "code"})
$ K$ f# I+ I o& k w0 M! g prometheus.MustRegister(httpDurationsHistogram) ( P) \, q8 T8 w, o, L
return func(c *gin.Context) <{p> ..... 8 i! c. _( E/ o; y8 ]
observer := httpDurationsHistogram.WithLabelValues(method, url, status) ! @' V3 V8 B% U+ t
observer.Observe(elapsed)
# E3 U& H5 R: C0 } if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), 5 H8 v1 r( W9 ^0 M$ ?
}) 0 @( g( L7 w( V! V a
}
2 t/ x/ V3 c9 m3 {* r/ H9 X } ; Q( v, w1 {) F$ Y8 a9 M
}
A+ V4 U# ]& y, n 使用 OTLP HTTP 导出 traces " D; f& b: Y9 j$ {) r
使用 OTel SDK 进行 trace 埋点:
! Z$ u4 @2 }5 B func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") 6 z& s$ H4 X& h3 o: Z$ a) r
span.SetAttributes(attribute.String("id", id))
$ u! ~& o& h8 `5 E+ v9 [ defer span.End() ' i1 K- k/ H, m: m6 [2 ], h) c
// mysql qury random time duration
1 s% M& s/ v- ]# P ? time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
& b% C5 g) w: k, I/ m err = db.Where(Book{Id: id}).Find(&item).Error
9 |! D7 w& y, _$ d: }5 i- C return
7 k7 g4 X; a, H( P4 t# {4 d% N } ( o9 b2 ^& n% `) `6 ~ [
使用 OLTP HTTP 进行导出:
4 M$ o9 r% }" ?$ } func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 0 E- u: e, T5 {
client := otlptracehttp.NewClient( ! N- ]0 y' T' X0 T* |
otlptracehttp.WithEndpoint(endpoint), 1 U; O* Q+ ^4 g. W: D% }, o
otlptracehttp.WithInsecure(), 1 X0 S/ z+ H6 D1 r# J0 h% o
)
6 T4 F% t$ I3 O& e. U exp, err := otlptrace.New(context.Background(), client) 2 c6 M5 k. i/ R6 N
if err != nil <{p> return err
& i" q1 n2 T- u: n( L# Q } $ g3 z# {- N S! F8 g# V& e
tp := tracesdk.NewTracerProvider(
; r0 I: I$ E8 W( ] tracesdk.WithBatcher(exp), " C6 P K. G0 b) G" U8 I
tracesdk.WithResource(resource.NewWithAttributes( 8 v* C- ?+ E6 Z' X9 N+ p
semconv.SchemaURL,
% J1 Z1 {. G+ f. w8 | a# K semconv.ServiceNameKey.String(serviceName), 0 i! Q6 o2 b0 F2 {( R
attribute.String("environment", environment),
$ H0 J4 s+ U8 N$ m" I6 u, w0 h1 M$ ] )),
7 w& }) y' _4 E5 s& _8 J ) ; C0 O; R, d- k5 O
otel.SetTracerProvider(tp) * ?' R& e* j' A. f! i; s
return nil
2 O. H2 z3 ^4 T7 q; ?, K& V }
5 A. \0 G# j5 y2 L 结构化日志 2 j/ Y" p# K) E `5 g
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: 6 |/ ^0 D( R% o" f1 b7 @8 o
cfg := zap.NewProductionConfig()
8 j4 o, H5 d7 e1 s; S cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} 2 r: Y8 N+ O2 P% W* o3 w
logger, _ := cfg.Build() & H' o; O5 y7 k; o
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) & E j7 S6 a' T4 q% I2 ?/ i
使用 OTel Collector 进行 metric、trace 收集 * J1 |" c1 y1 _) J9 i7 u
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
( ], C0 U8 w9 T5 ` 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
) D* R& o: C- h receivers:
: d% V" t, F% x* o5 j. ` otlp: : w/ R- O: Q' A9 E& j! ^
protocols: . k4 @$ A, f. c0 A/ W+ O+ G1 A
grpc:
& s! Y6 H9 x+ ?! _9 e' z http: * X4 `6 {% L( W7 F. Y2 Z
prometheus:
+ G6 G; O" y" F5 A% T config: ; ]4 I+ D' N, J0 h+ a
scrape_configs: 9 m+ b, Q4 N; E. d
- job_name: app 8 O( b6 H1 c) |& v: Q
scrape_interval: 10s
$ g" J* A! b2 g1 c$ o7 d* W static_configs:
9 V+ d; a- o* T$ M; P, u: Y) V1 K( p - targets: [app:8080]
; R1 }6 y" j; l% f4 `8 k8 r exporters:
) d1 J0 ?8 z! Z; c otlp: e2 {( k: `- m+ b
endpoint: tempo:4317
: M5 Q4 e3 f9 z1 Y4 s" ^2 f tls: 4 P' d6 {$ a4 C5 k0 R2 w
insecure: true
1 H! h2 ~1 Z! N prometheusremotewrite:
. n0 {' E+ {1 Z1 J# X endpoint: http://mimir:8080/api/v1/push
) P2 j: {/ s3 w- g: }, _! [) r1 K tls: 1 J/ e4 {" K- P2 o' j9 J& k F
insecure: true
# s# K6 m$ `: a7 C. \ headers:
$ k, y) Q8 k6 s4 e X-Scope-OrgID: demo
8 I7 J6 M% V+ U6 x5 [( L5 T+ ^& w processors: t* n. x8 C+ i* u) c
batch:
1 [$ ~8 f0 A0 G0 b" U, H6 y* s0 C- g service: / }+ a" n2 }, a
pipelines:
, M A, h( x8 j traces: 5 j, R6 _9 d0 l$ |" n
receivers: [otlp]
4 N: B- b' M. l7 E- N processors: [batch] / _$ c) t( i: W+ q- B& Y2 \2 f
exporters: [otlp] . g. B' x7 |' u" F. y
metrics:
+ D0 [6 @ O { I receivers: [prometheus]
8 l; c9 N/ U7 }0 h( ? processors: [batch]
2 K+ [/ m, j: b% Q7 s& m exporters: [prometheusremotewrite]
% v9 ?9 @! d) J+ R) v; ^ 使用 OTel Collector Contrib 进行 log 收集 3 X' ]& n U* b6 \! g
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
$ P. z0 g% Y; _; U receivers: Y3 r/ F9 ?6 b/ J; s7 i! ]
filelog:
' z0 B. q; M, C* ^, X" z3 p include: [/var/log/app.log]
7 R6 N1 c# z* L$ [3 N0 o1 T+ u8 } exporters: 6 B& M; u9 C; o' r. X$ c) P! [" A
loki:
+ L% K9 W8 A7 i" Y. y; P endpoint: http://loki:3100/loki/api/v1/push
8 W1 b+ K: a( y: I0 K: L7 }2 k/ W tenant_id: demo
1 |* K3 g: Q9 Q+ B5 a& [ labels:
: T& A' {+ Q, z: c4 i attributes: : n; j2 T- j& p9 i6 C% q
log.file.name: "filename"
2 C- R1 W, n. \4 S processors: % u4 q' U2 _3 ]7 r ?3 w
batch:
, j6 i% ?) \9 B9 K+ C8 U service: ( x) n' F7 H; i7 Y
pipelines: : g8 x- m; K1 T/ c) K6 o3 S! j5 }" @
logs: ) A8 t+ s( Q- K+ j% s8 G4 C0 Z, l
receivers: [filelog]
! f6 ~2 z \+ w processors: [batch]
2 @. r& G4 d- M& B& w- |1 V( N exporters: [loki] ! V# F0 [! \3 l; I
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
3 C5 w! L/ E1 { 总结 ! r" l+ c& Z/ K9 o5 I2 T( v
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 6 U, A3 x8 Q3 N/ O
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
) h. _, M6 q( e5 a$ w
6 _2 `; s3 f- O: M' d7 J$ u8 {) ^! a 责任编辑:
4 c% W3 N! L) n& {7 X
# C6 ?" t* L& G# s8 a9 g
% F6 B# K+ G3 V; ~7 T: O3 x3 P6 b" ]* Q1 t# }- g% h
' X& j- P1 l2 F/ } |