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

[复制链接]
; [/ D% d1 W7 W" _

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

9 J# K: o0 n' e. Z) L' B- g) A p, \: I) ]$ U( F

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

- V' J9 i s/ z x( h

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

) u/ J, f* D6 s8 u/ _& z

通过本文你将了解:

2 a, z p( b0 R7 {9 h1 y! {" V

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

" u8 h3 c/ H2 h0 l( s; O

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

" {8 w: ? y D2 L7 V# u. c

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

0 b2 g! Y4 o' B; S: o5 R5 x

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

2 k* ]* C3 ?! ~: P0 s' B- Y8 Y) @

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

# x! h* G6 U$ V, B! y9 t/ S* s

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

) S% D- j% K- U+ l

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

4 |, Z8 s( F1 t) o3 m1 j+ G

下载并体验样例

' J2 U- l( ]/ {- V; f

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

$ _6 d' E! E# {& I

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

% U4 S$ r6 V% I: p8 Z

cd prometheus-exemplar

* j, C1 b! r% j5 E' P

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

) q# Q! E6 k, z% R- m

docker-compose up -d

, F) A% {- S0 o

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

8 G- r! M, [5 C t2 }" U

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

2 h; m+ T/ H0 K+ W

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

) x- m4 T" {% x. v3 M g

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

" h. N' g9 m1 ^6 c

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

. ^# M6 e3 B* j4 K2 j0 N

整个部署架构如下:

4 t0 |" |: [' o" y: u3 e) j% T

