|
) Q2 P/ Y( z& O: h/ Q8 I
原标题:基于 Grafana LGTM 可观测平台的构建
# J% \3 F# }% G: k8 y5 l3 K* G9 R9 J- W! s$ e. S2 l) F
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
) Q5 W @8 }" A3 ~; ?$ u 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 9 r0 i9 {3 W, Y( k) h
通过本文你将了解:
* \, F2 S; C6 W6 m: z1 z2 x 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID " F5 P, ~, m0 W3 N1 d% p/ }
如何使用 OTel Collector 进行 metric、trace 收集
" k: H* ?6 `- m 如何使用 OTel Collector Contrib 进行日志收集 t% m9 H1 I9 U' ~- \6 ]) o
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
5 b# h( p' M" c9 E% i 如何使用 Grafana 制作统一可观测性大盘
$ E- G3 B- ^2 Q0 u# U 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 0 ?6 q% J, s% F+ Y/ V+ N
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 ; X- u, \5 J' g$ y- l5 ?" x
下载并体验样例 ) e5 ]5 L, i P' u% V$ P5 W+ l
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
; L* x$ Q' J$ P% t6 t( w git clone https://github.com/grafanafans/prometheus-exemplar.git ' b+ C% b9 r6 d* k( Z7 V e
cd prometheus-exemplar
; N2 {# S8 f! O4 M- Q: d 使用 docker-compose 启动样例程序:
% T; ^; \$ o/ F8 W docker-compose up -d 8 T% C; Z* h8 [' F1 n0 Q5 K: s! O
这个命令会启动以下程序:
5 _( v9 F' m- T1 j2 _) j6 | 使用单节点模式分别启动一个 Mimir、Loki、Tempo
/ w/ H% J4 @ P5 X1 o6 \7 E 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo , o/ \: t+ k" u7 s D5 ?
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
/ Y0 |! x& w5 | 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
' k9 ]2 d. V6 g% |1 H0 p5 f. D 整个部署架构如下: . X& I# n: q. a
 4 S, [* y: Y2 f8 [% O
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
! I6 t; a, M( o$ Y$ B1 i! G% C0 x wrk http://localhost:8080/v1/books 4 i/ _9 m4 U$ w* J5 z) z
wrk http://localhost:8080/v1/books/1
1 e6 h7 o. t. q: V' B! c% b 最后通过 http://localhost:3000 页面访问对应的看板:
" D7 ~/ Q9 m1 k j  2 r6 u0 O) ?% V8 |
细节说明 . y. z8 a: X5 a, [6 p5 r
使用 Promethues Go SDK 导出 metrics # |* l( y5 s' \' ^
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: & a4 n' V4 l" Y+ x
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
) \7 ?9 n3 O: {+ K& x7 {0 b Help: "Http latency distributions.", ; T7 Y0 x1 I& {! i6 F
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, & @2 N. f5 K3 _# r% _4 t4 b
}, []string{"method", "path", "code"})
/ ^3 X1 W$ f8 ~" Q" h- A/ A prometheus.MustRegister(httpDurationsHistogram) + L. {4 B* s+ |7 r2 `7 C& H( ?# f# W
return func(c *gin.Context) <{p> ..... ' I; {2 v: T3 j) s& ?& @9 P0 S
observer := httpDurationsHistogram.WithLabelValues(method, url, status) 3 i+ \$ @2 \/ Y: \# W5 K% L
observer.Observe(elapsed)
5 t: S' K/ u8 ^0 E* Y- U if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
! ~; V# \% g$ F5 \; V6 G ^3 Z6 v })
' E# P4 b& t! l } 7 X% }( y! f e+ e1 I6 a5 n
} 9 X H8 j% S0 F, T2 |% H$ N) f
} / [& s- s# O& v/ Z2 ^6 P. v5 w
使用 OTLP HTTP 导出 traces
( G( h. K2 Q! X5 @/ I# t# i$ b 使用 OTel SDK 进行 trace 埋点: 0 I$ b6 ?7 z B+ M q
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
/ n" i: D! l J+ |3 Z1 U" \ span.SetAttributes(attribute.String("id", id))
* h- @1 y* O8 { defer span.End()
0 w. B$ ~( L! N // mysql qury random time duration - A8 D6 P" j+ _, s
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
+ l8 d4 l4 b, f; U, f+ O% k1 m5 H7 } err = db.Where(Book{Id: id}).Find(&item).Error
7 U* s( V; q% C+ s+ {3 I S, y return 4 W3 y, ]* G) W& ~* j- g* O: y
} : U9 n6 B& r* ~. M
使用 OLTP HTTP 进行导出: , M/ j7 N6 X A
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
' a; T' ^3 m1 @: ]: s client := otlptracehttp.NewClient(
2 T) S$ I) f7 |+ {0 b& ^1 r otlptracehttp.WithEndpoint(endpoint),
% y4 v& `- T3 b otlptracehttp.WithInsecure(), ) |/ [) {. S( i: A
)
" d. F4 i4 T: T4 d* ]5 x exp, err := otlptrace.New(context.Background(), client)
9 `1 [2 u c- k0 P) @3 g if err != nil <{p> return err
3 |. O8 C( u/ {' {1 i }
2 |( J/ f; g7 V; ^" g' R y7 G tp := tracesdk.NewTracerProvider(
0 C8 G- V1 z+ E+ W tracesdk.WithBatcher(exp), 7 O( @- f' W- ]) Z
tracesdk.WithResource(resource.NewWithAttributes( + B- S+ K7 I0 w+ M% V& o" i
semconv.SchemaURL, ; e& m: [4 E# d$ V' b
semconv.ServiceNameKey.String(serviceName),
6 m% @% N& }8 r: S, X attribute.String("environment", environment), # t' S8 x1 e5 X2 K3 ]
)),
- Z; M% L6 K7 S )
; L! O0 ?5 k" t: @0 e otel.SetTracerProvider(tp) 9 Y2 y! Y+ v2 }+ F! O
return nil
; J; V$ p9 p5 n }
* n/ }! @# }( t/ F1 r* U& x- v 结构化日志
) C9 [" w: U4 [5 `5 c3 G: D; B4 a 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: " l* Q* F+ p$ c9 X
cfg := zap.NewProductionConfig() 2 Z+ i7 f& W' v% _& U R& H% F
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
$ }1 A' a: q4 t) A+ O$ K) Q logger, _ := cfg.Build() ' C0 Z1 o; k4 V1 h
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) 7 W; \9 q# C# [) f
使用 OTel Collector 进行 metric、trace 收集
( p& ?6 v( [* X0 F h0 g 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 2 p, K% \3 \ \! c- r5 r. Z
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
3 V2 g' t. w9 X \6 e1 V( T receivers:
1 K2 ^" Z- e4 `9 U otlp: ; t9 ?7 D) I V6 b! p/ r
protocols: 2 ?$ @8 q* M! } C$ r& S
grpc: % \0 j8 Y- d, q5 N; {
http:
" ~9 a* t, ~- b1 J( \+ X* V- }& R prometheus:
8 `+ C5 B7 k7 {* s g* y+ j( k config:
) Q3 q% n& Q5 B scrape_configs:
; f: h) O p/ _( P0 |5 j - job_name: app
" Y8 V% O4 Z+ B4 ?2 N1 x0 P scrape_interval: 10s 3 Q+ ~' ~2 E8 \+ a- j! f
static_configs: 7 {6 Y) l, E9 f. j* o
- targets: [app:8080] 9 U$ Z8 W. B* d, P2 }
exporters: # S5 U3 x3 f6 {( G7 d8 F7 b |
otlp:
5 s' b" ^6 N1 w7 y. ^$ ^+ h endpoint: tempo:4317
. P3 C0 L1 F b2 s tls:
2 R5 ^ H; c% T, ?& [( h+ A insecure: true
2 x! Z# k; ~% {7 H0 F! m prometheusremotewrite: 7 w2 T7 A" R3 d+ S0 ?" v
endpoint: http://mimir:8080/api/v1/push
2 m1 h9 X( Y/ I tls:
6 |* \' E2 q2 L. ]% r- t4 ? insecure: true
, w% R4 ?+ I: s4 y! t& r5 V2 t headers: + S0 M+ }6 \1 N. ]0 h
X-Scope-OrgID: demo . d4 t& {6 F- k8 B* `8 W: a) t& Y( D
processors: 0 W; f' m2 k( D. K/ R% }+ K' S
batch:
% c' T9 }4 J) _/ M) Q2 u service:
# B1 q; ~- b3 X9 @ pipelines: - f1 {" N4 N7 Q6 n6 T& s2 j
traces: 7 L5 n8 [. N' u
receivers: [otlp] 1 E* v* a3 E4 R: | z
processors: [batch] + H8 C3 F! s( u, w5 W$ z8 \( L: J
exporters: [otlp]
* }* E9 I( N x; P% a6 c metrics:
/ I( d5 m8 O# s; m receivers: [prometheus]
O' Q& ^5 E1 U: e1 O processors: [batch]
0 n7 x! I4 G% t; W( _$ c! A exporters: [prometheusremotewrite]
5 F- ~) {" S5 h* p2 U7 u 使用 OTel Collector Contrib 进行 log 收集 7 |: y# O1 t" Y! p8 {. ~. o1 m
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
9 H$ M5 L+ w+ U8 n9 R+ a& W receivers: 3 [/ p# N. \2 T/ s" Y
filelog:
2 H7 z& T$ r$ y, l' v include: [/var/log/app.log]
7 V# [3 }! i7 r' f7 E, \* ? v exporters:
+ V! T% E5 U" F0 u' Q1 }! Z loki:
, J% B& u9 p' n4 I% _7 q endpoint: http://loki:3100/loki/api/v1/push 3 o4 N" b& t3 t" y" |$ O
tenant_id: demo
4 k, H- B, e! u( O0 T' n labels: / h& C; r9 \( r/ w+ s, r$ Y
attributes:
+ [' P3 J; a4 j" h" L* f6 J log.file.name: "filename"
E1 G6 x* h1 k' z processors:
- W* n( Y" e7 m. o( P- q batch: * l5 A! ?( [$ h& f! V' U! `
service:
' n X ~2 w* t4 g% r. E pipelines:
& H2 b0 h5 _ I! z logs:
4 X6 Q. P6 [3 H. n: p% n receivers: [filelog] / Q2 G) J: c4 U/ ^3 Q
processors: [batch] ( p* |' M& o$ {6 G
exporters: [loki] 4 x3 @- ], e& \$ B
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 1 [: U% t5 r; x$ s; a
总结
) Q, n6 z8 `) L 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
* V/ n" P+ n7 M- D& h* g1 e 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
' _# ], u$ p/ `
8 k' i. T1 {+ ]% O6 S" M+ q- u 责任编辑:
/ @2 V( @# G3 f/ K3 q* t B# ?2 V# I+ K! ?5 c
& i2 M1 c3 S+ F. U
2 L, c. a. B3 O3 J8 n8 ^: M( E) p( n) j4 v1 }0 |% Q- r
|