|
; q/ D( n: m# F( E3 ] 原标题:基于 Grafana LGTM 可观测平台的构建
. m: T; R2 C8 y5 {6 o s2 k9 H2 }$ J( J5 J. ~9 d3 q5 H! C3 d
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
5 U/ J) o( p# }' p$ z 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 # F( a$ w* ~7 R" p9 S7 H
通过本文你将了解: + N: m5 b8 Z$ i% K
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
7 R* V8 s* u" Y! r( s' s& h 如何使用 OTel Collector 进行 metric、trace 收集 * K7 [3 p# L% J! @# f
如何使用 OTel Collector Contrib 进行日志收集
: {% z# m" L {6 K$ E, l. b5 G2 { 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
) }; H& l, r' N 如何使用 Grafana 制作统一可观测性大盘
# q1 a, {: A C/ \/ U( }( x. b: K 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 9 z! V' k* c4 i0 P: s
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
1 F7 s6 B; `$ x! O( _6 n 下载并体验样例 $ ~* a* {- }) U& z/ g2 z5 h8 S# M
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: : a' S2 o- D1 A; H
git clone https://github.com/grafanafans/prometheus-exemplar.git
! r4 U4 ^9 y1 s9 H cd prometheus-exemplar / z( {3 b8 r2 G2 v; J. K
使用 docker-compose 启动样例程序:
) R* X, I0 T; R4 G6 M* g% k0 ^ docker-compose up -d * k$ k0 y, [9 I4 ?3 |& J
这个命令会启动以下程序: 6 o+ ^1 {, c3 G8 i
使用单节点模式分别启动一个 Mimir、Loki、Tempo
( Y; E6 f- O h* `4 ] 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
! _4 Y& s0 e0 E3 R 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 " Z* R. w; ~+ Q6 B, L
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 " |6 k/ Y6 g+ C" Z2 U
整个部署架构如下: + v4 S% }/ C7 s4 @- s$ a! U. o+ s$ t
 . v3 ?2 y0 H) K8 w: i9 A& K
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: % O; I& V$ H, {
wrk http://localhost:8080/v1/books $ D) s; @5 \8 y' \( ^: b$ z/ c
wrk http://localhost:8080/v1/books/1 " _5 \6 W) c( A. Q9 }% d2 [
最后通过 http://localhost:3000 页面访问对应的看板:
# Q% n6 D% R" X. v. V 
, I9 Q A! I+ f6 K( K( L1 ?: M& X 细节说明 : |' t/ r& G e* t; h
使用 Promethues Go SDK 导出 metrics
6 w: H2 X' U( R% M( {' N 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: 0 ~, i+ r: R1 }$ D& m# v( C
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", 0 N6 R; X. {9 Q* w9 |# ~: b. _. G
Help: "Http latency distributions.",
, E# J: S$ u- L# s5 F+ N Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
& {+ k) i, v7 F# A }, []string{"method", "path", "code"}) * D7 `3 m+ c4 u1 ^" G+ y
prometheus.MustRegister(httpDurationsHistogram) z/ @+ g. K% f$ n
return func(c *gin.Context) <{p> .....
- U" c7 k1 A# |2 ~0 d& f observer := httpDurationsHistogram.WithLabelValues(method, url, status)
3 U( \6 L/ o' e observer.Observe(elapsed)
& Q, C7 H4 Y6 N `: d2 f, Q3 k0 C if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
% h( Y c4 v6 A' e6 Z2 t, X& f3 Y }) 4 T q# X% N4 Q+ L0 l
}
6 e9 k! y9 n+ h# Z5 A* ~/ l. B }
: Y, i# [, t2 i$ U/ q }
5 |# _1 p* v" B2 { 使用 OTLP HTTP 导出 traces 4 `0 z* {2 M, k. w
使用 OTel SDK 进行 trace 埋点: ) g/ t- o( J0 W
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") $ d) b7 f( O/ O g) F' r& ?. c
span.SetAttributes(attribute.String("id", id)) " y/ ?& k4 V' F: ?
defer span.End() + L, b7 S& v# @4 N" x
// mysql qury random time duration 9 h5 Q7 b+ I/ U# _
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
' s) i+ G; c, @1 ]# I" A err = db.Where(Book{Id: id}).Find(&item).Error ; ]& U% n+ F. }# E) n6 z$ T6 E5 j
return
c) Z; T; ~# B8 y [ }
" H/ l. ]. \0 p: ]" {- b 使用 OLTP HTTP 进行导出: . K4 c* F4 R9 e8 j) V0 _
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 0 E+ g& d7 E: N5 u) E3 J. [$ e% L
client := otlptracehttp.NewClient(
3 l- u: H( r1 ^( { otlptracehttp.WithEndpoint(endpoint), : X$ Z, e8 q8 l; j7 U7 T
otlptracehttp.WithInsecure(),
/ X; L; n2 d: p& {/ F. H$ y ) 9 @. m! S: ~/ z) a# y
exp, err := otlptrace.New(context.Background(), client)
( ^* B; g* a$ H- N5 G2 } if err != nil <{p> return err
2 X$ _- c/ I" b6 x+ K# I7 U }
% G1 B% m ]- G7 T& {# ^ tp := tracesdk.NewTracerProvider(
5 o2 _* {" ?4 ]1 i& b: y tracesdk.WithBatcher(exp), @- F9 N; ?/ _5 j3 V8 z
tracesdk.WithResource(resource.NewWithAttributes(
% w2 g" G: C, S- F r5 c semconv.SchemaURL, : j2 e2 X& [. I6 z+ M
semconv.ServiceNameKey.String(serviceName),
' E+ e" h/ x+ `' u attribute.String("environment", environment),
, U" L( k( w/ M6 J- r )), 8 I+ X, S9 O( u
) ' _3 r' G; c& P7 f
otel.SetTracerProvider(tp)
3 _( y' [2 }( J @, H return nil
1 z% _' P' H/ v8 P! {! l4 `" Q, C, g }
( a* d2 h! s1 E# P6 L: y* M 结构化日志 : w2 O1 A* r4 V# v' G
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: + P8 l+ s2 o* B& X6 q/ K* Y
cfg := zap.NewProductionConfig()
5 R6 e; j* J# u/ N+ ~ cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
$ M5 b& c; u7 \3 I& o$ F% e' e logger, _ := cfg.Build() - ]: h) w" ~- X; A5 M l7 K' `
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
) u$ _7 z3 r- Z' @0 | 使用 OTel Collector 进行 metric、trace 收集
+ T$ Y. Z( y9 H2 S2 u. I, o 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 ' |# [5 c/ x8 b
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: 3 x6 _4 o6 n, G/ p
receivers: ) r) N" D' o2 Z4 D) Z9 j% `1 R
otlp: " v8 w7 ~ C) X: q0 s8 E- i
protocols:
! F& j& g0 y2 O$ {& k grpc: 0 ?0 C8 V& t% L
http:
5 u/ N. Z |) e2 L( ^/ K8 j prometheus:
* Y) B& t# ]1 L) y/ X config: 7 o% A& o, e" _9 B* W
scrape_configs:
% |: ], X$ L1 X" _% I7 [ - job_name: app
0 I6 G! {* Y. [! |) N9 T# r! w scrape_interval: 10s s/ o5 T; k" d! T3 B/ e& @
static_configs:
9 d) l) \2 @! j1 f# X - targets: [app:8080] ; {8 K+ x$ F' i+ C# Q# |
exporters:
7 y0 m, j" b( S0 i otlp: ) u/ d& f! M1 i0 [. a
endpoint: tempo:4317 & J/ n$ \0 ~7 Q7 `7 G' h
tls: 0 P7 l* b; M- a- J
insecure: true ; u: I( P! E) c$ j/ a
prometheusremotewrite: $ z: R* A m- X( `
endpoint: http://mimir:8080/api/v1/push 5 v2 C# I! _2 @5 {3 h+ @) f
tls:
, |( R# w+ v8 G6 U% n1 l A6 S insecure: true / t6 O. c' t9 D; I
headers: % l( |0 _: i$ s8 f$ `7 V
X-Scope-OrgID: demo
+ f2 [7 n; x7 m9 A: ?' ] processors: 5 r* C3 ^# \. Y
batch: ( ~3 j3 F `0 N8 B/ j8 r! a4 f
service: * @: @ l, l* N& r) Q( n1 ~
pipelines:
R0 ^* S7 v5 l5 t. x$ v traces: * k% X% j; ^0 k' ]5 y$ r3 W
receivers: [otlp] 9 v# R/ H/ r& d) L# ]: R
processors: [batch] " \8 j9 ^0 ~( ~' ?3 M* A$ Y
exporters: [otlp]
4 o9 q7 x. w1 c2 N; u metrics:
- }; `: f' r0 r( ]3 q& E" q receivers: [prometheus] 7 F6 S4 p0 e5 A. p6 c6 o5 H
processors: [batch]
' ?: S( G( t5 n& i6 o exporters: [prometheusremotewrite] , ^# I7 W, x" D8 Q2 x4 e
使用 OTel Collector Contrib 进行 log 收集
9 Y) C9 j( F) W% h! T6 ?& ^! u 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: - Q9 R1 U: w3 T( {4 _
receivers:
- f6 N8 h, }6 K: ?4 t* Q filelog:
$ i5 i6 _/ A; _ include: [/var/log/app.log]
' j9 @3 u$ W+ m. ? exporters:
' g' X! d' H2 B, D2 l loki:
: [ a! W7 I: c endpoint: http://loki:3100/loki/api/v1/push . d Q; P) x7 {4 {5 `0 Y
tenant_id: demo 9 m! f. h1 `. }7 ]0 P3 O
labels: $ ~ X' o" O. D
attributes: * q1 e/ g+ h: r# i- }& Q# t+ Q
log.file.name: "filename"
8 D5 _- G- |3 S+ D4 v processors:
8 d0 ]* T! s4 A batch: $ Z6 Q; P4 z/ w; L( r
service:
$ L3 X6 o+ j6 C+ |6 g pipelines: $ B0 B4 e& Z d8 u" L
logs:
* f J( E0 Y7 Q9 P! R* \* W- P; T receivers: [filelog]
% ?3 {1 F, n4 Y% x4 p. p processors: [batch]
. q0 {. Z3 E7 a' E0 [7 T' G exporters: [loki] # }' k% w* R- M9 \( E! M; ? g% n
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
9 q8 K! h) p: a) j 总结 $ y' W% m. R/ L0 `% C5 D
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 7 a6 ]! `) |; i) k2 g5 C# v
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 " B' O3 m. S z6 U6 [
( h# b; g. z, a! r
责任编辑: * T* X& G6 d* M! Q }7 r
) y. y6 i. u% v
& L( P4 J, P/ A/ b1 i6 [
, I9 h# R: g# X! B: X6 ? o
* n6 Q6 ]7 ^0 ^9 m% n$ g% h |