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

[复制链接]
/ m# a/ g9 B6 N. p7 O# R0 y/ h

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

5 k* O0 P. X$ C ' f# L9 o( n/ N; X+ H

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

5 I# O* y$ g1 ^$ I7 _0 q5 [0 G

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

* x7 E) H# _& ]3 w: B

通过本文你将了解:

3 t5 _5 P, v; E4 f

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

4 }; |7 w9 M3 v" z; f! W5 E; \

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

8 D1 H5 r6 {- [, Z$ v4 s

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

5 X* f4 x, z8 P; @; J" L

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

9 J( p+ j) W8 k+ p+ a$ i6 h; |

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

( H/ G. L6 J3 ~

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

r3 D# Z, a" a7 e

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

& e# ]0 d/ j, d: w/ i

下载并体验样例

3 ^, ?& ^2 V( @$ F/ z7 v

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

- t8 Y5 `; V6 b h! ?

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

! ?: f- k9 v6 @; B& W

cd prometheus-exemplar

- ?6 y8 O5 v: O

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

/ @9 ?3 [4 ^& D, H

docker-compose up -d

+ X9 w/ T9 q/ R- X4 Y' e

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

/ B' r* `0 N. h' W& ?

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

( I1 }# J4 X/ `& S. R

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

$ J) x/ `2 I& F+ z; W

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

6 ~+ F9 \# C+ L6 A; y

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

* W- Z" Q) L u* I3 K- P% r+ B

整个部署架构如下:

- s) W9 t+ _3 U( Q- d6 _+ ^

1 O6 L& k, m; i

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

. q7 W! w& g v" j7 q. f

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

) a8 U/ K* Z0 D( s5 d

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

2 S" Y5 ^' K: N; m

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

2 L" q0 k+ X, W/ [- J6 N

2 x. r( e; @& d/ z0 r3 u+ E

细节说明

3 S8 }/ t4 [) I# f

使用 Promethues Go SDK 导出 metrics

) O9 F4 G8 N% W: ^/ a' D" H6 h

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

$ w& X. t) X. L9 M9 u! T/ ?

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

; z; ~5 N" i. b7 Y" j) C) _

Help: "Http latency distributions.",

% d, ?: X5 b- t0 s- I% z8 [

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

- X- v1 ] e! C

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

8 s' c$ f) _3 b0 w/ D; F

prometheus.MustRegister(httpDurationsHistogram)

2 D3 @" y. `- u3 ~

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

( y1 X0 o$ b, x' I

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

. P) j5 F" \ ?; m' Z |

observer.Observe(elapsed)

( i2 S) T4 W( p1 I0 V/ P' \* X) o

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

4 d8 e5 Q5 c A+ a5 E/ ?

})

1 ~& a) H- Q0 `- D( V# A

}

+ Z1 }, n& x6 J8 Q% [/ ?$ n

}

0 h1 A1 b" j `7 `. j

}

. x3 ?5 `1 G0 @; a* U, G

使用 OTLP HTTP 导出 traces

. O! `0 o- t0 Q2 @' N& O* r

使用 OTel SDK 进行 trace 埋点:

1 V, Q+ k8 W0 ^# A

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

9 v5 [# d1 t' s8 X3 K: `$ L

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

( T/ {) P. G5 T* W( s

defer span.End()

& u8 J) I: C' B, U; J) ?! Q

// mysql qury random time duration

1 A- g7 U0 D0 q+ x# n: g4 V5 F& q

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

3 t5 M+ l* x: t) Q) G9 t. F3 A

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

9 q3 U7 P/ J4 V9 ~# x, ?

return

% q* i" [ I* a* P3 S) t

}

! Q! e) U5 H. ]* G2 n

使用 OLTP HTTP 进行导出:

' W }" U" B$ {$ y

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

! a$ \5 K5 I6 E5 b

