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

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

[复制链接]
. ]* k8 }! d) w) @! y8 X a; q

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

6 ^+ r- b2 Z2 Q$ m0 x8 [+ o+ z # g, W! P3 i5 i. n3 F6 L8 Q

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

/ T4 y5 P' i+ J! e# c* j6 a

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

# N, F+ E" ]- @; U! d

通过本文你将了解:

; K! H; {7 h. c3 ]3 B0 ?8 Z

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

$ Q- L a$ h" f( ]- Y4 a1 j/ x

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

- _ w7 o+ m$ f! f. X

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

" J0 X$ f" _# s# _- o

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

, [/ P( y' M1 h! N8 Y: i; ?

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

$ b( S: W! ?# ^! I8 U* M5 U

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

5 H, \6 X* o+ q0 u0 |

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

4 F, H2 O8 L* Y8 l% e

下载并体验样例

- ~4 P2 {7 C2 c: L- `

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

: s. q, ?/ U. g. j0 A

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

+ c+ l! _4 |0 ]* ^

cd prometheus-exemplar

6 @/ t7 l0 V$ c8 z5 O

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

" J# F, o3 ^3 A% J& I7 p6 g) J* R

docker-compose up -d

1 z- N9 J& T$ o

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

8 }' N* _( t I4 Y+ x' c

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

6 _/ w& F# B; f. L

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

N$ t5 D- V" @# l, Q% T

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

1 B' X4 }/ k1 e6 |/ W

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

2 R& Q7 M! d$ T

整个部署架构如下:

3 K) d4 ]" B/ x8 a

! h% h7 U+ c: N, U% D7 R

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

9 C, r1 r! P7 L

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

% N* z, d/ S- g( t& f, p1 j! D8 c& d, `

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

" x6 }( l o) R- F" g- R

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

- T0 T1 J$ ? W' v' U' ?, P8 \

% I2 T, E7 J1 u: e

细节说明

; e6 U0 ~ G8 ]" {# ?! B' A: s

使用 Promethues Go SDK 导出 metrics

# |8 ]! u# R! r9 g" y

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

' Z8 O& V V) t; i6 N

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

7 J- m+ z7 Z( I0 h$ R' E* v

Help: "Http latency distributions.",

6 a: }, _4 r6 H( e3 n) o9 j; O

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

' [$ r4 i: I( @+ B2 I

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

: I( O* ^8 {) s K" |" \9 }

prometheus.MustRegister(httpDurationsHistogram)

3 i- {1 V2 \! W. _

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

' y& E$ K. F: `* E+ E) Q8 q

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

; M) b8 P# P' U! G0 t: m

observer.Observe(elapsed)

* E `6 o" o0 {4 ^0 H

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

5 ~& Y1 f9 | h; k

})

# d3 H% l- ~9 q: [# j/ m' r

}

% i5 z3 i+ x; ?* n- A. X6 j! t: G

}

7 f9 ~( H/ ]0 _7 V* ] a! u8 d

}

! a5 K; n a5 Y( q9 b, ^3 s

使用 OTLP HTTP 导出 traces

5 i3 B( p4 ^6 |7 G% Q$ F/ ]& c

使用 OTel SDK 进行 trace 埋点:

B+ ~ S1 e- o' y

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

/ u" l4 r! V' _

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

6 u# }5 @5 o* D2 e1 R8 U+ E

defer span.End()

9 d! S/ O3 t0 f' R& h1 J s, y

// mysql qury random time duration

F: O' t+ | c0 }2 u1 a* e

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

/ ^. {6 `# o0 Z: l9 ~

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

5 [ }: `) }5 x9 I3 s

return

5 j3 s0 Q$ T( @; V9 M

}

% X$ e; A) _8 ]- A$ V: s

使用 OLTP HTTP 进行导出:

3 w/ X+ T, ]( }

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

! t! ` b; O3 }; _

client := otlptracehttp.NewClient(

. W" A* h0 U7 M. _0 ] j0 m

otlptracehttp.WithEndpoint(endpoint),

6 P' X0 n) s/ L t

otlptracehttp.WithInsecure(),

5 I. H6 `8 b4 b

)

3 M: b0 f* X- R% Z0 U7 X$ G+ D

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

2 K; C2 \! n5 |6 ]6 O* z

if err != nil <{p> return err

' o4 j7 Y( ]$ X2 A- |

}

) e& [! l" ~1 {2 W9 q. K

tp := tracesdk.NewTracerProvider(

/ |( f$ |, H& z Y0 A+ z. w

tracesdk.WithBatcher(exp),

& R- g: X1 D& e8 Z

tracesdk.WithResource(resource.NewWithAttributes(

# |* `2 A9 g1 V+ l8 `, `# O3 [" A

semconv.SchemaURL,

' F* B/ x" I7 t9 s2 i

semconv.ServiceNameKey.String(serviceName),

$ L5 c5 u2 H' i% J, K

attribute.String("environment", environment),

" @1 G8 H( p" X4 D$ h% l% @

)),

) ^" P8 {2 q. _( V# Z b T

)

. T1 c1 @# t) R$ c7 `( H

