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

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

[复制链接]
3 w7 y1 R) M2 v( |

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

' y, Z0 U0 }* j: E5 I- f, J! {! U- S" Y

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

7 R1 Z9 d9 C( \5 E$ h' T1 z

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

7 e" A3 ]$ N X! t" y! I5 ?

通过本文你将了解:

$ F: l/ P( Y/ q y" w, `/ N6 i

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

! z% ~3 \/ |; y, W8 H1 r; @6 C- O

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

0 ~% e9 b1 l' x4 Q. T7 V

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

% C1 V5 D' ~0 V! I; f

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

0 k0 ?" r2 x- D. O( \

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

# z3 S& }. o: y& `$ g

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

0 n. N5 }* X1 c, ^9 C9 {% {, [4 E! E

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

]3 N# ]0 Y* H6 p( X

下载并体验样例

( i, i4 V! X; y2 @" F

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

9 N, l5 b- b1 ]8 t

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

7 u% X2 i; b( p: K6 i. s2 w& B

cd prometheus-exemplar

5 D; e3 m6 ]1 G

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

8 m, ~! x- P' ?+ h8 n: J0 y

docker-compose up -d

: Q' x6 ?# n, B) `7 ?2 D% |

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

5 R+ `4 i. M A: {! N' C* U& Z2 w

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

7 W C$ r# R- J4 g

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

% Z, }, c: \' g5 ]- \+ ^- ^3 g

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

5 n$ l2 x: u0 O! S8 W; m& c

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

% D9 n3 _' K8 _2 X( k

整个部署架构如下:

4 |- V9 B/ g5 V9 a1 v

- s; q+ Z4 w, ~# z

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

" K: d, y2 ?8 I" w" l/ Q5 G& s

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

( S) {6 A& s1 T/ V2 n% L I8 m0 E

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

" f8 F: `0 V2 }% L

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

. ?( A/ s7 ^( f3 m Q, E f0 C6 V

$ y& I. B' x" L7 e0 g$ P

细节说明

2 c( ^; Y3 q) J5 N- M3 J1 }2 p7 k

使用 Promethues Go SDK 导出 metrics

& [; v3 `7 }+ E7 c9 I* ?( M: |

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

6 O# ?) Q; ]+ ], h, l+ O h2 L

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

" a' N! ^6 @+ b8 f1 v

Help: "Http latency distributions.",

* K1 q: h. g6 I8 K/ K& U/ i

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

0 X$ | `8 P. y9 V+ e; u. X

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

8 K2 ?. b, F1 `/ q S

prometheus.MustRegister(httpDurationsHistogram)

1 ~+ A6 `( A, t

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

7 @. z0 i K+ v

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

" \- A) E. _3 C* h$ ]* r6 d

observer.Observe(elapsed)

& ]. R, z ~0 H+ m+ h+ _

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

! A4 w) s# a! ~ {* D: Y6 T

})

" y1 K% M) v b% Y! k' L! m+ [" `$ S

}

& E' `) N: z" i( H: h- X

}

7 d7 @. I! `* X" n1 R5 Q O3 v

}

" u* a, q$ j7 @9 C+ L1 u% e0 e

使用 OTLP HTTP 导出 traces

& O6 m7 Z" r: i# I4 G

使用 OTel SDK 进行 trace 埋点:

! l0 m9 W/ g( Z

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

8 o z# e, Q0 e4 X$ x

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

& _9 Z( k- m# ~8 W5 |/ I

defer span.End()

; y9 J- d) `/ f, k

// mysql qury random time duration

6 T8 l/ |. s4 B- M v L

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

