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

[复制链接]
% @# U) J/ K, d

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

) H6 L4 @2 [0 x7 c( F6 _ ) v6 o, @9 S2 a5 x( I

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

( y9 V3 q2 K4 E

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

3 j3 p, n% j; U2 v% V; [

通过本文你将了解:

2 G7 \( q1 s* _; W( \2 N) ^

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

4 n; N4 n, s- V" @+ R& M [8 M

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

* |5 Z* X! H0 q4 r# Y9 }( s* @

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

9 j, Z3 q$ }' z# m

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

% l& e$ M5 L- O; _7 K/ F4 V, h

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

* m0 z# i4 x; T! c3 \5 Y7 I3 z

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

+ B& I$ `% H& T8 G( U

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

) f' U. N6 u! |: U, U

下载并体验样例

0 @$ Q& f2 |, i- G; t2 s1 w

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

9 f" [5 t' q7 }

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

# ~1 j/ Z( U; g( o: n( q

cd prometheus-exemplar

$ Y3 `+ m* @$ T3 U3 C* }

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

9 H( l% _/ ^3 _; c$ x- R

docker-compose up -d

& Q( K$ W3 D/ P2 Y

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

0 V. _+ u4 ~( Q' p; d, S

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

4 b/ n' J1 K" K" W, i

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

& S5 m: N; p* P D5 e9 _

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

2 g: U! \% G7 {; d* }! L: @

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

$ ?9 y3 `5 u8 N4 [; d3 K& D3 O

整个部署架构如下:

# e: w" a" U; ], ~7 o

$ v* R! r6 p+ Z

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

& J8 W) a# N) k6 F) `. z6 m

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

: \& b( i* M% s5 |6 a2 l; G

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

. q4 k& s; t: u/ D1 l

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

2 ]/ \, F' P0 T

& ~' C' n0 Q& C2 S% m- G8 v& U

细节说明

5 G7 V* i; F9 j2 u

使用 Promethues Go SDK 导出 metrics

) v4 t4 `8 z V( h/ |

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

a0 {8 n+ g f6 D$ J" s

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

# P. {7 n, d9 }

Help: "Http latency distributions.",

* d# e! g2 ?8 ^# u4 c G

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

% W; T% L4 U. v. d, D8 ^

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

6 e) d% |; T" V+ a# ^% t6 N

prometheus.MustRegister(httpDurationsHistogram)

! x6 p% y& @7 r7 j k

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

! Y; ]8 ]( ~. b+ a; y d0 D

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

1 ?2 t$ e0 P; r) f/ v# L/ i

observer.Observe(elapsed)

: R! c C* S# O0 Y* ]

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

1 x! P' Y$ m+ A' s0 ?+ b! Q3 E: v

})

; M5 G! y1 I, X2 O2 L

}

/ x9 ^* d; i8 {) [6 ^2 x& ~

}

: Y/ h* o: i# }# Z8 n+ O m4 e6 ^

}

4 R$ m- k& i% x, Q3 V

使用 OTLP HTTP 导出 traces

1 ^. |9 b# m+ P

使用 OTel SDK 进行 trace 埋点:

- n- m) ]* ~: C) O

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

; |0 Y' L% K4 F

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

6 r4 S' W" z. m+ n3 }* C

defer span.End()

2 _. g) t' {$ ]7 ~

// mysql qury random time duration

0 h U; w( j5 L8 P

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

4 I# S' v' D+ v

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

& J, H+ T' c+ b: @ h$ k* v

return

3 r3 S( Z, n7 c* }

}

) I* W4 a- a/ }6 G

使用 OLTP HTTP 进行导出:

. E: r0 s3 M3 ~9 ~' }$ f5 ^$ }

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

! U- r4 x( K6 d3 [. i2 y3 y, @

client := otlptracehttp.NewClient(

* x/ W0 A' g6 K% Z. g8 a! f

otlptracehttp.WithEndpoint(endpoint),

0 j& S( S; x9 }( a% I

otlptracehttp.WithInsecure(),

& @4 M# R! o+ m5 m

)

! O! ^: p0 t2 u- L% H9 P2 N, [

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

3 z5 @- ^" t; C6 M, t$ E; X/ a

if err != nil <{p> return err

: D! }- u @5 {8 {" h

}

! O$ e, d, @$ v1 a0 C

tp := tracesdk.NewTracerProvider(

. H# _5 v) F( \' [' V: L6 K

tracesdk.WithBatcher(exp),

1 H8 h- H+ |( {6 }# m0 K! R2 z

tracesdk.WithResource(resource.NewWithAttributes(

" a& I% [+ A3 I( p X' j

semconv.SchemaURL,

/ Q* y& ~2 `6 Z! b6 |

semconv.ServiceNameKey.String(serviceName),

~- c) f1 U0 q1 q7 H9 I/ z2 t

attribute.String("environment", environment),

/ s) r& v1 n; q1 ~! p

)),

* X+ P B Q) g- Q4 [

)

0 C, s/ j2 X) N! Z, n

otel.SetTracerProvider(tp)

, K" G- s: z) M* ?# Y+ j

return nil

/ K( H/ w; i+ _0 Q) L. _# Y

}

