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

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

[复制链接]
" a7 n# v# w* Y" F' z

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

9 L! f4 W0 q" z; H- Q6 f. E ) c: s* c( ]+ O- p. S) ~

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

$ `8 R* c* [. S3 G/ k3 F8 X, f

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

* e0 H8 o* K. B4 |/ k# n1 J+ I1 C

通过本文你将了解:

2 P& e% ^& F. P3 G( q* r" y

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

! ~$ d" ~& Q, C @8 T4 D- T& k- H

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

, g3 R; e l1 M0 ]

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

; A4 ?9 v( c& t$ a: G$ w. N0 U

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

* X1 O8 }# [ c- n' R- x# f

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

" o" u& {2 `- g1 a/ ?

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

' O! r( d) S7 E# |: ~0 v2 O

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

& O2 r: O+ I' F9 X8 I

下载并体验样例

# {7 W$ h# I- P! s" H4 \

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

) \4 R. g8 p7 O7 T

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

! u% u e9 r8 z- f% `

cd prometheus-exemplar

- k/ s4 O9 p6 K5 P/ b W0 x

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

6 ?; f, E, C3 V3 h) P" K

docker-compose up -d

8 W, @! L# M8 m! l5 L* o

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

3 k; q& K* g( c8 a

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

; u6 K5 u. {: p& g1 J

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

& w" M& k: s$ B! w& \

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

+ \! u8 b; o) Z& d" q: M0 o

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

2 P6 e$ Z( p& \- C9 n: D( d) Z0 c( }

整个部署架构如下:

; e! z5 p" V3 n

( v1 g1 E) ` y5 U

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

! a# E+ r x8 r: ^0 p8 G3 l2 l* z

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

- R5 F) q6 {: h/ I; Z

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

4 h. d9 \6 i3 b& D

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

- e! t+ T8 \0 P

2 o2 i1 N2 R2 c; A2 [' Z6 z

细节说明

: k( @* d4 ^) L5 J* }1 F

使用 Promethues Go SDK 导出 metrics

! Y- X9 ]) _ f( T: J

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

! @& g5 W1 H/ O) Q6 ]/ Z- _$ V

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

8 p( ~+ |# ~( G

Help: "Http latency distributions.",

& ^- {8 E3 _# B2 ]( P* E9 L: `

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

3 z' K. o% e& e

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

$ K$ f# I+ I o& k w0 M! g

prometheus.MustRegister(httpDurationsHistogram)

( P) \, q8 T8 w, o, L

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

8 i! c. _( E/ o; y8 ]

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

! @' V3 V8 B% U+ t

observer.Observe(elapsed)

# E3 U& H5 R: C0 }

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

5 H8 v1 r( W9 ^0 M$ ?

})

0 @( g( L7 w( V! V a

}

2 t/ x/ V3 c9 m3 {* r/ H9 X

}

; Q( v, w1 {) F$ Y8 a9 M

}

A+ V4 U# ]& y, n

使用 OTLP HTTP 导出 traces

" D; f& b: Y9 j$ {) r

使用 OTel SDK 进行 trace 埋点:

! Z$ u4 @2 }5 B

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

6 z& s$ H4 X& h3 o: Z$ a) r

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

$ u! ~& o& h8 `5 E+ v9 [

defer span.End()

' i1 K- k/ H, m: m6 [2 ], h) c

// mysql qury random time duration

1 s% M& s/ v- ]# P ?

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

& b% C5 g) w: k, I/ m

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

9 |! D7 w& y, _$ d: }5 i- C

return

7 k7 g4 X; a, H( P4 t# {4 d% N

}

( o9 b2 ^& n% `) `6 ~ [

使用 OLTP HTTP 进行导出:

4 M$ o9 r% }" ?$ }

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

0 E- u: e, T5 {

client := otlptracehttp.NewClient(

! N- ]0 y' T' X0 T* |

otlptracehttp.WithEndpoint(endpoint),

1 U; O* Q+ ^4 g. W: D% }, o

otlptracehttp.WithInsecure(),

1 X0 S/ z+ H6 D1 r# J0 h% o

)

6 T4 F% t$ I3 O& e. U

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

2 c6 M5 k. i/ R6 N

if err != nil <{p> return err

& i" q1 n2 T- u: n( L# Q

}

$ g3 z# {- N S! F8 g# V& e

tp := tracesdk.NewTracerProvider(

; r0 I: I$ E8 W( ]

tracesdk.WithBatcher(exp),

" C6 P K. G0 b) G" U8 I

tracesdk.WithResource(resource.NewWithAttributes(

8 v* C- ?+ E6 Z' X9 N+ p

semconv.SchemaURL,

% J1 Z1 {. G+ f. w8 | a# K

semconv.ServiceNameKey.String(serviceName),

0 i! Q6 o2 b0 F2 {( R

attribute.String("environment", environment),

$ H0 J4 s+ U8 N$ m" I6 u, w0 h1 M$ ]

)),

7 w& }) y' _4 E5 s& _8 J

)

; C0 O; R, d- k5 O

otel.SetTracerProvider(tp)

* ?' R& e* j' A. f! i; s

return nil

2 O. H2 z3 ^4 T7 q; ?, K& V

}

5 A. \0 G# j5 y2 L

结构化日志

2 j/ Y" p# K) E `5 g

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

