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

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

[复制链接]
' T$ j! O( ^ t

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

0 x2 a+ P% H- P4 J; x4 f5 O ' P) `; ^# a! A9 l" q0 w

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

/ q3 Y& G9 Q% {9 n* L+ j

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

" g! d5 R6 Y; S7 z) ?- p

通过本文你将了解:

, O0 ]4 v7 g4 g$ i a

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

a3 R1 ~" ^/ Z. ]" X

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

* j' z' ^. u- B) d6 z! U; P

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

( g; H: K5 C; U0 G3 f7 }% Y- _8 y

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

8 O+ A" H2 V6 V' a2 B% t

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

. \2 y# \8 I! C2 m# r! \

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

* [/ k. {# _" z) \' r4 }7 M: J3 l

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

2 S5 }+ M. @, C0 y* E9 @/ N$ m

下载并体验样例

2 }" [6 V1 p |7 m8 [

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

0 O5 m i2 B" j9 z/ h. x

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

% r9 O; }9 \) {

cd prometheus-exemplar

+ A7 f+ }7 c% {( M+ f

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

\- F3 @0 b$ @, G) \% [

docker-compose up -d

' U e* c- a( B- b

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

1 E# S5 G# }5 A

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

9 C" X8 x# I& f6 {+ h! |5 T

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

0 m9 o3 j! a J+ u$ f2 ~2 Z& @% {2 c

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

/ ?* R: a& N, i k6 h8 ~! B/ k

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

8 i! y3 c) q6 g9 t6 D

整个部署架构如下:

0 Y3 ^/ }- r ~! o( P3 Z

! G9 X$ B$ w! c8 p+ o

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

3 O I. b+ `% j- M( L" b, b

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

+ j; `' j a3 w+ {) r" L

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

& }+ {# d, y$ m3 G$ k

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

0 n8 E, b/ T Q$ V" C' e/ @5 G- _

$ b1 t0 Y$ O7 V' p' F

细节说明

3 |0 L: I1 f1 L& ]* s5 s Z

使用 Promethues Go SDK 导出 metrics

5 x1 y Y5 v5 M1 R& b4 Q, B

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

?) y9 I3 X% S+ r. O

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

3 r- {& q, o3 K6 }* F

Help: "Http latency distributions.",

3 q8 @! T( r* P; Z

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

( C, k* b0 @' Z0 V b9 j2 Z

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

$ v7 x+ p4 A. I) s$ t. g" ^6 a9 N/ X4 i

prometheus.MustRegister(httpDurationsHistogram)

$ C l [7 r9 E+ |( Y% O

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

6 k4 h+ v# K$ |: C8 \9 L" v& _

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

. o( @$ w5 O! T; f3 r7 J

observer.Observe(elapsed)

# v) V! x* ]. B0 `% c7 ?/ l0 k

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

5 x$ R2 l6 W( n( a

})

: h0 X- m T* O5 X7 c \/ T

}

; E+ s5 \3 h/ c6 K

}

/ I3 r$ Z; o# Q

}

o. a" `7 C8 q9 }+ g, W

使用 OTLP HTTP 导出 traces

5 s* k( s6 T7 C" h

使用 OTel SDK 进行 trace 埋点:

3 C8 m8 j; p/ I+ S* @0 _$ D

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

6 I' D& t5 k. p0 O6 L" r

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

( G1 R: @1 {' H/ F5 Q3 ~" s

defer span.End()

$ O% b1 I& o7 S6 x: x6 z/ l

// mysql qury random time duration

% ~& ^* p& h% L; f/ {% y* p9 S1 m( Y

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

2 M, z1 P5 v! u. `

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

2 h; L& K, Q2 B$ P" O0 L

return

- J% Q4 H5 S/ e/ Y8 J

}

; w5 A0 u5 c1 @/ }4 G

使用 OLTP HTTP 进行导出:

( P$ Y$ r$ I) Y' I

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

4 M, u$ ^9 h, X2 v* y

client := otlptracehttp.NewClient(

) t. K6 C/ s% @2 e

otlptracehttp.WithEndpoint(endpoint),

) @" F' M( `5 |: |3 _

otlptracehttp.WithInsecure(),

. _9 ~2 n- H3 Y$ \0 v, j2 j6 W

)

4 f/ [$ Y! l% R3 K5 y$ W

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

: M) h/ |( o$ f

if err != nil <{p> return err

3 D) b0 I( ?2 |; I0 ^

}

8 u* i, ^% F3 h' K

tp := tracesdk.NewTracerProvider(

& b# a' i4 b; x6 k7 u

tracesdk.WithBatcher(exp),

# o* L% g7 z+ n% N* h

tracesdk.WithResource(resource.NewWithAttributes(

; r9 E! i6 {0 D' K$ \/ m) Z! B% r) c( G+ L

semconv.SchemaURL,

- f+ t$ {, h2 e( O( [- v! m

semconv.ServiceNameKey.String(serviceName),

, B8 B( a/ t1 C% \" q( Z6 Z8 ]

attribute.String("environment", environment),

8 ?0 K1 f, \" }! Q% e$ ?: U

)),

8 h# Y* V; u/ @/ {& q

)

/ J1 K" s3 V+ Y! t

otel.SetTracerProvider(tp)

: \* b! h0 \: e) e R6 ~& |

return nil

) `6 S7 C X; f; `* p. x6 u" A" N: \/ i

}

7 v4 J7 V6 C9 I3 ^2 r) M: \

结构化日志

. ]+ l# W) e3 y5 B* r. o- m9 s6 e

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

1 a ?3 G5 x& }& g

cfg := zap.NewProductionConfig()

- Q s/ T# M) ]0 m! z+ L7 X9 }6 ]( L

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

0 _: T# v" }: R. _8 W: D: `' O

logger, _ := cfg.Build()

6 M3 P! ~& D6 c$ X! c1 t0 [

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

- a$ g* n0 ?. V5 U3 M7 A. \

使用 OTel Collector 进行 metric、trace 收集

$ e7 V7 e9 s3 s# t1 o' Y4 L: D

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

. c4 a% I6 r: g' n

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

: s) J+ I# m2 P) o6 Q

receivers:

" V6 X. m+ D; i

otlp:

) Z" y% [, u' B: y# s5 r6 f

protocols:

% I/ D) \+ C" z- G2 b, q) e; X# F) d

grpc:

) W$ Z* c2 b+ ^

http:

0 Q- e4 ~& m& v" y8 L

prometheus:

7 h% Q0 t1 @+ }8 a9 V3 p+ E/ t

config:

2 z: U) |, u5 U/ ^; Z; U

scrape_configs:

; W9 M- w. ]2 }% Q" w8 @1 u

