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

[复制链接]
% m) [2 ~% p- M6 i; ]9 ^( h/ F

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

" y# Z) L1 n0 d( R! p0 Q+ j/ k 3 I+ h3 x7 e- ]4 O( g5 z

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

6 @- u" z# x& P4 f- P# {

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

0 z z9 Z/ K1 x3 }& o) ]

通过本文你将了解:

# j5 b% d6 l3 u, m! e

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

% @) R2 Z1 d1 h" |! a

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

+ K, W8 ^# K) U" s6 t

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

+ Q9 c2 P" P6 n- {( `# t

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

, c, F) ?3 b$ \9 w) {

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

9 \" \4 t) n9 b `0 K5 k' ^- n

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

6 |% B) H, \% x7 H. h, D- \! l

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

9 i9 U6 f& S! x a

下载并体验样例

: i1 ~/ F& e0 ~. ?# b

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

# k6 L* ?9 ^, ~5 _3 i- p i

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

; m' b+ e; Y$ f# x# ~, u. k3 O

cd prometheus-exemplar

! O) a/ ]# S) B, F

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

1 Y7 g. c2 ]) R% @" A

docker-compose up -d

8 h1 X0 H k( k/ T

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

8 T7 i: S+ [# C; }8 U1 r

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

5 ?, Q$ }* }# U

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

5 U) C9 y" j2 M

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

; m; ?! |4 w9 |" L7 x) w

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

4 R4 @9 S. M" ^# P

整个部署架构如下:

# B5 ^& B" T3 p2 [0 t

- ^/ Z5 p- d) ?# i/ e

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

0 q5 M* d$ ]0 h; }

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

' U" X" ~! C5 M) q

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

0 H9 X# u, H% g6 }8 h3 g& d

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

6 J, d) _6 h: x+ W' Y6 b

* f0 |3 q% B" w

细节说明

8 W5 L. T: M2 g; {9 b' ]

使用 Promethues Go SDK 导出 metrics

8 z: h G* ?& L" b

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

* m" ]2 {& j8 K3 S0 v

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

7 Y6 C1 C& v& F

Help: "Http latency distributions.",

4 s) j5 \7 Y% U% y& ?. d9 v

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

" [& c( S5 e! l

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

4 E& _8 j0 \. k0 t/ L

prometheus.MustRegister(httpDurationsHistogram)

+ G/ l; z# {7 s5 s% z% ]6 i

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

) \/ b% q l/ q' ^" U4 J

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

1 S+ s7 g$ }. ^9 |

observer.Observe(elapsed)

+ F; W+ ~1 r. e

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

7 s1 ]. Y; C7 g- d! d6 J

})

' w# _% ?9 O3 @' z% v1 u7 D

}

, U2 r3 q# f% Z" I* e$ f" e

}

5 Y8 t) c1 X c, k) i7 h7 F! u9 l- t

}

1 \! x! n4 c& E9 ?/ M- t5 Y

使用 OTLP HTTP 导出 traces

8 u( p L! }) ?4 p: }# l/ [

使用 OTel SDK 进行 trace 埋点:

& U" y& a, P0 |& s

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

0 q" Q$ D9 L8 F/ k% A& C% H |) b

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

$ q% J, Y9 S# D

defer span.End()

/ T( C0 N$ U% |" j6 q

// mysql qury random time duration

9 Z1 l* l0 A* z# S4 F$ R0 T6 J5 J

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

/ N. }5 w( u6 e1 M& J, \

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

- G; ^# \. O0 j7 n2 K- T8 m

return

" g: I* O- Q- V: J4 _+ o. S

}

; `8 d4 U* |* G3 X- }

使用 OLTP HTTP 进行导出:

# O0 S b R7 q2 Z$ v

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

4 d/ X( J; W" i

client := otlptracehttp.NewClient(

- s4 z+ @ \' {1 }2 L

otlptracehttp.WithEndpoint(endpoint),

1 \$ E" b+ {- X+ @* Q3 l5 P

otlptracehttp.WithInsecure(),

, Z6 A* R# d" |5 \+ A, E

)

3 @9 u9 ]! @9 q3 H8 M

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

. G9 a+ l) H8 b4 R/ h5 i2 [, W* l& m2 J

if err != nil <{p> return err

/ G: z4 n$ n( \6 r* }1 A5 a

}

8 Q$ p1 f' c: |

tp := tracesdk.NewTracerProvider(

& `4 r! s7 A h: u/ s' a8 r0 l

tracesdk.WithBatcher(exp),

5 c3 I6 @1 p- D$ n! @+ ~+ |0 k) g

