% m) [2 ~% p- M6 i; ]9 ^( h/ F 原标题:基于 Grafana LGTM 可观测平台的构建 " y# Z) L1 n0 d( R! p0 Q+ j/ k
3 I+ h3 x7 e- ]4 O( g5 z 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
6 @- u" z# x& P4 f- P# { 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
0 z z9 Z/ K1 x3 }& o) ] 通过本文你将了解:
# j5 b% d6 l3 u, m! e 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID % @) R2 Z1 d1 h" |! a
如何使用 OTel Collector 进行 metric、trace 收集 + K, W8 ^# K) U" s6 t
如何使用 OTel Collector Contrib 进行日志收集
+ Q9 c2 P" P6 n- {( `# t 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
, c, F) ?3 b$ \9 w) { 如何使用 Grafana 制作统一可观测性大盘 9 \" \4 t) n9 b `0 K5 k' ^- n
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 6 |% B) H, \% x7 H. h, D- \! l
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
9 i9 U6 f& S! x a 下载并体验样例 : i1 ~/ F& e0 ~. ?# b
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: # k6 L* ?9 ^, ~5 _3 i- p i
git clone https://github.com/grafanafans/prometheus-exemplar.git
; m' b+ e; Y$ f# x# ~, u. k3 O cd prometheus-exemplar
! O) a/ ]# S) B, F 使用 docker-compose 启动样例程序:
1 Y7 g. c2 ]) R% @" A docker-compose up -d
8 h1 X0 H k( k/ T 这个命令会启动以下程序:
8 T7 i: S+ [# C; }8 U1 r 使用单节点模式分别启动一个 Mimir、Loki、Tempo 5 ?, Q$ }* }# U
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
5 U) C9 y" j2 M 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 ; m; ?! |4 w9 |" L7 x) w
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 4 R4 @9 S. M" ^# P
整个部署架构如下:
# B5 ^& B" T3 p2 [0 t - ^/ Z5 p- d) ?# i/ e
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 0 q5 M* d$ ]0 h; }
wrk http://localhost:8080/v1/books ' U" X" ~! C5 M) q
wrk http://localhost:8080/v1/books/1 0 H9 X# u, H% g6 }8 h3 g& d
最后通过 http://localhost:3000 页面访问对应的看板:
6 J, d) _6 h: x+ W' Y6 b
* f0 |3 q% B" w 细节说明
8 W5 L. T: M2 g; {9 b' ] 使用 Promethues Go SDK 导出 metrics
8 z: h G* ?& L" b 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
* m" ]2 {& j8 K3 S0 v func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
7 Y6 C1 C& v& F Help: "Http latency distributions.",
4 s) j5 \7 Y% U% y& ?. d9 v Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
" [& c( S5 e! l }, []string{"method", "path", "code"})
4 E& _8 j0 \. k0 t/ L prometheus.MustRegister(httpDurationsHistogram) + G/ l; z# {7 s5 s% z% ]6 i
return func(c *gin.Context) <{p> ..... ) \/ b% q l/ q' ^" U4 J
observer := httpDurationsHistogram.WithLabelValues(method, url, status) 1 S+ s7 g$ }. ^9 |
observer.Observe(elapsed)
+ F; W+ ~1 r. e if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
7 s1 ]. Y; C7 g- d! d6 J }) ' w# _% ?9 O3 @' z% v1 u7 D
}
, U2 r3 q# f% Z" I* e$ f" e } 5 Y8 t) c1 X c, k) i7 h7 F! u9 l- t
}
1 \! x! n4 c& E9 ?/ M- t5 Y 使用 OTLP HTTP 导出 traces 8 u( p L! }) ?4 p: }# l/ [
使用 OTel SDK 进行 trace 埋点:
& U" y& a, P0 |& s func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
0 q" Q$ D9 L8 F/ k% A& C% H |) b span.SetAttributes(attribute.String("id", id)) $ q% J, Y9 S# D
defer span.End() / T( C0 N$ U% |" j6 q
// mysql qury random time duration 9 Z1 l* l0 A* z# S4 F$ R0 T6 J5 J
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) / N. }5 w( u6 e1 M& J, \
err = db.Where(Book{Id: id}).Find(&item).Error
- G; ^# \. O0 j7 n2 K- T8 m return
" g: I* O- Q- V: J4 _+ o. S } ; `8 d4 U* |* G3 X- }
使用 OLTP HTTP 进行导出: # O0 S b R7 q2 Z$ v
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 4 d/ X( J; W" i
client := otlptracehttp.NewClient( - s4 z+ @ \' {1 }2 L
otlptracehttp.WithEndpoint(endpoint),
1 \$ E" b+ {- X+ @* Q3 l5 P otlptracehttp.WithInsecure(),
, Z6 A* R# d" |5 \+ A, E ) 3 @9 u9 ]! @9 q3 H8 M
exp, err := otlptrace.New(context.Background(), client) . G9 a+ l) H8 b4 R/ h5 i2 [, W* l& m2 J
if err != nil <{p> return err
/ G: z4 n$ n( \6 r* }1 A5 a }
8 Q$ p1 f' c: | tp := tracesdk.NewTracerProvider( & `4 r! s7 A h: u/ s' a8 r0 l
tracesdk.WithBatcher(exp),
5 c3 I6 @1 p- D$ n! @+ ~+ |0 k) g tracesdk.WithResource(resource.NewWithAttributes(
4 f( g9 E0 `# ]* m4 d d semconv.SchemaURL, 6 S( L2 e% a& _: m3 O$ H& }
semconv.ServiceNameKey.String(serviceName),
8 S% ~2 J3 Q8 z% O0 x attribute.String("environment", environment), 2 B% U: }1 g6 T4 u
)), 0 O; j% g5 u; S
)
2 n- v% b! ^" A- v9 f7 i( W otel.SetTracerProvider(tp)
: ?' M# ?; r" e E$ a* j5 M return nil
9 q/ @4 R& L: O6 O! \) [* c } 6 i1 u% C& R! M& S
结构化日志 ' Z1 ~& r% C3 o v. t
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: 6 j ?- }$ e* K( ]0 u: ]; l
cfg := zap.NewProductionConfig() 1 S& R& G. i* m
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
/ N7 T, n) z, |* {3 ~8 y2 _ logger, _ := cfg.Build()
+ ?% d3 E8 B/ H4 g$ i logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) % y& L0 q: R" r' l
使用 OTel Collector 进行 metric、trace 收集
7 @/ P9 t) C7 H. \# C2 Z5 N2 g 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 4 n4 q- u' M0 K9 m" t4 M2 I7 t
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
7 }; b5 v" \/ i& D! h receivers:
6 W& x0 T$ L d8 G* ^ otlp: ( {, @9 B% w! G
protocols: * {# [/ [+ k+ A0 K" r. P" e
grpc:
: g2 z& P& a; h, x' K1 m http:
% y5 w5 y8 l5 {' q+ B prometheus: * Y8 v5 z2 m: E# T' [" Z1 }
config: 7 g \2 v8 U; q
scrape_configs: 3 h* A8 V9 o& n9 }
- job_name: app
8 e0 c ?1 {+ C$ S- P8 O scrape_interval: 10s
2 l7 Y* F- X5 c( Z static_configs:
6 q- C* w% U* x. U - targets: [app:8080]
( O% t# p3 G0 \, f exporters:
2 c3 |! E( y+ b9 y; x4 L otlp: Y. k. E. a, y& F
endpoint: tempo:4317 7 I0 D! A h( T" O
tls:
8 `! Q {- J0 C% x8 u+ l! u: Z insecure: true 5 B4 l+ P: u; b" Y T; f
prometheusremotewrite:
" C: }: [6 N6 U0 b endpoint: http://mimir:8080/api/v1/push - z' r4 E% E% B! l i0 O& y
tls:
+ J; _0 ]9 u+ |: j3 `' s insecure: true 9 S# @$ }& [& b3 j0 i
headers:
0 i+ L/ @ s5 j( s' g4 z4 {* O3 t X-Scope-OrgID: demo
3 w& E$ ]4 g( O$ N. o processors: + T% b: n" i W6 X7 X$ x
batch:
# D) v7 g1 D& w( P: W- _ service:
* ^, Q/ T% t- P! G" o" Z pipelines: 8 S& x% m3 x$ P. N' D6 n
traces: ( k( @0 @3 M' Q, b
receivers: [otlp]
5 X2 W* P5 W2 |; e, V$ ?/ r/ G processors: [batch]
7 `3 K* s, X M* V! n) ` exporters: [otlp]
2 |/ x! l- Q. s2 j9 Q+ A4 k metrics:
) |3 u4 [0 b0 v receivers: [prometheus] 6 f+ L6 Y+ C; }% n' M
processors: [batch]
6 W" q: s) a. d L. z& u exporters: [prometheusremotewrite]
! i* S9 x5 c# o" [* [; n 使用 OTel Collector Contrib 进行 log 收集 ! D3 M3 } e) D) ?
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: 7 }. n5 z4 B4 {
receivers: % U. N+ a) o" d6 O8 S' ]& W0 \" C
filelog: v3 R1 i6 k) S: A9 R+ o' s& p# u
include: [/var/log/app.log] + W+ Z9 ^! l2 s' q' D
exporters:
* Y8 w( ~. X- W! m7 v$ Q loki: " T; y5 L7 C W0 r+ ] P; x: h: ?* Y
endpoint: http://loki:3100/loki/api/v1/push
4 M- q5 h. m/ J% }1 ^5 R! P2 F& @ tenant_id: demo
, J/ K; o& z2 a& X labels:
; f7 A9 W9 p7 u E" t' Q attributes: / Q" U1 ?( U ~9 J) |) F7 X1 E o# |. L
log.file.name: "filename"
+ w- J7 `2 S+ J3 P H4 U processors:
* G& ~: D- i7 K( t( B4 Z batch:
1 A/ `. x5 q- V# [* @4 H0 x service:
6 Z: ]/ W4 ]* _: j# ] pipelines:
, L4 A; N2 t" |$ ^- O logs: ; Q h. A" ^" d) S0 u
receivers: [filelog] ) }; A4 [* X: ^! s2 T
processors: [batch] . m/ r( }4 b( {: u8 R3 @
exporters: [loki]
* w# t( u& Z# c7 s2 O) Q 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
8 S3 f9 U* q+ C( r$ Z d 总结
0 ?& p+ F! p# b& b! x 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 1 O1 y P) H1 f& Q0 Y
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 8 E, ~1 c4 ?1 ^9 b1 \
1 ?5 V8 c3 d* M8 W/ U6 W 责任编辑:
7 g$ o. f, T/ ?8 T# G; c; ?" u; g+ ?. k _+ m& m, T5 {
' h8 O G5 t& {1 Q
2 K0 e5 N& B- U: E2 S9 |: V: u; z
* y/ u0 y0 n( r; |5 J+ t |