6 |/ ^0 D( R% o" f1 b7 @8 o

cfg := zap.NewProductionConfig()

8 j4 o, H5 d7 e1 s; S

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

2 r: Y8 N+ O2 P% W* o3 w

logger, _ := cfg.Build()

& H' o; O5 y7 k; o

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

& E j7 S6 a' T4 q% I2 ?/ i

使用 OTel Collector 进行 metric、trace 收集

* J1 |" c1 y1 _) J9 i7 u

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

( ], C0 U8 w9 T5 `

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

) D* R& o: C- h

receivers:

: d% V" t, F% x* o5 j. `

otlp:

: w/ R- O: Q' A9 E& j! ^

protocols:

. k4 @$ A, f. c0 A/ W+ O+ G1 A

grpc:

& s! Y6 H9 x+ ?! _9 e' z

http:

* X4 `6 {% L( W7 F. Y2 Z

prometheus:

+ G6 G; O" y" F5 A% T

config:

; ]4 I+ D' N, J0 h+ a

scrape_configs:

9 m+ b, Q4 N; E. d

- job_name: app

8 O( b6 H1 c) |& v: Q

scrape_interval: 10s

$ g" J* A! b2 g1 c$ o7 d* W

static_configs:

9 V+ d; a- o* T$ M; P, u: Y) V1 K( p

- targets: [app:8080]

; R1 }6 y" j; l% f4 `8 k8 r

exporters:

) d1 J0 ?8 z! Z; c

otlp:

e2 {( k: `- m+ b

endpoint: tempo:4317

: M5 Q4 e3 f9 z1 Y4 s" ^2 f

tls:

4 P' d6 {$ a4 C5 k0 R2 w

insecure: true

1 H! h2 ~1 Z! N

prometheusremotewrite:

. n0 {' E+ {1 Z1 J# X

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

) P2 j: {/ s3 w- g: }, _! [) r1 K

tls:

1 J/ e4 {" K- P2 o' j9 J& k F

insecure: true

# s# K6 m$ `: a7 C. \

headers:

$ k, y) Q8 k6 s4 e

X-Scope-OrgID: demo

8 I7 J6 M% V+ U6 x5 [( L5 T+ ^& w

processors:

t* n. x8 C+ i* u) c

batch:

1 [$ ~8 f0 A0 G0 b" U, H6 y* s0 C- g

service:

/ }+ a" n2 }, a

pipelines:

, M A, h( x8 j

traces:

5 j, R6 _9 d0 l$ |" n

receivers: [otlp]

4 N: B- b' M. l7 E- N

processors: [batch]

/ _$ c) t( i: W+ q- B& Y2 \2 f

exporters: [otlp]

. g. B' x7 |' u" F. y

metrics:

+ D0 [6 @ O { I

receivers: [prometheus]

8 l; c9 N/ U7 }0 h( ?

processors: [batch]

2 K+ [/ m, j: b% Q7 s& m

exporters: [prometheusremotewrite]

% v9 ?9 @! d) J+ R) v; ^

使用 OTel Collector Contrib 进行 log 收集

3 X' ]& n U* b6 \! g

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

$ P. z0 g% Y; _; U

receivers:

Y3 r/ F9 ?6 b/ J; s7 i! ]

filelog:

' z0 B. q; M, C* ^, X" z3 p

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

7 R6 N1 c# z* L$ [3 N0 o1 T+ u8 }

exporters:

6 B& M; u9 C; o' r. X$ c) P! [" A

loki:

+ L% K9 W8 A7 i" Y. y; P

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

8 W1 b+ K: a( y: I0 K: L7 }2 k/ W

tenant_id: demo

1 |* K3 g: Q9 Q+ B5 a& [

labels:

: T& A' {+ Q, z: c4 i

attributes:

: n; j2 T- j& p9 i6 C% q

log.file.name: "filename"

2 C- R1 W, n. \4 S

processors:

% u4 q' U2 _3 ]7 r ?3 w

batch:

, j6 i% ?) \9 B9 K+ C8 U

service:

( x) n' F7 H; i7 Y

pipelines:

: g8 x- m; K1 T/ c) K6 o3 S! j5 }" @

logs:

) A8 t+ s( Q- K+ j% s8 G4 C0 Z, l

receivers: [filelog]

! f6 ~2 z \+ w

processors: [batch]

2 @. r& G4 d- M& B& w- |1 V( N

exporters: [loki]

! V# F0 [! \3 l; I

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

3 C5 w! L/ E1 {

总结

! r" l+ c& Z/ K9 o5 I2 T( v

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

6 U, A3 x8 Q3 N/ O

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

) h. _, M6 q( e5 a$ w 6 _2 `; s3 f- O: M' d7 J$ u8 {) ^! a

责任编辑:

4 c% W3 N! L) n& {7 X # C6 ?" t* L& G# s8 a9 g % F6 B# K+ G3 V; ~7 T: O3 x3 P6 b" ]* Q1 t# }- g% h ' X& j- P1 l2 F/ }
回复

举报 使用道具

相关帖子

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