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

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

[复制链接]
7 k# q3 G7 D: x& B

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

8 I) x' ?4 Y! a! _( d( D % ^" `1 X9 x) E4 U4 t% X/ e

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

6 b- _! ]+ G9 U5 E% Q

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

! E; A( S1 @' x2 T

通过本文你将了解:

$ Y: n- V2 |0 R+ T8 k0 p

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

' O) X }: R/ \* o4 z

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

8 p6 m5 b( x( \

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

' T! Q7 ]6 ^# m: t3 a6 v

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

S7 P- K Z3 }* ~( U8 C

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

8 w2 Y s) B/ d1 G. I' V

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

% K4 R/ y$ m+ @& J( C

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

, V6 |9 b' x* {) h

下载并体验样例

5 C& m m- _+ {) N/ u) `/ V+ H

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

; I7 j: D! I, i) m* h5 E8 ]

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

2 p: k" Q8 o* C: s5 P1 e

cd prometheus-exemplar

# Z& _8 h# d9 H4 ~# n- @( {( |

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

% `( B, S8 O1 U3 B# B2 d

docker-compose up -d

! D z9 C% `, U: y/ T9 U

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

) @! f( R8 w8 B3 ^% u% x

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

1 j8 Z% `7 T. S i3 |$ B u

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

& E1 V/ G& F8 e. Q1 f+ w

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

3 T2 S e2 y- D8 q

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

: b5 o3 `8 d% ]. h' y: D2 [

整个部署架构如下:

7 F2 o5 @" ^2 {+ g$ c

7 K" ^; j) U* `& S1 m j6 R2 s

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

$ M2 C* V2 h( v( g: e

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

6 z' D( L v+ k4 B6 q3 b/ I3 r7 w

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

% y& E4 Z% j) f) j: x* s

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

0 a6 x% t% X3 C/ B$ ?

7 J4 A$ n& a! e

细节说明

8 r. ~7 u( X8 l

使用 Promethues Go SDK 导出 metrics

2 ]: r1 i& h9 g. D+ S5 X

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

$ a a+ G0 ^, h, ~9 D

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

" B3 d0 Z; K- a$ r

Help: "Http latency distributions.",

+ O8 U3 d" \+ @6 {. d' J

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

* K* O5 h, X1 s+ j) ?6 m

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

2 l. P& u9 L& |" ]5 ~ k

prometheus.MustRegister(httpDurationsHistogram)

+ ]* F# W5 Z" m9 W6 A* j9 n. o, F

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

Y, q/ i3 U: K7 w$ K% c" {

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

/ x2 R% B( H/ }$ ~) \" D

observer.Observe(elapsed)

7 [/ N I+ ^! p( E7 G

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

$ n; k3 g. R$ D# h% m, l

})

g+ ], a- y( _7 w/ U5 p: M/ |( Z

}

. P' k$ i/ t A: I# P0 \7 o

}

) |# o2 d6 a6 S

}

3 b4 m9 r6 q- E8 {

使用 OTLP HTTP 导出 traces

7 n5 P2 E+ z) K; p& t2 N* _5 e! h

使用 OTel SDK 进行 trace 埋点:

: P0 f( Q. j: n- j6 _

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

/ v" V1 a) m4 m! P9 `2 T7 u

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

: _7 Q7 K2 e' \# S: `

defer span.End()

' f; I V$ t- l, O& b/ n' O

// mysql qury random time duration

N6 A O% ]; ?

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

( B, z7 M- }) Y. H! Y

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

4 B" j* x# W3 Q5 P) f$ Y. m4 z

return

% V+ o- [5 E6 W) T5 {8 J+ C8 [

}

# d3 ]" l$ D3 s' `3 U' U# I

使用 OLTP HTTP 进行导出:

4 q! k" `# x$ A( O' Y0 H, I& v

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

v3 `9 L& H( [, [& F# ^' f" T/ i

client := otlptracehttp.NewClient(

3 I# B- n3 W) a) |+ q3 Y

otlptracehttp.WithEndpoint(endpoint),

! v8 b5 V0 O7 g& r9 ^0 e) e+ L: T: `

otlptracehttp.WithInsecure(),

8 k( ~( y( K; e4 u5 e! |+ u2 ]* y$ C/ T$ P

)

) D- W* X m- c# n# P

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

' y/ m5 b% z" y9 `; x7 K

if err != nil <{p> return err

( a' j; N7 s# v, [. H4 Z

}

- @5 s; H. k8 P8 }1 a8 }

tp := tracesdk.NewTracerProvider(

& D% |. N! C6 j$ f1 e

tracesdk.WithBatcher(exp),

5 U+ F0 l! w# k( W

tracesdk.WithResource(resource.NewWithAttributes(

& a# }6 n# J7 ^, q0 _; J9 H

semconv.SchemaURL,

: ?6 E5 Y, {& \* a

semconv.ServiceNameKey.String(serviceName),

9 Z9 l6 C1 T9 V5 D0 ]! z

attribute.String("environment", environment),

2 \; B2 I; L$ R4 E

)),

- I) i( v0 x) m

)

# L9 U2 d% w. m4 S

otel.SetTracerProvider(tp)