client := otlptracehttp.NewClient(

/ }, @1 G5 ^& v$ n7 H/ [

otlptracehttp.WithEndpoint(endpoint),

- J& j( b @% |8 ]( t5 H

otlptracehttp.WithInsecure(),

; h( L& ^- i2 K" |2 g

)

6 B1 `- ^; p* }+ a* V6 _

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

9 ~5 \& ]& c$ W1 C, h" w9 l' c

if err != nil <{p> return err

3 m9 X3 L0 o+ w' B1 o( V c3 c7 [

}

, @4 P0 q& A. K0 `$ f

tp := tracesdk.NewTracerProvider(

9 f- w- j& \1 m$ P' H# a

tracesdk.WithBatcher(exp),

" g2 [$ C# k5 {+ ^- F, N+ d4 F3 Q

tracesdk.WithResource(resource.NewWithAttributes(

, P& v) {/ y) Q4 i- I+ i

semconv.SchemaURL,

5 l2 P. u# M& }/ q! t4 E' s+ j+ _- c

semconv.ServiceNameKey.String(serviceName),

5 w* O) H( l$ _0 {; i6 q

attribute.String("environment", environment),

4 z- |0 `/ V0 p

)),

5 W1 I- U" p" n

)

8 K" c# b1 x" }# }7 E( w2 X! V; A

otel.SetTracerProvider(tp)

, O4 b: b- d8 ]8 d

return nil

; ^6 q( p9 c2 W, q( V

}

* N, C- u* a& z3 }' O! Y5 D5 z

结构化日志

" N) R6 G6 |3 T1 S) p4 A

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

: K$ X) q. k1 Z: O3 P5 s5 A

cfg := zap.NewProductionConfig()

2 d# i1 \2 W+ z6 h; v+ k

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

8 R3 J- t& N2 h

logger, _ := cfg.Build()

) b4 T( N! l* a

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

! u0 \ X2 R" `: e" S2 l3 E

使用 OTel Collector 进行 metric、trace 收集

) j# B2 C/ J8 b1 ?

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

; @/ e2 n4 a5 \7 O: r/ O

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

6 M0 q `7 H/ {

receivers:

9 D3 {# S+ {1 K5 d( y! P

otlp:

+ N0 q' t9 @: j) z

protocols:

3 ^0 Q2 J. B$ s3 [9 l' y

grpc:

: |& j Q1 T8 Y* j6 \9 S

http:

9 p' p0 k4 O5 T- a; t' t% h

prometheus:

2 P1 ^3 ?5 |; V1 s8 |

config:

: E# q( _2 B' ]/ M: n$ k; |5 t" x

scrape_configs:

0 V# V" Q6 a. a: u. B, l6 \

- job_name: app

* V1 P' i: D3 E6 K/ P% B8 r1 W

scrape_interval: 10s

; p% y+ k) Z) b4 @3 Q

static_configs:

: [* [" c' G; |5 B8 G3 F) z

- targets: [app:8080]

z7 ]; l$ V0 B) Q' m1 Y- f

exporters:

# m: u2 l/ z1 ]4 _0 n; }5 F: S

otlp:

0 r; f4 \' N' o

endpoint: tempo:4317

" F' U8 W8 [) \$ s/ R/ E x

tls:

, U; E! g' A" Z# x0 o, {9 D

insecure: true

@- Q1 ?7 Q# R! W% r; R0 m) G8 }, a

prometheusremotewrite:

( U- F% s& r9 X9 l+ A" f8 ]

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

\6 w6 j- J5 n

tls:

: }- w% C$ D- M/ O1 V

insecure: true

7 c: b, F* p2 W4 z

headers:

! `! K/ `% j# Y! i

X-Scope-OrgID: demo

9 D" ^ a: v' d- Q% F; H

processors:

- r, s" `0 e6 g0 |2 d* o3 N1 Y

batch:

/ s( X* h4 D8 w3 G1 c

service:

% E( A. @# [9 M* l: W# i

pipelines:

& _4 F9 S6 t# k' I: C' i/ ]1 r# }$ C

traces:

3 h+ j2 g' o8 U9 a% {/ B; R

receivers: [otlp]

1 \7 x+ E; C" I* A$ d% P" v# T

processors: [batch]

( o9 n+ j: m0 v

exporters: [otlp]

" r, G% Z; h: V! Y# R3 ^

metrics:

' e8 L0 ^& H- W q0 {$ X2 a

receivers: [prometheus]

* W- y% E6 W% D" w+ s. K' x

processors: [batch]

; x- B2 Z! N: q/ E, d: n

exporters: [prometheusremotewrite]

6 W/ u( \/ f# l" I9 U7 X3 @% P7 W

使用 OTel Collector Contrib 进行 log 收集

! H' v) F9 X6 ]

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

* Z1 o9 f1 K* Q9 ^

receivers:

) u- x, I' I7 H& `- K

filelog:

: C3 A7 v5 R E

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

8 N+ V. z0 X k0 u- f, L

exporters:

' @+ M$ T2 N1 l- _/ J% e, K& h

loki:

$ t8 w/ h, y" F. d0 A9 O. _% z7 O7 C

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

* N* ]5 S3 P# G; _! v) d, X

tenant_id: demo

H$ v) A5 e1 c1 p. Q+ B

labels:

5 T, ~: E' }) i* Y' l

attributes:

5 \! ?. I4 v) [/ V

log.file.name: "filename"

* l$ Q) ]# R" A$ ^7 U

processors:

- V8 |5 h S, K7 j1 h/ Y! k8 u) x8 i

batch:

* j' v9 S: R6 P3 ]$ H% f: }, T

service:

; ?- x% W" x4 }4 d; v6 N: j( y

pipelines:

3 S8 M9 U$ |$ I% \

logs:

# M8 P* ^, H. J5 d$ ^) f& _

receivers: [filelog]

! ^1 @. h( d+ ? x; e

processors: [batch]

1 _1 Y4 p2 J, Y, {2 n# }7 K

exporters: [loki]

5 L1 V6 I& o% s4 D x$ s

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

) E5 q. O5 c" C% G% L! e

总结

) L/ O: H) X+ P( y; D4 B8 k% { Y

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

; S8 f# }5 w! R# ]

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

6 K' U% T) f: X . o" U5 {" r( ?! @+ H" T

责任编辑:

+ R0 ~$ g& b+ L4 J8 i' \1 X3 W' H; ~/ p # i K) _8 a/ [( y0 ~3 c ) ~& {& v4 w) x0 s3 Q; r, \( I$ _0 f4 _5 K
回复

举报 使用道具

相关帖子

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