9 \8 t. R( e" E/ I, r5 j9 O Y

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

$ b; G5 x" F& `

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

: e' ~: L! z' L0 V

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

6 T8 ?8 l: U' R& Z5 W E# T, I

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

3 z; ?( n: m. ]/ t, r

0 x' K, j7 n* P! ]1 ]1 o

细节说明

- [+ i. E$ A7 M# Y/ S& M- z: B

使用 Promethues Go SDK 导出 metrics

, g" A/ @# Z6 R! q/ x

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

- N& W' b' S0 f. r# W

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

6 C6 w. u. A& E+ e( J6 G

Help: "Http latency distributions.",

# j% m7 H2 ?" N5 ` N9 T

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

0 T6 V; z+ V ~$ [$ D" r

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

! G; w B) Z* n, N

prometheus.MustRegister(httpDurationsHistogram)

; P5 M9 b1 { ~! _8 f% _

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

G* s8 _1 o+ W8 z, r

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

$ y5 c' s. y; ~

observer.Observe(elapsed)

& H1 O+ C! D1 ]* N# a

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

( K. Q( O/ `3 {* l

})

$ W# L {* T2 u: \- a1 B

}

/ W& y5 P1 l2 O+ `2 I4 C

}

( L$ _$ t3 P5 ~% k- ]9 A) ~7 A

}

& `; p8 { ?. d9 h' G$ w7 u6 `

使用 OTLP HTTP 导出 traces

7 @" U" \2 h' U/ j! g

使用 OTel SDK 进行 trace 埋点:

3 I5 Q( w" ^2 N- e3 t

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

* F! ]+ }; x( o

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

5 T. X2 w6 ^! u+ P3 @. f P2 @

defer span.End()

4 |! {: v* z( _

// mysql qury random time duration

$ S" {" o4 h9 C: l+ J# c$ E

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

5 X8 a) w; \; e+ l: ?! }+ I( E

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

' f- z5 C' V/ ^) A6 B4 U

return

/ Q @# Y3 z( f* L) O3 M6 l* {

}

# B5 ~' b* e. J

使用 OLTP HTTP 进行导出:

* Y& @* E" p% h! g) m7 h3 B

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

( \4 l% r6 X4 c* z* i# c

client := otlptracehttp.NewClient(

. X. H$ s5 [8 g, r; B% X

otlptracehttp.WithEndpoint(endpoint),

+ @3 ^8 S H" V$ |: o

otlptracehttp.WithInsecure(),

5 |9 y0 I5 e( E8 c/ o% P

)

+ R+ v% e. x, V3 {

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

4 @' i8 p g8 I/ t' }" R- T+ _

if err != nil <{p> return err

) ^" E ?' o5 v5 a) L+ j% H0 Y8 W

}

; J$ {; [: J: H

tp := tracesdk.NewTracerProvider(

) g8 R, X3 Y) f% w7 m/ I; e3 \

tracesdk.WithBatcher(exp),

8 y& g, l) @7 w" ~( U9 Z& q+ g

tracesdk.WithResource(resource.NewWithAttributes(

0 i! u8 ~0 f$ P, x8 C

semconv.SchemaURL,

, V+ x# S2 t& K' ?& y( E) ~: n

semconv.ServiceNameKey.String(serviceName),

9 {5 F4 g0 a( s2 K' `

attribute.String("environment", environment),

# Y0 g) t9 r# ], q$ [, V* E

)),

2 o# O2 t( I$ n8 S/ b& R; m. f: ?, M

)

- ~, F z9 R3 r+ `

otel.SetTracerProvider(tp)

6 M+ _ d. ]7 _3 x( a# J: I8 a

return nil

, V& I, a3 m1 y

}

" ]) X# o8 I$ ?2 L

结构化日志

2 s: y0 L3 X4 l0 w

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

! z, Y8 u, @+ s4 ]2 d) ^. _, Y

cfg := zap.NewProductionConfig()

. E& T. K& ~1 i: [ _

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

( q' J' X8 O* z C

logger, _ := cfg.Build()

/ T7 O3 |, e. o' {1 V

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

' t3 V8 C9 p4 Y% I1 o+ K

使用 OTel Collector 进行 metric、trace 收集

+ R9 ~- |# _( W+ |

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

# m7 h# b, A* a Y+ F, I9 d8 k

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

& o- g% @! S0 T: [1 L5 w

receivers:

) `2 a6 f7 Z3 c, }3 E- F9 {$ k9 k

otlp:

: M" K1 D* h1 t) l1 F9 R

protocols:

" u! u- b; G, L8 X

grpc:

) g- o( Q& d4 C# Y/ H; y) X& X

http:

! }5 ~! `/ v6 O1 t- ~

prometheus:

% ?; t8 I/ k* z& G7 a/ d: c

config:

) K4 W b. z. x! s, f

scrape_configs:

/ ]% k) v0 R% o( g; [- @9 a

- job_name: app

& {5 g& n+ z8 d, L, {% x

scrape_interval: 10s

1 h+ |, }& J* T

static_configs:

! L$ m% N( p; L B

- targets: [app:8080]

) _1 V) x% c) X, I6 B7 o

exporters:

. ^2 I. E* C) F8 i9 t2 R! r

otlp:

: Z: {, a3 d# A. _

endpoint: tempo:4317

9 `* S% m. P M( u3 x& J

tls:

! q; J! K: S2 `& Z- R

insecure: true

: Z: p5 M+ e C. `5 g- e' N3 J3 \

prometheusremotewrite:

' I; F- b9 b$ k* z2 k& ?

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

2 F8 G! H8 ~+ ]6 k0 q

tls:

0 B+ i3 c/ i4 F' ]" B

insecure: true

$ W( `& i8 n; o7 P) H& L; w

headers:

3 {& u' P" p* g0 F$ Q6 Q0 s

X-Scope-OrgID: demo

( q+ e" W2 B: h6 I- c9 S

processors:

! d1 P7 h2 ~" j* V( ^* `; V2 B

batch:

. Z' u2 z) F F; f

service:

" y$ ^+ _2 W p6 y( ?" E$ ^

pipelines:

! |& N: P+ K/ A3 t/ i2 \

traces:

' f9 d6 p$ ]: k1 L3 ]

receivers: [otlp]

q/ ?2 w' L( A% j1 ]6 V9 A5 G% d

processors: [batch]

0 E+ U+ O) `' F, b

exporters: [otlp]

3 A6 `2 F( a H& Z2 M

metrics:

6 e6 Z {0 Q$ q& M

receivers: [prometheus]

9 d& y& P/ l: w1 Y) B, n8 `2 T

processors: [batch]

9 V. C' \* J& i& {4 ~" d

exporters: [prometheusremotewrite]

3 Q/ b ^, H! ]. w- m3 O) b

使用 OTel Collector Contrib 进行 log 收集

1 c1 _/ M4 b$ D$ @' o

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

6 I* d d! [/ ]9 U7 [ y

receivers:

5 |) t1 z7 n K1 b: x

filelog:

+ f- `! G1 r2 M" c a7 R

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

; Z$ v, |. a P' J5 W) O+ G

exporters:

& N8 m: X' i8 a* [8 U8 F% I s

loki:

9 X8 F6 ^4 Q9 R, |, I

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

6 b6 `1 A' c& W$ e0 w" N

tenant_id: demo

3 S3 r/ h/ l8 i, z$ f# s& ]6 p6 Q

labels:

9 x) p# n G! c) x. t$ F

attributes:

0 ]) S% Y6 D; f

log.file.name: "filename"

3 o. v2 `* L0 |" T/ Z

processors:

. p1 G2 V! o6 |6 r3 D

batch:

! i4 z7 ]0 i/ h

service:

2 G* w7 O8 H" u

pipelines:

' [+ N) ^* B* b1 ?3 U) a

logs:

9 U$ s, i" a4 V7 W5 w1 t" I* G

receivers: [filelog]

9 e" ]9 q. T/ M( N# \. t* U$ [

processors: [batch]

- R. F2 {: \% `! @' E

exporters: [loki]

$ o, c' P% _& n3 r

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

* ?4 D! _! U' y" F0 ?- b

总结

/ y& n! u6 p; P1 }/ `

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

1 c' O1 L) |) V4 o% Y3 d

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

# }. K) H" S; ?) Y7 C4 B; c% O4 |: h2 T* t$ p

责任编辑:

# i2 F- j/ a& ]: W4 a0 n6 ]9 Q B - E3 I: K- g) n1 W2 [: K' k / R: a0 f: q" c! E' v9 i& e- ] y, P. D1 H0 t $ A- C: v; }9 l
回复

举报 使用道具

相关帖子

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