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

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

[复制链接]
/ Q/ ?8 S& S s3 y# ^

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

- ~3 w" m } X& x 2 j" S, S* m: E ~" p: P" e) b

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

R, C0 |6 m/ _& F8 V3 l

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

, J" _* _) I( k1 ]

通过本文你将了解:

5 s. O# ?" i4 Q$ }

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

/ ?9 ?2 z4 r3 R+ L

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

+ U1 l( X8 I P# ?* z

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

. h: Q$ i: `; |5 }) ]

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

8 R8 }; |0 z0 O \

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

, |$ u1 v* }" M7 S

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

2 t- [: Y7 u5 }3 b: ^0 o

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

: d% Z$ I# C6 X; _! { d

下载并体验样例

" d& x7 c: A, `: Y) w+ y

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

/ S8 f4 }+ `3 `2 e

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

# @3 J6 e+ @1 m" }, Q, c

cd prometheus-exemplar

+ b! w4 O1 U, t6 m" Y

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

, n ^ J, D2 ]0 F

docker-compose up -d

4 G1 w& G0 u2 C! ]

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

2 P5 M& X* {0 t$ u; S! d3 w

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

3 Y% Q5 \) e! x" c# i" K: v6 \

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

7 M9 G% q, c1 C. \/ Z& i! v- V

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

8 @ q5 L# T$ G( z7 u" Y

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

# G1 [/ M+ F4 _- `

整个部署架构如下:

3 I% X7 ?4 h# L

5 m$ l! X) v8 E& b$ S3 O

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

2 P% m3 [. M1 }: R& z

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

: _9 w2 g' H4 d9 o5 }+ \3 a& {

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

1 L2 ]$ f/ r/ g# f: T; ~4 b

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

$ |% p0 y! _; d* A

0 J6 p; c9 b( p' ]8 n, ]

细节说明

# x, I! g# }: s- a5 `- s" H" K' w

使用 Promethues Go SDK 导出 metrics

) X' o) z; F7 b1 A1 ~. T g

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

. [& z! z3 M0 ]% l' c

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

( U& g/ o2 _; T0 }

Help: "Http latency distributions.",

. Z$ ?0 [3 G% a) Y

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

4 F4 Q! B# I% K

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

/ Y _$ r+ c' t+ J( ?

prometheus.MustRegister(httpDurationsHistogram)

' D6 T! A- ~/ y3 u. e) E+ s

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

5 V5 M5 k3 {: M2 I1 D8 e ]* i; X

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

# p" Q0 H& [* u! C) z

observer.Observe(elapsed)

( D. v5 L3 r6 v6 Y

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

) Q2 q& k0 y# B' l7 R2 N3 ?: Q

})

7 u6 [3 C' r+ n# v5 M. k' N, B

}

# w+ E c2 Z1 n; y( n) w

}

: ~/ {8 j2 `9 E

}

H9 ?3 D/ n5 w2 Y8 G

使用 OTLP HTTP 导出 traces

A' L. [9 R1 u5 c1 [

使用 OTel SDK 进行 trace 埋点:

# G: d, S& h$ A: a: M

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

" V s4 m# }3 [

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

5 T) m* N$ T2 C; ^) l$ X* g. R% }

defer span.End()

; M1 ~# q5 Z9 e6 `. g6 ?5 M' Q

// mysql qury random time duration

0 H" S9 D. t( `" K! e+ D

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

+ W s5 G: O8 V+ p; s1 k6 e0 K8 }+ ?

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

; j/ X2 D% h9 s+ J* j

return

, G# |0 @& L0 ? ]' u% R# B- g3 h

}

( s6 T0 I9 p9 g; {; p, ]/ o; R2 D

使用 OLTP HTTP 进行导出:

0 Q9 z) d/ U/ U) Q. T/ Z9 `4 Y

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

& N0 X$ `7 [" C) G. H

client := otlptracehttp.NewClient(

# b( Y! G' J3 i" V

otlptracehttp.WithEndpoint(endpoint),

, L; ^0 K' v5 d- ?

otlptracehttp.WithInsecure(),

7 T. _7 a6 _& ]' G2 H1 `# s

)

) A5 l% u6 c- ~) X) R: o1 x

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

% o5 h3 G# Z7 n. I! |. e/ ^

if err != nil <{p> return err

8 x6 t3 H7 G, ?8 _7 w6 z. u% c

}

% ` P5 u9 o' v6 f+ y- N0 \0 W

tp := tracesdk.NewTracerProvider(

; A6 G% K' Q) Q1 U) M

tracesdk.WithBatcher(exp),

! ^$ s5 a* ` ^( X. i5 {

tracesdk.WithResource(resource.NewWithAttributes(

" x S0 @! q' H) p5 \# V( `1 J

semconv.SchemaURL,

3 U% {. t, i2 I# s% I8 k! P

semconv.ServiceNameKey.String(serviceName),

7 i1 A! ^) s7 n+ l* f

attribute.String("environment", environment),

1 \$ p8 v, o1 u" Y' \% Q

)),

1 w& {' L$ e0 x1 A0 X

)

7 e- U1 U8 X# H

otel.SetTracerProvider(tp)

) ]) k/ X: y" B o, f

