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

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

[复制链接]
2 }+ N0 V+ {, g# @7 _

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

& W; w; c9 I; f, z* ? 0 f# x9 U0 G: I6 E$ V( s0 _6 @

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

( _- G" o8 `2 f* r5 d! F$ K

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

2 H3 r# G( D1 `8 X% _$ ~; ]

通过本文你将了解:

- p% T8 e2 u* e2 L9 t2 ?7 l3 O( b

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

4 a: `1 R8 E- K$ {$ g0 C

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

8 O, _/ F, V# H) V. _

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

& W( M" f3 R& A" X' g1 [* E

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

* x" \, {, g0 |: u/ n

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

' b' r; ~' U7 M! C0 ]

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

0 ~( y! X4 Y7 O

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

% `$ i$ X) h5 [# E

下载并体验样例

1 Q4 z _" ?6 u) A, b8 k+ Z/ p

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

/ }! Y. N& u0 Q- e! h+ J; g& D

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

5 k' F2 ?! h2 ]+ r/ d8 V) G- C7 J

cd prometheus-exemplar

! y: {( S7 ^1 }. L

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

, w. O. Z6 A! {0 h/ `

docker-compose up -d

' q+ l: B- Q* ^4 z$ @

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

9 A1 `7 h) B a

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

" y9 J6 k% Z% W

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

9 u4 I2 Q) v" k' X! y5 q; u' c

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

: B0 M) B" Y0 D: L: i

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

5 {) Q# |- m: |% q f

整个部署架构如下:

7 q% m5 R* s3 |. M2 i

. N5 V5 V7 w- s" r

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

4 ~- M# e; W h2 B7 ~

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

, E: Z. B9 ^1 A7 _0 s

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

s! L" Y( e" W% a7 H; E! }5 m

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

$ Y5 }+ Z' ^* s1 E6 O% L; S

- N5 M( l- v; U% T

细节说明

, v. s2 ?! @4 Z* p% U a- @

使用 Promethues Go SDK 导出 metrics

6 W! M4 T6 l' l6 y

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

& w6 B6 B; O! r. l0 L1 _* c

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

: d7 g# L" c0 N3 v9 Y

Help: "Http latency distributions.",

4 }& j, R4 ` {" K) v/ A9 H

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

% d2 v: j2 {0 i2 w& y

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

$ I- V. v4 E% c7 L& ^, `" R

prometheus.MustRegister(httpDurationsHistogram)

( b: w! {- K* q) E- A# N

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

9 ^9 D! H0 w" `! J

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

/ F9 g/ u# {" P8 [$ O4 s2 X' l# A

observer.Observe(elapsed)

2 v0 U! R0 x* i- \! g4 d; t: M

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

- [+ x+ H! x+ y

})

% b* a/ {" Y4 x. Y% w' {' p2 C

}

1 W7 M) C9 W, J

}

) I" f/ W' G) t$ J3 _; X, P

}

. ^# @# h6 i, G8 D& t

使用 OTLP HTTP 导出 traces

5 o1 _5 ?5 D7 D D. q1 m1 U" [

使用 OTel SDK 进行 trace 埋点:

3 U* P, K* f$ Y4 J1 i3 j

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

9 s- c0 w; k' b" O+ n/ D& @0 `2 Z

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

