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

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

[复制链接]
3 r4 D& E* P/ f0 b

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

0 P2 j$ @ Y5 z2 t/ k$ e; C6 J9 v ^$ N& }

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

/ n: N7 f2 x' E

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

3 H9 l7 v4 z1 q. u

通过本文你将了解:

& g# Y: O+ s2 M/ {

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

9 M/ ?! J) K* c

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

* |/ p0 k0 }/ e5 d# x; c: d

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

5 S: A% c7 W2 @' D

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

/ q: E0 D4 b# y0 V( x! v

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

- Z3 `( G V, M1 u" V& h

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

/ Z. ]2 c, Q+ `

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

- \6 ]: I# x( L0 f1 A6 {+ E9 P

下载并体验样例

8 w! k' d% w- w

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

$ f& P' ~+ O% E' p! m, e: I7 z4 m

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

4 g: z1 Z$ M9 f$ u, P4 o

cd prometheus-exemplar

" x( L8 o' ?! z: f

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

$ b1 @4 I5 i% L3 L* U& e

docker-compose up -d

+ Z( E- f6 ` |' {! z7 N. g

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

2 m6 Q+ [. m* w" g' @; D2 e) a

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

4 f( b' |, Y9 Q) m

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

* H! h- Y# I8 {6 P

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

4 V- p( g9 ~; e4 |9 w% l; V

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

$ J& C1 |* g* o6 w6 u2 @* c: Y

整个部署架构如下:

H; i( D& r2 L( [: J# S

2 v9 ?( h* V5 x. e x5 V1 l: c. I

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

, |2 H; f: |3 d. Q& y% U

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

/ b* B: v- r) j0 u

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

$ | }: T2 G& r( c, P

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

/ E' f9 K" G% e: Z7 H6 x

" n4 h/ p2 O7 S3 A, z

细节说明

X; c. w1 {) n+ ~0 N/ V

使用 Promethues Go SDK 导出 metrics

, {. U# C. N. X0 h) j

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

, {; ^* o3 Q0 N7 M$ M

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

; |, z" O) ]" d( c

Help: "Http latency distributions.",

6 ?, }: Y1 e+ n

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

+ ^' [8 }9 b \. c4 ?: V1 X1 i

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

% [; i; H( V' |, \* p( G/ R

prometheus.MustRegister(httpDurationsHistogram)

' ?' O& n' R! Y& I. g

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

& j- P2 ^1 }2 e j

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

+ V9 [: ]+ _4 X6 d9 m+ X

observer.Observe(elapsed)

; {) ^) m) F- n- c/ v. O/ B

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

% w0 X, w& D" Y- s0 j

})

& \8 n& \$ `! q" }

}

! d+ c3 A: H! G: Y2 R, W& m

}

! {" o$ Y5 K1 \' `# \4 C" w; z( ~8 _1 ]

}

8 W) P3 G8 o3 a9 j8 H- y* Y. K, M4 N

使用 OTLP HTTP 导出 traces

6 H+ @( d) k5 F% I+ g; n, p3 `

使用 OTel SDK 进行 trace 埋点:

7 D. w! U8 d- V+ c, k2 y$ k# w

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

' z& v1 y" [2 Y# ~/ N

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

7 F, O+ q* W1 x- D0 I

defer span.End()

7 a5 X1 l& |/ J6 M) E" m0 T

// mysql qury random time duration

N* [- W' q' ?+ u9 c

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

5 _# S, @ h X+ J

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

% R$ l7 b. d) L7 h

return

7 d# w8 W2 H; O' J! m) X* _

}

0 r) q9 U1 T1 G2 G+ J" b

使用 OLTP HTTP 进行导出:

8 Y; z1 y7 ] V' I. U {

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

- ?4 ?" n- d1 j7 _% N' p, g3 v

client := otlptracehttp.NewClient(

0 N7 T9 a1 a9 r$ D% [! ~

otlptracehttp.WithEndpoint(endpoint),

$ O0 L: b7 k6 K1 k

otlptracehttp.WithInsecure(),

9 o# ~% S1 Z5 O, O4 I

)

1 g7 i3 N5 N! W' N5 e5 B0 @, f; E3 k

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

% _, P D" n( r6 U

if err != nil <{p> return err

* m7 `7 |: P' O

}

6 [8 n7 [2 Z6 G8 ^0 [

tp := tracesdk.NewTracerProvider(

D3 l( h/ W" O4 e* ^* K

tracesdk.WithBatcher(exp),

; M3 { W) R$ l1 X# P1 X( J0 N

tracesdk.WithResource(resource.NewWithAttributes(

1 u$ V! x; T7 r/ o

semconv.SchemaURL,

& m% p) F/ O: [8 y. t: t, b

semconv.ServiceNameKey.String(serviceName),

- w- U" Y) `1 F) q% B' @ {0 T

attribute.String("environment", environment),

+ g6 N8 l) ^) ? Q& p- i

)),

) z: Z4 ]2 t1 f

)

% s# O, N4 g+ C7 e

otel.SetTracerProvider(tp)

& u6 U$ u; s5 C5 a2 y4 V

return nil

7 O3 N6 X U0 x6 n

}

