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

[复制链接]
: V, U, Z3 h. j; |6 @2 p2 N

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

0 o$ b# R6 p2 g z% N / N! G& k8 w# {4 i9 a+ O+ `

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

9 `) g7 G& [5 R0 Q/ W( T

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

+ J1 I6 s) P% u3 N3 ^# |

通过本文你将了解:

( Q! }2 T/ M# F$ K3 @9 J3 I5 \+ P

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

+ m$ D2 @( v5 Y( Z- f% @+ ]

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

! u- ^% B! z) {' `; }9 y2 M' A! F& g

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

* Y |; d( n4 d+ J0 H7 T

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

1 L* D% b6 p1 ^

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

0 E; w! J @& d4 c8 n, D& A

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

( F" f6 N" {5 B# I$ o$ L- y

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

' M" m/ L! v. Q9 f- f! w. T

下载并体验样例

, ]3 {% ^3 r( z8 H& n- y

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

# D1 x- q8 j) h% i3 L" ]

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

! [6 W, Y( E# m' A9 k! ~9 r- X. y

cd prometheus-exemplar

( u7 @5 v1 Y0 M

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

0 t0 W. h7 t: y/ g. g0 r! {2 n

docker-compose up -d

; u5 s' Z) K! D. r% u5 f- U

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

& P2 R% ?! x8 y4 @( O0 N" z/ A

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

4 q- g& z5 [. C, _4 x

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

1 m% V. ~% ^) `/ v

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

+ j; K: k1 d4 |/ t4 B

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

3 F0 z c( S! ]4 U% z. [ F0 A

整个部署架构如下:

+ }2 ^- V6 ]2 y. C

1 Z6 N5 M& j8 x$ ~% R- @9 c! c

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

1 }+ z2 h2 {% D5 C

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

8 `2 |. ]1 f" n* C' l' {1 i

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

. s$ ~3 G/ d4 }! l2 u7 d7 |' t

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

`) J- U: v% o2 y( R) P

, u9 j# `, \" K

细节说明

3 G) G: L: W6 A5 L! b8 C% O+ X

使用 Promethues Go SDK 导出 metrics

4 o( Y g! z, Y$ l6 \1 n

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

2 W; H8 f6 t1 \% A

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

7 e+ i0 z, N" n7 l

Help: "Http latency distributions.",

3 ^/ S3 b% q$ s* h; R3 x6 r, H. \( i/ _% G

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

$ a0 j+ M7 \! g" v

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

& ~8 K" s5 u, O5 Z/ q& h

prometheus.MustRegister(httpDurationsHistogram)

& m( V+ N1 p* W& F6 x, Z% k4 _

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

, Y- E8 N2 O, G3 ?4 M" J; v

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

/ Q5 X z6 [8 |

observer.Observe(elapsed)

8 j% B' ^1 b6 j* A6 \

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

! t) ^8 ]" ]5 j

})

% j" a0 V0 W5 G5 ~- I# l3 }- L

}

, b F6 M" T1 V' S2 e% e

}

) z( g/ v' [& U |6 c0 \

}

! G- F( c4 C* l, e

使用 OTLP HTTP 导出 traces

( u$ O) k/ b& u# K" O+ I; X

使用 OTel SDK 进行 trace 埋点:

1 R a& Y& P4 I, g# I/ R

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

6 Q E# @, v; d

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

/ ~. F/ I. \% i! T q( e

defer span.End()

4 h8 s0 |1 Z3 V/ L# P$ d

// mysql qury random time duration

3 ]$ C6 _, Z4 g9 b$ K. Y

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

3 D% z0 h$ s2 }5 e; r( M5 k& j

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

' B7 w; S: a: Q9 n& c& ^7 |# b4 ]

return

5 X; {1 s3 K" g( ]4 S. `/ z7 p

}

* ]- K4 B* p; Z m. k) }* V

使用 OLTP HTTP 进行导出:

7 {6 p6 g+ W# @& x* E

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

% V2 \% b; l ?6 W6 a, f6 y3 y

client := otlptracehttp.NewClient(

; c7 M. E9 Z: ~. l. Q/ ^

otlptracehttp.WithEndpoint(endpoint),

( c5 ?8 v# Q0 G6 C5 B6 _

otlptracehttp.WithInsecure(),

9 B) ]. A8 s& u9 Z' }% J; v ~) }

)

R4 r( s- w7 `% W& @- [ {* R

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

' m# [8 a# H: g$ G+ b% C6 j

if err != nil <{p> return err

8 ?* J: q! c2 @7 D" e

}

* @5 W/ f Z3 g6 }2 ^( d

tp := tracesdk.NewTracerProvider(

6 N7 L, E6 r/ ?; }

tracesdk.WithBatcher(exp),

; R; f( Q6 G4 Q: w% \7 K

tracesdk.WithResource(resource.NewWithAttributes(

2 p( i; x$ Y% ~* j$ o( X8 p

semconv.SchemaURL,

# Z( c1 W8 l4 a: j

semconv.ServiceNameKey.String(serviceName),

- N# i5 B+ p- m. w2 x2 E$ q

attribute.String("environment", environment),

& |5 p; C7 t- u6 ?9 u7 V$ A

)),

$ A2 D( @) J$ \5 s g( V/ i

)

5 o0 ]7 [2 W9 X, S

otel.SetTracerProvider(tp)

A; p U: M3 ^5 q {' ]2 m

return nil

4 r; h5 x7 D$ q" f. L o. u

}

6 c+ l" h: ?9 R/ ?0 A* C1 n

结构化日志

' Z! ]; ]' O3 i

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

5 n5 @ X$ p3 s" D9 S6 B5 f, W

cfg := zap.NewProductionConfig()

4 C* `3 [7 j( m. M5 d

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

7 J& ]) K% J! q+ A3 S% t

logger, _ := cfg.Build()

# y7 r% y. n$ z" a

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

% x8 _# G7 K, }3 D# P& O

使用 OTel Collector 进行 metric、trace 收集

8 B8 Q! ~; h1 n+ F

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

7 }8 x9 ]4 |; c- M$ S0 r

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

