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

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

[复制链接]
8 x7 G) |9 L9 `1 F

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

$ D- O1 h. _! q ~8 ` " F4 Q! ^6 g7 |8 U

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

( p j$ x9 N) D" }

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

. r3 W: f7 y- t. v& X+ U

通过本文你将了解:

0 L7 ~& Z) \0 B; @! |% W7 ?9 ~. @

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

/ h; h/ |% _9 C U

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

' G7 l1 F- b% N

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

5 k! h- F8 H7 ]

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

1 F5 @) F) p( E% ~5 E

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

. K/ i8 {. K& m0 r7 g( P

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

7 o' d/ |9 s0 L! x. i, j

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

! W3 K& s$ S1 z% r7 \

下载并体验样例

/ I' I# x& \' l

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

; [) i% h% a3 e1 M, M

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

5 k, E7 d( S1 o" ~7 V

cd prometheus-exemplar

y* g5 i8 F9 B+ U7 H

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

( G% R1 l8 K; |8 H( M6 a% S

docker-compose up -d

2 ~# H/ d8 s d. h

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

. k6 W: v7 P9 m' B0 e2 d

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

! c4 D# N/ s4 U+ s: O! k/ h

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

8 k. A8 ^' T! A/ q

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

! }8 Z$ [- \2 U# b) `

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

! U1 i- `& @+ V1 T/ m6 ^

整个部署架构如下:

- A, `9 R G9 l+ ?

$ T" `5 n6 W7 ]$ o

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

2 Q. y9 j5 P% W1 F$ ?- _; W* ?

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

* C* K! s% A, h O6 z( Z8 d* b3 B

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

* L, O7 u/ r' Q* `

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

/ `: U5 L& X) p a

/ K6 c. Q+ j% u2 U

细节说明

. y8 ^! F3 B, \5 U" A

使用 Promethues Go SDK 导出 metrics

/ ^% F, v2 c# v6 o

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

1 s' q8 b5 E2 C6 R( i- m4 p [2 t7 X# b

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

, B# \& d1 l: ?/ ~2 `4 o, Q

Help: "Http latency distributions.",

, Q" H! G7 x3 R+ A R

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

% L. }* ~$ a3 h; O. S0 I

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

: U; W& u# K# S) f8 B6 f1 `0 o

prometheus.MustRegister(httpDurationsHistogram)

5 x# h1 q, t. y; S+ M

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

7 m9 @1 {, D$ W0 u

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

/ S2 o8 h" Z( Z& a: f+ l% o8 R& Y

observer.Observe(elapsed)

/ l1 J9 n, I' t; @, @; e8 p# G

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

1 I# X" O( C+ j& M+ g7 E

})

/ h+ T. u+ ]# X& r

}

' Q- Z& W" e" Z) ~/ Q5 ~

}

; l$ _$ ?0 f D A% l

}

' } k, n' Q7 T9 ~, X" v+ k

使用 OTLP HTTP 导出 traces

& _9 _2 ]$ E/ e

使用 OTel SDK 进行 trace 埋点:

7 L" I) F' m: e; i

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

1 Q: D' _% O2 a1 ]# f

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

+ c' ~( G O* j+ t5 i7 q

defer span.End()

. G1 E& ]# s, W# o6 G

// mysql qury random time duration

$ \" B* {* J& m$ q

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

