/ L* D+ }. z: M( L3 N n 原标题:基于 Grafana LGTM 可观测平台的构建
% q; w( m+ L) C: z9 g( C% z! a3 n" F) H! c* }' l0 Y2 J7 k
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
$ w/ p7 u( }! D3 j 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 # `, K/ N$ y- M6 V. k: j0 a& |
通过本文你将了解:
! J% p! _6 q7 t4 g5 ]" ] 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
' f' O/ _" k% p) v( | 如何使用 OTel Collector 进行 metric、trace 收集 / J1 V+ Z7 Q. |1 y$ y6 N9 |) g
如何使用 OTel Collector Contrib 进行日志收集
) G9 |' O3 }) H, ?) W 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 & x/ d. z( t( ~1 D% L: X+ q& U
如何使用 Grafana 制作统一可观测性大盘
; o+ l( w( {8 n" c! N 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 ! g) h. D& Y" Q+ i3 X! T
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
+ r9 p& H# [& Y T+ K" R" X 下载并体验样例
8 Q" \% \" R0 ]3 c 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: 7 y3 y5 J! \* F! n
git clone https://github.com/grafanafans/prometheus-exemplar.git
9 {. I2 y; {+ e$ L+ ~ cd prometheus-exemplar
& b4 ^, S' Q' x+ s7 @# H W 使用 docker-compose 启动样例程序: + }9 g7 N X" i4 }: }0 {- h
docker-compose up -d ( U S. ?6 r+ ~" X
这个命令会启动以下程序:
2 D" n; k1 N n 使用单节点模式分别启动一个 Mimir、Loki、Tempo % p5 }) t4 u0 J. x( `
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
* R8 C/ [ C/ z2 @# k# g, ^ 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
Y0 S3 k; y( P" ?+ P 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
" a0 ? g* L; a8 }/ K 整个部署架构如下:
8 Z8 n' A2 P5 i" i0 w' |3 z 2 X+ Q% I+ w* G5 X y3 U
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
5 B2 f% @. }$ S/ b+ F4 F# c. g wrk http://localhost:8080/v1/books ! s7 l' ~# y; B: ?7 Y# V: k+ _
wrk http://localhost:8080/v1/books/1 ) h3 Y4 v9 ]$ d& y1 ^5 k4 W
最后通过 http://localhost:3000 页面访问对应的看板:
$ L! w( P/ O, j' J- j- _) b4 W
( h6 s V4 V, P2 i9 B5 L 细节说明
, \% k( l5 X# k5 j/ |+ @* G' Q1 r 使用 Promethues Go SDK 导出 metrics % x* r0 V# d" y9 J* ~2 l; { A @
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
/ e& m8 }4 _2 ?# F$ l; D! Y func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
/ B3 C1 J2 D0 d1 r H& ]/ [ Help: "Http latency distributions.", $ z# o# O$ M3 J8 ~! E
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, . f8 c7 E% D! ^
}, []string{"method", "path", "code"}) 2 P& \3 j" E9 a3 F6 V# Y
prometheus.MustRegister(httpDurationsHistogram) 8 O' Y+ p r/ n5 Y$ I+ E7 M6 B
return func(c *gin.Context) <{p> .....
: h- S" u* X4 J. C" G5 N0 |5 g observer := httpDurationsHistogram.WithLabelValues(method, url, status)
( _0 f- i+ Z. C" b( G observer.Observe(elapsed)
B0 C% D+ k0 U) o) e6 B1 l) A if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), : W: X1 m e$ W( _
}) # |5 m! w" q' f, ]" H/ L
}
% H+ _) `. E' c1 {$ }+ q } * U4 J. G( c9 W& e
} % U- x3 n. D- ? U5 |1 \ g' J# s, Z
使用 OTLP HTTP 导出 traces
2 O5 ]) q, T& p' _( h 使用 OTel SDK 进行 trace 埋点: # @5 W. {' S/ ~, C
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
: B* k' { A- C4 X span.SetAttributes(attribute.String("id", id))
% Q1 j; d6 V: H ^# V0 ^ defer span.End()
& Y, p! M# K+ W6 T g // mysql qury random time duration
: U- P" x" v9 s3 y time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) h& V2 I. W+ g1 k/ J2 i
err = db.Where(Book{Id: id}).Find(&item).Error
# y+ h# \8 w/ m$ E. W1 i1 ?" k return
1 I7 a( O$ @! [; H; @ }
8 v+ F) s, `/ u' c6 x- D 使用 OLTP HTTP 进行导出: 0 F0 I5 i+ {9 y- T! }6 s
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
0 N+ l0 \2 R; D( F client := otlptracehttp.NewClient(
( Q# s: V+ Z- l% z. P3 L8 e, y u otlptracehttp.WithEndpoint(endpoint),
+ n: N O$ ?# s9 q otlptracehttp.WithInsecure(),
( E! l8 x4 o: y1 N4 N )
. l0 m2 Z& q" S7 s$ b2 s exp, err := otlptrace.New(context.Background(), client) 5 o5 }* z2 M6 A) U$ C
if err != nil <{p> return err 7 l l" y4 {* w0 j( m. g6 Z
}
9 T) c2 O# a8 t, @3 S tp := tracesdk.NewTracerProvider(
6 |& C7 }& ^* V" p tracesdk.WithBatcher(exp), ) Y0 I) h( d+ i( ?
tracesdk.WithResource(resource.NewWithAttributes(
% N: l8 f1 g/ W. c; R5 \ semconv.SchemaURL,
6 x; T+ U( E" A' Q8 L semconv.ServiceNameKey.String(serviceName), ! u$ i4 X* m" _% O9 l* ^9 E$ c
attribute.String("environment", environment),
; v, ? g9 }, c& y' ]* P) X )), & M: {1 e" m+ {# }: N
) . v" t0 z/ t% e" [( a( q# D
otel.SetTracerProvider(tp) . m' q# M# Q- k1 R
return nil : S# O4 w& M! |& W( g9 r3 y# Y( }
} 7 q! G$ o) `8 T& u3 ~4 k
结构化日志 ) i. |# g6 G1 m$ L" {
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: ( r0 T/ o- R* u4 [0 a
cfg := zap.NewProductionConfig() 8 y, n0 w+ i f3 G/ t' p5 P% P
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} - l" L% A% C9 a k! O7 g
logger, _ := cfg.Build()
" F/ y) `7 g; l( j! N/ z- d logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
5 c8 `2 L2 a) t4 \1 A9 ` 使用 OTel Collector 进行 metric、trace 收集 & o5 O( G/ P) T% z% Z
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
3 Z7 W9 m! T4 H0 j- l 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: " z% @; F. @5 Z1 S
receivers:
* O* W, N8 J g9 C3 u6 H4 A0 d otlp:
2 z/ h" J2 I' j1 U3 Y( ? protocols:
9 j( ?- d8 P+ Y1 y |! _7 ~' ] grpc: + q" X3 u' `/ \
http:
0 t- i% O, D; G/ Q prometheus:
- k' l7 ^- N4 d* V) k; P; w config: / e6 N9 b& x. q2 J0 c
scrape_configs:
. `) j% H; Y0 J0 n3 X0 R8 T - job_name: app
% z2 W: D$ m ^1 O scrape_interval: 10s
5 m5 r6 e S8 s& l. f$ S: k1 ~9 ]6 [ static_configs:
3 h5 l5 ?/ q; }* g! X2 L. V - targets: [app:8080]
8 @. j `- j- e, D7 f' w exporters:
* f* c. o q4 K( H# \7 F. | otlp:
3 J) Y5 P" I8 {* \3 z) ]. ~6 m& y endpoint: tempo:4317
/ e Y8 C& A: m3 c' W: \+ \ tls: 0 r. t/ \, K0 r- o: T
insecure: true
U. M S O- B0 g- e8 | prometheusremotewrite: # \3 A3 }' }8 c8 @9 ~: |( U4 S
endpoint: http://mimir:8080/api/v1/push $ Q9 M( `: v7 o% }
tls:
; Y! p) D7 {, I: l7 r R insecure: true
2 p$ k& Z4 w* x( P0 G& _$ V headers:
: o( S9 `+ E) Y4 j X-Scope-OrgID: demo
3 ~. q s1 Y% z* p' e/ V processors: ' ~0 G r B5 e' |2 \( t9 v
batch:
6 \; G! V! \" n: i" f' d* \ service:
2 G C+ ?/ h( T+ m; K% J# r9 } pipelines: / E6 `" j+ x* O# ~/ S8 A
traces: ( H+ a; h! J/ L/ j0 v
receivers: [otlp]
; \; K+ x$ y8 ~1 G9 c processors: [batch]
5 S* D$ _% O, J8 U$ z exporters: [otlp] 3 L) l* O+ i) E8 ^- Q7 M$ e
metrics:
4 a" P0 `8 g p receivers: [prometheus] ! {5 [- {- \) n; X. d
processors: [batch]
! l- b$ ?: V. b$ h0 ] exporters: [prometheusremotewrite] 1 F. k- ^8 b. M
使用 OTel Collector Contrib 进行 log 收集
2 B4 A2 h: C3 l4 Y. a0 r/ V 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: 8 R! w# V! g5 s( D7 p# @
receivers: % W8 p! i& B& _8 ~) j, U
filelog:
4 U0 i/ m* G+ V9 j1 e, a2 i include: [/var/log/app.log]
1 w% z& n1 {1 N& y exporters: ( ~4 g% k E1 k! k4 O0 U$ u1 r( F
loki: 6 y( i: k' f% f6 X) M8 J5 x' {" Y
endpoint: http://loki:3100/loki/api/v1/push " @6 ~0 u1 S% W
tenant_id: demo
5 ]% ~2 g# B0 W labels:
$ p4 |5 {: \8 k+ {% J attributes: 6 e! A) e9 {2 h1 ^' p1 o
log.file.name: "filename"
; w- d6 @4 L6 I4 W) Q" }2 i' L$ ~; W processors:
3 _# o3 D& B! S$ ]6 R8 n' i5 k batch:
# M' ]8 \! Y( V0 E1 a! c* M service:
! \" X0 ]; }( o K1 M# V6 H pipelines:
- y* `1 v1 M, E$ T- O" C, ] logs: 3 @* ? z, l4 l) F9 P4 U' v8 f
receivers: [filelog]
0 k# A8 f# ~4 r0 B processors: [batch]
+ @7 b9 [3 T/ a$ k- \) O exporters: [loki]
) o' V' ~% S; X2 i+ {4 u: S 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
; z7 p ~) v0 N$ N7 p# Y0 W& W6 ? 总结
I. P8 e$ L0 v 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
0 P2 S$ n* E$ j% A2 `* q) m 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 , _; H( q# j0 K: |% D" K, Z' o4 X
- i. h1 i. E5 u0 T! r4 x# i( N. i8 [ 责任编辑: ) `- y1 A* j7 Z( Q: ]' V
0 S- a8 X0 P, v* ~
7 A" y) s6 N5 l; w, v
; P) ]2 }2 q; `/ ~- e: Z. S) T/ i8 [. r
|