5 N! H& W% p8 o5 m6 ~- ^7 M

结构化日志

1 A) M; |4 \! j/ A, `

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

H2 m( l7 D1 A5 ~" s

cfg := zap.NewProductionConfig()

/ b, U% |4 ]- {9 q& u* {& s1 ?

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

$ `3 u2 ]9 I" n- m$ H* r/ `

logger, _ := cfg.Build()

% U8 J4 ]6 }5 `& ^4 G/ [

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

* h9 Y" W, r' u A6 q

使用 OTel Collector 进行 metric、trace 收集

. j7 ~' V8 N. X" i

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

" e. r6 Z4 Y: c$ R$ T9 z% ~) P

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

2 k- O w4 c+ i. }; D; `

receivers:

. l5 V; C' Z. r# N

otlp:

9 m$ D+ \7 c3 _2 z j. ^: u t

protocols:

. `0 |; ?; b9 L+ {2 \% }. V6 h

grpc:

0 p! O: u$ v. {4 Q$ Y

http:

. l) t" n6 { w2 z) u% R1 _1 {

prometheus:

7 I, o" k+ c$ W( ?' e9 P2 x& l

config:

8 v: V6 K2 \0 F5 M! A

scrape_configs:

+ |& L1 `3 d9 ^3 l- ^

- job_name: app

! _) V7 A' ~2 u( V0 {8 ?8 Z% \

scrape_interval: 10s

* d$ Q" q! ^, ~1 b3 T+ U& b

static_configs:

6 q" W9 |/ t1 S( ?$ s

- targets: [app:8080]

- p" O0 [# N8 x' O5 F z( `

exporters:

0 r& R6 d: l$ P# E5 z- x' k! B! W

otlp:

6 p# ]" x8 I" r5 @; L$ G0 Z8 N( T

endpoint: tempo:4317

) E% }) F! }/ w3 f& i3 g) I

tls:

5 u2 r9 q2 V9 \5 Z

insecure: true

* G: h% W- G' F- U; ^" y! Y

prometheusremotewrite:

/ e, q9 [# [5 k% b! }( k

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

/ v9 P5 r4 E9 `( ]) U$ w

tls:

2 |! z* Z& j" Z3 c) R$ u2 }9 b

insecure: true

O. ~5 P; z, V( `, \

headers:

5 j; W; E, u; |% ]$ P+ a

X-Scope-OrgID: demo

{& h% ?% l8 H9 a& d( b) c

processors:

! \/ {8 R1 d/ K. D4 ^" i

batch:

# R2 |3 a, d9 F7 H1 i

service:

6 k! `/ g( v+ U) ~+ @

pipelines:

1 v8 F' W1 b8 Z0 X2 f' P6 s8 P

traces:

* }( T/ j, U% D1 P

receivers: [otlp]

. A) x& _* D4 V6 @ J- {

processors: [batch]

8 B6 L J" M+ I' S8 G$ J

exporters: [otlp]

# v) x+ X7 S2 K* s

metrics:

0 }- j0 r/ S& f, H% p

receivers: [prometheus]

D/ S7 e( {$ p- s7 {7 O) A

processors: [batch]

7 m2 g6 }7 y3 H1 ?$ _2 \4 @

exporters: [prometheusremotewrite]

# A, f% l K9 D' {$ ^

使用 OTel Collector Contrib 进行 log 收集

. ]$ O' z7 B8 O* V% `" y

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

& g' W/ P1 K+ N9 D6 A

receivers:

. k7 L& a& I+ ~# \8 W2 m6 t

filelog:

8 u; K Y( H% r4 r

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

& V% V2 V9 o' j% Y. ]5 b

exporters:

; H+ V% u- X* q

loki:

4 e+ I! o" |0 L: J% T

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

7 W: ]7 ]6 x7 G4 {

tenant_id: demo

) o4 D2 A. E' |6 u- \" u+ V% E

labels:

( z5 \" B$ m; S

attributes:

7 q* J* c& A$ n, c% W. a. S

log.file.name: "filename"

% g; G0 G1 j% u7 v) S

processors:

7 @+ A: U% U& B% x. f2 _

batch:

& w! X6 A C* o. [2 q% r

service:

0 ~7 d, E& _1 m9 o6 n4 c

pipelines:

4 ~3 x; P' [4 ^" N" Z$ M7 E$ f* L

logs:

4 m* N9 m* `2 I$ V. k9 O( }

receivers: [filelog]

6 g+ K8 }5 F0 T- [) u R6 Q0 V0 m

processors: [batch]

! q' q0 o; |% |0 l1 t5 H

exporters: [loki]

1 t: F. U4 O0 A* L/ D

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

6 |5 O' M! `/ {' ^4 E9 F& j

总结

2 `! `. V p' G: T& i

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

/ ? \3 w' _4 O0 R" h8 _1 d

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

- }8 l6 q1 ]% t+ ^7 L. I( u 4 T0 O9 B( ~! i P( s# U7 B

责任编辑:

5 O1 R; y2 ~, F" D8 G $ m; v/ H% @0 Q $ @& I: O, W \- Q1 z2 b4 A# S; G/ s2 {+ s3 { # q/ A. P. V* m5 o
回复

举报 使用道具

相关帖子

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