|
2 }+ N0 V+ {, g# @7 _ 原标题:基于 Grafana LGTM 可观测平台的构建
& W; w; c9 I; f, z* ?
0 f# x9 U0 G: I6 E$ V( s0 _6 @ 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
( _- G" o8 `2 f* r5 d! F$ K 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 2 H3 r# G( D1 `8 X% _$ ~; ]
通过本文你将了解: - p% T8 e2 u* e2 L9 t2 ?7 l3 O( b
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID 4 a: `1 R8 E- K$ {$ g0 C
如何使用 OTel Collector 进行 metric、trace 收集
8 O, _/ F, V# H) V. _ 如何使用 OTel Collector Contrib 进行日志收集 & W( M" f3 R& A" X' g1 [* E
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 * x" \, {, g0 |: u/ n
如何使用 Grafana 制作统一可观测性大盘 ' b' r; ~' U7 M! C0 ]
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
0 ~( y! X4 Y7 O 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 % `$ i$ X) h5 [# E
下载并体验样例
1 Q4 z _" ?6 u) A, b8 k+ Z/ p 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: / }! Y. N& u0 Q- e! h+ J; g& D
git clone https://github.com/grafanafans/prometheus-exemplar.git
5 k' F2 ?! h2 ]+ r/ d8 V) G- C7 J cd prometheus-exemplar
! y: {( S7 ^1 }. L 使用 docker-compose 启动样例程序:
, w. O. Z6 A! {0 h/ ` docker-compose up -d ' q+ l: B- Q* ^4 z$ @
这个命令会启动以下程序: 9 A1 `7 h) B a
使用单节点模式分别启动一个 Mimir、Loki、Tempo " y9 J6 k% Z% W
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo 9 u4 I2 Q) v" k' X! y5 q; u' c
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
: B0 M) B" Y0 D: L: i 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 5 {) Q# |- m: |% q f
整个部署架构如下:
7 q% m5 R* s3 |. M2 i  . N5 V5 V7 w- s" r
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 4 ~- M# e; W h2 B7 ~
wrk http://localhost:8080/v1/books
, E: Z. B9 ^1 A7 _0 s wrk http://localhost:8080/v1/books/1 s! L" Y( e" W% a7 H; E! }5 m
最后通过 http://localhost:3000 页面访问对应的看板:
$ Y5 }+ Z' ^* s1 E6 O% L; S 
- N5 M( l- v; U% T 细节说明
, v. s2 ?! @4 Z* p% U a- @ 使用 Promethues Go SDK 导出 metrics
6 W! M4 T6 l' l6 y 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: & w6 B6 B; O! r. l0 L1 _* c
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", : d7 g# L" c0 N3 v9 Y
Help: "Http latency distributions.",
4 }& j, R4 ` {" K) v/ A9 H Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
% d2 v: j2 {0 i2 w& y }, []string{"method", "path", "code"}) $ I- V. v4 E% c7 L& ^, `" R
prometheus.MustRegister(httpDurationsHistogram) ( b: w! {- K* q) E- A# N
return func(c *gin.Context) <{p> .....
9 ^9 D! H0 w" `! J observer := httpDurationsHistogram.WithLabelValues(method, url, status)
/ F9 g/ u# {" P8 [$ O4 s2 X' l# A observer.Observe(elapsed)
2 v0 U! R0 x* i- \! g4 d; t: M if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
- [+ x+ H! x+ y })
% b* a/ {" Y4 x. Y% w' {' p2 C } 1 W7 M) C9 W, J
} ) I" f/ W' G) t$ J3 _; X, P
}
. ^# @# h6 i, G8 D& t 使用 OTLP HTTP 导出 traces
5 o1 _5 ?5 D7 D D. q1 m1 U" [ 使用 OTel SDK 进行 trace 埋点:
3 U* P, K* f$ Y4 J1 i3 j func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
9 s- c0 w; k' b" O+ n/ D& @0 `2 Z span.SetAttributes(attribute.String("id", id))
8 x7 P; S% {3 a5 X defer span.End() U! ]( A: l- u H8 x& W7 L/ [$ a' t
// mysql qury random time duration + I4 {( D3 i! J8 }0 h+ s) e( a
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
% c4 E5 h9 P- S5 z" I. c/ M& [ err = db.Where(Book{Id: id}).Find(&item).Error
1 m' V: K2 s% o. Z7 H: b7 w) R return - Y& f+ \7 V$ A2 \0 v _
}
- z ?( m0 ?1 h! t- b# w4 Y 使用 OLTP HTTP 进行导出: + r/ R- V1 S8 P7 |6 @! ]0 F
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name : A( P0 ^% I! y8 X* W! J B
client := otlptracehttp.NewClient(
6 s1 i( R2 U" v, j otlptracehttp.WithEndpoint(endpoint),
6 j H" P/ i2 x. P+ S otlptracehttp.WithInsecure(), ) G* `* c! z5 J
)
% k" Y8 g/ i8 u1 J& t4 f$ u: | exp, err := otlptrace.New(context.Background(), client)
: `/ r$ v0 z5 r0 I9 L3 w" n if err != nil <{p> return err ( h- D, G$ w9 r [. a/ q2 e$ @
} 1 Q3 h3 S: {8 T5 G% p
tp := tracesdk.NewTracerProvider(
1 L/ b9 x1 t- @+ q tracesdk.WithBatcher(exp),
. s" O; p' E& S8 q3 { tracesdk.WithResource(resource.NewWithAttributes( " M( r' K: L9 m7 r
semconv.SchemaURL, : ~, H n9 U* a8 ?$ G1 B8 h! y
semconv.ServiceNameKey.String(serviceName),
3 G9 k# Z- i6 l1 d attribute.String("environment", environment), T% V, d. \+ y5 A0 K; f- c
)),
' R6 ]* g: r5 K- C! g1 Z! y )
- ^9 d1 Y- O! b( H f# y otel.SetTracerProvider(tp) * L, T* t) I: ^
return nil
2 a/ o' ^! Q* ^) n7 D } ; x: y7 X; n8 p
结构化日志 / @- y. c2 ~' R! x- t! l$ }; E
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: # F# H4 y0 t4 J; Q& M/ e0 q
cfg := zap.NewProductionConfig() 6 ^" n2 l* A3 ` |: P" b/ q
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} ) e* r) N- s4 v I5 b: d
logger, _ := cfg.Build() 5 y0 `* n9 H( k% T
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) 1 ^- |# F' q1 l* p/ e, I
使用 OTel Collector 进行 metric、trace 收集 3 L# C5 |' f9 `1 q9 F1 v
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 5 G% O+ z) e# [6 e6 ~/ a1 q
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
8 L4 r+ g* ?" Q! e3 _ receivers: ^ k: L M" @+ w0 t4 H& Y. d
otlp:
; Z) [- h1 P2 U% i4 v/ ^3 b protocols:
% o( h9 e2 Y5 w( { grpc: ( y" y! v3 C( j3 _+ Z
http: " J* a" i. ]; \' {6 M* |0 d
prometheus: 6 E+ t$ @# n3 Z8 b% y" I& m2 D0 ~
config: 0 x) ^0 K( G* ^+ r- W; Q
scrape_configs:
) Q+ } R! A; S# `3 p - job_name: app * A. P) H) z: D3 F8 f$ S. o( V
scrape_interval: 10s
5 `' e1 U4 R4 x; q static_configs:
/ {$ h! I4 X7 ]+ s1 w# |8 L6 P: e - targets: [app:8080]
# U% W, {( G, h8 t* |# c exporters:
% L# l0 r: O/ { otlp:
0 m6 [/ r1 R" q& I1 Q0 T$ e endpoint: tempo:4317 0 z+ i# \1 b, ~3 p1 B+ t
tls: , v# O0 F3 I2 L: k# l
insecure: true
' `8 B5 X' U5 }* o; K2 d' \1 o) h prometheusremotewrite: 2 A( k# O0 }+ `" D' C( O
endpoint: http://mimir:8080/api/v1/push : B* D& t: q) @4 P I* z: ]
tls: # @& {% U( Q9 D, Q) f/ G
insecure: true 0 }9 s9 E7 Q% X% h1 ^9 S6 @
headers: " ^! ?% Q: l: M4 V |- s
X-Scope-OrgID: demo
5 a0 \+ ]% t* A" h0 ` processors: $ @+ c- W6 Y$ X2 h& S
batch:
; u* f- C) b a' Q5 F4 P: G3 s1 S service:
, y$ q& y7 M3 B7 G pipelines: : Q& y5 Q/ b! }1 W: T. V
traces: + }# I- g" M, q6 w C
receivers: [otlp] . r" b3 l" V# C! Z2 ^ c( ?
processors: [batch] 9 e5 J+ F8 `" e7 K
exporters: [otlp] ) G/ O+ Q M/ F* r/ C2 |
metrics: . g# N* J: L' ~& H0 w
receivers: [prometheus]
' [' Q! ~. R* x2 X5 p$ I7 I processors: [batch]
' X9 K- ]6 t0 D% l4 ~ exporters: [prometheusremotewrite]
8 t; |' e9 |/ y 使用 OTel Collector Contrib 进行 log 收集 8 w1 w# u" Z% e _0 d) O$ z
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: ' r# |2 v: M8 ~- q
receivers:
0 t" \, I# q8 q1 ? filelog: ) B! F; a, [ L w: D$ `
include: [/var/log/app.log]
7 T4 a" u5 X8 V7 F4 C exporters:
$ t, V" f" o$ y/ Z( Q2 J* l loki:
0 g" } H$ }) L u, u endpoint: http://loki:3100/loki/api/v1/push 3 {# g0 t* C5 {! A5 R
tenant_id: demo
) `* u* O! f- F: w labels: ! J& h& i1 c1 m7 n7 c+ A" R" s
attributes:
- T; Y) x% o* g log.file.name: "filename"
2 W8 ]6 H- \' C3 o processors:
) d4 P9 d7 L! D1 _ `, w/ [- ^ batch: ; _" @& ?) q9 n
service:
/ @: m3 Q: O3 q3 D% N$ U pipelines: # z! A4 [% R* K$ z8 w9 P
logs: : S* v# Q: P$ a0 [! E
receivers: [filelog]
, L) w! R* J' Q1 R6 y& s5 n* j processors: [batch]
7 K! I6 g$ u% p4 s& A" b: A7 b, j exporters: [loki]
% Q* i' A" Z5 l# G( v t& @' l 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
& U- s* ~! ]8 W" z% U6 m 总结
8 L1 Z, u% L- f* m) N) V- n 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
7 x, V% v' j8 u 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
; ?4 X( x3 j0 ?7 u( Y1 i' s
1 Q; ?) A; R4 { O7 z 责任编辑: 1 h( I; M F) N1 u4 M
4 ?( U3 _1 N4 ^2 J5 P# u4 h* _; A" L y( }
& }: }1 q& z. F) e0 `* L3 ~9 V* n* u6 ]
|