|
% l t8 w7 h W7 e0 D ?
原标题:基于 Grafana LGTM 可观测平台的构建
- K6 B$ @) |1 {$ V% W$ A# E" L! t6 Y# _2 h$ b; _( ^9 K8 h# O
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 % [$ q% D2 S8 D6 C3 f7 ]! i
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 4 _8 _$ A( a' w5 ]' Z" h! h
通过本文你将了解:
4 ~0 Y% t9 d# V3 i4 q$ j 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID # m+ ]. j& _3 B& @; @7 u+ c
如何使用 OTel Collector 进行 metric、trace 收集
7 u6 c: p9 h# s 如何使用 OTel Collector Contrib 进行日志收集
: G& C: p4 ?4 K, K0 g, ^* ~ 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 0 Z1 q' `7 R" d# d ~( A3 |4 x0 g
如何使用 Grafana 制作统一可观测性大盘 4 t6 S* s. d0 y. W/ e
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 5 `/ K: F# [& y4 K4 J8 z c6 d
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
4 I8 B% x. o9 v0 Y# ~. ~/ Y 下载并体验样例
& n& ?9 k5 |% J; E6 O0 d q 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: & [, X3 d% ~0 F* S* T6 H
git clone https://github.com/grafanafans/prometheus-exemplar.git
: i; X/ C0 W0 ~" ?" I cd prometheus-exemplar
- T' f. x/ P" f D: p, ~9 k/ P 使用 docker-compose 启动样例程序: ! @) `9 u% l9 s9 p. y* S
docker-compose up -d 2 f: o/ m' P! X% {4 z( d
这个命令会启动以下程序: / U* _6 h/ V! l3 ]9 m5 t, s
使用单节点模式分别启动一个 Mimir、Loki、Tempo r5 ?$ [9 V8 I/ H7 i4 `3 G
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
1 {% X( m9 P2 H( g1 s) v G, L 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 ; i5 M) k. F$ S) q) d3 V
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 9 T& i* f& _ O/ y" p y( [
整个部署架构如下:
# }( j' v/ F, C, @0 i8 @* k' f 
, H/ H$ F1 a3 t. F8 Y 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
3 C ^7 X3 H) h7 f* Q( R( U$ f3 o) O0 r wrk http://localhost:8080/v1/books ) J6 A% u1 _$ V ^! ^' G
wrk http://localhost:8080/v1/books/1
/ k9 Q7 m* ^& P9 j% D) _, H3 u 最后通过 http://localhost:3000 页面访问对应的看板: # ]4 M1 L0 l5 |7 Z7 }
 9 [1 C y4 O; I
细节说明
& C: m6 q2 I: T5 h3 j' ^/ i 使用 Promethues Go SDK 导出 metrics - \. A% A4 ~9 D' L q
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: 6 G+ Q/ r/ v5 | x! ^2 B+ _0 s
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
0 E$ g3 I M, E) K, x# q% E( p Help: "Http latency distributions.",
% I2 E: m1 ^" Q+ \ Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, # V1 i; ?( p5 n8 v; T( S7 y
}, []string{"method", "path", "code"})
$ H3 O0 @- V# h; V+ ^ prometheus.MustRegister(httpDurationsHistogram)
. w" e) a! j# \; z, `2 f: } return func(c *gin.Context) <{p> ..... 1 E% I9 G0 w0 e8 n
observer := httpDurationsHistogram.WithLabelValues(method, url, status)
$ @" q, S/ f7 z5 x observer.Observe(elapsed)
2 ]0 ]" Z0 B; {/ X! O* W4 S if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), ) ]: o/ O! x8 d" Q% T% M
}) 9 v; e% S. N& M. [0 V
} ! |7 ^( O" a- S' \ e9 c2 k" _0 h
} : b3 F1 `" G! ?* c* l# W+ d
}
% F0 X8 K# v! R. R0 i 使用 OTLP HTTP 导出 traces
2 P0 O! R: @# a, c; w 使用 OTel SDK 进行 trace 埋点: 8 v1 {& x6 y4 x
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") ! ?; l) v& \% z8 c! s$ v: D- R
span.SetAttributes(attribute.String("id", id)) . s# Y& v7 E4 U$ ?
defer span.End() ; z* m4 D: y4 `6 z0 V$ M. c
// mysql qury random time duration
% Z3 N, M/ o a. t( g8 K time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) & w* {- E' b/ H- o0 D6 K7 o
err = db.Where(Book{Id: id}).Find(&item).Error
6 L _- D: \# `; V- X+ b+ R3 O return
( R, w. L( |( W8 v- s9 e" h6 j1 j# P }
7 W- @5 K0 z$ ?) B 使用 OLTP HTTP 进行导出: # U. v: v) I1 K7 o) m9 |: r
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 5 l2 Q* q9 Y* A( P: s( T2 Z7 O
client := otlptracehttp.NewClient( 0 n7 D) L5 S) y7 A$ [- d
otlptracehttp.WithEndpoint(endpoint),
' h- m2 r" r& s# |4 @) q otlptracehttp.WithInsecure(),
4 c' e8 {. a, o" W) i; s )
4 F8 I; w1 U }! o# E exp, err := otlptrace.New(context.Background(), client)
) E4 J! j v/ Z$ l% D if err != nil <{p> return err ( k1 n' m( u0 J4 @# X7 P
}
' ~, M. o$ y+ u tp := tracesdk.NewTracerProvider( 9 ^" X5 f- n& k# ~
tracesdk.WithBatcher(exp), # M# r/ i+ c/ b* s, p9 _! ^
tracesdk.WithResource(resource.NewWithAttributes( ' M k/ i& h/ y6 |) G
semconv.SchemaURL,
. R7 Q8 S0 h9 J6 B6 } semconv.ServiceNameKey.String(serviceName), 6 P7 h2 d. N) p4 @ k
attribute.String("environment", environment),
* g! N2 i: @4 y! m$ {# [& f )), % U7 g Y% `4 f- p
)
# s/ n* ]6 \2 T: e& G7 X9 Y) o otel.SetTracerProvider(tp) ' P/ ?: v+ Y {' \8 k0 w
return nil 1 Y4 f3 F5 o2 K# R! C
}
0 |. g4 U2 J" j# H6 |+ M 结构化日志
! ~4 w \; m# S) c9 p' W# Y% A 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
" w. r8 A2 ?# p$ n' T$ b' q/ | cfg := zap.NewProductionConfig() 9 W, i; \; f9 F1 f+ L
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
7 S5 E) B) F7 c) D logger, _ := cfg.Build()
4 m2 p: v( _6 p/ t5 ]' } logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) : g9 Z. I3 l2 K- l) S, \, P* R% i
使用 OTel Collector 进行 metric、trace 收集
- E4 c; W3 s+ E% Y 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 4 q& v4 q K& Q) A# ~- U/ U4 t
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
- y" x: w# K" J8 d* D! e a8 j receivers:
+ y* @: o: _6 N9 {" j otlp: , o% w& H, d& B8 j, H0 }
protocols: - `- D- E, H( K
grpc: ( C* m3 v8 {/ P: x
http: 9 P; Y4 S( t' s8 J7 R% P9 o
prometheus: ; \6 X+ D& H( Z0 a
config: ( G2 g+ e* m& a* I; ^, }
scrape_configs: ; m. ~: n$ A7 y4 J- {$ M8 ?
- job_name: app
$ y, K( \, Q) ^. | scrape_interval: 10s
. T! d i1 ^% z& {0 r( W+ w static_configs: 1 x# ~# r4 R3 V8 A. Z
- targets: [app:8080] # I2 c$ d- S* R9 ?) q4 L3 B
exporters:
2 ?' F1 T- i% Y! { otlp:
5 c: e$ s2 y8 w+ s endpoint: tempo:4317
/ _6 {: d1 T6 `4 o tls: , A0 F4 o3 b% I$ r9 ^) @2 V
insecure: true 7 N" q8 s. O; x6 C8 J u
prometheusremotewrite: 7 l2 Z* A: Y& q# T- Y
endpoint: http://mimir:8080/api/v1/push
, {; k( v _$ V! W* S tls:
- m" G* \6 `& D) A! j0 Y insecure: true
7 S& K. j4 f( P, o7 f, Y( m headers:
5 U$ Z5 f" n% [/ m9 @+ O1 ?; W X-Scope-OrgID: demo ! K: d z4 l- c( G% Q
processors:
0 W @& F* |$ d1 k1 ^9 k9 m( b. v batch:
: m# o3 Q+ F5 i3 m& q service:
9 v7 {# M3 y" P! `# z pipelines: . Z! i+ v4 d, L1 O, W) [
traces:
- Y1 T8 T$ M$ v, n receivers: [otlp] - i T2 \( g/ O# i$ B P. K+ c( q
processors: [batch]
9 d/ x4 f4 {; k" d- t7 {% v' s2 D exporters: [otlp] 4 p, h0 w k8 Q8 g$ I) a1 ~- v
metrics: ) v+ c; q% e/ {, J$ D
receivers: [prometheus]
; j6 d" _/ t# q/ s" B* I' B processors: [batch]
/ c( f* l3 N- a% X: K9 D9 L8 z exporters: [prometheusremotewrite] : q1 ]5 m0 ^* I
使用 OTel Collector Contrib 进行 log 收集 3 `. r. m& M F* E2 ]
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: 4 `0 w V$ t! h6 R6 j' w# p
receivers:
H- N {6 e% A- w2 A) q filelog:
: K+ N7 s* u# _/ S) U% X include: [/var/log/app.log] ) f" V+ Z7 Z" d5 _
exporters:
1 R2 B/ m& d: A R% t+ V loki:
! z( {1 { A- z( G endpoint: http://loki:3100/loki/api/v1/push
* S3 D7 p% {5 \) K" u( q tenant_id: demo " t9 G! r6 B' {! Z; M$ v. [ X$ y
labels: : q7 |- a* G1 I0 N! X
attributes: 7 h9 S: u. a0 a6 K z( n, Z
log.file.name: "filename" ! j2 D5 p$ v' L/ L" A, W: a/ X
processors: ( i4 n+ p( L- F
batch:
! Z) [$ ^$ u( ?$ K5 S0 I4 A service:
6 g4 c/ A. J5 |: Q$ f! _! V9 X pipelines: 9 k5 b! x' d* G1 Q' H! C8 T% t" Z
logs:
8 m6 R! ^( ~) Y receivers: [filelog]
$ k+ [+ |( k" I processors: [batch] ) d( Z/ m2 L! v6 T( L- ^- I7 _
exporters: [loki]
! @* O5 s- a8 c$ X9 ` 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
( C2 P4 a1 E$ K5 R9 y7 j0 K! z5 k- h 总结 2 h# ^( L: J7 Z7 l: k$ q$ G
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 # l7 e7 Y* Y% t$ K
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
7 }9 H/ W7 k) m# W0 E8 }8 t0 }) o u, P
责任编辑:
+ Z& u+ f3 ?# s1 O
( t/ M, X' ^/ i) q' I2 {
! X F: C* e9 j0 y" Q; _
. A# N5 J2 Q, [4 x+ z/ V
6 `6 f5 Q: w: _) b |