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

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

[复制链接]
( p! w$ R8 T' _) x; n

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

) ?0 _0 Y% y- v! S7 c7 U - H- f5 |. I: N& \ W" V

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

/ a- p' |! U, b+ g2 c

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

, O8 u: h' N4 J o) J

通过本文你将了解:

3 {# z0 D9 f9 O: F

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

( |% r- m$ G3 B- O5 V

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

7 Q' H5 C8 X1 u. M3 H

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

: b( }2 C' C) C. ^5 c2 r7 ^+ o

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

* N: e. d: v, V7 u8 o

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

1 W3 T' ^1 A W& q1 \& K

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

T+ n0 Y( B0 u2 k8 l( i

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

L8 n/ |; L7 \: L4 H3 X1 I2 [

下载并体验样例

: L7 E v" h5 O* Y

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

, [6 n. w8 j& e7 ]( s

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

; L. {4 w0 D6 a1 ?

cd prometheus-exemplar

2 K4 i3 E& R, H' U! U

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

# j4 P+ a1 X2 e

docker-compose up -d

2 C' q& c2 [- Z' j

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

. @1 ]' W/ K7 Q, d d6 l" V

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

/ X; S8 n* O& ]9 J* ?3 R( @8 k: P

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

7 ], \4 M! d8 k3 v

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

1 j3 V+ P+ F# Z P( s- v1 @0 J4 t4 s

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

X# u1 M$ c3 |1 H4 h( u2 T# S

整个部署架构如下:

' s$ o8 U, J f; W% c# z& [

# p9 ?+ m! E7 Y% w

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

; R0 b' k4 U5 G v7 T

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

- r. D( R% S! k; a

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

/ C" L* l9 l$ j) a5 @' Q' o) j- M* \0 L! S! @

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

& A7 O& S2 c! M: f8 N

% O* [& z: A; b% c" z

细节说明

: r2 P2 O5 l: f# P, ]

使用 Promethues Go SDK 导出 metrics

7 A, s0 a4 I$ L8 W

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

/ x: n! \! n; N' \# E. x) T3 I9 L

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

* G0 h& L2 ]$ x/ i2 J, K

Help: "Http latency distributions.",

. w* U. w K6 X$ z/ i( j

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

* K8 S; k, B) Z7 v% n; U

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

) e* n E% O4 w; E! l9 s- j% K

prometheus.MustRegister(httpDurationsHistogram)

' l, Y# M; I& ^% s

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

; v6 F9 H& ]! x

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

; O$ o/ ~ u6 p" L8 w4 a9 ]

observer.Observe(elapsed)

% S$ K) i/ v' r$ w3 {

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

0 Z9 B# v2 s* K0 T: q) y

})

8 U* I7 M0 |6 {" L2 V1 M4 z) S. \

}

( y* d+ r7 J8 o8 { R- G! l, ~

}

# s) v z/ C% Z

}

' _* }0 M) ]$ x7 {

使用 OTLP HTTP 导出 traces

) O7 ]2 i/ R9 z) }1 D

使用 OTel SDK 进行 trace 埋点:

8 n5 c6 P) f' P5 C# v, N$ W

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

& D. S8 a" U* j J& k

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

& C. f) `5 w0 s4 o+ a

defer span.End()

/ u7 R8 ]* W' W/ S

// mysql qury random time duration

! j) {3 Y; K/ k' s# Q

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

9 c7 ~$ g! t' G

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

7 H9 E# G) I8 |

return

- ?0 q& }0 ]/ m

}

" Z6 x1 H. p7 N- S- Z [7 r

使用 OLTP HTTP 进行导出:

4 l( C$ m! T1 Z8 z% { @6 W

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

$ |3 X! K: E% A

client := otlptracehttp.NewClient(

$ [$ i8 g/ S2 \

otlptracehttp.WithEndpoint(endpoint),

9 l, u. a) R: b) U8 q$ ~/ U0 J

otlptracehttp.WithInsecure(),

; l. M s2 L) \5 g

)

- _/ C6 q+ r2 j% N( i

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

8 _4 [( |' F/ F) `, `1 f5 A; |

if err != nil <{p> return err

" w c% T! J) X( [& w) }

}

" h" {/ z+ [( @

tp := tracesdk.NewTracerProvider(

; }' n$ L) ?. S) S6 A( w

tracesdk.WithBatcher(exp),

4 T! V5 a8 T" v0 f! q, V, V4 D

tracesdk.WithResource(resource.NewWithAttributes(

' P7 j- t* k& b% o

semconv.SchemaURL,

2 D# E+ g/ [* }( P

semconv.ServiceNameKey.String(serviceName),

! o/ Q4 t+ N) a7 R0 \

attribute.String("environment", environment),

% Q" k; j2 V+ u! S1 z* P

)),

& n. R0 O. ~' c# h8 B9 O' u1 U

)

$ c( f$ S9 Q, x8 k# {

otel.SetTracerProvider(tp)

8 r% k, S, B, E: c7 u' L" q& \

return nil

9 v+ L8 j6 a+ p, r( m, {# j

}

" V T( H) @$ W

结构化日志

# t1 ^) @# f7 b# [* ~! L

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

0 L0 k' \; a% e$ _: t, l1 \& T: v

cfg := zap.NewProductionConfig()

9 h4 D6 A$ \# z: `( y* r; O9 }

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

