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

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

[复制链接]
1 i7 S! H8 L5 { O$ {

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

# b# W- E$ C' e+ W6 R/ U% S e! D " J$ x) n7 w! C# k

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

$ l- Y! Y$ S9 D

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

, A+ v! F8 |: ?! {* C

通过本文你将了解:

# c6 V+ g( V6 W2 {! c- p. {- o

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

- ^1 ]& j. b1 T$ q* V$ I3 K+ u9 P

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

1 R- t3 ~$ p! g3 w J+ b1 c2 u

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

# [2 b) z. |. J, f g( t

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

. n/ {6 {& B/ c7 S2 i6 P

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

s ^2 j/ o# {* ?

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

$ [3 L$ O! B3 a. m5 x5 |

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

1 c0 m* v0 d$ O! ~* q

下载并体验样例

) Z- }% z, {: z( W4 F

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

5 S6 Q' o# p D/ P( Q

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

2 u; a' a# y$ h

cd prometheus-exemplar

/ F- w$ g4 n- C, h4 Q

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

2 E1 c# P9 A! v& |/ k' B

docker-compose up -d

8 j& f! g3 J: q6 y; _% t

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

g3 U5 z" i' c) v H0 \/ B0 P

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

# n; A1 E4 ]: S1 F3 F

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

0 }; ^. ]6 b" ~4 ?+ \ e$ W

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

; ^' E# x1 I- n& [1 B3 U

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

# M% P6 y& U/ b3 q. M

整个部署架构如下:

1 G/ g6 ]& O/ E9 a8 _0 e9 F& S

8 N1 U" \4 l/ s3 q2 Z" Y

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

7 P# `* ?) R8 g" {2 {

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

1 U" J6 ?( a3 x6 f4 w& c

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

0 y D: r$ O+ Y$ d7 y5 J

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

/ }9 E; o0 O' n. g2 W# W( ~

0 l4 {: X$ d2 a$ ^* h% f% Z

细节说明

# t% o/ q$ ~' ?! Y3 W9 Y: U

使用 Promethues Go SDK 导出 metrics

4 U& @& G" O* ~! K0 [* w' a

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

$ h6 A8 y# G( e1 I

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

/ s# `, N; D; ^/ T& D

Help: "Http latency distributions.",

6 @# k2 _8 `" `4 q0 v7 u/ l

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

4 F5 h7 B% g4 k( [$ k

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

1 k5 ^1 r/ k! W- s4 o' q

prometheus.MustRegister(httpDurationsHistogram)

( m3 q) g( f, u+ H/ y

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

/ ~$ ^0 Y- O/ z" N6 I* |1 I4 W, t

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

6 K) w8 T; Z, q" p t9 `

observer.Observe(elapsed)

4 e4 p$ _# a+ } \

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

0 b4 r2 x0 f& T$ v. y5 {9 K1 P

})

) V5 s4 i V) Z: M M

}

2 ]/ h. \4 u( y+ v& S9 }

}

6 j7 A% M# ^+ R: _) u

}

& B. t/ m, @8 u X: k% N j! K

使用 OTLP HTTP 导出 traces

: ~) Y& b/ W; f3 b4 U2 J; D: s. C

使用 OTel SDK 进行 trace 埋点:

