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

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

[复制链接]
5 Q: n$ C6 L+ o7 B

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

6 S% [4 r e7 x9 b: x5 i7 j9 e % q* ?4 n; k! W7 C6 [: n

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

' `6 O( d; g# u# y0 v

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

4 p H' A4 N, I* N; g& O t

通过本文你将了解:

* J. Z2 q' _/ N

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

5 \" P, Z- o Z: b

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

0 g" A; C$ E+ u. N' F

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

' B/ }. t. @' d' r/ W

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

$ D/ A' l0 z* K6 U

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

" {. r8 y7 D4 L2 O" ]

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

4 P" K5 Y, Q+ H# W

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

. X" u S1 y& W6 p5 z

下载并体验样例

/ ^" Z) @ }9 |& ^8 @( R* [

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

( x6 \8 l7 Q8 }6 T6 ^% t7 N0 }

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

7 ] T3 O8 |4 m# Q; y5 b6 j

cd prometheus-exemplar

( e K+ q* ^) d- p C. G D+ r

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

3 u: r: C7 g% x+ |; }

docker-compose up -d

8 k& a( W( y) [

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

" s6 e: ~6 h% ~: r* E2 X

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

9 W0 j4 q) x) u+ N) K8 w7 r! g

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

1 a/ r& Z& b3 W* @- w$ y; m

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

: W+ a- z7 N+ c

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

3 v3 {; m+ J; |* N+ t2 s& J

整个部署架构如下:

6 ?; a' I! e1 H

4 A, E5 m# P/ m% u* i3 l

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

1 m& C$ O+ E" t) Q8 j5 F( `7 m; l

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

9 [/ f ~, T5 i5 w- N; y0 r) }

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

[6 q, l$ @) u( U5 f

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

+ E) N$ K' V- H, U* Z( F

! L7 w+ K# o2 D; i+ r3 g

细节说明

/ n p0 n( o2 ~: b& D

使用 Promethues Go SDK 导出 metrics

( a/ k1 p2 I; e9 E' b

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

. g2 }* e9 h$ @) O$ k

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

