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

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

[复制链接]
( W4 z" x3 \8 W5 q

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

* ]. f6 T8 x5 x' O, {" l+ X9 n6 e; e* {9 S# P( Q

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

& P/ r" V# f# P. T0 @

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

, Q: ^% [3 {' `2 X& r! m p; S

通过本文你将了解:

5 K3 }9 M i6 p+ @

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

: U/ i7 E2 i2 |$ }- y1 e$ U5 }

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

# N# r% j8 {" L$ Z! O9 h$ K

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

1 q. w! \3 x) |/ z" i( _; p

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

# E1 w" g; n, V5 h3 x# l

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

& \/ \0 N4 _; V

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

4 A6 P6 s( t2 t3 ^6 T7 z6 S

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

5 H# w4 n* Q+ H2 T

下载并体验样例

' [4 G q8 ]% b5 k

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

6 k) h7 h$ n% u9 } n

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

# Q/ z5 J- y6 W

cd prometheus-exemplar

7 r9 H8 R1 T% n9 ~4 F; P

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

% W% h( {$ ~- Y+ v

docker-compose up -d

0 _! S. X/ V g! D" r: N# G

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

* I! ]# z# E1 `( D2 H

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

5 D& r: V+ A+ N' q5 H: \8 M

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

: K( M3 }0 b: {' r8 u+ K

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

4 b& L7 P* l$ U8 ?0 `

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

$ J; J* I% M* d4 o

整个部署架构如下:

$ X& |2 p+ u8 `* i* Z

2 p- H" _9 S$ C5 }

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

* X$ G8 }6 B+ S) L+ j F

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

; X3 K- r# W) ?! J/ _

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

* K1 K' b% a* k4 J3 D" Q* p! [

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

) z' E; C- a& r# f3 E

) [. {$ L P& W* N6 i1 b6 x

细节说明

5 J' p4 d+ A# H) {% ?

使用 Promethues Go SDK 导出 metrics

# O& k5 C/ K" C/ r. v9 d& m

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

* ^7 ~! S: l# d- J) O8 }6 ?2 R

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

_9 ?8 h; Y! x* G' D0 D

Help: "Http latency distributions.",

8 Z, J3 h; q% H0 d3 F. r) p

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

& _* n* x6 p9 E0 S8 v7 p& C

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

# P# I( R( a8 x( f

prometheus.MustRegister(httpDurationsHistogram)

' e- r q, J4 }; @

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

' }4 L/ F& {( X, [; k

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

' {/ M. N1 a0 r) Q

observer.Observe(elapsed)

_8 q6 K8 w1 e5 S8 N2 M+ s" M

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

7 q! |* r$ {* S1 D1 S- M

})

6 z/ h& H1 W5 G$ m" d4 k+ `0 b! W6 c G

}

: U7 ^9 ?4 K, I! S; H6 t! |! n

}

2 [# o- s, a/ u+ s4 U

}

- w4 A8 u" c+ z

使用 OTLP HTTP 导出 traces

5 O4 g. T* c0 D! t

使用 OTel SDK 进行 trace 埋点:

, x' P& k& ]* J2 I; @( x

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

( [: c8 e$ {7 ]5 p2 J, i7 ?

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

7 S: W$ [# _$ u4 `# Z1 U7 N3 O

defer span.End()

; \" s' ?9 j2 a5 i8 P

// mysql qury random time duration

5 y0 Q. C3 s8 ]9 w5 |

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

8 t# s3 o3 f# T' X" L* \* I

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

# ]6 u+ W) r. s& R" \

return

. M. f4 c( Z# G1 T7 ^$ }$ u

}

* }& @5 `7 f( i+ L4 g

使用 OLTP HTTP 进行导出:

# \% Y2 i( D4 ?( b/ p! x7 Q, R

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

9 i, X) w# O0 E, ^6 k

client := otlptracehttp.NewClient(

/ S- f( w* y" E2 A

otlptracehttp.WithEndpoint(endpoint),

; a4 Q1 J7 ?& V, h+ R5 D/ ~

otlptracehttp.WithInsecure(),

0 r1 q! }# E5 t7 g" f0 B

)

3 X- @4 d! U) @) S% ^

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

# k4 F3 }& D$ q

if err != nil <{p> return err

- r# r1 G/ y; h5 }

}

5 x: [: A( S7 g% H2 M% @$ R

tp := tracesdk.NewTracerProvider(

# O0 O- s0 v) ^: D' h& m! p

tracesdk.WithBatcher(exp),

3 Q% P9 y5 H; t* w3 B6 M: N* I

tracesdk.WithResource(resource.NewWithAttributes(

4 `/ I* B! |& f& m- q6 z

semconv.SchemaURL,

4 K& C, Y5 V( v" B3 @0 p$ i

semconv.ServiceNameKey.String(serviceName),

7 A& y) v4 B0 G1 y( h& B9 y3 p

attribute.String("environment", environment),

# Q7 a% `4 h; `/ u; t' q

)),

& m% o* T. x$ l

)

: n8 U* g8 f' q1 v5 m

otel.SetTracerProvider(tp)

