|
+ E; I/ f- e X" ~) h 原标题:基于 Grafana LGTM 可观测平台的构建
' T4 o% N0 q$ j/ f/ R4 P( R
* a- a3 E, g7 H1 `7 s 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
3 u1 c( `8 a( S9 s+ _ 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 + N+ w8 j! _' ^ j# l+ x
通过本文你将了解: % A# x, T+ P* N1 C/ @; g3 S1 w+ t; o
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID ; ?- i. F7 N( U8 t+ N5 T+ t
如何使用 OTel Collector 进行 metric、trace 收集
2 V; m% n$ f) O' n+ Q9 m& u" H t; w* | 如何使用 OTel Collector Contrib 进行日志收集
# w# D* [' d7 X 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
2 e u# L& T) I6 |: S8 W 如何使用 Grafana 制作统一可观测性大盘
" B) |7 p' H; C7 C( l 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 % [, Y# Y) \9 x, U
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。 6 P" y q3 `' A( f. T
下载并体验样例 & t0 E! X: W8 P' @) q6 n. x1 r
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
" S/ _2 x4 h- m, Q git clone https://github.com/grafanafans/prometheus-exemplar.git
+ F2 O; M- A" u4 }0 d0 Q cd prometheus-exemplar 6 i3 r: E+ y+ B
使用 docker-compose 启动样例程序: - g& `2 z' {0 @0 d8 ]6 m) Z- w
docker-compose up -d ( N3 W- d0 ]/ @
这个命令会启动以下程序: 1 D" d7 ?- q. [+ C
使用单节点模式分别启动一个 Mimir、Loki、Tempo 1 ?# ?. \ R. H* L0 j
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
( |# i% w: j9 p3 S 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 ' z4 D1 }& p' y( e- [- j* m8 t0 L- e3 Q
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 2 ~0 c/ s: G. ]
整个部署架构如下: 5 L8 f/ a. g0 @1 K6 N$ J1 ]7 D" I
 0 F" V5 Y" ^& {: B. }% l \- v$ g
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
# R {3 s6 S1 h- o' s$ j! S: R( ^ wrk http://localhost:8080/v1/books
8 y+ N2 X9 |" y8 d {& b+ G wrk http://localhost:8080/v1/books/1 5 R2 u+ ^( k* K2 e9 v
最后通过 http://localhost:3000 页面访问对应的看板: . w) F$ w& P& `

1 C3 u6 ^7 h! Y8 H2 j4 \ 细节说明
9 N$ G/ Z3 l1 \1 v/ b' z5 K 使用 Promethues Go SDK 导出 metrics ) n, L; ~/ R! u; @: I' N3 `. `
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
0 ?1 A' ^) }: s, ~# z ` func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", 2 e* N% F" y u( w/ O
Help: "Http latency distributions.", 7 r) v/ w: q( t m3 L/ Q! U
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
1 ?* H7 Y4 L: J+ O# D# f+ R }, []string{"method", "path", "code"})
- m2 C1 n# d" s prometheus.MustRegister(httpDurationsHistogram)
, `. H4 u* B! y6 n% h; }& D return func(c *gin.Context) <{p> ..... 9 w4 v! C2 R& c& {( [1 T0 d2 q' Q
observer := httpDurationsHistogram.WithLabelValues(method, url, status) 2 N4 _- F9 H7 S/ c
observer.Observe(elapsed) 9 T4 _* |- K8 a U+ Y6 l
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
5 K* K+ @# m4 E1 i) K9 r6 J }) - d/ S' S2 \2 W7 I2 a
} ; X1 o& q0 G1 A! _
} 5 t% ?8 l t l3 [$ R4 s
} ! W3 A. @1 T1 a
使用 OTLP HTTP 导出 traces
3 K$ A4 Z8 I, R$ @ 使用 OTel SDK 进行 trace 埋点:
5 ^* h9 ~) ?9 u: }1 ^* }7 e& {: a func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
* u- O( b6 ^6 ]! m( g. Z9 N7 y span.SetAttributes(attribute.String("id", id)) % ^- O. B8 t; @. A$ F3 N
defer span.End() * O1 d- P) _. E3 m( k
// mysql qury random time duration . ]# J* D- l1 B$ V
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) % F8 r0 O4 X" }6 y l* i G
err = db.Where(Book{Id: id}).Find(&item).Error
4 G% t' a' }6 `/ D3 J/ b/ f return ! u5 O7 K* B2 F: j. j
} & F0 t6 l) D# o9 y
使用 OLTP HTTP 进行导出: 2 W; ?3 ^& ?5 Y3 @0 ^' w4 i
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name + J9 f' g8 L2 G9 F
client := otlptracehttp.NewClient( 4 Z/ Y" M* ]1 K' z Y0 `$ T% q1 C
otlptracehttp.WithEndpoint(endpoint), % [' f2 j2 f5 g2 V. o: d( |
otlptracehttp.WithInsecure(),
6 o9 `3 \- K! C ) 5 b5 n% c' _6 N! c( C+ x+ u
exp, err := otlptrace.New(context.Background(), client)
Y. N0 G9 J$ t- C, N# Z( g if err != nil <{p> return err
5 m9 M% @4 R- d) p: S# w. N3 Z } 8 T* g# s; Y2 |0 P
tp := tracesdk.NewTracerProvider( 9 J" d0 q* X3 m# Y$ H2 f# o
tracesdk.WithBatcher(exp),
, d) r+ X, K9 |: V tracesdk.WithResource(resource.NewWithAttributes( " @& q6 i$ T4 C1 a
semconv.SchemaURL, % U p0 I! j$ g, Q* r. ^3 E8 b0 w! u
semconv.ServiceNameKey.String(serviceName), " }3 a+ Y/ d( y* p
attribute.String("environment", environment),
7 Q2 ^0 I: h$ [* }% m )),
5 G6 h2 R6 K5 T$ E ) e' }' K" y0 o+ _
otel.SetTracerProvider(tp)
3 W1 g# U" r Z+ Y# T% g return nil 9 l8 D0 a& t7 t
} 6 H) T) w+ ?& R" F2 e: r
结构化日志
0 s5 Y% J6 @6 ^5 q# J 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: 7 n: C* }5 ^! r
cfg := zap.NewProductionConfig() 3 k8 a9 u1 O& v/ U
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} ' x$ M% L: f4 K+ Z8 Q7 S" K
logger, _ := cfg.Build() & O l8 b% w' {% Q3 W# B2 p
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) 6 }- I, D3 }( R
使用 OTel Collector 进行 metric、trace 收集 " F L1 O+ y: t5 Z$ U' m5 K
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
" p8 [( M6 ^0 T 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
7 L5 j8 o5 m5 Y2 W# }7 O receivers:
8 o& O$ ^+ _0 x* g9 H9 a otlp:
$ L+ X" K3 N$ @9 Y% o) S+ R; k. o protocols: 2 g5 F' }- Q# }1 W+ ^& q
grpc:
6 L% I" g. @6 S( `- a* t# U) S http:
: A9 L( L1 Q: ^( ~! H+ h prometheus: & S. @8 J' S2 G1 x+ R/ z+ \+ g
config: , _; a% M% ?' N9 G) |6 }+ H+ S
scrape_configs:
7 Y1 \9 o) `0 ~% K6 v* h3 q - job_name: app ( g3 }# s, J; T/ u, a+ O
scrape_interval: 10s
, o& o7 I; c* l1 g0 V2 w3 e static_configs: * ]* i& Q4 H: ~
- targets: [app:8080] , [- m" r1 _- J+ t v7 n
exporters: : y0 Z# P- P7 ^; D4 e8 Q
otlp: ) \7 ~* r8 F; D, d, ?$ U
endpoint: tempo:4317
# W( r8 C" v4 c" I tls: & i1 u0 J- l* t" E* O# {
insecure: true - n! r- Z: f+ ]7 M4 b! w6 k J, V
prometheusremotewrite: 9 I, f" U1 `/ p. [
endpoint: http://mimir:8080/api/v1/push
" ~9 z( {0 t; W M tls:
! g% ^7 A6 c( s" Z insecure: true 5 K% ~" u L' W: U" J- C6 w o
headers: 3 @! ^. F4 z1 R9 G! E" ?+ i
X-Scope-OrgID: demo 5 z( w2 }% }3 q9 }+ r% P
processors: 9 k0 P0 P- p. t
batch:
" K/ I! ~' r5 r2 C: v z# H; [% b service: " j" S7 V9 B( e4 N
pipelines: ' m: j0 }, R2 K4 A
traces:
1 b6 F$ N3 n( G, K( ^ receivers: [otlp] % x) ~4 x8 L1 t- g8 f$ o- |# Q9 ?
processors: [batch] + g- t! X$ q4 v# E" {6 V
exporters: [otlp] 6 g1 i. y" W# s* o% m. `3 O2 Z$ P
metrics:
" @3 @* w$ V0 |1 u0 @2 R* E& v/ M* c receivers: [prometheus]
; S( e+ b. P" E, w) t4 w processors: [batch]
& B! V; \4 k0 f( v; e6 ]4 S exporters: [prometheusremotewrite] 1 \3 h+ k* X# M
使用 OTel Collector Contrib 进行 log 收集 $ U: R/ x% r4 Z$ B
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: $ I1 U; ?, _' a
receivers:
2 M9 d0 |& j$ I$ `1 n6 @1 g1 p9 T filelog: ' @" k4 c0 z5 g# p7 A9 `3 A
include: [/var/log/app.log]
# ]" f r, r, S3 ?- u2 E exporters:
$ S6 q* G" {- n' e& V loki: ; q. x: z7 H9 m; [* T4 |
endpoint: http://loki:3100/loki/api/v1/push
( l! [. I6 f) F% \1 o% O2 e tenant_id: demo
: a/ \" S: _ w! e labels:
; P' p! E( x! h attributes: # ^" h4 T3 q2 T
log.file.name: "filename" - ~# \" ~& E1 M: h/ T, p
processors:
) d6 h3 b8 A" f* L/ V8 R8 z7 c4 d batch:
9 X1 L, G3 ^3 ]& F- k7 C8 `' ? service: _/ s( j, d% i9 m- p2 @7 d% i ]
pipelines:
, b" ^1 e7 }( D8 t% u! N logs: : l, d; g7 v4 U7 H" L
receivers: [filelog]
h2 ^# q$ A8 E5 H processors: [batch] ; F0 d( W0 D: k9 `
exporters: [loki]
* n5 Q$ P! h; X0 g& G# M, N+ Y 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
7 B* }8 u0 Y$ `5 m% b) f, w 总结
! u. i( S* Z: Y 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
* F K2 z& |5 q 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
0 G$ l0 s$ m0 b! W% L% ]
+ e6 l% ^! N F0 t: S 责任编辑: / ` R: ?6 j/ f0 ~8 i
' K; u* Y5 n: f- e3 `1 }7 `- B8 i! n( c& d! P7 V: ]/ `
5 E' Z1 d+ T6 `# K3 f- T* e- f5 E7 u
|