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

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

[复制链接]
$ m- v6 Q1 B4 G- ]

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

; F$ M3 y7 u5 H2 S$ f* a' j0 A! C : k6 o+ a, M( g8 p# g$ U D

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

# t( S1 y" |! c+ ^, h; T& Z& P

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

8 u- M! _. ?6 }6 `0 i0 p

通过本文你将了解:

" p, n) \$ N& U9 v- A

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

; U% R( M; O5 j

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

6 H# L# ]0 b7 M: w

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

- d8 V; H; M4 m& v J- V- c& c0 z

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

* i0 i: n0 u1 C- g/ k

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

5 ?. |# i& W# \9 S

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

, u! q* J" ] X8 x+ H- C' _1 E) |

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

. y6 L2 U3 Q# q f- t( Q$ d: i) O% s8 n

下载并体验样例

2 U b8 ]% W: Z2 W7 o

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

# p8 m `9 L( W7 M; M1 y

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

1 I: C' k4 ~; f4 O! O4 b

cd prometheus-exemplar

. E0 c- {7 x2 t, { B! D4 N

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

! M& x& }: e8 l7 o% L0 m7 A6 @3 x, f

docker-compose up -d

3 s, K0 k- W p8 T. y1 ~

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

/ Q2 y+ d( p' p9 M3 Y) [9 m

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

2 f& u3 h3 \5 x; q( W5 i3 O+ O0 ]% @

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

+ B/ Q5 o1 @0 d7 |8 x( g

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

( I% D& ~2 L0 @/ v6 b/ y

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

" V: g, f5 p* G7 P# n2 ?( |

整个部署架构如下:

8 u$ ]7 h1 C- ~! ~

! V, l2 e$ S, W0 v! y; a

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

0 L0 k5 B$ \5 k# N! P. i

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

5 Y" q1 W5 E3 Y% t

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

, W1 @* X, [3 N3 `

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

% c& P4 x6 Z. X7 H

- _, K7 B6 b# L0 c/ [8 b) |& I) s

细节说明

. v2 Z* a. @) z9 q6 o( s7 m0 b

使用 Promethues Go SDK 导出 metrics

& K. o% S1 n3 g9 C- M( f

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

, ^1 j/ T1 [1 V

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

' B8 L; b& g6 \; g! P9 A' } Q' x

Help: "Http latency distributions.",

. w R% x* e' G8 x

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

& v$ n N2 U" M& [2 G' b7 P

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

/ Z) N" F1 x. t. R( m

prometheus.MustRegister(httpDurationsHistogram)

! T7 J w, d+ l/ l: \

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

/ A" R, p1 O& g% g7 U3 _9 ~

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

) }. E# V& ]# r. K* h, k( O$ }2 A

observer.Observe(elapsed)

. g/ R/ r7 g0 ^) e, S9 q

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

+ a! h% Y) ]7 u! a% t: K. f

})

3 n/ {- y3 l/ K6 a

}

6 z1 z- S4 f0 h

}

6 l# x4 L7 y' L0 }' C/ P& I* j0 |* B

}

0 C, t! m5 U! l# C# z/ H

使用 OTLP HTTP 导出 traces

+ C h5 d/ I+ h' o" U. |

使用 OTel SDK 进行 trace 埋点:

5 y3 Q0 V! H4 f* S* r( V

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

# e) N) ]0 v; d

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

3 ]7 `0 y9 F/ O' X2 h) h, @

defer span.End()

: r: ]' ~; M7 D, v

// mysql qury random time duration

6 q3 `1 g2 U, a

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

5 [/ I$ B( Z8 x) v y' D3 `: C

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

; R4 l5 ?6 s- [

return

9 X2 P& l( M1 P/ z( g/ X

}

# ^) r% r# M( ^ L- y+ P6 T2 m

使用 OLTP HTTP 进行导出:

6 }4 C6 X' o; i6 N

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

, i5 X$ z# F* P9 L5 e8 ^0 B! R

client := otlptracehttp.NewClient(

2 C R0 K" r; r

otlptracehttp.WithEndpoint(endpoint),

2 b8 u6 ]! ], ]6 b3 N0 [6 p: L

otlptracehttp.WithInsecure(),

" ]3 K: I8 |. [7 f c

)

/ n6 u: y- v; T8 f% h& c

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

7 z( G" H5 o) p

if err != nil <{p> return err

4 K' [& {: C1 Y$ b0 a

}

# e( V! s: c, p. O* u9 t# H1 [( t

tp := tracesdk.NewTracerProvider(

7 O+ z Y! R, g9 u

tracesdk.WithBatcher(exp),

5 w. I" y& Z( H/ \, Z" m

tracesdk.WithResource(resource.NewWithAttributes(

: A. e( ]" X* \: ^0 [

semconv.SchemaURL,

0 t4 s# o8 c/ X1 q

semconv.ServiceNameKey.String(serviceName),

0 W& D" p; L& i) N H- v

attribute.String("environment", environment),

5 t; w2 B' Z4 T9 m8 m

)),

6 D' E& a9 \( q' z6 I

)

7 i% S9 }- O1 E1 h! s- D$ k

otel.SetTracerProvider(tp)

- y( }; o( I( l1 {0 i% ?2 v0 c

return nil

+ }! d4 {' Y5 S y: o1 f, ?

}

o- N% x/ A1 r6 b* V! E

结构化日志

+ v& x) \/ l& e3 K: y3 I

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

6 f& [1 I' [, n3 O# T

cfg := zap.NewProductionConfig()

3 P* v" H+ s! G, i. _! x

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

+ ~+ n# F9 s& `* q# \+ V

logger, _ := cfg.Build()

3 T1 B1 J' {7 n4 }

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

- }6 }9 P/ [0 @0 T( V- P

使用 OTel Collector 进行 metric、trace 收集

9 l: j- X( k* M" H

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

: d! ^, c; a8 t4 o$ ]# d

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

7 ]" O, R! `5 x+ r8 a

receivers:

9 {: m; x& v: U& I& }+ d: E$ _

otlp:

- f7 R0 t) N' G5 s

protocols:

% A r6 I2 N; m8 c0 |6 N6 L

grpc:

* C2 J3 |5 N8 x/ X1 |

http:

6 }/ R# L! L/ K% i3 p

prometheus:

2 \ a5 E) P6 P0 D% X+ c5 C9 _5 }/ C

