收藏本站 劰载中...网站公告 | 吾爱海洋论坛交流QQ群:835383472

原创 基于 Grafana LGTM 可观测平台的构建

[复制链接]
) r" [6 o x: H" l; l

原标题:基于 Grafana LGTM 可观测平台的构建

, z% g& }; ]5 S! |1 ~% ^7 x \ r, x9 w. B+ A

可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。

% o- \% Z8 a/ v# C& [ D U! w

目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。

0 \/ h" j" m" D" c% q+ D: B% C" d3 b

通过本文你将了解:

$ B3 L" |& o# @9 c0 j2 U/ F% E

如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID

7 u- s& s4 N0 A& B8 X6 W

如何使用 OTel Collector 进行 metric、trace 收集

7 q3 k+ p! p: ?7 T9 y

如何使用 OTel Collector Contrib 进行日志收集

8 ?0 Z6 f z6 X& `7 K+ V3 ^& L

如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储

3 G- Y4 g" ]) A) g6 y& V% l9 v, O0 a

如何使用 Grafana 制作统一可观测性大盘

- J2 C& K& U4 V: H. `7 O

为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。

1 O% u$ E( p3 P& y, ?

当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。

9 A' G" r; s( h! A+ P

下载并体验样例

7 I/ K5 |/ |6 @$ V9 B

我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:

8 [) I9 l, N! n! H

git clone https://github.com/grafanafans/prometheus-exemplar.git

" r9 E/ \- ?6 i% {6 G

cd prometheus-exemplar

$ p7 g- I' {+ y) x0 t

使用 docker-compose 启动样例程序:

$ W o$ t0 y- g/ m E' ]

docker-compose up -d

8 @$ n" F9 r; i; X0 p

这个命令会启动以下程序:

2 y- A# ~$ E9 T! H/ |: W

使用单节点模式分别启动一个 Mimir、Loki、Tempo

9 q* ^* {6 p T5 j/ _6 j! [

启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo

4 h# x$ }: H0 m+ ]) T x5 x

启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问

) o h% @( a$ ~. w) ]% Z* K2 C2 T

启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问

+ @' k' i) ]; f$ C6 t% `

整个部署架构如下:

( T8 K! S) f, H# ~, ?

3 o! w& ~* @) M8 F* ]& n: C8 R

当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:

2 q0 l8 y: ?2 V0 j' g/ }" K! C7 I1 x

wrk http://localhost:8080/v1/books

! L6 c3 I% H; n* Q" L/ C+ Q

wrk http://localhost:8080/v1/books/1

" f% A+ G2 A4 r* K- s+ n8 _# m% g

最后通过 http://localhost:3000 页面访问对应的看板:

, i2 a1 E: S8 [

( z- W E. g3 R' w, R" S

细节说明

4 Z4 a8 n8 Z* ?0 ~+ ?, h, k

使用 Promethues Go SDK 导出 metrics

3 Z! w/ n: Y" n. x

在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:

: ^& X9 S& G1 s' X5 B

func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",

7 P. f# T+ F8 n `3 b

Help: "Http latency distributions.",

6 f3 j8 X" T) o' ^. E

Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},

5 O; F( x2 @0 C1 F$ l6 d

}, []string{"method", "path", "code"})

# n. b4 h4 n, ?7 [. e4 Z. g2 Q

prometheus.MustRegister(httpDurationsHistogram)

; S" \" ~4 v& N N* Z

return func(c *gin.Context) <{p> .....

$ {% i! r* T+ a# `/ _" P5 b

observer := httpDurationsHistogram.WithLabelValues(method, url, status)

% U* E+ p; e. ?* ^. p. H

observer.Observe(elapsed)

% ]& K+ Y8 Q, p7 F- P Q9 l

if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),

6 W/ R8 { u- l2 C: x

})

1 S; w/ p2 D+ T' Q5 v

}

4 U5 _* o$ Y( @* w

}

5 h) S$ R o% f/ I

}

