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

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

[复制链接]
: d$ t0 K: K7 ^8 \ d

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

: F( F. D+ }7 C# z: p: A6 m% f4 t, G( Y- T" M' b" F' T2 E3 d- N

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

i3 d, F: J. I1 u8 T! p5 k7 W$ G

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

! O2 D0 e+ ]$ d4 l; M, ~

通过本文你将了解:

N# X0 B. v% }9 Y: v

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

f; J$ P4 s" L8 t' s

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

* W7 j4 D \" e5 j9 P2 r8 Z

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

H8 k9 X k8 y+ C' k7 F- f n1 a

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

8 {* `: z( A1 q, T% b+ i

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

; u( } G3 X! ?( J

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

Y. R! m ]7 X

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

: M; |( a7 ?, ?* p7 a

下载并体验样例

& l* V( G" C# J1 d3 _5 Y; M7 F) m

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

% r7 K6 Q. [# _

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

4 S: D) k1 d. R9 n5 B9 s( Z% ]

cd prometheus-exemplar

* G0 O) k1 d6 O/ X. R }4 I% `

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

+ P1 d, q8 j% y2 C a& C$ S7 Q

docker-compose up -d

) \# W: U" q) x* k

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

2 b2 t2 `. [' n' O( w& A

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

1 P0 R B3 c9 Q9 r

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

$ s# m0 [7 `+ Z

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

- n" y; e/ s7 @- Y3 U

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

8 x$ J) ]# x Q- q$ N6 q& L

整个部署架构如下:

0 O7 s, z3 V* y) w

+ e' ^3 {0 P+ k" M! B4 h

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

% G" `6 ^! W9 k- L

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

" y* Z2 F/ u/ V& E! p; j/ ^$ c

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

0 i3 N3 J$ r% Y- u9 Y) c

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

! J& s! a2 K& U6 M

1 b4 X+ [. S- W: @) ^

细节说明

( L9 @' F, t( B) _- {, j1 l

使用 Promethues Go SDK 导出 metrics

! W' g* I$ k; X0 j/ N

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

" Z0 I: @8 m* I+ `/ I/ n) u. T

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

. r+ ]: ]: |4 E) B' L

Help: "Http latency distributions.",

0 \- z4 Q$ l0 i/ [

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

1 P5 o. j: }% d8 p

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

. u v5 P: J/ H ^; P8 L

prometheus.MustRegister(httpDurationsHistogram)

* Q* d! R _; y! A( w/ n$ o

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

8 z6 V; j$ T+ K+ |& m+ j

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

9 | w1 }" @6 F7 I; m

observer.Observe(elapsed)

; a/ u0 s% f% {( j' c

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

# F. d1 y& `4 N* {

})

: w1 J4 o p1 x5 l: w! b: a3 V

}

5 J2 V J4 [! D: s: z9 ~& }

}

) Z3 a( z; `, E- S6 @

}

& T, g6 \/ s, y& e

使用 OTLP HTTP 导出 traces

1 d3 u5 G, T7 o

使用 OTel SDK 进行 trace 埋点:

6 @0 _2 U3 p6 b# [6 b, \

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

3 N; p, k: @" s" I, \/ m3 U; x9 O

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

' e& E: g! n, J

defer span.End()

: @( w& |2 S8 s q/ C. J# K, B

// mysql qury random time duration

0 H& U0 u$ R, ^+ l# m5 w

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

' e1 V+ D% E9 y

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

) b1 j$ F- B/ Z3 m) j- Q

return

+ ?. c, s* E' f' J, r' u

}

. M" K1 e3 c, t3 K# Z8 L/ @

使用 OLTP HTTP 进行导出:

( n" F: p& d' ~) W8 `2 n

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

9 E, w+ M& X1 ]! E- r

client := otlptracehttp.NewClient(

' ]2 }" O* G; _( ]8 w

otlptracehttp.WithEndpoint(endpoint),

$ R2 ]: s% }% E0 Y3 I

otlptracehttp.WithInsecure(),

% Z3 H( S2 ^+ X$ h. i# J7 L6 D

)

' O4 d, W Y: R1 P. Z

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

3 `7 N) ~0 B9 [/ X( f1 k$ }: H

if err != nil <{p> return err

( A/ V8 W0 l7 X0 f

}

; o; s. J8 J* Y1 C2 O

tp := tracesdk.NewTracerProvider(

. m7 v. f% p' ?8 [ J4 P

tracesdk.WithBatcher(exp),

8 J$ ]- b4 j( y6 |4 _& Q$ \! D* G

tracesdk.WithResource(resource.NewWithAttributes(

4 f. z! C- i& g6 {. C) e' I

semconv.SchemaURL,

- R, _" J9 |# C9 K1 C1 N6 y

semconv.ServiceNameKey.String(serviceName),

2 k# G& V p+ [- N2 r

attribute.String("environment", environment),

1 E0 [6 P- t' H* X+ }# m( N

)),

1 I8 b9 Q. u5 ], Z' R

)

/ r' `& B3 N3 N+ b* k

otel.SetTracerProvider(tp)