, J4 r' ^. H6 }2 h2 D

return nil

: f: Z0 P. y \. |/ u& `

}

6 C, t+ Q0 E4 t: _* T

结构化日志

* K# ^2 Q$ P* s# N5 u

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

# h b( g7 R, H

cfg := zap.NewProductionConfig()

( y" c: t% v$ z1 n* C

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

' X! h: d" E! h

logger, _ := cfg.Build()

* t' @3 h0 t F! `

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

^# t. E$ `: `" j0 T$ L

使用 OTel Collector 进行 metric、trace 收集

; h( ~ Y; u6 C& X4 Q# L8 E

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

9 u$ o: K" c T, R4 R$ |+ k

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

. k2 w$ S( O+ a$ k3 X' \) G( r

receivers:

/ j2 `4 Y* k1 o( ]3 m1 D1 h* |1 P

otlp:

+ {2 K) T& r# h r6 }0 F/ T

protocols:

5 G" q/ P( d5 R# D

grpc:

+ O/ d& v6 y$ U' `! [/ k

http:

* Q/ f3 x( W* }6 F

prometheus:

5 f9 l) X& I/ q: A K

config:

& r. V+ T, h# Y, ]

scrape_configs:

! H% `) J) ?6 l9 E: j6 E* }

- job_name: app

7 O0 a: k% P. t4 r

scrape_interval: 10s

+ x# k0 b/ j3 X g" ?! _5 [

static_configs:

( Q' [; ^3 _, J3 o

- targets: [app:8080]

" n. ]0 @. \) u: }9 e

exporters:

' M( I S) h9 c" q

otlp:

% f2 Y% ]7 D& @8 R. o }0 i

endpoint: tempo:4317

! K6 I$ V# V2 k* N! Q6 Q

tls:

6 `4 l6 |& m7 t+ q1 o( \

insecure: true

* i5 |( ]/ y# K3 Z, q7 v

prometheusremotewrite:

+ e7 ~1 C4 r& Q9 \6 o

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

: W$ i1 X: M% c& w1 [( w+ s! o$ l

tls:

- d5 X% c) n) z5 q! J0 `: R9 R

insecure: true

; g. s! W: l% { `6 k2 Z

headers:

, q: g+ P6 e& K0 F) y

X-Scope-OrgID: demo

) H/ y1 O4 I, G0 R. k5 \

processors:

[# b$ d. B9 Z2 U' B/ H- F

batch:

9 B. Y, z, h6 Y* M3 v6 e1 G# P0 U

service:

1 y! _ E% }* Z3 L5 f8 I

pipelines:

" p2 [* u/ w% ]3 u1 m; {( j" g; s

traces:

w( C! _( ], v5 P( K

receivers: [otlp]

( `; A% A4 j4 P

processors: [batch]

* a2 f. ~4 @( E) i5 ?" y& w5 G

exporters: [otlp]

/ ?. t" I7 V- l

metrics:

0 T/ m- b- k9 W- n

receivers: [prometheus]

. ]2 E/ O9 F$ ~) k! e `5 B5 b

processors: [batch]

# q0 g* o+ z5 H

exporters: [prometheusremotewrite]

% T r# U* ?) O) i5 g: {2 f

使用 OTel Collector Contrib 进行 log 收集

& l! M8 a: J) c1 _% q! L- V

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

& s$ Y6 g/ H1 u6 o% d

receivers:

4 n' H% E i8 _8 P

filelog:

# z, d/ H0 q' @# U4 I, g

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

; \* v; _) l8 l9 L- d0 l

exporters:

# U# m& Y" c) Y0 p

loki:

" a, t4 t1 j: p2 {4 i* x2 S6 z+ m

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

! ~' L( ?& b7 k: m6 ^3 E

tenant_id: demo

0 h' A0 m8 m- s3 J* a

labels:

' f7 x f4 v- u; z1 q4 ?! {

attributes:

& _7 g6 L0 [% h( R/ J4 `: J

log.file.name: "filename"

6 l2 s# M4 X& @3 ^1 Z

processors:

! Y* M' G' Z3 R% O$ x8 B

batch:

/ Y l, p, f9 Z$ G

service:

. T9 z& W6 z3 v/ \& k, S/ G

pipelines:

0 P `% B' g. P- ^( f7 M4 s; ^

logs:

+ f& a2 d$ U$ s9 t% z& I" I7 D! P

receivers: [filelog]

, u0 U7 l6 I3 g \' _) X

processors: [batch]

. m& K6 m- l1 l7 M9 E C# |

exporters: [loki]

; \9 z/ v4 s& _% {

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

( O" E6 T) f! t

总结

) w4 k+ k6 |# } R9 `8 Y

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

1 H; S( _6 s# R9 d

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

5 I' D+ C+ _1 i' r8 B3 {9 N F3 { w* e$ k r. K1 e1 m

责任编辑:

; K2 k5 ?' r. S6 n, b 2 w. _; R2 ^+ [2 x7 |, D! t4 q! z! f: X, {( L' u) f" S 4 `5 D0 {1 P+ k! D 5 W0 a# [, H& B9 Q
回复

举报 使用道具

相关帖子

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