# ?6 q) a9 m% E8 X0 w, W* X# k$ }

receivers:

- {, y, d) ]4 \6 f

otlp:

5 i7 x( J$ D6 W' F" T

protocols:

# q. d9 _1 G5 a

grpc:

+ A; b! J0 G, ^

http:

& e9 W6 h, {! F) G

prometheus:

, \2 d, N. U; A% ^* N8 X' K5 P

config:

1 [2 O( d( v2 X8 Z3 t

scrape_configs:

% V, w) e, q B s' _( P

- job_name: app

& ~* d9 z2 N9 y6 T

scrape_interval: 10s

9 Q0 _2 t! W' v( L( j

static_configs:

2 o/ x2 b! U0 S. T( O, G% ]$ q1 I

- targets: [app:8080]

" I' D8 w Q+ r

exporters:

9 i/ r" z% P0 ^8 l0 \

otlp:

! l [( E) R4 ]2 P

endpoint: tempo:4317

4 A9 g9 c$ ]5 G) B. i. E h

tls:

' u% [: ]+ }2 f( E4 D, o

insecure: true

/ j N2 S- i5 L& [( ]" n+ U5 k; s

prometheusremotewrite:

+ M9 d& q; y, S) e( @8 A; _

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

/ g% l' }/ u* ^3 u! C

tls:

4 G8 a2 D! e$ c( v6 |+ c

insecure: true

/ e7 U7 _/ Z+ `+ B

headers:

8 O" e. P: n* M y6 R3 E4 o

X-Scope-OrgID: demo

( h2 q" E0 X' E4 ?! _ t$ A

processors:

( p3 z+ H9 T+ Q

batch:

" M4 L( Z/ I( M, w- e/ d

service:

2 @4 D, v3 |: T! z& `

pipelines:

- G: Z: b4 \' v7 n. c

traces:

2 J; ]6 L h. p% _. ~/ ~, `- O3 X

receivers: [otlp]

. ?' f$ Q, d5 g! e( P/ W

processors: [batch]

9 O) ^5 d, I" v& z

exporters: [otlp]

8 F j. k$ O& P2 Q5 c9 }' a2 O

metrics:

/ V' `9 p( ~- J6 G, {, i

receivers: [prometheus]

+ C1 U" f5 M) ~' E1 L

processors: [batch]

' b: V* J2 d( O9 i1 _9 c4 u

exporters: [prometheusremotewrite]

4 W5 B1 q, s8 w7 t

使用 OTel Collector Contrib 进行 log 收集

$ F, Y* `' w+ \

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

3 c: g, [9 M3 {+ I3 \# g

receivers:

9 j& @$ w+ @, p0 u; c- I

filelog:

* `% i; {/ u# {8 O

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

: q5 L& s' N5 l( C& J/ y

exporters:

/ t8 i) F1 ]# Y6 b& `$ y/ L

loki:

7 N( l# D4 k8 J2 j

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

& e; O+ B9 K A0 z! z

tenant_id: demo

6 L4 F- f$ j" b! e8 v

labels:

1 k' v9 U; U8 e

attributes:

' b7 `6 B4 [; F2 j4 Y H

log.file.name: "filename"

4 Q4 h' R3 P+ O- b5 f% t

processors:

1 c7 a. \* N3 ^- i& |6 K, R

batch:

) c W7 a* |0 h. X

service:

" t5 C7 m$ l& m0 n5 ^) |) h

pipelines:

% N! w$ g5 a: D' ~; \: A' u a' a

logs:

3 L- ?% K0 B0 w. ^" g3 D

receivers: [filelog]

! |8 S& I! J. w5 T* K$ s

processors: [batch]

& ], h' g5 I6 S& D* N

exporters: [loki]

: |8 L3 ^( [5 G1 I5 F6 r8 `

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

9 M, F. k1 n- v- h; }

总结

+ u+ D$ L% d! l7 z5 G* ]* `

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

8 g& f& w! Z/ E' g& r

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

, L& S% T3 T! r0 _) f+ J) h. W8 L* `" h

责任编辑:

* S) E2 L+ p- z4 Q a% f. G1 z 9 w% z: S- X" U9 ~" ^" z 0 \6 a% I- m1 X0 \ S% F$ L) c* w" I- c! O - _. _* _! m, G, J

相关帖子

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