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

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

[复制链接]
. _& s n0 L, ?( |7 N

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

, s4 S4 F8 h* X7 {8 F: @ i$ s- A, Q( k

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

; B' {/ L) G$ W" y) w4 _" _

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

3 H; f+ ]4 D! S. U0 B" B% b

通过本文你将了解:

' ?" W( M' D% W7 M0 D# E1 @

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

0 A- ]* u9 V& V: `+ p2 c

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

" ~9 U" e$ I- u) n9 Y

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

" t" J! F4 e+ N& s# C. |& _

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

/ }; I: F9 l- j. q& N# j4 j4 Z

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

" Q* f" A" ^( y( @) E+ p$ B) q. u

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

- ^9 r [: A: R$ |6 M5 T

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

4 E1 S0 Z& \8 n* L, r

下载并体验样例

1 ]! A7 }! {3 s8 f. i% U

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

, }" x) r3 d6 l

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

+ ~4 {/ I2 V z

cd prometheus-exemplar

4 a! b! R# R6 P) a8 P0 ?3 [3 D

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

4 E" z5 U) _" a7 a' A4 A4 d j

docker-compose up -d

0 q8 G( D1 M# i8 ]9 y* Q

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

# q+ n, K# @% o; Q+ ?5 I3 M+ @

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

; |* j9 A2 p$ x% K3 Z7 F) i# m1 [

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

! d0 U& g1 n( @4 ~! n% @/ {, F1 H# i1 y

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

) i: k5 w; t5 h# w

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

" H! T2 b8 s8 u& q" t1 g7 r3 I

整个部署架构如下:

( G! g: D* ~# `' }" _$ z! e

: N2 F4 F+ y# e3 h& }& g- h

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

- p( `; J" R6 g, w; C0 s

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

$ P& G" ^& M( M( o3 o3 G$ V

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

$ x, G7 v d' X2 d

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

9 L- u% R7 O9 y2 J; {

- L3 t8 d3 ^* u7 t; g! i

细节说明

- A+ Z; z5 p2 g2 f

使用 Promethues Go SDK 导出 metrics

, e1 A* n) @4 c7 w4 f: c

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

9 U7 @8 O; ]2 r4 \+ C5 C5 }

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

5 I4 t+ c* ~4 @2 @$ M2 e

Help: "Http latency distributions.",

+ n, w5 M9 {7 n7 g, H$ I, A

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

( Q1 G( z- P' W9 l5 V- O) Q d- M

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

. t3 W" z) [' n7 c; C: D

prometheus.MustRegister(httpDurationsHistogram)

8 i6 f' m( z" ]9 R6 y; K

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

. W4 G* s H8 M. X6 H5 `1 L

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

. a* o5 }1 }; G& N3 O+ y: q- j

observer.Observe(elapsed)

6 b1 v* J% u- @/ z( ]5 k

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

, F0 ~0 z/ M& t

})

/ q1 @1 i9 B) z- c$ S7 z. l5 i' N w

}

5 [' E- X! ]1 P5 P* a$ |. i

}

`& B# S5 n0 H* n! f" N }

}

/ j; c- ]) u: c% @; y

使用 OTLP HTTP 导出 traces

( ?, W4 Q$ i! H/ x$ F! D5 H

使用 OTel SDK 进行 trace 埋点:

, t6 ]& \% A- J, u% r5 }7 L; V( G

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

9 L" g7 i* T" R$ {& _' \& M, C n

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

% c2 _9 D6 z1 L5 _

defer span.End()

' }1 }9 ~6 u/ O) X" q

// mysql qury random time duration

3 B" D- l# l4 c

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

9 {( y4 d+ k: u1 s- q

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

; V1 g. U+ x% k1 u% x! g7 X9 }. v

return

% I, F, o2 ?/ o$ g

}

% M8 ]1 Q' } e. C# D! F7 N; o

使用 OLTP HTTP 进行导出:

5 ]9 p) X9 n4 V8 k, T( s

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

& ~" r9 v K6 b9 `; S* u

client := otlptracehttp.NewClient(

6 F* @3 Q5 y. Q0 @7 S

otlptracehttp.WithEndpoint(endpoint),

1 P: N- n9 ~1 ]' N) R

otlptracehttp.WithInsecure(),

) F. j. `0 x. ~4 l( C5 D

)

( e- u+ B7 M$ z3 G# I

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

7 X8 ^( s M7 S

if err != nil <{p> return err

1 Q8 k$ v* H' R

}

7 s# N) Y- O3 w0 R6 w# t# A

tp := tracesdk.NewTracerProvider(

% ~ b" `' C% f {

tracesdk.WithBatcher(exp),

