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

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

[复制链接]
% l t8 w7 h W7 e0 D ?

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

- K6 B$ @) |1 {$ V% W$ A# E" L! t6 Y# _2 h$ b; _( ^9 K8 h# O

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

% [$ q% D2 S8 D6 C3 f7 ]! i

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

4 _8 _$ A( a' w5 ]' Z" h! h

通过本文你将了解:

4 ~0 Y% t9 d# V3 i4 q$ j

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

# m+ ]. j& _3 B& @; @7 u+ c

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

7 u6 c: p9 h# s

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

: G& C: p4 ?4 K, K0 g, ^* ~

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

0 Z1 q' `7 R" d# d ~( A3 |4 x0 g

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

4 t6 S* s. d0 y. W/ e

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

5 `/ K: F# [& y4 K4 J8 z c6 d

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

4 I8 B% x. o9 v0 Y# ~. ~/ Y

下载并体验样例

& n& ?9 k5 |% J; E6 O0 d q

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

& [, X3 d% ~0 F* S* T6 H

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

: i; X/ C0 W0 ~" ?" I

cd prometheus-exemplar

- T' f. x/ P" f D: p, ~9 k/ P

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

! @) `9 u% l9 s9 p. y* S

docker-compose up -d

2 f: o/ m' P! X% {4 z( d

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

/ U* _6 h/ V! l3 ]9 m5 t, s

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

r5 ?$ [9 V8 I/ H7 i4 `3 G

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

1 {% X( m9 P2 H( g1 s) v G, L

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

; i5 M) k. F$ S) q) d3 V

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

9 T& i* f& _ O/ y" p y( [

整个部署架构如下:

# }( j' v/ F, C, @0 i8 @* k' f

, H/ H$ F1 a3 t. F8 Y

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

3 C ^7 X3 H) h7 f* Q( R( U$ f3 o) O0 r

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

) J6 A% u1 _$ V ^! ^' G

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

/ k9 Q7 m* ^& P9 j% D) _, H3 u

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

# ]4 M1 L0 l5 |7 Z7 }

9 [1 C y4 O; I

细节说明

& C: m6 q2 I: T5 h3 j' ^/ i

使用 Promethues Go SDK 导出 metrics

- \. A% A4 ~9 D' L q

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

6 G+ Q/ r/ v5 | x! ^2 B+ _0 s

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

0 E$ g3 I M, E) K, x# q% E( p

Help: "Http latency distributions.",

% I2 E: m1 ^" Q+ \

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

# V1 i; ?( p5 n8 v; T( S7 y

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

$ H3 O0 @- V# h; V+ ^

prometheus.MustRegister(httpDurationsHistogram)

. w" e) a! j# \; z, `2 f: }

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

1 E% I9 G0 w0 e8 n

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

$ @" q, S/ f7 z5 x

observer.Observe(elapsed)

2 ]0 ]" Z0 B; {/ X! O* W4 S

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

) ]: o/ O! x8 d" Q% T% M

})

9 v; e% S. N& M. [0 V

}

! |7 ^( O" a- S' \ e9 c2 k" _0 h

}

: b3 F1 `" G! ?* c* l# W+ d

}

% F0 X8 K# v! R. R0 i

使用 OTLP HTTP 导出 traces

2 P0 O! R: @# a, c; w

使用 OTel SDK 进行 trace 埋点:

8 v1 {& x6 y4 x

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

! ?; l) v& \% z8 c! s$ v: D- R

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

. s# Y& v7 E4 U$ ?

defer span.End()

; z* m4 D: y4 `6 z0 V$ M. c

// mysql qury random time duration