' I. ~% C# k; G+ E/ _# u' Z7 {

logger, _ := cfg.Build()

, ]. O# R+ W6 W0 {

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

, t. ~% e# d! \9 R. N) S2 p0 t

使用 OTel Collector 进行 metric、trace 收集

% v: {4 r- R: @5 Y0 w

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

: T* p, f; k$ a6 J: e" S2 }+ I

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

1 E Z1 |) B+ h; `: _. T

receivers:

o- Q7 J. b" r' T4 P

otlp:

4 Y6 w- [$ E( y2 Y! V% Z

protocols:

$ X1 S7 \+ D9 v, N9 R

grpc:

4 p: H; e- O* H+ Z

http:

& i0 L- B/ l. m4 C

prometheus:

' `. p3 ^2 o- B4 I

config:

4 f4 {& z$ o0 w( F6 K7 t

scrape_configs:

# j% L$ g# M r" {" q

- job_name: app

8 m, S+ T' O2 \+ L; X. a7 u( B

scrape_interval: 10s

2 w. z4 t7 G. w8 [/ U4 f0 B

static_configs:

. C, b8 Z3 d; @

- targets: [app:8080]

& v. l( x/ m& L+ p& n! O2 Y" j

exporters:

% }. [" d7 \; ~2 f. E

otlp:

9 l( j! A/ k, q% W# B' M+ R

endpoint: tempo:4317

0 k( t$ S7 z! c0 o

tls:

) C' p8 l. A z+ X1 L

insecure: true

# x$ i- i- n. ?2 e4 o

prometheusremotewrite:

( z) q, w0 z, P& _/ T& i/ I

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

' o# O2 o5 p1 n2 n3 I6 F' q

tls:

4 N5 v% o% t( r

insecure: true

. ~7 ^. [! i6 X: j9 S

headers:

. o1 t* t0 b% n1 N' v* t

X-Scope-OrgID: demo

+ U/ x; V) J" p+ U

processors:

& [3 [0 T7 N' V

batch:

5 L2 p* ~9 i# m

service:

$ b/ b5 ~" n5 a5 i4 e) U+ \) ?

pipelines:

( S) z5 Z" `( E5 T4 m# g7 R

traces:

0 {: a% N/ x0 v5 W. ]

receivers: [otlp]

+ p+ S) @3 I$ {* x/ ]( I

processors: [batch]

# n" C' x) l. }7 n* j

exporters: [otlp]

+ l8 {# b; ?1 U+ r; j$ r" Y

metrics:

# D: L# k6 p) I8 ^$ Y1 L

receivers: [prometheus]

% L W: V$ P: J: Z

processors: [batch]

& O1 r" K" c+ R- N* v

exporters: [prometheusremotewrite]

) o7 W+ Q8 ?: s1 n( l

使用 OTel Collector Contrib 进行 log 收集

; Q7 o/ A% @+ | t! |( c

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

3 m# v1 [8 O1 P0 }! {- I

receivers:

) X3 e, X+ N! R: A D" j

filelog:

7 |7 v6 ~5 }, O# s' v6 n, \

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

' H7 m9 {% Y; }

exporters:

$ e1 q: [' b, t

loki:

$ F' a! D/ e+ m9 b; [

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

9 C' f8 l) U% f

tenant_id: demo

: m) p: z8 ]; S5 Q- Y+ ~

labels:

: m3 [1 n: O3 S

attributes:

! k% L7 F/ f6 B1 I# L; m

log.file.name: "filename"

% E! J( a! [+ l8 J* c t

processors:

2 N4 m9 }3 S: p

batch:

; E- G' Q+ b8 Q# e7 u" \( L: j

service:

$ M/ h" y6 q5 ^' l$ b: ~! D* c( k

pipelines:

( S X* c+ t( `

logs:

& G5 k. e8 X# C

receivers: [filelog]

) a) M3 l h* J y8 o$ Q$ _

processors: [batch]

+ W; G) j3 q- u' I

exporters: [loki]

* i O; _' U8 f8 t

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

1 |/ K) ^/ ~9 ]3 q! T& N; `

总结

* R5 l5 c6 A, U) E' _

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

: @" k% r- E! O: s

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

+ L2 O/ a. \1 Z% R - ]' P; i9 g. m' x( V* {1 k3 j

责任编辑:

T& r a" X8 ]+ X & W% w) d1 P3 V3 I$ D ; N$ G, a' ^; \5 L 1 ^' N$ o1 C7 \ 2 F, J" ^% ~9 h3 ^5 o( \# V; V& C4 I
回复

举报 使用道具

相关帖子

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