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

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

[复制链接]
0 {7 g8 ]- i; `6 G" V0 y3 I

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

: ^9 ]- Q$ G! w M1 ]: r 9 x' x2 s5 S7 R4 ~

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

! q2 R2 [$ F2 N

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

7 u: {: L5 e9 u# m% u# B

通过本文你将了解:

! H q% O1 R: ~( \! q8 p1 g; |

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

) T) \6 ]; R7 _! n! ?- A- p

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

5 ?/ t# J4 W8 U

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

& W/ _- v2 p5 l1 q. c4 a

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

0 Q6 h: K5 c% c) B

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

" {: W8 P0 r1 t- ^) B( W

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

2 ^: T& i" L# ?5 ~7 l8 Z$ Y4 R

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

9 @& \ T* V2 T9 {& x2 o* Z6 \ |2 D

下载并体验样例

0 t1 h9 P- [% T" S. D

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

! S. W0 f" ]. G* E. r8 w- G' {, J

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

2 s8 \) h" y- J* v4 t2 y+ z

cd prometheus-exemplar

{& w4 E5 Q+ O; T% h( F( d

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

* G: q& M" q# [" f5 C* J3 j9 O

docker-compose up -d

4 D, }* y; B& B) b3 ?' Q- S

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

- W0 K9 f2 h( Q* x# s& `! C+ ~0 c8 ^

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

3 B& w. |6 ~1 y

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

! V, X' J# P6 {1 V# }8 I/ |. H

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

