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

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

[复制链接]
; q/ D( n: m# F( E3 ]

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

. m: T; R2 C8 y5 {6 o s2 k9 H2 }$ J( J5 J. ~9 d3 q5 H! C3 d

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

5 U/ J) o( p# }' p$ z

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

# F( a$ w* ~7 R" p9 S7 H

通过本文你将了解:

+ N: m5 b8 Z$ i% K

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

7 R* V8 s* u" Y! r( s' s& h

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

* K7 [3 p# L% J! @# f

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

: {% z# m" L {6 K$ E, l. b5 G2 {

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

) }; H& l, r' N

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

# q1 a, {: A C/ \/ U( }( x. b: K

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

9 z! V' k* c4 i0 P: s

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

1 F7 s6 B; `$ x! O( _6 n

下载并体验样例

$ ~* a* {- }) U& z/ g2 z5 h8 S# M

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

: a' S2 o- D1 A; H

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

! r4 U4 ^9 y1 s9 H

cd prometheus-exemplar

/ z( {3 b8 r2 G2 v; J. K

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

) R* X, I0 T; R4 G6 M* g% k0 ^

docker-compose up -d

* k$ k0 y, [9 I4 ?3 |& J

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

6 o+ ^1 {, c3 G8 i

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

( Y; E6 f- O h* `4 ]

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

! _4 Y& s0 e0 E3 R

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

" Z* R. w; ~+ Q6 B, L

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

" |6 k/ Y6 g+ C" Z2 U

整个部署架构如下:

+ v4 S% }/ C7 s4 @- s$ a! U. o+ s$ t

. v3 ?2 y0 H) K8 w: i9 A& K

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

% O; I& V$ H, {

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

$ D) s; @5 \8 y' \( ^: b$ z/ c

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

" _5 \6 W) c( A. Q9 }% d2 [

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

# Q% n6 D% R" X. v. V

, I9 Q A! I+ f6 K( K( L1 ?: M& X

细节说明

: |' t/ r& G e* t; h

使用 Promethues Go SDK 导出 metrics

6 w: H2 X' U( R% M( {' N

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

0 ~, i+ r: R1 }$ D& m# v( C

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

0 N6 R; X. {9 Q* w9 |# ~: b. _. G

Help: "Http latency distributions.",

, E# J: S$ u- L# s5 F+ N

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

& {+ k) i, v7 F# A

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

* D7 `3 m+ c4 u1 ^" G+ y

prometheus.MustRegister(httpDurationsHistogram)

z/ @+ g. K% f$ n

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

- U" c7 k1 A# |2 ~0 d& f

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

3 U( \6 L/ o' e

observer.Observe(elapsed)

& Q, C7 H4 Y6 N `: d2 f, Q3 k0 C

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

% h( Y c4 v6 A' e6 Z2 t, X& f3 Y

})

4 T q# X% N4 Q+ L0 l

}

6 e9 k! y9 n+ h# Z5 A* ~/ l. B

}

: Y, i# [, t2 i$ U/ q

}

5 |# _1 p* v" B2 {

使用 OTLP HTTP 导出 traces

4 `0 z* {2 M, k. w

使用 OTel SDK 进行 trace 埋点:

) g/ t- o( J0 W

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

$ d) b7 f( O/ O g) F' r& ?. c

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

" y/ ?& k4 V' F: ?

defer span.End()

+ L, b7 S& v# @4 N" x

// mysql qury random time duration

9 h5 Q7 b+ I/ U# _

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

' s) i+ G; c, @1 ]# I" A

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

; ]& U% n+ F. }# E) n6 z$ T6 E5 j

return

c) Z; T; ~# B8 y [

}

" H/ l. ]. \0 p: ]" {- b

使用 OLTP HTTP 进行导出:

. K4 c* F4 R9 e8 j) V0 _

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

0 E+ g& d7 E: N5 u) E3 J. [$ e% L

client := otlptracehttp.NewClient(

3 l- u: H( r1 ^( {

otlptracehttp.WithEndpoint(endpoint),

: X$ Z, e8 q8 l; j7 U7 T

otlptracehttp.WithInsecure(),

/ X; L; n2 d: p& {/ F. H$ y

)

9 @. m! S: ~/ z) a# y

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

( ^* B; g* a$ H- N5 G2 }

if err != nil <{p> return err

2 X$ _- c/ I" b6 x+ K# I7 U

}

% G1 B% m ]- G7 T& {# ^

tp := tracesdk.NewTracerProvider(

5 o2 _* {" ?4 ]1 i& b: y

tracesdk.WithBatcher(exp),

@- F9 N; ?/ _5 j3 V8 z

tracesdk.WithResource(resource.NewWithAttributes(

% w2 g" G: C, S- F r5 c

semconv.SchemaURL,

: j2 e2 X& [. I6 z+ M

semconv.ServiceNameKey.String(serviceName),

' E+ e" h/ x+ `' u

attribute.String("environment", environment),

, U" L( k( w/ M6 J- r

)),

8 I+ X, S9 O( u

)

' _3 r' G; c& P7 f

otel.SetTracerProvider(tp)