+ n6 P! X% m( Q, T

Help: "Http latency distributions.",

/ k" M/ q3 O; v @5 d

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

# [& N4 Z' M$ M& z+ e

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

$ @ W' J& Z1 M$ s' L6 H

prometheus.MustRegister(httpDurationsHistogram)

, s8 V0 l. }/ [

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

7 Q0 r* w" A# {' o; ]

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

( t- B- U( r* ~. h6 q, z

observer.Observe(elapsed)

, U( S; h0 [, C3 [& r

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

6 J9 k7 F, C4 y3 G3 k

})

, x, V t2 b; ^/ h( Q1 V, @$ i% H

}

# q1 |- b0 E* O) l3 [" n9 [: d5 A

}

! t! B; n- ?- Y

}

! _* e; P' i2 M( W$ p8 }8 M

使用 OTLP HTTP 导出 traces

7 Z/ R# @4 u: b5 d: T' S

使用 OTel SDK 进行 trace 埋点:

+ |: i7 g% I4 T

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

b$ N1 f6 g% Q3 p

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

1 @# q+ J7 @+ x9 A; y

defer span.End()

4 x* n( ?' y& o% l4 N3 y

// mysql qury random time duration

S7 f$ Z c9 \6 z! C2 E

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

) i. r r; p( K+ F3 U0 _

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

% _, K: f4 A$ t: S0 n" N

return

4 J- R" f j" V1 m

}

& `$ k( Z0 h: h7 M j* F! u( B

使用 OLTP HTTP 进行导出:

9 ` K, Y' y B8 K

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

0 B; K" h$ D$ `* f' [: q. A

client := otlptracehttp.NewClient(

! T+ M9 C: h- q2 T/ n3 T

otlptracehttp.WithEndpoint(endpoint),

- f$ K& b' X' l+ y9 `4 j9 S" E$ I% E. \

otlptracehttp.WithInsecure(),

- L: U- w: l& v

)

# m6 B \# D! ^1 e O% ?

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

5 Y& Z9 D, X6 t. e2 S* g& f5 O

if err != nil <{p> return err

. c8 p$ }1 a8 i! N5 p, x" O

}

' `5 I' `, i% R1 h3 J X

tp := tracesdk.NewTracerProvider(

9 \! W! k% V% f# c* [

tracesdk.WithBatcher(exp),

: f+ z8 I8 A6 {7 i

tracesdk.WithResource(resource.NewWithAttributes(

/ {7 E. [ G6 k9 @! e

semconv.SchemaURL,

% Y! c& i, m# ~- m9 w

semconv.ServiceNameKey.String(serviceName),

4 g" q" w7 S4 r

attribute.String("environment", environment),

8 r$ G. e$ O3 A4 ]+ C2 l6 w

)),

, g0 R; w; ~4 W2 v+ q0 w

)

- E/ Z" x: c/ y3 N' J

otel.SetTracerProvider(tp)

$ b% i5 N& N& W1 @% N" J

return nil

# v6 S8 D H. e1 E% L

}

, d" W1 H5 N6 j6 s

结构化日志

7 j. ]3 h# g) r7 q

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

( f# n" |; ]- Z7 S

cfg := zap.NewProductionConfig()

3 e, O$ i- K9 a/ ?

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

& U/ E$ y2 V: k% y2 |% Z

logger, _ := cfg.Build()

& L( ?3 J! f/ h4 u+ k

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

* ]9 Z3 l( R2 x4 ~6 I) q$ U

使用 OTel Collector 进行 metric、trace 收集

+ @, u/ b- z5 Y! L* c% A

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

. f/ W+ J0 q; `- k5 w9 g

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

% p0 T5 V7 M% U" L

receivers:

/ ~+ z) \. q' ?0 i4 [ v

otlp:

& q+ b/ e" q6 A

protocols:

0 h3 m3 E* b! b+ `7 P3 r

grpc:

! j' t6 d$ O) O

http:

; s, {) P0 N. w1 _8 F F; g- E8 x6 ?

prometheus:

! u: Q9 N6 J4 x5 W3 Z$ x8 W+ m" F! w

config:

& z$ F( b4 v# g2 o+ U

scrape_configs:

5 L: t1 N, _/ N

- job_name: app

3 y, o+ W o6 {2 B; d

scrape_interval: 10s

1 Q6 W v% g+ B S

static_configs:

" d/ b/ K2 ?) e( c6 w

- targets: [app:8080]

) N- W& `5 a9 R! Y" T, O

exporters:

5 ?6 w4 x* E4 i0 f

otlp:

$ D) M$ R, A5 P9 u$ \

endpoint: tempo:4317

, `+ N$ |8 U8 Z! b

tls:

: U! |0 ?2 f- D+ L

insecure: true

( p/ D' _1 |. B! b% w+ v

prometheusremotewrite:

6 J' Y, N& `! p3 e) F$ w: ?

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

9 c9 n4 O% K3 \

tls:

2 ^6 p) o7 G: G5 w

insecure: true

/ x; Z ~: [# ~$ e3 c6 s+ f: t% U

headers:

$ v2 U, S _' b

X-Scope-OrgID: demo

% _. [. c: x6 V

processors:

! A; U2 z J' P) w

batch:

/ D3 Z9 B3 i4 B! Q; K4 U

service:

+ f* n; M2 y- \1 t) R

pipelines:

* q; f- z& @3 q* A4 T/ U) T

traces:

u6 }0 \/ X# `* ]8 j

receivers: [otlp]

, L4 q+ A. c, i8 w

processors: [batch]

7 a) E a! q8 R M5 f- U; J' }

exporters: [otlp]

3 }, n7 F& C* K8 T0 B4 s

metrics:

$ n% C! Z. _0 i5 C, z% I0 R% V

receivers: [prometheus]

* ~4 Z( P. p' K F

processors: [batch]

% h# j3 s2 f1 y; c+ Y+ R7 N

exporters: [prometheusremotewrite]

9 l- q5 z- m. n' _0 g9 C

使用 OTel Collector Contrib 进行 log 收集

9 O7 g9 f! h6 o- }$ O* w0 ~

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

" f- ]1 {$ H% z: F& h; ^

receivers:

& x' y6 E. e" ] P" k, C/ @! Z

filelog:

7 T8 h7 G+ t3 A( g9 b ^

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

_; N% ]4 y Q1 ]4 E3 i

exporters:

; t/ K% r+ S! j0 F* M4 L

loki:

/ W) Q7 i$ w" B9 e! G

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

a+ X. r% E: k3 {1 @

tenant_id: demo

- d8 P# i1 h |: H1 _+ N

labels:

: f Z6 i# }% l) _; \

attributes:

& p- ~* p8 M& J& J& {

log.file.name: "filename"

7 `% g8 c* f* L/ F) u' w4 r6 Y9 j

processors:

5 ~8 x9 b8 ?) m3 D

batch:

8 Y0 D( Z& E$ \, _# L1 S: J8 x: M$ V

service:

9 m2 v' Z( W2 M9 s5 y

pipelines:

8 P( z3 r+ X7 D! ?# `

logs:

; e# U( O, j1 l, N/ t7 J5 U, c! g

receivers: [filelog]

# O( `9 E8 Z( [3 F) C; u+ @4 ~

processors: [batch]

L& e0 x" n% h- V3 f+ Z& x

exporters: [loki]

: }* i: r) n1 N0 t; |' z! h& a2 T* {

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

. Z h- S6 _3 r6 U$ |

总结

9 b6 q5 y( k3 G: `

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

7 v9 |. G |3 k; D

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

" J! C4 R, r4 q: d; V& T. C * a j5 \4 V) ~3 y. [8 P7 E

责任编辑:

1 s9 S4 B1 o' \/ v; P6 r 9 k0 S5 A" w: \( ? e6 f+ E J; a$ m! a8 | 3 N; p8 c X' F4 _/ k9 w* C + C3 C! O! l" V: f" E, X, M
回复

举报 使用道具

相关帖子

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