4 ^2 ]5 [# D$ Z8 ~

tracesdk.WithResource(resource.NewWithAttributes(

( g5 { H' r5 o+ c* }

semconv.SchemaURL,

' C& r9 P/ M+ m# |, L/ {

semconv.ServiceNameKey.String(serviceName),

' F# k, m& E/ d

attribute.String("environment", environment),

% K) y8 v3 {4 |& o

)),

5 K9 C% u( I2 L( V- D: s

)

/ c5 T0 n9 }$ Y

otel.SetTracerProvider(tp)

+ j1 r- Y0 r, [$ r1 P& S O

return nil

6 ]" _6 W7 [, A& [* Y1 l

}

% {3 |& I. P! i# N) ~

结构化日志

9 k) j( l O5 X2 `' M0 l9 T- v

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

$ S4 T* J9 K" n) w. a& p* h3 D

cfg := zap.NewProductionConfig()

! {, D, n' y; q L# `

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

! h8 |' o6 U4 e' d

logger, _ := cfg.Build()

9 _( X- x9 e& ^/ ], k$ C

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

% v7 e; q2 M: I9 c2 g

使用 OTel Collector 进行 metric、trace 收集

$ u! q7 s4 G+ ~) b* N" Q7 f

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

$ ?/ C- v# m* X4 j$ c

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

# m& m+ Q9 c5 l2 A- ]

receivers:

* D. \ S# O+ u4 _

otlp:

& F7 r) e+ b3 i' o: X1 Q- q

protocols:

( k1 ~( q( E2 X# Z

grpc:

) ~% H8 h: _' u% X9 n

http:

$ g3 M! X2 {/ y

prometheus:

R1 B7 M* N a6 F4 f2 ]! v5 {3 k+ k

config:

2 P; y, {$ a& v

scrape_configs:

! F, Z% j! z, W: y7 M* X. j8 y

- job_name: app

. K9 [* W, ^& a2 D7 e

scrape_interval: 10s

6 D- n; ?$ s) g- h

static_configs:

/ F- S2 w8 e# W0 [

- targets: [app:8080]

" l/ g# n0 ~- m \, {' b4 `

exporters:

* w( a6 u0 n) B0 i

otlp:

; c& h" Z6 K- f9 J" w9 j1 o

endpoint: tempo:4317

: T1 _. v( }/ ]1 I* L5 s

tls:

8 t, E: G- G, Q( c

insecure: true

; a' W8 B! O9 q% L+ ~7 I; S

prometheusremotewrite:

% Y! T: b& N. z# b" ^9 k

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

2 H# f7 `! l4 x3 M1 g# q

tls:

1 d: j8 O4 ?/ h- x" D2 L

insecure: true

' E9 ^, P- b6 y$ |- o& a

headers:

; M- q! R/ G9 Z

X-Scope-OrgID: demo

' d7 h2 i7 x3 V4 `2 v$ |

processors:

5 P* `& C, d: v1 [# Y' j) z0 ~" a0 ]

batch:

# Z: W6 k% ], j5 I

service:

2 d4 P& V5 ^& K' N3 m+ @& ]4 ~

pipelines:

) E; F F. o" P" Q, K2 h

traces:

/ J. t; B0 b* M9 _" Q* j$ U7 i% G6 G

receivers: [otlp]

0 f7 J* ^ r: I5 T4 E) [- ?

processors: [batch]

1 w5 A% h! F8 l/ o9 C }3 t

exporters: [otlp]

2 l( O4 D' F" a# K3 x G+ p" D

metrics:

* o x9 f9 ~1 p( f

receivers: [prometheus]

$ O2 X) B0 F/ c q3 E. T9 }" t

processors: [batch]

3 T4 l$ \! |- C

exporters: [prometheusremotewrite]

" H; v a/ b. G0 V

使用 OTel Collector Contrib 进行 log 收集

( f! a& a# U: y# y( W- o9 U. R$ v! d& @9 u

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

- I! F/ N# H6 a7 {6 l# r1 H

receivers:

9 o# I( k% p" Y

filelog:

6 T" V4 i; O# T @8 F# c

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

5 T% c8 ?; M7 d$ Z* Z* I d. O( S

exporters:

4 l' {8 S. B3 {* Z8 f* f% ^

loki:

- K2 a4 p- ?) S

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

! s4 Z% f2 V6 C# Z' Z9 o2 N7 M% _

tenant_id: demo

; Q# t; u# x/ r5 h

labels:

# }# K8 P, ~6 f9 B8 Q$ k+ o

attributes:

. h+ p4 K8 e* a- f: S: [

log.file.name: "filename"

/ d% o1 J/ r8 z

processors:

' `) n( f' v4 a- l& A. K

batch:

8 E* J3 J1 @3 A8 O- `

service:

7 t5 }8 ?- l; ^9 v

pipelines:

7 N% z4 J9 O9 P9 q, }

logs:

; z: a( u! R& t8 e" m4 a1 E

receivers: [filelog]

+ t9 E: d/ x4 x$ C+ K( L

processors: [batch]

5 t- f- x% K! J% r

exporters: [loki]

$ ^& J1 K! f" f; P s; y; D

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

" b( `0 q: N b5 B" P

总结

/ q/ b, |( W" m) |9 i& }

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

" m( w5 J! {, f4 v$ ^- l1 b8 |

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

c8 p B4 m& M5 u * t- L. a7 C/ i h6 r- S/ ]

责任编辑:

2 _1 G/ y |) h: w. |- f+ n1 _+ s, b # X0 }2 V4 \5 }1 j% H. S# I ; ?% `4 m# u- |7 L# n, h * h3 O0 B, J. x# I 5 K3 ?2 {6 x1 ` C4 V; o
回复

举报 使用道具

相关帖子

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