config:

5 {# T l! V4 }6 M# y6 b6 E

scrape_configs:

, B1 c ^/ c0 S) L. g

- job_name: app

7 y) t) i' l7 C X* n! E: n2 @

scrape_interval: 10s

y6 c3 c* o* A

static_configs:

6 ^6 J$ t; g# j5 M Y% L# ^

- targets: [app:8080]

8 d6 { c) n3 F! j1 ]9 d

exporters:

8 c2 s; ?+ s. U" |

otlp:

7 F' s1 v$ w0 ?% a$ N

endpoint: tempo:4317

( z- M% H7 E0 L% b$ T Z9 k# r

tls:

4 F, d3 o' v* p

insecure: true

( [8 W, H- {* o9 f3 `' G

prometheusremotewrite:

' v/ ]+ e" j: q& i# ~5 c

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

7 E" h6 W4 N* t# p! J8 y# M1 x

tls:

$ _9 N, r+ _$ r: Q* F" s

insecure: true

! t3 G" L3 E1 Z1 P1 \

headers:

1 \% u# h% W8 |+ K

X-Scope-OrgID: demo

7 ~: Q2 a+ h) z( G I" N+ Y

processors:

1 `! D% B4 j& m1 k

batch:

% z! i! e) Q$ w5 \1 v" w3 k' L. N

service:

9 h" @0 G- H+ J/ ^- n4 {1 Y: j

pipelines:

5 O+ W: u3 r9 _1 e2 E

traces:

4 ~0 R. M; r" N- |9 j. K

receivers: [otlp]

+ P0 o8 _* Q! R* C6 l1 A1 X

processors: [batch]

3 `0 J/ E# _6 Z! a4 ^: Z3 t) c

exporters: [otlp]

$ X( T) A5 v8 A- `4 z) F. O- N

metrics:

4 x2 w+ l: R: Q

receivers: [prometheus]

. R( \& U$ d7 J0 K

processors: [batch]

2 G% T7 { i, v9 B5 Z4 x& R4 ?

exporters: [prometheusremotewrite]

) x3 D- p# {+ q4 v; V

使用 OTel Collector Contrib 进行 log 收集

, D- c% V0 u1 W* N

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

! P4 i0 ?6 U1 E6 p6 g

receivers:

3 j7 r& z1 }. z- D

filelog:

& J- v1 U1 f2 ~. C( H% C

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

/ e7 |$ ^+ L% S: ^2 b1 l

exporters:

- s; ^2 o9 K9 D. _

loki:

. A; _0 A% S# O& n

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

5 h( T$ A( B* Y

tenant_id: demo

% e5 X& n0 U% y; w

labels:

" c% E# S, [2 L0 s5 j5 T. e6 f

attributes:

0 j2 S7 @6 ~ A D; J' X5 C! B

log.file.name: "filename"

. n; T- s( R) ?& ~% X' \

processors:

4 [+ _/ P0 P [8 f

batch:

4 Q4 k- B3 C F1 J

service:

% |8 d u: |; L- X9 y# Y; `6 I

pipelines:

3 |: y9 R( i) k. P4 Z7 S; l* ` J

logs:

* \3 e. h; g* q J

receivers: [filelog]

3 q3 R' B2 l) ]3 R

processors: [batch]

& _$ G [; d: H- n

exporters: [loki]

$ F% ^# E4 {; O( j9 K5 `0 r

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

* u2 o& e& b; d

总结

! a4 m1 b: x/ P0 J

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

1 Y1 [; ?1 `3 |; i) |. f! Q

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

& _# H1 |6 A: d2 @ $ S) y0 c, X+ q, g; Y6 ~* n9 d

责任编辑:

1 O0 H6 q3 a* N, v4 l " o5 c4 R6 ^( T0 b* j7 w8 W$ H3 m 9 m: s4 _: N% ~% d3 |5 Q 6 ~% `" Z9 }( d; S " J; i3 {- o. ~# m- g
回复

举报 使用道具

相关帖子

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