return nil

( D3 f- W& h9 [: G ^ Y

}

' T# t! m o u4 f& X0 m+ a& i

结构化日志

9 u3 a: m/ w( o! O( v

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

- w+ g) f% O8 l8 E: Z" t2 B

cfg := zap.NewProductionConfig()

0 b! C8 s# T1 L" R9 y4 A, r; F

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

! ^; R% E1 Z: Q8 Y {9 j" N, X/ S9 L

logger, _ := cfg.Build()

) ?: W; {+ n/ q+ I1 U* |# x/ n( H

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

6 ]* v6 {. u2 s1 \6 _3 `

使用 OTel Collector 进行 metric、trace 收集

" O% A4 l1 O. r! @3 e

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

& Q f- N' b; ^4 a3 R

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

# S4 E: ^7 |$ |/ k, i9 P* g

receivers:

: P# p, N+ d# E v

otlp:

( Y# r v7 z; ?" O% `* m

protocols:

7 m- }- ^, h' P* F% D1 X* P3 k

grpc:

0 h. s# ^2 f9 g

http:

, Y7 I8 Z. g; u. x$ y, V/ n

prometheus:

# T; @- k$ i: V! ?6 e

config:

4 e& g( ^' Y6 |% E' l/ O

scrape_configs:

l9 J( t3 N) p, K. t9 s

- job_name: app

6 w* M* R g$ s( G% J) w

scrape_interval: 10s

$ Q$ d, C" N( z; k, M M

static_configs:

/ }2 v) |5 X7 W* ` v, Z4 ~

- targets: [app:8080]

- h+ T; Z# ?$ Q0 M+ y V. j9 e

exporters:

% S- Q) q. ^8 q" P3 V% Y$ E

otlp:

9 B* ~% M* a/ R* S) e

endpoint: tempo:4317

- g/ J* A" g1 l/ l' {- `

tls:

; p" q1 F, w5 y& O

insecure: true

. z( P- }' Q! d2 U+ m9 w7 c7 C

prometheusremotewrite:

2 ]5 ^, f5 K% R& b; i! ^5 v7 p

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

% Q+ M% r7 ~. d

tls:

1 C: j! h$ e+ y; i# S1 T

insecure: true

; K5 \ E; `) z6 u

headers:

3 k1 X3 L9 \& }8 E

X-Scope-OrgID: demo

; f j6 K9 S. N7 q" m' A

processors:

3 U9 ^/ l8 G7 l; G6 Y5 e( B T

batch:

7 U* R, O4 P$ S, I8 ~9 L1 R1 s0 |

service:

+ u X' v0 D# v/ U

pipelines:

4 ]4 m, D5 t# Q* E7 C( K

traces:

; y @ |4 V2 d! ~( V

receivers: [otlp]

9 g q5 Y; v) M! w

processors: [batch]

* C0 ~% x5 i8 [' }6 Z- j

exporters: [otlp]

) h) K, a8 K% q( b9 _

metrics:

]) r7 }" `+ Y9 i8 O8 P0 ^

receivers: [prometheus]

, t# z+ i: _* w% Y+ D

processors: [batch]

7 z3 \& t% t W1 r/ Z" b: N( Q

exporters: [prometheusremotewrite]

8 Y; \9 k3 B$ ~2 U# e

使用 OTel Collector Contrib 进行 log 收集

8 u% A( e7 n* r" Q3 c1 S U- W8 B

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

" d, k% E- Z! u4 ^

receivers:

! n6 R6 s# E' ~

filelog:

9 F/ ~' X3 q ~

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

! m5 t- H' l$ P) Q( F$ _

exporters:

u9 z2 ~5 N3 l5 J

loki:

1 ?- F% }, j8 o) |! G, v+ ~$ H

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

) y9 p( n, I" z, b" ^; ~: {

tenant_id: demo

2 ]) o* y, b5 a6 ? N& f6 z# f

labels:

; L" p* h# V, i* \: `$ k+ w

attributes:

9 r2 G7 A8 d6 v- o

log.file.name: "filename"

4 |2 _3 j$ f6 G# }

processors:

( z4 X3 n4 C. D4 _6 x* n" L

batch:

2 q' q* G% Q* V' r& n

service:

7 Y6 A: r3 t2 j( ~

pipelines:

1 |- S. O% b2 I/ q% m, S& f: H

logs:

; O b! v6 W+ I& Y7 |" r, Y

receivers: [filelog]

# U& g2 o- Q2 g& G

processors: [batch]

7 o9 C; y& b0 `( ^' T

exporters: [loki]

+ u1 F2 y, W9 r7 d) L5 a

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

! u. O: w1 A# l6 F

总结

9 }9 }4 R- c1 r/ i8 N) t! F) x! ?

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

/ u; B# B6 E# ?

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

+ Q* X( l( v! R; c # ?3 d( f d* `0 ^+ o

责任编辑:

4 M1 A4 s0 e3 Z, o H + S! U$ L Q6 h- I . h& t) G) d* z8 w ! Q) N: H, d8 X2 u: U b2 J) ?( E3 n- a
回复

举报 使用道具

相关帖子

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