- job_name: app

) r; E+ B/ k: z9 A

scrape_interval: 10s

0 ^% j1 `3 N2 j. m) Q

static_configs:

, ?* K9 V& e$ A! i+ D$ O0 M# ~$ ]* {$ H

- targets: [app:8080]

/ n# Z, f/ |( T! G7 b% V

exporters:

7 V3 k: j$ e; z8 d0 x

otlp:

- G+ \$ T6 c* c; h' X! @

endpoint: tempo:4317

7 {* s/ ]; j8 P5 U3 s) \0 ]

tls:

( R8 n9 z( ~6 z0 ^# w. I; f

insecure: true

/ L _. V9 ?9 z6 @

prometheusremotewrite:

) ^+ ^' A4 _7 Y' K! w- P

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

]( f: H- |+ c2 u$ N1 O# ~

tls:

M4 R& i+ r- \" G" o# @% ~

insecure: true

2 D v( f3 `+ }3 z: W, A

headers:

; A8 ?0 @4 p5 l6 S3 |, u

X-Scope-OrgID: demo

`- B8 P! S* X; g" W% r

processors:

$ u$ k+ j2 Q |" g; v2 D6 ~

batch:

* u) R0 X' G7 S2 h9 K! L- h

service:

! S6 v* z% `. D% J% J; O5 H

pipelines:

/ b& g( z$ H$ E- T

traces:

1 d" k. Y+ D* `

receivers: [otlp]

. H9 z9 g1 T! O; H- u7 R! q9 r

processors: [batch]

; Q) N' c# I* M

exporters: [otlp]

$ d: O! \# Q. S' L) E

metrics:

0 I8 P$ y8 S) G

receivers: [prometheus]

" E0 O0 I2 }7 W6 N7 @8 @

processors: [batch]

5 ] A- q$ I. \8 ?9 k4 t" Y$ u. c

exporters: [prometheusremotewrite]

+ x) x! ^' D# p0 f) a

使用 OTel Collector Contrib 进行 log 收集

1 H1 o/ r+ ?) X

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

7 I- `1 s# f u- U+ L

receivers:

: ], u9 z" B L( A

filelog:

9 q, ]% _1 O) Z4 w

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

2 O- j, X9 w/ y* L" n

exporters:

3 v K) K1 N) s6 {# a. Y* w

loki:

+ s; J0 @% e3 P6 r) t: e

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

7 K5 e- |, u7 O4 h+ y5 k+ E2 e" ]; w

tenant_id: demo

+ N9 c# O! U7 z7 y+ q9 A- Y

labels:

# A9 w" M# ~1 p' s% n# k% @, i

attributes:

6 I) M" F5 H k9 `" W: m* u0 S% N

log.file.name: "filename"

4 A. F& c/ E' A3 |: b$ t8 }

processors:

t$ y1 _3 H4 ?8 _) |) h9 |, N+ N

batch:

- D; d+ a/ l8 o/ p; t* d6 J& I

service:

) s, M, X( E& k1 }, c" e

pipelines:

5 t( q0 _4 }. w1 X

logs:

2 n' E0 w" R& O+ n

receivers: [filelog]

( y. F7 K/ W' Z" K& O

processors: [batch]

M$ d' G% g- s* k: N1 `, J- |( }% v9 w

exporters: [loki]

7 D- J, J" ?9 I% E, U2 O; X

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

2 Q% k3 |3 `$ T, t8 D3 W9 `

总结

! T* a; C7 b4 {* F% e

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

1 F: S$ P" S7 @; l/ D3 k& c: H

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

2 J4 u6 _6 o( r! l0 e T * L# h; Q! S2 f- }/ P

责任编辑:

# D2 V! D- B& ~9 y9 \# c # `9 X+ ^& c( I7 ?6 ] r" K! T1 m - g' l o# {; }+ M* p# U* C, M1 C6 i, o$ { B* x7 {
回复

举报 使用道具

相关帖子

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