tracesdk.WithResource(resource.NewWithAttributes(

4 f( g9 E0 `# ]* m4 d d

semconv.SchemaURL,

6 S( L2 e% a& _: m3 O$ H& }

semconv.ServiceNameKey.String(serviceName),

8 S% ~2 J3 Q8 z% O0 x

attribute.String("environment", environment),

2 B% U: }1 g6 T4 u

)),

0 O; j% g5 u; S

)

2 n- v% b! ^" A- v9 f7 i( W

otel.SetTracerProvider(tp)

: ?' M# ?; r" e E$ a* j5 M

return nil

9 q/ @4 R& L: O6 O! \) [* c

}

6 i1 u% C& R! M& S

结构化日志

' Z1 ~& r% C3 o v. t

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

6 j ?- }$ e* K( ]0 u: ]; l

cfg := zap.NewProductionConfig()

1 S& R& G. i* m

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

/ N7 T, n) z, |* {3 ~8 y2 _

logger, _ := cfg.Build()

+ ?% d3 E8 B/ H4 g$ i

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

% y& L0 q: R" r' l

使用 OTel Collector 进行 metric、trace 收集

7 @/ P9 t) C7 H. \# C2 Z5 N2 g

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

4 n4 q- u' M0 K9 m" t4 M2 I7 t

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

7 }; b5 v" \/ i& D! h

receivers:

6 W& x0 T$ L d8 G* ^

otlp:

( {, @9 B% w! G

protocols:

* {# [/ [+ k+ A0 K" r. P" e

grpc:

: g2 z& P& a; h, x' K1 m

http:

% y5 w5 y8 l5 {' q+ B

prometheus:

* Y8 v5 z2 m: E# T' [" Z1 }

config:

7 g \2 v8 U; q

scrape_configs:

3 h* A8 V9 o& n9 }

- job_name: app

8 e0 c ?1 {+ C$ S- P8 O

scrape_interval: 10s

2 l7 Y* F- X5 c( Z

static_configs:

6 q- C* w% U* x. U

- targets: [app:8080]

( O% t# p3 G0 \, f

exporters:

2 c3 |! E( y+ b9 y; x4 L

otlp:

Y. k. E. a, y& F

endpoint: tempo:4317

7 I0 D! A h( T" O

tls:

8 `! Q {- J0 C% x8 u+ l! u: Z

insecure: true

5 B4 l+ P: u; b" Y T; f

prometheusremotewrite:

" C: }: [6 N6 U0 b

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

- z' r4 E% E% B! l i0 O& y

tls:

+ J; _0 ]9 u+ |: j3 `' s

insecure: true

9 S# @$ }& [& b3 j0 i

headers:

0 i+ L/ @ s5 j( s' g4 z4 {* O3 t

X-Scope-OrgID: demo

3 w& E$ ]4 g( O$ N. o

processors:

+ T% b: n" i W6 X7 X$ x

batch:

# D) v7 g1 D& w( P: W- _

service:

* ^, Q/ T% t- P! G" o" Z

pipelines:

8 S& x% m3 x$ P. N' D6 n

traces:

( k( @0 @3 M' Q, b

receivers: [otlp]

5 X2 W* P5 W2 |; e, V$ ?/ r/ G

processors: [batch]

7 `3 K* s, X M* V! n) `

exporters: [otlp]

2 |/ x! l- Q. s2 j9 Q+ A4 k

metrics:

) |3 u4 [0 b0 v

receivers: [prometheus]

6 f+ L6 Y+ C; }% n' M

processors: [batch]

6 W" q: s) a. d L. z& u

exporters: [prometheusremotewrite]

! i* S9 x5 c# o" [* [; n

使用 OTel Collector Contrib 进行 log 收集

! D3 M3 } e) D) ?

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

7 }. n5 z4 B4 {

receivers:

% U. N+ a) o" d6 O8 S' ]& W0 \" C

filelog:

v3 R1 i6 k) S: A9 R+ o' s& p# u

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

+ W+ Z9 ^! l2 s' q' D

exporters:

* Y8 w( ~. X- W! m7 v$ Q

loki:

" T; y5 L7 C W0 r+ ] P; x: h: ?* Y

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

4 M- q5 h. m/ J% }1 ^5 R! P2 F& @

tenant_id: demo

, J/ K; o& z2 a& X

labels:

; f7 A9 W9 p7 u E" t' Q

attributes:

/ Q" U1 ?( U ~9 J) |) F7 X1 E o# |. L

log.file.name: "filename"

+ w- J7 `2 S+ J3 P H4 U

processors:

* G& ~: D- i7 K( t( B4 Z

batch:

1 A/ `. x5 q- V# [* @4 H0 x

service:

6 Z: ]/ W4 ]* _: j# ]

pipelines:

, L4 A; N2 t" |$ ^- O

logs:

; Q h. A" ^" d) S0 u

receivers: [filelog]

) }; A4 [* X: ^! s2 T

processors: [batch]

. m/ r( }4 b( {: u8 R3 @

exporters: [loki]

* w# t( u& Z# c7 s2 O) Q

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

8 S3 f9 U* q+ C( r$ Z d

总结

0 ?& p+ F! p# b& b! x

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

1 O1 y P) H1 f& Q0 Y

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

8 E, ~1 c4 ?1 ^9 b1 \ 1 ?5 V8 c3 d* M8 W/ U6 W

责任编辑:

7 g$ o. f, T/ ?8 T# G; c; ?" u; g+ ?. k _+ m& m, T5 { ' h8 O G5 t& {1 Q 2 K0 e5 N& B- U: E2 S9 |: V: u; z * y/ u0 y0 n( r; |5 J+ t
回复

举报 使用道具

相关帖子

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