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

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

[复制链接]
- M9 C& o& X1 f

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

9 h* K7 S: Z: w8 H5 P , }6 P9 x: `+ o+ ^/ E

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

9 W- ?1 h' u* ]8 u' r

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

' w' \3 F+ ~2 x

通过本文你将了解:

5 u. h3 T* ^. B- N' A( v% f$ T/ ^

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

. o A# j2 m8 z$ Q6 e

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

% }1 i y+ k& D" h$ q: R

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

0 B% t8 Y9 U' n8 s: c

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

7 Z7 v# F! M: y# k6 e7 O+ k

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

) Z( x# Q; A( Y2 q) ?5 E0 y

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

3 `; B; |4 B8 I K, I0 \7 m" z' I

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

" l; i; G. e! F& G3 L5 `0 o

下载并体验样例

+ u6 }- J, M' }8 t3 J/ E; U/ ^8 Y

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

% V: S1 z6 r6 g( I" S# L1 ?& c

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

& {. e4 ~ Q# ~' w4 {

cd prometheus-exemplar

2 u0 y {5 z* n: G1 L* @

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

! L* ^ Q1 t# `% `

docker-compose up -d

3 w5 W9 d/ Y1 r$ |6 B( X

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

7 x( y, t1 R+ f- _

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

( Q; H- e2 q4 f, f8 V# L% Y, \& r

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

3 [+ T; P' c, Q5 Z$ B

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

& M8 B' M9 o3 D# r0 N8 G3 O

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

8 Z! N5 k' J. I& F: [6 o

整个部署架构如下:

4 q5 n3 \$ ^; y2 \3 i( X

8 b6 [3 L" C( u

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

2 Z' N1 M, Q0 E0 g0 H4 |; j) H

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

0 u: T% ?! @0 i1 W$ u

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

( @& ^0 B( z6 U# S# `& e" ]" I6 @- Q

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

4 D5 g# k3 N! h2 ^+ R- z: ~

. _- }% W/ w0 l* y9 @3 p) M

细节说明

8 P/ Q. p8 ?/ z$ V1 ~( J8 b

使用 Promethues Go SDK 导出 metrics

* [9 H6 ^1 G/ B0 B9 i7 `2 T

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

( i, A2 K, I6 v+ x/ S, p' h7 ?

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

+ s. C$ z' g8 H5 x

Help: "Http latency distributions.",

' P/ t, Z5 H3 R2 ]% O y5 ?

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

* ~/ E; _. {5 I; z; E" I

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

$ K% z1 `4 I( B2 t' P& O" R

prometheus.MustRegister(httpDurationsHistogram)

/ D! K, l$ `! }5 W, Q p

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

8 Y" M; W4 P; J" b

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

S( R0 \) N% N% b7 T8 `6 a- w

observer.Observe(elapsed)

& ]- H6 o, d4 g% |

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

! n3 {! T r4 L q c

})

: L0 H& z1 [2 l b# G. c% d

}

( M$ `6 O) ^* h9 Q6 m

}

8 C' ]+ z1 U3 n& {+ d9 R

}

6 G; G) w+ f1 H; ~6 h4 L

使用 OTLP HTTP 导出 traces

" u$ u2 t, K- g/ Y t! A/ [. O

使用 OTel SDK 进行 trace 埋点:

( M% z+ S T) j% A* U K

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

5 K# x; X% N- B# p

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

6 [6 K+ j" x/ `2 F6 K" x2 y

defer span.End()

o9 H% U. ^7 P& y9 @: r' ?5 R

// mysql qury random time duration

. P: t2 w k* y6 _; I; T5 E# V

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

$ [9 |) n9 x0 a0 S" t

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

( i* g0 K! u" h6 ]

return

( R% j8 R/ E, \. a$ l

}

, M. N* z" q3 H

使用 OLTP HTTP 进行导出:

5 N$ f8 M; r2 v% y6 F: g

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

1 \: \6 u4 ?8 u d) e9 c

client := otlptracehttp.NewClient(

8 e4 A& K8 }& \' N, V

otlptracehttp.WithEndpoint(endpoint),

3 J3 H9 O2 b$ S, v* ]( H) i

otlptracehttp.WithInsecure(),

+ z, }" e: n/ J8 _7 ` i j. g

)

$ f" s0 `' d |& Q* v* t, _. B

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

1 }+ \7 q9 O. t; X( O |

if err != nil <{p> return err

3 N- y5 M a0 c( _) b& o' j/ B8 A

}

! I" `, J: X+ B

tp := tracesdk.NewTracerProvider(

- M1 S, B0 _' Z7 I% U; S% G

tracesdk.WithBatcher(exp),

9 B" f* @! r* G" w

tracesdk.WithResource(resource.NewWithAttributes(

& H" Z7 R; E7 ]( B; ?

semconv.SchemaURL,

c$ A" ?0 {" @4 x( c& @

semconv.ServiceNameKey.String(serviceName),

& J ]# @; i5 j

attribute.String("environment", environment),

% T* J4 @" F0 z

)),

+ H# o' n7 O! o: z4 ~7 a

)

7 t4 i! }5 O& K8 W

otel.SetTracerProvider(tp)

+ ?$ Z. {* f* ]2 d

return nil

" }, M( D+ g e' @/ b+ y

}

! R1 C* `# O+ l0 |! ^* F, J L& F

结构化日志

' P) w& K! Z2 ~3 Z

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

! t. l" j0 q1 d$ z6 [

cfg := zap.NewProductionConfig()

# v, P) m1 ~2 Y- k5 n5 z

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

7 ]4 h, A7 Z3 C4 P/ Z6 K ^# s

logger, _ := cfg.Build()

9 c: ^( d7 L5 ]5 L; A+ h7 z! |) V

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

8 \: j' f, K+ M- H* Z

使用 OTel Collector 进行 metric、trace 收集

! F% p a, e& f) D) I: w

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

: P5 L: q2 u+ A6 m y

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

" a8 C- G/ T! f

receivers:

" ~, ?0 Y2 p( t: Z+ k, q; _

otlp:

* a9 S5 M! n( Y# W- {( T& _0 Q+ |

protocols:

& u# c% H5 b* p# K

grpc:

& O# G( G% r6 M' s) M) X

http:

2 x) ~3 q" c3 M

prometheus:

& n& L) a" j' W8 S, E

config:

! S- n6 b4 [' w9 m* f0 F

scrape_configs:

- Y5 N, B; [- [' G* M

- job_name: app

9 e7 W& D. y/ q$ ?3 c; b

scrape_interval: 10s

' ]8 _1 g' v) R

static_configs:

9 r3 y4 B2 [' F5 H% V

- targets: [app:8080]

* g( W) {7 S# k8 P( I) ^

exporters:

0 S( X7 z: n6 A) @, m3 M" Y

otlp:

' r7 K9 X" g8 K- {1 U0 {

endpoint: tempo:4317

# s) {* H( ?, }$ p2 C- X F

tls:

3 Y5 Y0 R+ _8 \) A# e

insecure: true

9 \! D. `# l5 G9 |+ k. y. M

prometheusremotewrite:

8 j3 I! r. |. k# ]- N- u

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

: `1 G6 f P. M8 C# M+ H4 X: Y

tls:

- v/ r+ u6 x& i" b4 M1 Q/ Q

insecure: true

; Q5 t. k5 s' q6 \' d$ I

headers:

7 N) }; \9 y9 T5 f9 h

X-Scope-OrgID: demo

m1 `6 n" }* O& x2 [9 O0 E

processors:

0 s, y7 w8 H/ T

batch:

0 Z$ Z7 k* t# v; f

service:

1 E5 h+ S9 N, Z; k( r" s+ M

pipelines:

" B4 K/ D+ } k/ j/ o

traces:

3 ~+ P* x; j' m# w$ R( `2 h [/ s

receivers: [otlp]

9 X* q! U3 ?$ i8 J

processors: [batch]

2 j! O. g+ _' m. E" o

exporters: [otlp]

& T7 L6 H7 L% M0 N4 P; V) I+ a

metrics:

6 |1 j$ b; | L& n! L+ I

receivers: [prometheus]

8 {7 o# o1 P/ r0 V

processors: [batch]

6 f! Y) S9 F( ^4 Z9 P

exporters: [prometheusremotewrite]

# a3 ]4 J. g8 t/ z. H

使用 OTel Collector Contrib 进行 log 收集

3 ^5 v$ v* V7 D' ^( k6 p& F

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

; C( K" q, g1 Z, v3 y3 [, h

receivers:

! G5 q$ ^) r5 }3 w0 A0 P% G

filelog:

- w- x: \) M; C* R/ b

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

1 o9 e: q/ m! j! f5 k8 u

exporters:

+ ]- D: U: b$ f; \7 W7 p. A% k' w

loki:

1 x; N# W0 v. X* s& o( T

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

) I, Z7 Y! n! |7 T9 t+ t S

tenant_id: demo

1 } W6 t# Q' K3 B

labels:

9 N* A* W! }" Y

attributes:

4 h8 a9 ?" ^" m3 O6 }

log.file.name: "filename"

( _! y; O7 f% n5 b. k+ a' M

processors:

1 p! q2 J- v: ?) Z1 |; O

batch:

6 w4 j5 u Q1 Y6 g

service:

) v+ \+ d; Y0 d8 [. x5 Q& b

pipelines:

: G% A; Q7 e* N) a* ~' s+ m

logs:

; z" ^5 T& ~! f

receivers: [filelog]

) z; a, K" |) @ i+ K

processors: [batch]

/ o7 Z: U1 O7 b4 n: g5 e

exporters: [loki]

4 i/ L/ v! b1 A+ ]* U- O0 D

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

* V x3 c5 d/ u& q) E& |- Q

总结

% u3 H4 B/ w: Y2 M

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

! J* w3 \/ o, B$ d7 ~0 P% F! Z

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

8 u+ h r+ m/ q: X 9 r5 k% q: ~1 s& F$ @7 ?

责任编辑:

2 ?; K. ]* I2 k& Z4 t" g / a( Q) J% G, W7 f ) U& M' V. E, {8 h% y$ q" @0 ? ^+ |4 a 7 _( |% S$ b# H, n0 {% h. p+ u# T
回复

举报 使用道具

相关帖子

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