* `0 K! y7 K. h; B' D

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

! ~, j6 ^5 z$ Q- X! L

return

) _1 k* K: i/ o: c; V; f2 A2 ]- }# Q

}

0 u5 m- c0 U) U+ T1 J

使用 OLTP HTTP 进行导出:

$ ?& H* X$ B9 v; Z% ]& V

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

: I. `% _( _ V2 [/ t

client := otlptracehttp.NewClient(

5 I& g+ X$ d4 ~; F% o( f0 i* G

otlptracehttp.WithEndpoint(endpoint),

7 j( L8 V9 @8 X4 d Y3 J9 ?3 G

otlptracehttp.WithInsecure(),

- R8 j B7 _, f

)

2 N J @/ b0 c5 ]2 p

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

0 \! O: I& ~. I

if err != nil <{p> return err

" Y* q( U# f% B% C8 i

}

2 h, B# r% D. g& l0 U1 {1 p) V! B

tp := tracesdk.NewTracerProvider(

& K# z3 [: u* s3 ^

tracesdk.WithBatcher(exp),

5 D# A2 L1 R Z% _0 m# f# d

tracesdk.WithResource(resource.NewWithAttributes(

Y: d' @+ x; @8 Z) M; B* U& G

semconv.SchemaURL,

9 H( y/ F2 {6 i3 |& S* p

semconv.ServiceNameKey.String(serviceName),

* L% C' r# i C# y

attribute.String("environment", environment),

# A: Q& M2 G. O

)),

( |. g5 z/ _/ F3 p9 B

)

9 a, |) M5 D- Q/ ^/ N1 p6 b* {

otel.SetTracerProvider(tp)

9 y) ?# u- u% o8 L& V9 l) m3 p- f

return nil

0 f q. s3 e/ O; v

}

0 ^$ [& N' w; G7 a# L

结构化日志

7 { O) M4 R; G1 c

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

% |$ D5 D) y2 o3 h5 e4 h: s

cfg := zap.NewProductionConfig()

9 v" C$ ^3 A3 u

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

2 a1 S! V! x. h0 m1 {- x3 s6 j

logger, _ := cfg.Build()

( q+ \% m W# w

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

) ?! c! \5 w5 ?& L

使用 OTel Collector 进行 metric、trace 收集

$ ]0 G% Y4 M5 U% n1 S9 ^

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

+ i& ]$ ~5 }+ _( D9 j

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

$ c& f' Q% x4 T: D# u

receivers:

8 P1 k% i7 U' U

otlp:

: ]' e+ R! f( R7 c9 Y

protocols:

+ E. h: S8 z, `# T

grpc:

2 W) Y1 J3 i: K) t b8 ?

http:

9 |3 x0 V, |8 |# x2 g

prometheus:

" L" a3 Z% s* [6 E( u

config:

' @! F4 e: V- b5 M* l% r

scrape_configs:

, W2 W) S# x* v9 M+ H: Z! T* f$ T M

- job_name: app

- f% |4 ?8 R( ^0 Z$ ]" `( d

scrape_interval: 10s

* p" a' l1 l. m. X

static_configs:

& D/ ?. _2 Y% n

- targets: [app:8080]

' a7 I* x, I; S3 D5 K0 x

exporters:

' |( z( j# M- V" g. Z' \: I7 u$ f

otlp:

B( Y: ~6 O+ S3 S' `- f

endpoint: tempo:4317

# w7 M- K j& e( V( t# ]2 X: W4 R

tls:

( Z2 q$ @; g( L; b! |9 f5 e( T+ U. \

insecure: true

, z" ]; X+ |$ K" q

prometheusremotewrite:

7 x! _$ f: R& w2 {8 d

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

0 K: ]/ X. q& N

tls:

]( w9 V a4 Q3 j+ m& X

insecure: true

/ f) m! Y7 E9 m- q

headers:

- u! f$ U6 d- K' L

X-Scope-OrgID: demo

% g6 Z% E; d' j$ m5 ^ J4 n# ~+ |( d

processors:

8 B" m$ D# }5 R5 a. u

batch:

1 p3 | b3 l5 s- J3 r/ K! W6 e

service:

1 o0 r) r3 o+ r( m

pipelines:

1 [2 f6 L5 h; W: |" Z/ f

traces:

* u }9 q0 \/ o

receivers: [otlp]

( G0 ^2 ~' d' \! K# C* ^: ^

processors: [batch]

+ Y8 Q: g0 w9 D/ ~

exporters: [otlp]

- y2 R. c* V9 W3 S2 A/ V f

metrics:

3 D0 y5 m9 m4 E5 B& U s

receivers: [prometheus]

r v% b4 y3 j% O7 x

processors: [batch]

1 B& }9 A+ b! ~ W6 P3 D8 q/ N6 e

exporters: [prometheusremotewrite]

% m, H* m+ s8 v' {4 o u2 f8 L, }

使用 OTel Collector Contrib 进行 log 收集

- {9 N2 V. z$ S" C2 S% }, o

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

5 R2 D0 w" L* d

receivers:

- F( O9 C- [/ l: A7 S

filelog:

* N& T0 j/ Y$ Z0 l* W- ]4 y

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

3 L" C7 `; x7 q: R/ V

exporters:

; [( M( y" i2 Y% n

loki:

% C9 u5 N, a/ W+ i

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

0 q: z0 O0 x! S$ x

tenant_id: demo

3 E8 T# V) D, v: P' D$ x

labels:

, }* P/ s- J1 y" p- P

attributes:

7 S/ d- T% H2 i: S3 t2 h. K8 s

log.file.name: "filename"

' n6 v K; z, u8 h' `

processors:

9 I. J6 p! u4 [3 b; h

batch:

, U" h; ~2 ?! h2 t$ C. u: N6 |9 L2 p

service:

8 [3 Y% w N3 r

pipelines:

! S. X) c# }; y. r. \& D; r8 I- e& c

logs:

* l- |5 M& \5 v2 `3 t; l# O; y

receivers: [filelog]

- V* Z1 l& c. s, L1 s

processors: [batch]

0 L1 K$ |$ i& C$ C7 t8 H4 E

exporters: [loki]

6 j {- c2 Y1 f; K- R4 `4 }7 h; E& ^, a

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

V/ J6 s4 O. ^* j0 L$ M! ~

总结

1 I% g3 p1 X8 Y5 }# ~$ P

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

9 T3 s) q7 n1 S% b M8 R

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

0 e) m0 E9 m3 P 9 a" U3 m- x/ f. @ `' c* f7 R

责任编辑:

- z; k% W7 v+ G1 R1 I4 [6 Z& p1 l 6 W1 `6 `1 Z$ x1 u- l& O6 ]0 X& p6 W5 S. T+ x) E' C8 n4 X* x0 D & z8 X0 E v/ b0 u' K3 D3 k 0 `. a6 D0 n( m
回复

举报 使用道具

相关帖子

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