otel.SetTracerProvider(tp)

7 n' }+ b$ l7 C8 F* A. i

return nil

* R. d/ {0 h. \ a3 G5 `2 m

}

9 H( Z6 v1 x0 u4 ^ A

结构化日志

) Y& U# V5 z9 X( I2 e6 V

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

: X2 H6 ]+ G# K

cfg := zap.NewProductionConfig()

1 [. m2 p- G; g; g( O& W& G9 v/ O% Q

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

1 B, g& q; k: X7 j! }3 G3 ?2 a

logger, _ := cfg.Build()

) O* U" e& L. v P. l# |

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

1 o; F! m$ J5 Y" _2 ]

使用 OTel Collector 进行 metric、trace 收集

. [ U4 i5 `( B! t

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

' D. g3 k! o; [/ R

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

; f1 @( q% E S, n9 h8 m; k; |

receivers:

: r* o- u! n* ?4 j; e+ L6 J

otlp:

* i, ?' O- F9 o& X; F5 v

protocols:

0 e9 ]% X) v# o

grpc:

7 P- A J7 w- U5 G' ^, i( k. w

http:

! g9 l3 a" v6 x, M/ g

prometheus:

0 d/ [1 z0 C( b& y" z' |

config:

( e u# L; j8 r' G& H

scrape_configs:

" M! k# `! U# S* a

- job_name: app

7 L' O) [4 h7 w0 N6 @

scrape_interval: 10s

* u: w/ v8 G. z

static_configs:

7 ^, ~: g0 e/ q" {

- targets: [app:8080]

+ x8 X$ X3 {" q2 Q2 q5 C9 Z

exporters:

: h% J1 I& n$ r# G7 }

otlp:

, w. D- P! g- R/ R

endpoint: tempo:4317

& P$ w U% ?+ ^

tls:

# P/ h; F. S" |* r j! L2 t$ l

insecure: true

/ @5 o/ F& \ L

prometheusremotewrite:

8 M3 c+ v* s' j

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

% W/ ?2 _4 i: B% l L* j

tls:

4 o/ J3 z% i% g9 J# f1 c

insecure: true

3 a# @7 B) U+ c' N' V. U0 J

headers:

, m5 a# w% t6 @# S# J& a9 l5 n2 P

X-Scope-OrgID: demo

9 N0 q8 @4 l/ ]

processors:

! s- Q+ |! c5 ?0 c I

batch:

9 J1 p* z. h+ i4 K3 P- I9 ?6 M

service:

+ Z4 l) `7 b6 D. e- |2 j

pipelines:

* Z+ u9 r( t7 n' Z

traces:

6 t* ]% d' \4 e1 g/ ~7 P6 }

receivers: [otlp]

. N# U- c! `0 P, {

processors: [batch]

) m* ], d6 s5 t4 U1 V w4 Z

exporters: [otlp]

1 S4 j& D% \- t) ?7 A

metrics:

( v& q; [& k. K3 s

receivers: [prometheus]

" ?4 q- l9 v# P8 @

processors: [batch]

8 t0 ^4 s, Z' B" y8 S2 R3 y) N- m

exporters: [prometheusremotewrite]

% Q5 R- |5 W. g2 E9 E

使用 OTel Collector Contrib 进行 log 收集

. L/ m/ ]1 n! G2 N0 a

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

: ~9 t; [, L' _: x" u

receivers:

2 J! h$ ~2 e, | G% u8 Z

filelog:

" d3 T" m2 [4 A# I& ^ d' S/ l t

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

/ J) k7 ?$ R# S3 j* L

exporters:

: K# X6 ~* o" p: X( z4 p% w# j0 j

loki:

# F1 i% W: ~# E- d

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

m4 ^, v( h% ~ d9 _7 Y

tenant_id: demo

) V$ N5 A/ ]( G

labels:

, N X( b) j+ f5 F# g% n. D6 i$ [9 p

attributes:

/ k7 j" m! {8 G0 _( U+ N

log.file.name: "filename"

0 s6 b4 @ i1 z

processors:

* V1 }+ w; {. B# u# F5 X+ O: C

batch:

! Y$ z. K7 y! l

service:

; e- k2 {4 G0 M* P& ]6 I. t

pipelines:

`* N* V4 M& g! |& A0 c

logs:

. I* k6 Q+ s4 Q

receivers: [filelog]

+ D' }( [1 c. q

processors: [batch]

; o- Q, z& b& T; G

exporters: [loki]

$ e! W, p0 G" d1 I; R. [" G

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

u( B; V3 w- H* B) q$ o

总结

5 x8 G4 Z) Z; A, S9 n5 q z$ J) @

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

5 g/ A# k' y; w0 M) v+ h6 U

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

& ^* V" Z& N3 P6 | : c. D/ o- Z6 o7 T7 a

责任编辑:

- r3 h4 p! z: N0 L V! z6 K, m$ K" v1 ]5 Y 1 G' G/ B" L' x$ c! d- s : c* T& S: N% O* ^; t/ p3 E" }! W# n' U6 A. v0 T
回复

举报 使用道具

相关帖子

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