. D9 |0 H# N' V! B5 o/ @

return nil

' M7 v9 H* D( C$ E. G

}

( m* k/ M$ }+ _& s( s. l

结构化日志

, |6 O8 U: _( \3 I9 G% N

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

/ p$ u, A3 {7 }- z, Q7 `

cfg := zap.NewProductionConfig()

* G6 E1 @9 D) L

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

3 L6 M0 S7 e) A+ b

logger, _ := cfg.Build()

% {1 ]& H" f/ @# u5 Y. y

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

' R' s/ E- R" f W

使用 OTel Collector 进行 metric、trace 收集

! y9 [1 f: T8 B- B5 n- w' D" s

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

; ~( ^. {& x; r

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

9 ~, {+ s% ?5 u

receivers:

' L4 K; _; H g5 r

otlp:

3 M5 s+ M& F0 c' }! g

protocols:

1 d7 z, m v+ w9 [" u: t" k$ H$ ?

grpc:

$ l5 b+ l' d5 M" A

http:

$ z5 `3 W8 N6 c) h0 D$ y

prometheus:

+ _; K0 j1 g) g/ ?5 I# q( V/ p

config:

o# {0 T4 X1 B, b8 P5 k! ?3 X

scrape_configs:

9 R9 O) S# C7 n5 L; b8 m

- job_name: app

`' w! X9 \) ? }4 ^

scrape_interval: 10s

- Y$ A8 A/ s3 p0 h% A

static_configs:

: B" p$ Q. a: N( g( y

- targets: [app:8080]

/ C4 U& \' a ?/ [9 F

exporters:

b( {$ b# D: a8 P5 m% H9 p" f B0 x

otlp:

) ~8 e0 R$ O2 ]. w6 X8 }

endpoint: tempo:4317

+ l) g; E; @4 a

tls:

( j% }) ~) n+ U! ]- |2 B3 b

insecure: true

+ h/ z, r& S) w) d0 I& N

prometheusremotewrite:

N+ M/ T3 }8 }. ^7 ]

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

4 m! n% U2 w n6 ]) n/ s

tls:

, W8 Z3 J' D" W2 J

insecure: true

3 T* x; |2 t! B

headers:

- I" H0 w( w( h

X-Scope-OrgID: demo

6 S8 u9 ?, n2 S' w) ~/ v+ m$ j

processors:

( b5 ]3 G. {$ j+ x

batch:

8 P' v! F" i0 u9 s8 b

service:

3 Q8 n3 d) G1 ]" y: g; g1 M. N( e" I

pipelines:

# _; R/ R& V# Y0 W- w

traces:

* v3 |' g. D' g4 C8 K& J" o& Y

receivers: [otlp]

7 l7 D5 z1 g4 R3 H% i# z; u

processors: [batch]

' r& L! J, j# m O: s

exporters: [otlp]

) A0 j4 i5 m1 C2 _4 V# e

metrics:

1 N2 V2 v- B7 `' x% k7 [9 f

receivers: [prometheus]

0 O# q1 u8 g* F. Y! j

processors: [batch]

! [4 _* e W. Q1 e! n! b% f/ ]

exporters: [prometheusremotewrite]

0 n) M. R6 P1 ]6 a, c

使用 OTel Collector Contrib 进行 log 收集

$ \! @# ] T, i: G, K

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

* v/ z# I, {, h- Y+ P

receivers:

& D% s+ g) q. @2 g

filelog:

2 N+ ^; _- Q" R. A8 m$ J

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

2 M7 S( E- o( O" j. Q1 ~3 k

exporters:

* E- U) c) Q E: v. ^! S3 \/ X

loki:

* ` b& B- D1 a+ M/ R) N+ m9 u

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

s: v# A+ Q& e E" I- y

tenant_id: demo

# g" ^2 U* Y9 f

labels:

" k' j7 D6 v7 P4 b

attributes:

2 `5 z# F6 ~& a& g6 U

log.file.name: "filename"

* |1 F: I: e" y) c: ?6 {) X

processors:

( H. m/ e& o; O" R1 s

batch:

! ?2 n$ ?% Y0 b8 c( O+ R( K

service:

: m1 p' v! j$ k

pipelines:

p% C' |& K# A% }

logs:

% p( L% \5 l* s/ W& {: G

receivers: [filelog]

* @, Q) ^0 Y# x' a [2 V- Y

processors: [batch]

. L* w; m) \% V, W

exporters: [loki]

' F( r/ V+ l! E6 b! x1 H- o3 @% h

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

+ X; \8 ]9 ^+ _ w4 q) O

总结

) Q7 ~7 a0 y( C

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

& c- K$ @ T- l1 ~9 s

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

- k+ \: I( ]' h: q ; m# }% b) f v" S* e1 `) G+ B

责任编辑:

( D( }- C' C. |* F1 U$ d2 e ?$ C 0 Y2 k3 E: Q& M6 r( g( C+ }% G b, i2 I5 e+ b1 d & A: m( n$ ^7 w7 s4 q1 @/ j$ F& }
回复

举报 使用道具

相关帖子

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