" D/ H/ k0 M; q. D) s

结构化日志

8 E# H/ F8 l x6 c- j" g) d6 h& f

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

! r5 D f7 b- E$ l* [

cfg := zap.NewProductionConfig()

3 C9 U8 M( Y/ C( d/ v5 y G

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

, V. q) T1 {7 |. k

logger, _ := cfg.Build()

1 i' l- w/ c0 c; U. z

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

6 \0 {2 c& N- ~/ {2 `/ w

使用 OTel Collector 进行 metric、trace 收集

8 u u: D# Z: s# z% X7 n

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

# z c/ Q4 ^1 a0 i7 a: U) |* S

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

$ [; n" E& V( C

receivers:

( V; r: O @, d% X/ U0 d

otlp:

6 ` c/ n- |1 ? }

protocols:

) ^7 Z: u% Y) E4 }$ t$ `

grpc:

- m% c( x: s2 }' G+ e) |/ C* J; R

http:

- n' h* P" l2 k* o

prometheus:

- B8 f" H9 ~/ ^# {. S- g! f

config:

( J; z4 o) c& n. B A a; u

scrape_configs:

+ h, O3 b- p4 s* `

- job_name: app

" g3 @2 F0 p. T7 r6 [$ x

scrape_interval: 10s

2 s5 @9 f* `& l& F1 v3 ~* [" A4 N1 ^

static_configs:

( V3 h9 s# y/ l' i( k2 [7 I

- targets: [app:8080]

- S9 b N, n) M0 G8 s% Y

exporters:

! q, a' @1 i: U# x

otlp:

: |: M0 b2 r9 C7 h2 X

endpoint: tempo:4317

: T, S% y6 Q& H& d* c" V- @3 L

tls:

2 f3 f3 B& w, ]2 r i

insecure: true

8 L/ ^& i# r2 {" g3 o. h2 b

prometheusremotewrite:

. u& f& h# [/ g: G K$ z, q, d4 z! S

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

( n% F, [' l! K/ j2 H0 `' O5 n3 q- j% s7 g

tls:

: K( m, j6 A$ A& X

insecure: true

5 @ F, W) U+ y }' E" n. P B( M2 R

headers:

- s5 B8 n6 w" b

X-Scope-OrgID: demo

, S! ]% z3 V- t% i

processors:

( C2 `9 o1 w/ y$ N9 z" h! _; O

batch:

3 j& H: P) J5 E* Q( a( c7 c

service:

, y3 C. E, ^1 ?2 Z7 K" \1 i- K

pipelines:

5 O+ p& q. F* @" y0 _

traces:

L2 D+ Y6 j7 `

receivers: [otlp]

( M# d0 R1 o! @- k+ d6 R7 p

processors: [batch]

* r; g; ?/ [% _

exporters: [otlp]

0 N" ]" O$ x0 d" g' G3 _" m

metrics:

) [4 Y* q) m( a- N

receivers: [prometheus]

+ R# M7 l. S1 p. A9 a# i

processors: [batch]

3 k. x! r8 t% C; j+ k$ y

exporters: [prometheusremotewrite]

) q3 M9 z9 ^; f" I! N

使用 OTel Collector Contrib 进行 log 收集

5 d8 g# D' s. f! V' s

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

/ k( K: ~% _0 M7 L+ f

receivers:

( N$ U6 w) a( B0 c# g5 u* \

filelog:

' ~& M2 O! E& j- s# @

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

4 K u/ G- R0 `1 W' j

exporters:

- c5 N6 T3 e5 g: \! p. {

loki:

1 R$ c, [- h3 Q! \1 K6 j

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

& w1 t+ k& k) R' [

tenant_id: demo

( p" \; N" k3 ]6 ^4 a/ e: C. V

labels:

6 o2 n5 }$ W+ o, [0 e7 |0 ?' h

attributes:

7 O3 ~, |! E4 M" T0 c2 z& E

log.file.name: "filename"

$ D. I1 Q w% W+ ?1 M1 E+ U

processors:

' [: G9 x- e) a8 I+ R. ^7 J* y: W. e

batch:

$ n6 p$ Z+ {$ k8 N) K; o

service:

, V# o' `) F( T4 P5 l. i* C) s

pipelines:

# q; O/ i$ Z( g' ~ z

logs:

+ A' b$ Y7 w) B" U+ H

receivers: [filelog]

& c0 j% ?3 q5 P1 j) a

processors: [batch]

' J5 U, N6 b7 H4 I, f

exporters: [loki]

3 h+ Y5 q- Z8 r7 H; M

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

* n. R! Y: ~' ]& L

总结

2 o K0 c( h% B5 t4 u

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

4 G; J6 y" q8 J) p$ q4 v

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

4 r8 Y/ P w7 D2 \; q' U : g/ Z5 V |. `( \

责任编辑:

2 {: c+ k* R6 w4 @3 G' f ' l! s8 l8 h3 \# M0 { ) G" b( V8 _6 o! J1 z8 g; `+ B/ a" Z6 k 2 k) A+ l/ `/ Q3 o9 K8 }
回复

举报 使用道具

相关帖子

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