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

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

[复制链接]
) Q2 P/ Y( z& O: h/ Q8 I

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

# J% \3 F# }% G: k8 y5 l3 K* G9 R9 J- W! s$ e. S2 l) F

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

) Q5 W @8 }" A3 ~; ?$ u

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

9 r0 i9 {3 W, Y( k) h

通过本文你将了解:

* \, F2 S; C6 W6 m: z1 z2 x

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

" F5 P, ~, m0 W3 N1 d% p/ }

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

" k: H* ?6 `- m

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

t% m9 H1 I9 U' ~- \6 ]) o

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

5 b# h( p' M" c9 E% i

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

$ E- G3 B- ^2 Q0 u# U

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

0 ?6 q% J, s% F+ Y/ V+ N

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

; X- u, \5 J' g$ y- l5 ?" x

下载并体验样例

) e5 ]5 L, i P' u% V$ P5 W+ l

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

; L* x$ Q' J$ P% t6 t( w

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

' b+ C% b9 r6 d* k( Z7 V e

cd prometheus-exemplar

; N2 {# S8 f! O4 M- Q: d

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

% T; ^; \$ o/ F8 W

docker-compose up -d

8 T% C; Z* h8 [' F1 n0 Q5 K: s! O

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

5 _( v9 F' m- T1 j2 _) j6 |

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

/ w/ H% J4 @ P5 X1 o6 \7 E

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

, o/ \: t+ k" u7 s D5 ?

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

/ Y0 |! x& w5 |

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

' k9 ]2 d. V6 g% |1 H0 p5 f. D

整个部署架构如下:

. X& I# n: q. a

4 S, [* y: Y2 f8 [% O

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

! I6 t; a, M( o$ Y$ B1 i! G% C0 x

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

4 i/ _9 m4 U$ w* J5 z) z

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

1 e6 h7 o. t. q: V' B! c% b

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

" D7 ~/ Q9 m1 k j

2 r6 u0 O) ?% V8 |

细节说明

. y. z8 a: X5 a, [6 p5 r

使用 Promethues Go SDK 导出 metrics

# |* l( y5 s' \' ^

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

& a4 n' V4 l" Y+ x

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

) \7 ?9 n3 O: {+ K& x7 {0 b

Help: "Http latency distributions.",

; T7 Y0 x1 I& {! i6 F

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

& @2 N. f5 K3 _# r% _4 t4 b

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

/ ^3 X1 W$ f8 ~" Q" h- A/ A

prometheus.MustRegister(httpDurationsHistogram)

+ L. {4 B* s+ |7 r2 `7 C& H( ?# f# W

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

' I; {2 v: T3 j) s& ?& @9 P0 S

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

3 i+ \$ @2 \/ Y: \# W5 K% L

observer.Observe(elapsed)

5 t: S' K/ u8 ^0 E* Y- U

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

! ~; V# \% g$ F5 \; V6 G ^3 Z6 v

})

' E# P4 b& t! l

}

7 X% }( y! f e+ e1 I6 a5 n

}

9 X H8 j% S0 F, T2 |% H$ N) f

}

/ [& s- s# O& v/ Z2 ^6 P. v5 w

使用 OTLP HTTP 导出 traces

( G( h. K2 Q! X5 @/ I# t# i$ b

使用 OTel SDK 进行 trace 埋点:

0 I$ b6 ?7 z B+ M q

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

/ n" i: D! l J+ |3 Z1 U" \

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

* h- @1 y* O8 {

defer span.End()

0 w. B$ ~( L! N

// mysql qury random time duration

- A8 D6 P" j+ _, s

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

+ l8 d4 l4 b, f; U, f+ O% k1 m5 H7 }

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

7 U* s( V; q% C+ s+ {3 I S, y

return

4 W3 y, ]* G) W& ~* j- g* O: y

}

: U9 n6 B& r* ~. M

使用 OLTP HTTP 进行导出:

, M/ j7 N6 X A

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

' a; T' ^3 m1 @: ]: s

client := otlptracehttp.NewClient(

2 T) S$ I) f7 |+ {0 b& ^1 r

otlptracehttp.WithEndpoint(endpoint),

% y4 v& `- T3 b

otlptracehttp.WithInsecure(),

) |/ [) {. S( i: A

)

" d. F4 i4 T: T4 d* ]5 x

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

9 `1 [2 u c- k0 P) @3 g

if err != nil <{p> return err

3 |. O8 C( u/ {' {1 i

}

2 |( J/ f; g7 V; ^" g' R y7 G

tp := tracesdk.NewTracerProvider(

0 C8 G- V1 z+ E+ W

tracesdk.WithBatcher(exp),

7 O( @- f' W- ]) Z

tracesdk.WithResource(resource.NewWithAttributes(

+ B- S+ K7 I0 w+ M% V& o" i

semconv.SchemaURL,

; e& m: [4 E# d$ V' b

semconv.ServiceNameKey.String(serviceName),

6 m% @% N& }8 r: S, X

attribute.String("environment", environment),

# t' S8 x1 e5 X2 K3 ]

)),

- Z; M% L6 K7 S

)

; L! O0 ?5 k" t: @0 e

otel.SetTracerProvider(tp)

9 Y2 y! Y+ v2 }+ F! O

return nil

; J; V$ p9 p5 n

}

* n/ }! @# }( t/ F1 r* U& x- v

结构化日志

) C9 [" w: U4 [5 `5 c3 G: D; B4 a

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

" l* Q* F+ p$ c9 X

cfg := zap.NewProductionConfig()

2 Z+ i7 f& W' v% _& U R& H% F

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

$ }1 A' a: q4 t) A+ O$ K) Q

logger, _ := cfg.Build()

' C0 Z1 o; k4 V1 h

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

7 W; \9 q# C# [) f

使用 OTel Collector 进行 metric、trace 收集

( p& ?6 v( [* X0 F h0 g

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

2 p, K% \3 \ \! c- r5 r. Z

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

3 V2 g' t. w9 X \6 e1 V( T

receivers:

1 K2 ^" Z- e4 `9 U

otlp:

; t9 ?7 D) I V6 b! p/ r

protocols:

2 ?$ @8 q* M! } C$ r& S

grpc:

% \0 j8 Y- d, q5 N; {

http:

" ~9 a* t, ~- b1 J( \+ X* V- }& R

prometheus:

8 `+ C5 B7 k7 {* s g* y+ j( k

config:

) Q3 q% n& Q5 B

scrape_configs:

; f: h) O p/ _( P0 |5 j

- job_name: app

" Y8 V% O4 Z+ B4 ?2 N1 x0 P

scrape_interval: 10s

3 Q+ ~' ~2 E8 \+ a- j! f

static_configs:

7 {6 Y) l, E9 f. j* o

- targets: [app:8080]

9 U$ Z8 W. B* d, P2 }

exporters:

# S5 U3 x3 f6 {( G7 d8 F7 b |

otlp:

5 s' b" ^6 N1 w7 y. ^$ ^+ h

endpoint: tempo:4317

. P3 C0 L1 F b2 s

tls:

2 R5 ^ H; c% T, ?& [( h+ A

insecure: true

2 x! Z# k; ~% {7 H0 F! m

prometheusremotewrite:

7 w2 T7 A" R3 d+ S0 ?" v

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

2 m1 h9 X( Y/ I

tls:

6 |* \' E2 q2 L. ]% r- t4 ?

insecure: true

, w% R4 ?+ I: s4 y! t& r5 V2 t

headers:

+ S0 M+ }6 \1 N. ]0 h

X-Scope-OrgID: demo

. d4 t& {6 F- k8 B* `8 W: a) t& Y( D

processors:

0 W; f' m2 k( D. K/ R% }+ K' S

batch:

% c' T9 }4 J) _/ M) Q2 u

service:

# B1 q; ~- b3 X9 @

pipelines:

- f1 {" N4 N7 Q6 n6 T& s2 j

traces:

7 L5 n8 [. N' u

receivers: [otlp]

1 E* v* a3 E4 R: | z

processors: [batch]

+ H8 C3 F! s( u, w5 W$ z8 \( L: J

exporters: [otlp]

* }* E9 I( N x; P% a6 c

metrics:

/ I( d5 m8 O# s; m

receivers: [prometheus]

O' Q& ^5 E1 U: e1 O

processors: [batch]

0 n7 x! I4 G% t; W( _$ c! A

exporters: [prometheusremotewrite]

5 F- ~) {" S5 h* p2 U7 u

使用 OTel Collector Contrib 进行 log 收集

7 |: y# O1 t" Y! p8 {. ~. o1 m

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

9 H$ M5 L+ w+ U8 n9 R+ a& W

receivers:

3 [/ p# N. \2 T/ s" Y

filelog:

2 H7 z& T$ r$ y, l' v

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

7 V# [3 }! i7 r' f7 E, \* ? v

exporters:

+ V! T% E5 U" F0 u' Q1 }! Z

loki:

, J% B& u9 p' n4 I% _7 q

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

3 o4 N" b& t3 t" y" |$ O

tenant_id: demo

4 k, H- B, e! u( O0 T' n

labels:

/ h& C; r9 \( r/ w+ s, r$ Y

attributes:

+ [' P3 J; a4 j" h" L* f6 J

log.file.name: "filename"

E1 G6 x* h1 k' z

processors:

- W* n( Y" e7 m. o( P- q

batch:

* l5 A! ?( [$ h& f! V' U! `

service:

' n X ~2 w* t4 g% r. E

pipelines:

& H2 b0 h5 _ I! z

logs:

4 X6 Q. P6 [3 H. n: p% n

receivers: [filelog]

/ Q2 G) J: c4 U/ ^3 Q

processors: [batch]

( p* |' M& o$ {6 G

exporters: [loki]

4 x3 @- ], e& \$ B

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

1 [: U% t5 r; x$ s; a

总结

) Q, n6 z8 `) L

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

* V/ n" P+ n7 M- D& h* g1 e

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

' _# ], u$ p/ ` 8 k' i. T1 {+ ]% O6 S" M+ q- u

责任编辑:

/ @2 V( @# G3 f/ K3 q* t B# ?2 V# I+ K! ?5 c & i2 M1 c3 S+ F. U 2 L, c. a. B3 O3 J8 n8 ^: M( E) p( n) j4 v1 }0 |% Q- r
回复

举报 使用道具

相关帖子

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