% Z3 N, M/ o a. t( g8 K

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

& w* {- E' b/ H- o0 D6 K7 o

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

6 L _- D: \# `; V- X+ b+ R3 O

return

( R, w. L( |( W8 v- s9 e" h6 j1 j# P

}

7 W- @5 K0 z$ ?) B

使用 OLTP HTTP 进行导出:

# U. v: v) I1 K7 o) m9 |: r

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

5 l2 Q* q9 Y* A( P: s( T2 Z7 O

client := otlptracehttp.NewClient(

0 n7 D) L5 S) y7 A$ [- d

otlptracehttp.WithEndpoint(endpoint),

' h- m2 r" r& s# |4 @) q

otlptracehttp.WithInsecure(),

4 c' e8 {. a, o" W) i; s

)

4 F8 I; w1 U }! o# E

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

) E4 J! j v/ Z$ l% D

if err != nil <{p> return err

( k1 n' m( u0 J4 @# X7 P

}

' ~, M. o$ y+ u

tp := tracesdk.NewTracerProvider(

9 ^" X5 f- n& k# ~

tracesdk.WithBatcher(exp),

# M# r/ i+ c/ b* s, p9 _! ^

tracesdk.WithResource(resource.NewWithAttributes(

' M k/ i& h/ y6 |) G

semconv.SchemaURL,

. R7 Q8 S0 h9 J6 B6 }

semconv.ServiceNameKey.String(serviceName),

6 P7 h2 d. N) p4 @ k

attribute.String("environment", environment),

* g! N2 i: @4 y! m$ {# [& f

)),

% U7 g Y% `4 f- p

)

# s/ n* ]6 \2 T: e& G7 X9 Y) o

otel.SetTracerProvider(tp)

' P/ ?: v+ Y {' \8 k0 w

return nil

1 Y4 f3 F5 o2 K# R! C

}

0 |. g4 U2 J" j# H6 |+ M

结构化日志

! ~4 w \; m# S) c9 p' W# Y% A

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

" w. r8 A2 ?# p$ n' T$ b' q/ |

cfg := zap.NewProductionConfig()

9 W, i; \; f9 F1 f+ L

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

7 S5 E) B) F7 c) D

logger, _ := cfg.Build()

4 m2 p: v( _6 p/ t5 ]' }

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

: g9 Z. I3 l2 K- l) S, \, P* R% i

使用 OTel Collector 进行 metric、trace 收集

- E4 c; W3 s+ E% Y

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

4 q& v4 q K& Q) A# ~- U/ U4 t

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

- y" x: w# K" J8 d* D! e a8 j

receivers:

+ y* @: o: _6 N9 {" j

otlp:

, o% w& H, d& B8 j, H0 }

protocols:

- `- D- E, H( K

grpc:

( C* m3 v8 {/ P: x

http:

9 P; Y4 S( t' s8 J7 R% P9 o

prometheus:

; \6 X+ D& H( Z0 a

config:

( G2 g+ e* m& a* I; ^, }

scrape_configs:

; m. ~: n$ A7 y4 J- {$ M8 ?

- job_name: app

$ y, K( \, Q) ^. |

scrape_interval: 10s

. T! d i1 ^% z& {0 r( W+ w

static_configs:

1 x# ~# r4 R3 V8 A. Z

- targets: [app:8080]

# I2 c$ d- S* R9 ?) q4 L3 B

exporters:

2 ?' F1 T- i% Y! {

otlp:

5 c: e$ s2 y8 w+ s

endpoint: tempo:4317

/ _6 {: d1 T6 `4 o

tls:

, A0 F4 o3 b% I$ r9 ^) @2 V

insecure: true

7 N" q8 s. O; x6 C8 J u

prometheusremotewrite:

7 l2 Z* A: Y& q# T- Y

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

, {; k( v _$ V! W* S

tls:

- m" G* \6 `& D) A! j0 Y

insecure: true

7 S& K. j4 f( P, o7 f, Y( m

headers:

5 U$ Z5 f" n% [/ m9 @+ O1 ?; W

X-Scope-OrgID: demo

! K: d z4 l- c( G% Q

processors:

0 W @& F* |$ d1 k1 ^9 k9 m( b. v

batch:

: m# o3 Q+ F5 i3 m& q

service:

9 v7 {# M3 y" P! `# z

pipelines:

. Z! i+ v4 d, L1 O, W) [

traces:

- Y1 T8 T$ M$ v, n

receivers: [otlp]

- i T2 \( g/ O# i$ B P. K+ c( q

processors: [batch]

9 d/ x4 f4 {; k" d- t7 {% v' s2 D

exporters: [otlp]

4 p, h0 w k8 Q8 g$ I) a1 ~- v

metrics:

) v+ c; q% e/ {, J$ D

receivers: [prometheus]

; j6 d" _/ t# q/ s" B* I' B

processors: [batch]

/ c( f* l3 N- a% X: K9 D9 L8 z

exporters: [prometheusremotewrite]

: q1 ]5 m0 ^* I

使用 OTel Collector Contrib 进行 log 收集

3 `. r. m& M F* E2 ]

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

4 `0 w V$ t! h6 R6 j' w# p

receivers:

H- N {6 e% A- w2 A) q

filelog:

: K+ N7 s* u# _/ S) U% X

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

) f" V+ Z7 Z" d5 _

exporters:

1 R2 B/ m& d: A R% t+ V

loki:

! z( {1 { A- z( G

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

* S3 D7 p% {5 \) K" u( q

tenant_id: demo

" t9 G! r6 B' {! Z; M$ v. [ X$ y

labels:

: q7 |- a* G1 I0 N! X

attributes:

7 h9 S: u. a0 a6 K z( n, Z

log.file.name: "filename"

! j2 D5 p$ v' L/ L" A, W: a/ X

processors:

( i4 n+ p( L- F

batch:

! Z) [$ ^$ u( ?$ K5 S0 I4 A

service:

6 g4 c/ A. J5 |: Q$ f! _! V9 X

pipelines:

9 k5 b! x' d* G1 Q' H! C8 T% t" Z

logs:

8 m6 R! ^( ~) Y

receivers: [filelog]

$ k+ [+ |( k" I

processors: [batch]

) d( Z/ m2 L! v6 T( L- ^- I7 _

exporters: [loki]

! @* O5 s- a8 c$ X9 `

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

( C2 P4 a1 E$ K5 R9 y7 j0 K! z5 k- h

总结

2 h# ^( L: J7 Z7 l: k$ q$ G

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

# l7 e7 Y* Y% t$ K

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

7 }9 H/ W7 k) m# W0 E8 }8 t0 }) o u, P

责任编辑:

+ Z& u+ f3 ?# s1 O ( t/ M, X' ^/ i) q' I2 { ! X F: C* e9 j0 y" Q; _ . A# N5 J2 Q, [4 x+ z/ V 6 `6 f5 Q: w: _) b
回复

举报 使用道具

相关帖子

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