3 _( y' [2 }( J @, H

return nil

1 z% _' P' H/ v8 P! {! l4 `" Q, C, g

}

( a* d2 h! s1 E# P6 L: y* M

结构化日志

: w2 O1 A* r4 V# v' G

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

+ P8 l+ s2 o* B& X6 q/ K* Y

cfg := zap.NewProductionConfig()

5 R6 e; j* J# u/ N+ ~

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

$ M5 b& c; u7 \3 I& o$ F% e' e

logger, _ := cfg.Build()

- ]: h) w" ~- X; A5 M l7 K' `

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

) u$ _7 z3 r- Z' @0 |

使用 OTel Collector 进行 metric、trace 收集

+ T$ Y. Z( y9 H2 S2 u. I, o

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

' |# [5 c/ x8 b

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

3 x6 _4 o6 n, G/ p

receivers:

) r) N" D' o2 Z4 D) Z9 j% `1 R

otlp:

" v8 w7 ~ C) X: q0 s8 E- i

protocols:

! F& j& g0 y2 O$ {& k

grpc:

0 ?0 C8 V& t% L

http:

5 u/ N. Z |) e2 L( ^/ K8 j

prometheus:

* Y) B& t# ]1 L) y/ X

config:

7 o% A& o, e" _9 B* W

scrape_configs:

% |: ], X$ L1 X" _% I7 [

- job_name: app

0 I6 G! {* Y. [! |) N9 T# r! w

scrape_interval: 10s

s/ o5 T; k" d! T3 B/ e& @

static_configs:

9 d) l) \2 @! j1 f# X

- targets: [app:8080]

; {8 K+ x$ F' i+ C# Q# |

exporters:

7 y0 m, j" b( S0 i

otlp:

) u/ d& f! M1 i0 [. a

endpoint: tempo:4317

& J/ n$ \0 ~7 Q7 `7 G' h

tls:

0 P7 l* b; M- a- J

insecure: true

; u: I( P! E) c$ j/ a

prometheusremotewrite:

$ z: R* A m- X( `

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

5 v2 C# I! _2 @5 {3 h+ @) f

tls:

, |( R# w+ v8 G6 U% n1 l A6 S

insecure: true

/ t6 O. c' t9 D; I

headers:

% l( |0 _: i$ s8 f$ `7 V

X-Scope-OrgID: demo

+ f2 [7 n; x7 m9 A: ?' ]

processors:

5 r* C3 ^# \. Y

batch:

( ~3 j3 F `0 N8 B/ j8 r! a4 f

service:

* @: @ l, l* N& r) Q( n1 ~

pipelines:

R0 ^* S7 v5 l5 t. x$ v

traces:

* k% X% j; ^0 k' ]5 y$ r3 W

receivers: [otlp]

9 v# R/ H/ r& d) L# ]: R

processors: [batch]

" \8 j9 ^0 ~( ~' ?3 M* A$ Y

exporters: [otlp]

4 o9 q7 x. w1 c2 N; u

metrics:

- }; `: f' r0 r( ]3 q& E" q

receivers: [prometheus]

7 F6 S4 p0 e5 A. p6 c6 o5 H

processors: [batch]

' ?: S( G( t5 n& i6 o

exporters: [prometheusremotewrite]

, ^# I7 W, x" D8 Q2 x4 e

使用 OTel Collector Contrib 进行 log 收集

9 Y) C9 j( F) W% h! T6 ?& ^! u

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

- Q9 R1 U: w3 T( {4 _

receivers:

- f6 N8 h, }6 K: ?4 t* Q

filelog:

$ i5 i6 _/ A; _

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

' j9 @3 u$ W+ m. ?

exporters:

' g' X! d' H2 B, D2 l

loki:

: [ a! W7 I: c

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

. d Q; P) x7 {4 {5 `0 Y

tenant_id: demo

9 m! f. h1 `. }7 ]0 P3 O

labels:

$ ~ X' o" O. D

attributes:

* q1 e/ g+ h: r# i- }& Q# t+ Q

log.file.name: "filename"

8 D5 _- G- |3 S+ D4 v

processors:

8 d0 ]* T! s4 A

batch:

$ Z6 Q; P4 z/ w; L( r

service:

$ L3 X6 o+ j6 C+ |6 g

pipelines:

$ B0 B4 e& Z d8 u" L

logs:

* f J( E0 Y7 Q9 P! R* \* W- P; T

receivers: [filelog]

% ?3 {1 F, n4 Y% x4 p. p

processors: [batch]

. q0 {. Z3 E7 a' E0 [7 T' G

exporters: [loki]

# }' k% w* R- M9 \( E! M; ? g% n

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

9 q8 K! h) p: a) j

总结

$ y' W% m. R/ L0 `% C5 D

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

7 a6 ]! `) |; i) k2 g5 C# v

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

" B' O3 m. S z6 U6 [ ( h# b; g. z, a! r

责任编辑:

* T* X& G6 d* M! Q }7 r ) y. y6 i. u% v & L( P4 J, P/ A/ b1 i6 [ , I9 h# R: g# X! B: X6 ? o * n6 Q6 ]7 ^0 ^9 m% n$ g% h
回复

举报 使用道具

相关帖子

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