|
. ]* k8 }! d) w) @! y8 X a; q
原标题:基于 Grafana LGTM 可观测平台的构建
6 ^+ r- b2 Z2 Q$ m0 x8 [+ o+ z
# g, W! P3 i5 i. n3 F6 L8 Q 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 / T4 y5 P' i+ J! e# c* j6 a
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 # N, F+ E" ]- @; U! d
通过本文你将了解: ; K! H; {7 h. c3 ]3 B0 ?8 Z
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
$ Q- L a$ h" f( ]- Y4 a1 j/ x 如何使用 OTel Collector 进行 metric、trace 收集
- _ w7 o+ m$ f! f. X 如何使用 OTel Collector Contrib 进行日志收集 " J0 X$ f" _# s# _- o
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 , [/ P( y' M1 h! N8 Y: i; ?
如何使用 Grafana 制作统一可观测性大盘 $ b( S: W! ?# ^! I8 U* M5 U
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
5 H, \6 X* o+ q0 u0 | 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
4 F, H2 O8 L* Y8 l% e 下载并体验样例
- ~4 P2 {7 C2 c: L- ` 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: : s. q, ?/ U. g. j0 A
git clone https://github.com/grafanafans/prometheus-exemplar.git + c+ l! _4 |0 ]* ^
cd prometheus-exemplar
6 @/ t7 l0 V$ c8 z5 O 使用 docker-compose 启动样例程序: " J# F, o3 ^3 A% J& I7 p6 g) J* R
docker-compose up -d 1 z- N9 J& T$ o
这个命令会启动以下程序:
8 }' N* _( t I4 Y+ x' c 使用单节点模式分别启动一个 Mimir、Loki、Tempo
6 _/ w& F# B; f. L 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo N$ t5 D- V" @# l, Q% T
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 1 B' X4 }/ k1 e6 |/ W
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 2 R& Q7 M! d$ T
整个部署架构如下: 3 K) d4 ]" B/ x8 a

! h% h7 U+ c: N, U% D7 R 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 9 C, r1 r! P7 L
wrk http://localhost:8080/v1/books % N* z, d/ S- g( t& f, p1 j! D8 c& d, `
wrk http://localhost:8080/v1/books/1
" x6 }( l o) R- F" g- R 最后通过 http://localhost:3000 页面访问对应的看板:
- T0 T1 J$ ? W' v' U' ?, P8 \  % I2 T, E7 J1 u: e
细节说明
; e6 U0 ~ G8 ]" {# ?! B' A: s 使用 Promethues Go SDK 导出 metrics
# |8 ]! u# R! r9 g" y 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: ' Z8 O& V V) t; i6 N
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
7 J- m+ z7 Z( I0 h$ R' E* v Help: "Http latency distributions.", 6 a: }, _4 r6 H( e3 n) o9 j; O
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, ' [$ r4 i: I( @+ B2 I
}, []string{"method", "path", "code"}) : I( O* ^8 {) s K" |" \9 }
prometheus.MustRegister(httpDurationsHistogram)
3 i- {1 V2 \! W. _ return func(c *gin.Context) <{p> ..... ' y& E$ K. F: `* E+ E) Q8 q
observer := httpDurationsHistogram.WithLabelValues(method, url, status) ; M) b8 P# P' U! G0 t: m
observer.Observe(elapsed)
* E `6 o" o0 {4 ^0 H if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
5 ~& Y1 f9 | h; k })
# d3 H% l- ~9 q: [# j/ m' r } % i5 z3 i+ x; ?* n- A. X6 j! t: G
} 7 f9 ~( H/ ]0 _7 V* ] a! u8 d
} ! a5 K; n a5 Y( q9 b, ^3 s
使用 OTLP HTTP 导出 traces
5 i3 B( p4 ^6 |7 G% Q$ F/ ]& c 使用 OTel SDK 进行 trace 埋点: B+ ~ S1 e- o' y
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") / u" l4 r! V' _
span.SetAttributes(attribute.String("id", id))
6 u# }5 @5 o* D2 e1 R8 U+ E defer span.End() 9 d! S/ O3 t0 f' R& h1 J s, y
// mysql qury random time duration F: O' t+ | c0 }2 u1 a* e
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
/ ^. {6 `# o0 Z: l9 ~ err = db.Where(Book{Id: id}).Find(&item).Error
5 [ }: `) }5 x9 I3 s return
5 j3 s0 Q$ T( @; V9 M }
% X$ e; A) _8 ]- A$ V: s 使用 OLTP HTTP 进行导出:
3 w/ X+ T, ]( } func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
! t! ` b; O3 }; _ client := otlptracehttp.NewClient(
. W" A* h0 U7 M. _0 ] j0 m otlptracehttp.WithEndpoint(endpoint),
6 P' X0 n) s/ L t otlptracehttp.WithInsecure(),
5 I. H6 `8 b4 b )
3 M: b0 f* X- R% Z0 U7 X$ G+ D exp, err := otlptrace.New(context.Background(), client)
2 K; C2 \! n5 |6 ]6 O* z if err != nil <{p> return err
' o4 j7 Y( ]$ X2 A- | } ) e& [! l" ~1 {2 W9 q. K
tp := tracesdk.NewTracerProvider( / |( f$ |, H& z Y0 A+ z. w
tracesdk.WithBatcher(exp), & R- g: X1 D& e8 Z
tracesdk.WithResource(resource.NewWithAttributes( # |* `2 A9 g1 V+ l8 `, `# O3 [" A
semconv.SchemaURL, ' F* B/ x" I7 t9 s2 i
semconv.ServiceNameKey.String(serviceName), $ L5 c5 u2 H' i% J, K
attribute.String("environment", environment), " @1 G8 H( p" X4 D$ h% l% @
)), ) ^" P8 {2 q. _( V# Z b T
)
. T1 c1 @# t) R$ c7 `( H otel.SetTracerProvider(tp) 7 n' }+ b$ l7 C8 F* A. i
return nil * R. d/ {0 h. \ a3 G5 `2 m
} 9 H( Z6 v1 x0 u4 ^ A
结构化日志
) Y& U# V5 z9 X( I2 e6 V 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
: X2 H6 ]+ G# K cfg := zap.NewProductionConfig() 1 [. m2 p- G; g; g( O& W& G9 v/ O% Q
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
1 B, g& q; k: X7 j! }3 G3 ?2 a logger, _ := cfg.Build() ) O* U" e& L. v P. l# |
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
1 o; F! m$ J5 Y" _2 ] 使用 OTel Collector 进行 metric、trace 收集 . [ U4 i5 `( B! t
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
' D. g3 k! o; [/ R 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
; f1 @( q% E S, n9 h8 m; k; | receivers:
: r* o- u! n* ?4 j; e+ L6 J otlp:
* i, ?' O- F9 o& X; F5 v protocols:
0 e9 ]% X) v# o grpc:
7 P- A J7 w- U5 G' ^, i( k. w http: ! g9 l3 a" v6 x, M/ g
prometheus: 0 d/ [1 z0 C( b& y" z' |
config: ( e u# L; j8 r' G& H
scrape_configs: " M! k# `! U# S* a
- job_name: app 7 L' O) [4 h7 w0 N6 @
scrape_interval: 10s
* u: w/ v8 G. z static_configs:
7 ^, ~: g0 e/ q" { - targets: [app:8080]
+ x8 X$ X3 {" q2 Q2 q5 C9 Z exporters: : h% J1 I& n$ r# G7 }
otlp: , w. D- P! g- R/ R
endpoint: tempo:4317
& P$ w U% ?+ ^ tls: # P/ h; F. S" |* r j! L2 t$ l
insecure: true / @5 o/ F& \ L
prometheusremotewrite: 8 M3 c+ v* s' j
endpoint: http://mimir:8080/api/v1/push % W/ ?2 _4 i: B% l L* j
tls:
4 o/ J3 z% i% g9 J# f1 c insecure: true
3 a# @7 B) U+ c' N' V. U0 J headers: , m5 a# w% t6 @# S# J& a9 l5 n2 P
X-Scope-OrgID: demo 9 N0 q8 @4 l/ ]
processors:
! s- Q+ |! c5 ?0 c I batch:
9 J1 p* z. h+ i4 K3 P- I9 ?6 M service: + Z4 l) `7 b6 D. e- |2 j
pipelines: * Z+ u9 r( t7 n' Z
traces:
6 t* ]% d' \4 e1 g/ ~7 P6 } receivers: [otlp]
. N# U- c! `0 P, { processors: [batch]
) m* ], d6 s5 t4 U1 V w4 Z exporters: [otlp] 1 S4 j& D% \- t) ?7 A
metrics:
( v& q; [& k. K3 s receivers: [prometheus] " ?4 q- l9 v# P8 @
processors: [batch] 8 t0 ^4 s, Z' B" y8 S2 R3 y) N- m
exporters: [prometheusremotewrite]
% Q5 R- |5 W. g2 E9 E 使用 OTel Collector Contrib 进行 log 收集 . L/ m/ ]1 n! G2 N0 a
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
: ~9 t; [, L' _: x" u receivers: 2 J! h$ ~2 e, | G% u8 Z
filelog: " d3 T" m2 [4 A# I& ^ d' S/ l t
include: [/var/log/app.log]
/ J) k7 ?$ R# S3 j* L exporters: : K# X6 ~* o" p: X( z4 p% w# j0 j
loki:
# F1 i% W: ~# E- d endpoint: http://loki:3100/loki/api/v1/push
m4 ^, v( h% ~ d9 _7 Y tenant_id: demo ) V$ N5 A/ ]( G
labels: , N X( b) j+ f5 F# g% n. D6 i$ [9 p
attributes: / k7 j" m! {8 G0 _( U+ N
log.file.name: "filename"
0 s6 b4 @ i1 z processors:
* V1 }+ w; {. B# u# F5 X+ O: C batch: ! Y$ z. K7 y! l
service:
; e- k2 {4 G0 M* P& ]6 I. t pipelines: `* N* V4 M& g! |& A0 c
logs: . I* k6 Q+ s4 Q
receivers: [filelog]
+ D' }( [1 c. q processors: [batch] ; o- Q, z& b& T; G
exporters: [loki]
$ e! W, p0 G" d1 I; R. [" G 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
u( B; V3 w- H* B) q$ o 总结
5 x8 G4 Z) Z; A, S9 n5 q z$ J) @ 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 5 g/ A# k' y; w0 M) v+ h6 U
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
& ^* V" Z& N3 P6 |
: c. D/ o- Z6 o7 T7 a 责任编辑:
- r3 h4 p! z: N0 L V! z6 K, m$ K" v1 ]5 Y
1 G' G/ B" L' x$ c! d- s
: c* T& S: N% O* ^; t/ p3 E" }! W# n' U6 A. v0 T
|