' x$ j2 ]' Q( O1 ~- U

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

9 g% z9 ?* a! q: o3 [- j+ w8 B( t

return

' `: f& C) F. ^* P a

}

3 ?/ v" ~5 W6 x4 \+ ?: {2 k

使用 OLTP HTTP 进行导出:

( Y/ ^9 q+ c3 O2 u* A( V

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

$ h( [6 l$ X) G1 |- V. @

client := otlptracehttp.NewClient(

' Y7 |4 q. {: T6 I! C- y8 F

otlptracehttp.WithEndpoint(endpoint),

+ y6 h; h; G- b$ l+ R2 C

otlptracehttp.WithInsecure(),

' R6 A5 C3 M5 B

)

6 a) A( J$ Z% W: |4 S7 b& M

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

1 {7 V+ C; W# W; T

if err != nil <{p> return err

( C4 a& V) F: O" D

}

2 @0 O9 L ~- ^/ O+ ?

tp := tracesdk.NewTracerProvider(

^& N& c* y4 q: R% C* U& j [+ I

tracesdk.WithBatcher(exp),

6 ^, ^ k4 J/ c# z- s" b

tracesdk.WithResource(resource.NewWithAttributes(

$ r- v3 X. R3 i7 c; g1 _

semconv.SchemaURL,

/ S* I6 p: T- V, R9 l- t

semconv.ServiceNameKey.String(serviceName),

+ e" M0 v3 {2 q- l/ d

attribute.String("environment", environment),

. l3 F9 v: `; L$ f* g/ U% m& I5 v

)),

( q$ F- I- J) D

)

* ]3 R$ d. G4 t) S+ B7 |

otel.SetTracerProvider(tp)

. B3 S* D5 }: B3 Z

return nil

/ E6 x+ {3 m, [% Q; Z4 C

}

5 W. H% s/ y) m- x* F5 e6 Z) m

结构化日志

& e8 K' M. q, D) H! v

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

- h- R7 t! z* y

cfg := zap.NewProductionConfig()

- V. m0 a6 X7 x! o3 _1 E6 h

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

$ x5 b- y! f9 W6 ~8 l

logger, _ := cfg.Build()

( d& m0 z. {! f2 a

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

+ u: O* Z* U/ @: Q$ |/ E* V

使用 OTel Collector 进行 metric、trace 收集

% u; E. f- ^! m9 {

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

! |6 A; k" Y0 d5 X4 v+ x

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

& \7 ^/ |, K+ u2 j: V5 S

receivers:

6 _ h4 l4 F: \

otlp:

; x5 ]6 Y' O- P; g- H; T

protocols:

4 _. f1 M! b5 P, h

grpc:

4 A/ w5 O- C3 N6 h; }, S7 p

http:

& V+ C' B. l$ Z% P' U- R% }

prometheus:

: B8 p7 E3 \6 { o/ a

config:

! R9 l: h9 _2 _5 Z) R$ o6 Q

scrape_configs:

3 X( @( v# \1 }& ?

- job_name: app

/ N1 v: l) Z; x/ l# X; P

scrape_interval: 10s

. _% Q2 ?% `+ G3 O

static_configs:

! j/ v' T: \ e! T$ d1 ]

- targets: [app:8080]

4 D' N9 I) w+ C* @

exporters:

4 d) c& B5 w/ P

otlp:

) Q+ F0 L n0 D8 j# j2 g; I# N

endpoint: tempo:4317

" y5 N0 f5 ~" S. |4 e) T

tls:

6 _2 O' D/ H" v+ {# y. j9 c Z

insecure: true

1 f7 p' m) g. c2 ^% P" l: T6 O: o* n- V8 j

prometheusremotewrite:

% X* N8 h" Z! d1 D

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

8 m5 h) v# u: f. j

tls:

9 \5 s% M( f8 u6 G' s0 B8 g. k8 B' l

insecure: true

% E6 \. T& \4 s8 D& P

headers:

3 s! N4 M7 ^4 }: ?

X-Scope-OrgID: demo

/ ]6 G' i0 [" w. ?1 P

processors:

0 o) b% X2 z, e) m

batch:

# n. h7 e7 @( K6 Y/ P# d

service:

. [( Z5 g' n' N* {& J$ \

pipelines:

$ G+ W& C8 h V6 i l, B

traces:

x1 ?" h* a5 {0 ]% \

receivers: [otlp]

* e- H. F6 t4 c8 _& u0 g3 [3 \- _

processors: [batch]

3 B# `, M5 U; L7 M$ p

exporters: [otlp]

/ r, k" g" m. ~' V$ G

metrics:

; d# |) g. y* n* m9 \7 F, m5 m

receivers: [prometheus]

3 {0 R. j: L! ?/ H* l, Z$ M

processors: [batch]

1 d' N# Z5 ~4 ~2 W

exporters: [prometheusremotewrite]

& u$ Y W9 E( V5 \

使用 OTel Collector Contrib 进行 log 收集

6 I* j* g6 }7 k7 j

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

A( y& C% V4 A' h

receivers:

' L4 K5 [3 X+ J4 t

filelog:

( p3 j/ u% \1 A" l- ?: e

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

! ]. e* A1 s5 }1 S5 @+ w

exporters:

7 v$ B3 h0 H, w

loki:

^" d8 ~1 e+ A# y$ C

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

7 T/ A- b- ?* M* f9 J0 t ?

tenant_id: demo

- r2 f* t# ]) ^' t- f, L

labels:

2 ]+ _8 C! U, K- u8 w( J

attributes:

+ {; A+ h8 \" _3 s

log.file.name: "filename"

; Y |- E$ M3 ?1 Q+ J0 E

processors:

& L L2 R6 d0 w5 S/ W7 h2 y

batch:

2 Y- O& F9 K' i) y

service:

* i& A% C' F" S

pipelines:

1 j8 S. e* E) z- K8 ^/ E

logs:

# i% _; e( v& T

receivers: [filelog]

/ u' D8 [/ g( s1 x7 y, M( ?

processors: [batch]

8 f5 k( E! p# R) [# _6 b; N

exporters: [loki]

7 ^7 \" _ X! p0 [3 ?/ H

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

1 K# H4 x$ A# Z) T4 P4 D5 y+ c0 H& I

总结

% y! u0 F3 G- J

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

% U+ G. m. C* f

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

0 D( @1 j6 @& B6 t0 X' `) r. o9 @# L, x& }8 K0 ]4 k v! Z

责任编辑:

3 R* a+ h; q9 H! ]. I 9 j! e! o) z& N( K: J 2 ^3 D5 f1 X; x" l . Q# y+ R B( {* i0 H/ I % R, V% T% H8 L/ n4 o" e1 U0 z
回复

举报 使用道具

相关帖子

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