- r8 {3 q% ?4 x; v C, s- d2 c6 X

使用 OTLP HTTP 导出 traces

4 c& z/ [. ~$ z3 ^

使用 OTel SDK 进行 trace 埋点:

4 {" x; ^& O0 B

func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")

; b6 d1 ~/ U! z0 j5 j4 U5 C, V

span.SetAttributes(attribute.String("id", id))

9 i* D1 s3 o4 U# T

defer span.End()

. `0 x% @. H' }% r6 O

// mysql qury random time duration

) t) w c; B' t. j1 o

time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)

; \( ]7 t# p$ D8 V* ]& K N; U

err = db.Where(Book{Id: id}).Find(&item).Error

. d$ i! w8 `- g2 `' ~2 u0 E

return

* \7 A. x# e) p( }2 n1 B, C

}

4 h, P% a, J! P% E9 N

使用 OLTP HTTP 进行导出:

* B' c2 S, H' G9 W: k1 V8 Z; F

func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name

( i3 s- ]9 J, D% S5 m% P

client := otlptracehttp.NewClient(

3 Z3 a' S8 Z7 J

otlptracehttp.WithEndpoint(endpoint),

& j1 K- X+ y$ i

otlptracehttp.WithInsecure(),

& W$ u x3 w# [- T) h8 ]( X0 N

)

9 i* C: n3 k' C0 p o/ t& C' f; ]

exp, err := otlptrace.New(context.Background(), client)

: l s+ O0 M0 h9 e5 `5 E/ V

if err != nil <{p> return err

# X: e: C ? y5 K

}

: V3 T) l/ Z# Q) f. M' y) o, u

tp := tracesdk.NewTracerProvider(

2 u, I6 b# _0 V, \9 }. {

tracesdk.WithBatcher(exp),

6 A" s. i0 w- Y* w* r

tracesdk.WithResource(resource.NewWithAttributes(

* R% k* J' W6 }* I* N4 _$ b2 o

semconv.SchemaURL,

6 V8 Q4 f9 G2 O

semconv.ServiceNameKey.String(serviceName),

8 p8 ~+ U1 A% t1 d6 j

attribute.String("environment", environment),

) J8 Y% B0 H- L, x, e6 P/ t

)),

* P/ [8 }1 H5 l* _# |% q

)

6 K# `" {* N5 `7 p; ?

otel.SetTracerProvider(tp)

) l0 l( ~. t. R0 @: W# P* I5 F

return nil

& O/ {6 Q" ~8 l- F( J1 z

}

' Z3 X+ n k/ \" K

结构化日志

+ ?6 s$ ]0 q$ D$ v# L* `. B