0 c8 c7 {0 o3 U- S& J5 j

return nil

! M: N+ {& u' U. L

}

5 V7 v- |2 C" Q, X! L

结构化日志

2 d9 ?% r" V/ T3 r* ` ?( C c9 }

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

- x, d& g3 ~! j7 q7 u4 B# w

cfg := zap.NewProductionConfig()

8 j0 F! R4 ?) a% @% u6 B

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

6 H; u" G5 d5 R% q+ l( r- f

logger, _ := cfg.Build()

: P, [# [9 k ^4 W

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

. O+ A7 ~2 Q+ U# r- h8 b

使用 OTel Collector 进行 metric、trace 收集

" S) C( _9 Y9 W6 a" t3 q% U% N, z

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

7 J$ x6 }" T+ h/ Q0 ^/ @

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

; _ i3 S4 V, [

receivers:

9 o! L* B( s0 x* m- i# y {0 P

otlp:

* g1 t8 o* ^* Y' c3 x+ G( [

protocols:

1 A6 ?; n3 c( V* A3 c6 w, a

grpc:

. c, p% \' v. E3 Z- l- W1 }+ N1 K

http:

- k; l# K# R! h# \& N4 ~9 B6 s. p

prometheus:

0 A V' k# m/ n2 u# w4 v3 @

config:

& V4 U# x g9 a Q. @

scrape_configs:

: I+ v: V1 l+ ^/ \) |4 t, M3 u

- job_name: app

2 W# `6 a# @ r5 q( A

scrape_interval: 10s

) X" I9 o$ u" R+ P3 G. f% r8 k

static_configs:

' L2 J$ U" ~1 r1 U. l; c' O J

- targets: [app:8080]

$ x) n2 }. v% H: W: C

exporters:

9 j- K/ D/ [: z

otlp:

! K. N: a" b6 b8 Q3 i" ?& O" \# C/ Z

endpoint: tempo:4317

# j& l9 `; _/ V7 M% f$ x2 H9 z/ n# }

tls:

$ X6 T9 A& `2 S

insecure: true

0 y+ B/ b& z5 q5 n3 k, _- n

prometheusremotewrite:

; W4 O2 w6 w- P0 _+ W6 ^ E0 Y

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

% M, t; `3 B3 {) e/ z

tls:

1 ~* a- }# k4 R* A

insecure: true

$ R1 }- n" m; r% Q! h3 O+ y

headers:

7 \& z$ g) Q( Y! K% O' w9 Q

X-Scope-OrgID: demo

. x3 P' X' A( d: k' |

processors:

5 y. h! u! c4 _/ U# a5 N' y3 z3 Y

batch:

/ G$ c: l2 P% {7 V9 M

service:

4 V1 g' f9 L7 ]5 }1 I& r( B8 d

pipelines:

) ^ q0 i7 j& ?; a$ ^8 T0 I) F7 W

traces:

5 }4 w% h/ w; R. H% O5 E7 K

receivers: [otlp]

* s p) B( w2 O0 e

processors: [batch]

- X- M( O0 }3 |# O- A3 V

exporters: [otlp]

+ ?2 U2 }2 m1 U: j& h C8 ~

metrics:

# M+ c/ A/ {8 o/ [/ C9 P

receivers: [prometheus]

0 O( u5 s' R/ C3 d3 k

processors: [batch]

2 i8 x/ P4 V, v. j+ b9 u

exporters: [prometheusremotewrite]

4 Q, n. v0 m: ^8 [* l. O5 R/ f

使用 OTel Collector Contrib 进行 log 收集

9 I+ j( [3 z( Y& y, @6 U2 v& i

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

0 L" O n% l6 q" P+ U

receivers:

1 ?* R$ b2 z& C( R5 ^ J9 c8 ~3 L

filelog:

$ {& x$ [$ H, l1 t& d5 J% j

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

3 P6 @9 G4 `' G5 l% L/ }- a7 o

exporters:

1 C {5 K7 l$ M& V3 u: d

loki:

3 ^: \# U( s% u

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

- ]0 y4 l$ z7 Z) ~0 Y8 M

tenant_id: demo

& u+ z7 k* d0 p! J

labels:

% o w+ h) l0 w

attributes:

/ z+ {+ t9 `$ g( R8 b: ^8 T; d

log.file.name: "filename"

m: n) e" V) P7 r" l

processors:

# g3 P+ V! D+ ]

batch:

( F- K9 M% ~1 w

service:

$ W2 J4 r6 `2 ^

pipelines:

( W+ y9 Q4 ~9 g3 T

logs:

7 Z8 S: A3 E& l' D

receivers: [filelog]

; u- |# ~: l0 d* y* f! P5 B

processors: [batch]

X* }6 ]4 Y. d3 W/ u( I

exporters: [loki]

0 _: U7 [& m$ G$ R8 b+ I( K

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

7 [/ y# F' u: q% Y, @7 }

总结

z8 a7 M7 [# N

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

- y: Y: m A2 w4 ~2 t7 T

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

" a/ S5 p, J! c0 T3 M7 {0 D # i7 _8 D1 [+ k0 B0 q. C! n

责任编辑:

R# U) p) v8 s# Z1 F0 B 6 f% O8 O- `& Y& P+ m ; V) D2 y9 }: p* [$ V& L" P8 r% Y$ S 3 Q& ^ F( f: D! s+ Y3 |
回复

举报 使用道具

相关帖子

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