8 x7 P; S% {3 a5 X

defer span.End()

U! ]( A: l- u H8 x& W7 L/ [$ a' t

// mysql qury random time duration

+ I4 {( D3 i! J8 }0 h+ s) e( a

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

% c4 E5 h9 P- S5 z" I. c/ M& [

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

1 m' V: K2 s% o. Z7 H: b7 w) R

return

- Y& f+ \7 V$ A2 \0 v _

}

- z ?( m0 ?1 h! t- b# w4 Y

使用 OLTP HTTP 进行导出:

+ r/ R- V1 S8 P7 |6 @! ]0 F

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

: A( P0 ^% I! y8 X* W! J B

client := otlptracehttp.NewClient(

6 s1 i( R2 U" v, j

otlptracehttp.WithEndpoint(endpoint),

6 j H" P/ i2 x. P+ S

otlptracehttp.WithInsecure(),

) G* `* c! z5 J

)

% k" Y8 g/ i8 u1 J& t4 f$ u: |

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

: `/ r$ v0 z5 r0 I9 L3 w" n

if err != nil <{p> return err

( h- D, G$ w9 r [. a/ q2 e$ @

}

1 Q3 h3 S: {8 T5 G% p

tp := tracesdk.NewTracerProvider(

1 L/ b9 x1 t- @+ q

tracesdk.WithBatcher(exp),

. s" O; p' E& S8 q3 {

tracesdk.WithResource(resource.NewWithAttributes(

" M( r' K: L9 m7 r

semconv.SchemaURL,

: ~, H n9 U* a8 ?$ G1 B8 h! y

semconv.ServiceNameKey.String(serviceName),

3 G9 k# Z- i6 l1 d

attribute.String("environment", environment),

T% V, d. \+ y5 A0 K; f- c

)),

' R6 ]* g: r5 K- C! g1 Z! y

)

- ^9 d1 Y- O! b( H f# y

otel.SetTracerProvider(tp)

* L, T* t) I: ^

return nil

2 a/ o' ^! Q* ^) n7 D

}

; x: y7 X; n8 p

结构化日志

/ @- y. c2 ~' R! x- t! l$ }; E

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

# F# H4 y0 t4 J; Q& M/ e0 q

cfg := zap.NewProductionConfig()

6 ^" n2 l* A3 ` |: P" b/ q

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

) e* r) N- s4 v I5 b: d

logger, _ := cfg.Build()

5 y0 `* n9 H( k% T

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

1 ^- |# F' q1 l* p/ e, I

使用 OTel Collector 进行 metric、trace 收集

3 L# C5 |' f9 `1 q9 F1 v

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

5 G% O+ z) e# [6 e6 ~/ a1 q

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

8 L4 r+ g* ?" Q! e3 _

receivers:

^ k: L M" @+ w0 t4 H& Y. d

otlp:

; Z) [- h1 P2 U% i4 v/ ^3 b

protocols:

% o( h9 e2 Y5 w( {

grpc:

( y" y! v3 C( j3 _+ Z

http:

" J* a" i. ]; \' {6 M* |0 d

prometheus:

6 E+ t$ @# n3 Z8 b% y" I& m2 D0 ~

config:

0 x) ^0 K( G* ^+ r- W; Q

scrape_configs:

) Q+ } R! A; S# `3 p

- job_name: app

* A. P) H) z: D3 F8 f$ S. o( V

scrape_interval: 10s

5 `' e1 U4 R4 x; q

static_configs:

/ {$ h! I4 X7 ]+ s1 w# |8 L6 P: e

- targets: [app:8080]

# U% W, {( G, h8 t* |# c

exporters:

% L# l0 r: O/ {

otlp:

0 m6 [/ r1 R" q& I1 Q0 T$ e

endpoint: tempo:4317

0 z+ i# \1 b, ~3 p1 B+ t

tls:

, v# O0 F3 I2 L: k# l

insecure: true

' `8 B5 X' U5 }* o; K2 d' \1 o) h

prometheusremotewrite:

2 A( k# O0 }+ `" D' C( O

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

: B* D& t: q) @4 P I* z: ]

tls:

# @& {% U( Q9 D, Q) f/ G

insecure: true

0 }9 s9 E7 Q% X% h1 ^9 S6 @

headers:

" ^! ?% Q: l: M4 V |- s

X-Scope-OrgID: demo

5 a0 \+ ]% t* A" h0 `

processors:

$ @+ c- W6 Y$ X2 h& S

batch:

; u* f- C) b a' Q5 F4 P: G3 s1 S

service:

, y$ q& y7 M3 B7 G

pipelines:

: Q& y5 Q/ b! }1 W: T. V

traces:

+ }# I- g" M, q6 w C

receivers: [otlp]

. r" b3 l" V# C! Z2 ^ c( ?

processors: [batch]

9 e5 J+ F8 `" e7 K

exporters: [otlp]

) G/ O+ Q M/ F* r/ C2 |

metrics:

. g# N* J: L' ~& H0 w

receivers: [prometheus]

' [' Q! ~. R* x2 X5 p$ I7 I

processors: [batch]

' X9 K- ]6 t0 D% l4 ~

exporters: [prometheusremotewrite]

8 t; |' e9 |/ y

使用 OTel Collector Contrib 进行 log 收集

8 w1 w# u" Z% e _0 d) O$ z

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

' r# |2 v: M8 ~- q

receivers:

0 t" \, I# q8 q1 ?

filelog:

) B! F; a, [ L w: D$ `

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

7 T4 a" u5 X8 V7 F4 C

exporters:

$ t, V" f" o$ y/ Z( Q2 J* l

loki:

0 g" } H$ }) L u, u

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

3 {# g0 t* C5 {! A5 R

tenant_id: demo

) `* u* O! f- F: w

labels:

! J& h& i1 c1 m7 n7 c+ A" R" s

attributes:

- T; Y) x% o* g

log.file.name: "filename"

2 W8 ]6 H- \' C3 o

processors:

) d4 P9 d7 L! D1 _ `, w/ [- ^

batch:

; _" @& ?) q9 n

service:

/ @: m3 Q: O3 q3 D% N$ U

pipelines:

# z! A4 [% R* K$ z8 w9 P

logs:

: S* v# Q: P$ a0 [! E

receivers: [filelog]

, L) w! R* J' Q1 R6 y& s5 n* j

processors: [batch]

7 K! I6 g$ u% p4 s& A" b: A7 b, j

exporters: [loki]

% Q* i' A" Z5 l# G( v t& @' l

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

& U- s* ~! ]8 W" z% U6 m

总结

8 L1 Z, u% L- f* m) N) V- n

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

7 x, V% v' j8 u

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

; ?4 X( x3 j0 ?7 u( Y1 i' s 1 Q; ?) A; R4 { O7 z

责任编辑:

1 h( I; M F) N1 u4 M 4 ?( U3 _1 N4 ^2 J5 P# u4 h* _; A" L y( } & }: }1 q& z. F) e0 `* L3 ~9 V* n* u6 ]
回复

举报 使用道具

相关帖子

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