这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:

) x5 J6 z( v5 [) {1 ~( ?2 E

cfg := zap.NewProductionConfig()

& l0 e1 \" z. y& W4 ]

cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}

6 Z& k$ F2 a& _5 [

logger, _ := cfg.Build()

y& `- c- {% ]+ X$ S; m$ T

logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))

9 a3 O8 \: O% y, l5 Z) q

使用 OTel Collector 进行 metric、trace 收集

" a1 \4 t% R; C6 d7 ]& Q. q3 u

因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。

. B5 J4 L6 V2 v

针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:

1 Z1 o# z5 F* L0 f

receivers:

( E7 Y( y8 S: {% M1 `% z/ i+ C# K' i

otlp:

5 K% m& s- _; P: t# C# ]( g

protocols:

" u2 Q+ j1 n. Y1 ^8 w: |6 y

grpc:

, U) D7 D0 ^5 q2 ]

http:

9 Y2 h+ g$ a- k1 P

prometheus:

0 q4 V3 V1 _" T

config:

2 x" \9 K/ o7 E! J" _6 l5 W5 M& r# h

scrape_configs:

( A2 d8 q: m7 b9 f# j

- job_name: app

1 d! g( ^: v9 }8 [2 Z) g% g

scrape_interval: 10s

: T9 Y2 H2 \( l8 Z- x# }

static_configs:

; d" c2 D$ ~4 O& ^/ k+ R/ f$ F

- targets: [app:8080]

) l. z" P! S5 n) T7 t* J: r

exporters:

7 T; Q' d8 d- a; V( a3 y: R& ~

otlp:

9 [0 \6 n& N* I+ d

endpoint: tempo:4317

. Q! d4 V r; L6 f+ E

tls:

9 t9 n1 W7 x$ L Z( P

insecure: true

; [2 t k) L+ R9 e

prometheusremotewrite:

, O3 P# n- E! z) y. f% p# k

endpoint: http://mimir:8080/api/v1/push

) |* k! J0 h4 z$ E: ^' c

tls:

5 D# F* z% }3 n9 p3 e& V

insecure: true

/ [, I+ J- x7 a; ^6 a: `

headers:

0 e' E, ^& ~( ~8 \. U

X-Scope-OrgID: demo

2 X9 s& g, f7 ]+ t# G- x

processors:

5 ]# \3 u0 j- B, |- ^& @

batch:

% J: ?- J- J3 W4 q

service:

0 x9 m/ Y3 u1 m' X; p: ~, Z

pipelines:

# ~/ x [5 ^5 {, b

traces:

% T$ O8 W O$ K5 T- C

receivers: [otlp]

4 _6 N- q: R) b# H( {9 U! J

processors: [batch]

+ h' e7 B9 d0 q$ ?% A& Q' Y

exporters: [otlp]

9 J. K; e6 J- B" x% t8 |3 R w

metrics:

* C, E1 H9 G; `) s

receivers: [prometheus]

2 U) \" T1 s9 i) f6 ~

processors: [batch]

% J i* h7 v# j9 H& W9 V: U9 y

exporters: [prometheusremotewrite]

3 ~9 m1 _& x. ?$ k

使用 OTel Collector Contrib 进行 log 收集

: i4 e. S; G) j. y: x$ n9 O

因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:

1 ^/ P% [1 L# s4 J! L2 N3 G7 S

receivers:

; c2 E& D0 b2 a/ v2 E* T

filelog:

, Q/ k3 f: V4 M; h& D* l

include: [/var/log/app.log]

* {, u4 F- Z8 ~- P4 g1 i

exporters:

: P( O% }& s( K9 k

loki:

1 r7 ?# b3 j7 {2 i- a

endpoint: http://loki:3100/loki/api/v1/push

: F- h- S G. ^8 h' i, d

tenant_id: demo

% y0 u( \9 G3 j v2 h% H

labels:

3 ~ N0 |) q; `' x

attributes:

! \+ K6 z" t) q7 o* T0 P! w& C3 o3 z

log.file.name: "filename"

. G# G! v, S. p0 t# Z

processors:

# N5 A& Y8 N7 N5 e1 j

batch:

6 d& A" R# A5 ]# E/ L8 O8 }4 y

service:

# y2 w7 y+ ?% T1 F; E! D1 j

pipelines:

" m3 {8 e5 ?" G: }" U7 M

logs:

. [7 @! E j4 n

receivers: [filelog]

% w4 ]! h, o' Q9 { r& y8 }

processors: [batch]

$ `) u" z+ y! d7 C5 A7 |

exporters: [loki]

, ?# S8 \0 d0 O3 O" E. k9 U

以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。

4 e/ V! H' \- r4 |6 l+ D

总结

3 U& S! t8 R* _* [

本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。

0 e+ H* t: Y2 O5 h% v: N8 y9 I: g8 v

这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多

- s) ]& U* V7 ?: K4 E- {1 b4 F) m3 Y0 L( C, l$ E% F2 R9 y

责任编辑:

4 W! \! g4 u. u# j0 h2 z% @& N " m9 m5 q% _# o) ~ $ e% {7 f" D+ p. w " j" \8 L( Z" _' Z# ~/ l - f. T9 v5 K% K8 m
回复

举报 使用道具

相关帖子

全部回帖
暂无回帖,快来参与回复吧
懒得打字?点击右侧快捷回复 【吾爱海洋论坛发文有奖】
您需要登录后才可以回帖 登录 | 立即注册
汉再兴
活跃在2026-2-8
快速回复 返回顶部 返回列表