4 p( d/ T' L- s1 B2 I6 A+ [0 S! L5 _

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

% y3 Z4 p0 m* s( m. ^' C

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

2 a" o- L; k* w8 l

defer span.End()

* M0 J* ~8 }" B) X: v& l, N' ]

// mysql qury random time duration

( A" I N) I2 O, r5 x% j: j

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

8 k2 ?9 e6 X0 u# |- J

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

; ~. j( d* s2 I8 P, V6 a/ y

return

' W1 j! y0 h! b8 A- n7 f5 s2 p

}

* j* E8 g) l: e$ x

使用 OLTP HTTP 进行导出:

/ [/ `. `4 |! o- ~9 t" z" a

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

h; J) u p9 q: l( |

client := otlptracehttp.NewClient(

& w: ?8 i o" A! K

otlptracehttp.WithEndpoint(endpoint),

3 L: Q0 a6 o( D% f9 E) d. @

otlptracehttp.WithInsecure(),

5 I" w) u6 Z" d4 Z" l6 v9 l

)

* E6 U7 H+ i" P) S) X9 R$ u

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

9 X4 _' B2 O' p4 p

if err != nil <{p> return err

' L- `" X# Q9 A7 ~5 |

}

4 J+ I0 ?" E, \5 v4 o3 `2 A7 N

tp := tracesdk.NewTracerProvider(

! P/ |# T O, w- D

tracesdk.WithBatcher(exp),

p6 k$ w1 }- `) r+ r8 Q

tracesdk.WithResource(resource.NewWithAttributes(

( ~" l4 c) h+ z1 q: L7 Z8 v

semconv.SchemaURL,

" V% ` W; D7 `$ N, d4 G& s* u

semconv.ServiceNameKey.String(serviceName),

, a: z* e" }8 l

attribute.String("environment", environment),

2 B( T7 u5 g K @% i/ ^

)),

3 w+ f) @7 V* |% l4 ~% K7 Q

)

' h; E; ~+ H# _% ~8 I {! K

otel.SetTracerProvider(tp)

$ } R; @) g/ k! b

return nil

`( l7 t" Z1 ~* I# ~% H

}

& v7 t+ X6 v0 R- S( P6 M8 Y* A% Y H

结构化日志

, ~+ X/ ]. x" s) s. A( ~2 j

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

1 D, f3 E6 y! U8 e2 L, S

cfg := zap.NewProductionConfig()

3 ^- } e# }% _6 h" |

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

4 `2 v) l7 j$ N" W8 z1 n* @

logger, _ := cfg.Build()

+ b4 D, Z" F1 V) m" F" C' Y6 e/ |

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

7 T# U+ d( B! }8 ?2 z& g1 z

使用 OTel Collector 进行 metric、trace 收集

+ c& Y+ [4 U* W! D* V9 S8 ]4 A6 s

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

- k/ [2 A" Q; H

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

. z1 c% |; d4 ~+ Y

receivers:

4 N6 J' \; A/ l1 ?3 s

otlp:

_. |" x; f" D5 r" w. A8 Y! x

protocols:

& u6 \' L6 ?; }4 T

grpc:

2 k, c9 _' z2 K( L+ c9 @

http:

) `" |1 v: `; K7 l- V* L; W$ m) r

prometheus:

+ ?4 U$ C' g" H& g: J

config:

& J( N: Q3 ~' ?% u, Z5 _

scrape_configs:

A; Z7 e. P/ W6 B

- job_name: app

5 z5 h( {. I+ M* J* ?. J3 M7 i

scrape_interval: 10s

- _0 l: B! W( X

static_configs:

( f) a* F0 D: M9 e2 }% @

- targets: [app:8080]

7 H9 W% o. A% D5 \: C4 I% [

exporters:

4 P2 F, V. Q/ e2 u ]$ h* o4 `

otlp:

3 [* e! R, x) E" d) k

endpoint: tempo:4317

% w- L5 A* n: ]) C

tls:

3 j, f1 ~( Q9 }* B2 q( o- p2 l

insecure: true

( Y* l) z" v) W2 V

prometheusremotewrite:

/ L$ L9 S8 L7 Q

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

# @6 d! u6 e. C# C& u

tls:

5 O0 ^3 u1 ?* g% a* w- k

insecure: true

% ^, H2 q) x0 ^. z) e6 S

headers:

0 Q( E2 c) b% g

X-Scope-OrgID: demo

0 d( V5 O% a/ \! M

processors:

) D8 l, v; Z. t: P F. {

batch:

9 a+ e; ~( S2 L0 F

service:

9 J5 `% o3 [4 N

pipelines:

* z9 Q& k V/ P1 m( X6 E

traces:

! v2 u& Y1 J1 Z6 K" m( r9 _- L6 J7 ]

receivers: [otlp]

+ ~* M/ v7 L8 g/ E2 c$ P8 O% t

processors: [batch]

, u, _6 N; Z a& X

exporters: [otlp]

$ X2 n/ v5 f2 i P) f3 F

metrics:

1 z9 K, A' Q* a$ z- d% |9 \

receivers: [prometheus]

3 j+ K F* R# j0 g! Q

processors: [batch]

5 x5 f8 i {8 B. w) X( W& \

exporters: [prometheusremotewrite]

; d% c& f/ e& J, B

使用 OTel Collector Contrib 进行 log 收集

( u \2 O. [6 p! f- G8 g& d

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

$ p* z: I4 e7 P

receivers:

* T5 Y) h+ c: g' K3 E$ v/ p) n4 R

filelog:

0 J! P3 V1 t. K/ k% Q5 z' V7 h

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

6 P P/ b0 F5 o. ~9 u6 v& R

exporters:

6 b9 e, W% l9 Z& O

loki:

9 x/ K3 q5 z5 c

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

; n3 r% P, Y' g( m

tenant_id: demo

6 v' F2 K( i V

labels:

% B7 Q) W5 X. H. ^! Z' c) q

attributes:

7 d8 T/ G: ~- k, u7 B% ?

log.file.name: "filename"

( ` _5 x/ |# y1 N

processors:

, Y( _1 \1 J. N' m O+ o

batch:

6 B% ^: y+ K5 q( G( k/ r0 |

service:

' O- w& N9 i: `9 @9 y

pipelines:

1 n$ \% W, I, v- ] a* L

logs:

4 [0 Q* W- G: H+ {! `, ~% h+ U

receivers: [filelog]

0 \" g/ i3 j6 g7 @

processors: [batch]

$ l4 b' ]! m* }" z0 X. w2 t

exporters: [loki]

% _2 c b. b8 c& I; S5 I1 e

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

X" j6 Q2 B& b" R/ v

总结

/ {$ z P7 `7 H4 B! O1 y* S5 ]( g

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

# w/ S: m" l: e

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

: `& s/ t$ q$ u8 ]! G2 \1 M& C. y! j! j4 G

责任编辑:

( b& i; S* z6 Q( p % z' n( }7 e& _7 P5 }) N% Q/ l1 O a1 T) h. E8 R+ c 6 o) h4 R: A. |6 q! T: v$ `( L7 W7 D
回复

举报 使用道具

相关帖子

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