! i+ r6 |) r( g+ d4 C6 Y4 E0 K% W

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

* h6 f2 ? A; v1 `' @5 _

整个部署架构如下:

% Z) ]" q0 {5 I* R8 \9 y* M* [

: H3 E% G' k% x% B5 v1 x6 Q3 J, X# {

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

- x! ~" ~3 ~/ U5 Y' D w6 ]; s8 ]

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

9 [6 m: f b3 t; a8 l

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

$ P: w: d1 F( \/ D

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

+ ]# Q) L5 z d: w" ^. c3 I

5 B' O# o/ j+ A6 D

细节说明

) i! b. D2 Y* l% q1 s; v& v

使用 Promethues Go SDK 导出 metrics

& ?9 k& v1 h0 c7 S; \0 ?

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

6 `" ^$ S- I& d4 `: p- U* f

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

. s2 x% f' o V+ q r: E

Help: "Http latency distributions.",

( F7 T6 e" n4 U& o' b! E" X4 N

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

* C1 |$ k O# }

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

% H( |+ c% s9 `2 K+ R5 s

prometheus.MustRegister(httpDurationsHistogram)

- B8 q, ]6 @9 [4 y5 r$ U: u7 D

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

% p: m6 f A4 }" h0 g4 \

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

, T# U, S$ l8 S' {6 X& \1 j' V2 |

observer.Observe(elapsed)

2 k$ n' c9 o2 T( f" f( y

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

6 F {, p1 ^; L0 E

})

4 O3 `- n6 `4 i

}

: O, J: h; \/ x) s6 n# g

}

- e* F5 O7 R f( w1 j$ C

}

5 P; Q2 ]/ E6 Y0 h, y; t

使用 OTLP HTTP 导出 traces

3 C- \9 p- _$ n7 @

使用 OTel SDK 进行 trace 埋点:

" y3 m: {/ M& Z) B

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

" g, v ]& s9 J$ A |

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

3 f8 a. n2 i+ L$ Q; P

defer span.End()

. Q- g1 c3 u0 P# G8 F# m) `+ s

// mysql qury random time duration

+ M' e3 z. f5 @* a1 I# a

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

/ \. @$ \+ `1 q- Q+ r) [4 T

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

: ~5 ?1 b; T( y9 y

return

& u) Z' ~+ B/ p0 z5 Z; F

}

$ q- O5 v5 E" ]: S2 J4 g& A

使用 OLTP HTTP 进行导出:

# ~$ q" A0 e/ r$ K# F" m

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

& p3 R7 Z# a3 E) f9 i; x) A

client := otlptracehttp.NewClient(

x; u& B5 W0 h% E! `, f

otlptracehttp.WithEndpoint(endpoint),

' d" i9 r/ @" {# v0 U

otlptracehttp.WithInsecure(),

5 |7 j$ p9 G! D8 F; u# L# o$ j

)

3 i' ]5 q* }7 F

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

. e" m7 s/ u$ P' Q# x# p$ U2 F* s

if err != nil <{p> return err

' C# E2 [4 n8 k

}

5 Q h. x4 r2 A3 l5 r( j

tp := tracesdk.NewTracerProvider(

- ~- z" f4 e' Z: _- b- X/ j ^( ?5 o

tracesdk.WithBatcher(exp),

) M* P+ P9 s* |) e+ O

tracesdk.WithResource(resource.NewWithAttributes(

" _+ v, @3 ^5 H- T9 O' G) A' s

semconv.SchemaURL,

# N: q# o$ ^: `9 q- q' M5 \

semconv.ServiceNameKey.String(serviceName),

2 T* u% H2 l3 i' s7 d4 b

attribute.String("environment", environment),

, {; r9 v9 q0 Q3 T t1 x g

)),

# a$ W( Z9 g6 x+ ?# L+ D! ~1 S

)

; J! t% A$ [6 j

otel.SetTracerProvider(tp)

, I7 q- Y4 N& g2 F, `4 G8 ~

return nil

4 }5 L; X* F* Y8 R B

}

1 [* w( w$ o0 p& B% n

结构化日志

) z) N1 B+ m0 E; V; d

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

+ j* \1 k! A; i# V: X

cfg := zap.NewProductionConfig()

! D) X" O# @; o5 G7 x

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

6 Z5 i: s( Y/ M% i( n

logger, _ := cfg.Build()

; F; a3 e, m( l$ O' F' k

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

. ~% v/ ^- j! Y4 ]. R! X

使用 OTel Collector 进行 metric、trace 收集

% d7 s$ t, T6 D

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

% w3 S5 t0 z$ D% u

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

' F# Q- ?' f+ L$ e% |4 F" V

receivers:

! R$ ^. W Y! e

otlp:

7 i5 O5 Z9 \# E; o# c- y

protocols:

" {, G5 z' B. v3 d' i5 w

grpc:

+ u s: L% y* w s+ _2 @7 u/ r

http:

) T0 @, p2 N$ N% ]$ N! h8 R, \5 l

prometheus:

& Y; l# X1 ]# a$ W7 K" A

config:

$ e2 @" m, F `+ g" F

scrape_configs:

5 w8 F2 n# K B" N6 p# K

- job_name: app

0 X4 z% S4 K* M; q1 U" Z

scrape_interval: 10s

7 i- z) E$ ^& s8 W. |* v5 l

static_configs:

7 v6 E7 Y! k( `% a1 w

- targets: [app:8080]

: m& E0 b3 s; Q. }% b

exporters:

6 w, Q& Y! V/ B( }, t4 v

otlp:

2 A5 V- f& {2 k: N$ Z$ g

endpoint: tempo:4317

: }' R" `* o& V/ F- w {

tls:

* i' X9 K" w3 H3 `% k

insecure: true

( b; j3 R/ `' ?4 y/ @. Q- D! h. S

prometheusremotewrite:

- H+ S+ h/ R: V: @& C

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

1 c d B1 n- i$ J- Y

tls:

6 l3 M2 Q6 A/ i' B7 l! e6 A. A I! Q" U

insecure: true

( F# M( ?) t+ p2 a9 E- ]

headers:

& l1 ]9 r0 l6 w

X-Scope-OrgID: demo

0 y" Z. j! @+ S0 j- @3 I; @% {+ P! Y. R

processors:

1 b$ g& U! j9 U2 A* x

batch:

- f. j6 V! I+ T: K: Y7 y" j9 k

service:

! ~) w y9 t8 L( U' Q

pipelines:

! s. m8 `2 t, `8 J: c- j

traces:

) J& [' \3 G! U$ D

receivers: [otlp]

# u# f) K( B; e, O+ Y9 [6 G

processors: [batch]

# A( I( D0 O9 @$ c/ g- Q( H, ^

exporters: [otlp]

& o+ r5 l+ h! `- o) p1 y

metrics:

9 |" I0 r$ Z; V3 U: W) {

receivers: [prometheus]

5 L% J) R, |* o a( M# r

processors: [batch]

: m5 {- I Y- R

exporters: [prometheusremotewrite]

9 M: }/ t- m/ x8 @, D

使用 OTel Collector Contrib 进行 log 收集

5 I/ C, S# H- j5 v& }

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

: F0 {7 C3 n' V1 y1 x' G% I, l

receivers:

6 B# t2 Q' X, U

filelog:

, p _$ s8 Y T: E0 f, [' }

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

# [+ Y1 G0 t5 b% m% U ~( G* o

exporters:

" P. J# p. ]" ?* b( M

loki:

5 d& r; ^: t0 J3 t# A' V+ z

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

4 g4 F4 @5 ^# k1 d8 d) z/ ~! A7 V- X# A/ n

tenant_id: demo

9 K9 v* l% u& S

labels:

7 N3 Y1 a* l3 j+ t7 `

attributes:

% k) U, N- W8 L: ], {: o4 c% c

log.file.name: "filename"

' ^6 M0 M, X; P" @0 Q

processors:

9 a8 _0 s' i$ V4 b* V( Y

batch:

& i6 B, \, F7 _( h! `7 N

service:

- t! Y$ U+ ^6 k

pipelines:

: `- Z0 V4 G& ]3 H+ v

logs:

+ w6 y$ O; r- p9 @

receivers: [filelog]

! [: B% @6 i; P; I

processors: [batch]

0 t/ }8 q9 t$ _/ [0 x

exporters: [loki]

. K/ V8 Y2 l e0 v3 N( v+ j

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

* x2 `- ^4 k: g: ~+ Q

总结

; l* q; C; g7 c

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

( n/ I' D' h* b3 j; E! ?3 D

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

5 l) _2 _9 ^0 ^2 H! p ! C- a. l$ D A0 ?1 t" @0 T

责任编辑:

1 Q4 F6 _: @1 v2 ]* K; b7 x( K 9 r5 Z2 Y$ a& P1 x% `1 h" z9 q5 i9 U0 }: T- E/ _5 c & j% ?8 M9 H% s9 m q ( m4 u' p) t% d& `3 z8 j( i6 T8 _
回复

举报 使用道具

相关帖子

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