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

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

[复制链接]
+ E; I/ f- e X" ~) h

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

' T4 o% N0 q$ j/ f/ R4 P( R * a- a3 E, g7 H1 `7 s

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

3 u1 c( `8 a( S9 s+ _

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

+ N+ w8 j! _' ^ j# l+ x

通过本文你将了解:

% A# x, T+ P* N1 C/ @; g3 S1 w+ t; o

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

; ?- i. F7 N( U8 t+ N5 T+ t

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

2 V; m% n$ f) O' n+ Q9 m& u" H t; w* |

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

# w# D* [' d7 X

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

2 e u# L& T) I6 |: S8 W

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

" B) |7 p' H; C7 C( l

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

% [, Y# Y) \9 x, U

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

6 P" y q3 `' A( f. T

下载并体验样例

& t0 E! X: W8 P' @) q6 n. x1 r

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

" S/ _2 x4 h- m, Q

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

+ F2 O; M- A" u4 }0 d0 Q

cd prometheus-exemplar

6 i3 r: E+ y+ B

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

- g& `2 z' {0 @0 d8 ]6 m) Z- w

docker-compose up -d

( N3 W- d0 ]/ @

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

1 D" d7 ?- q. [+ C

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

1 ?# ?. \ R. H* L0 j

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

( |# i% w: j9 p3 S

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

' z4 D1 }& p' y( e- [- j* m8 t0 L- e3 Q

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

2 ~0 c/ s: G. ]

整个部署架构如下:

5 L8 f/ a. g0 @1 K6 N$ J1 ]7 D" I

0 F" V5 Y" ^& {: B. }% l \- v$ g

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

# R {3 s6 S1 h- o' s$ j! S: R( ^

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

8 y+ N2 X9 |" y8 d {& b+ G

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

5 R2 u+ ^( k* K2 e9 v

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

. w) F$ w& P& `

1 C3 u6 ^7 h! Y8 H2 j4 \

细节说明

9 N$ G/ Z3 l1 \1 v/ b' z5 K

使用 Promethues Go SDK 导出 metrics

) n, L; ~/ R! u; @: I' N3 `. `

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

0 ?1 A' ^) }: s, ~# z `

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

2 e* N% F" y u( w/ O

Help: "Http latency distributions.",

7 r) v/ w: q( t m3 L/ Q! U

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

1 ?* H7 Y4 L: J+ O# D# f+ R

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

- m2 C1 n# d" s

prometheus.MustRegister(httpDurationsHistogram)

, `. H4 u* B! y6 n% h; }& D

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

9 w4 v! C2 R& c& {( [1 T0 d2 q' Q

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

2 N4 _- F9 H7 S/ c

observer.Observe(elapsed)

9 T4 _* |- K8 a U+ Y6 l

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

5 K* K+ @# m4 E1 i) K9 r6 J

})

- d/ S' S2 \2 W7 I2 a

}

; X1 o& q0 G1 A! _

}

5 t% ?8 l t l3 [$ R4 s

}

! W3 A. @1 T1 a

使用 OTLP HTTP 导出 traces

3 K$ A4 Z8 I, R$ @

使用 OTel SDK 进行 trace 埋点:

5 ^* h9 ~) ?9 u: }1 ^* }7 e& {: a

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

* u- O( b6 ^6 ]! m( g. Z9 N7 y

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

% ^- O. B8 t; @. A$ F3 N

defer span.End()

* O1 d- P) _. E3 m( k

// mysql qury random time duration

. ]# J* D- l1 B$ V

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

% F8 r0 O4 X" }6 y l* i G

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

4 G% t' a' }6 `/ D3 J/ b/ f

return

! u5 O7 K* B2 F: j. j

}

& F0 t6 l) D# o9 y

使用 OLTP HTTP 进行导出:

2 W; ?3 ^& ?5 Y3 @0 ^' w4 i

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

+ J9 f' g8 L2 G9 F

client := otlptracehttp.NewClient(

4 Z/ Y" M* ]1 K' z Y0 `$ T% q1 C

otlptracehttp.WithEndpoint(endpoint),

% [' f2 j2 f5 g2 V. o: d( |

otlptracehttp.WithInsecure(),

6 o9 `3 \- K! C

)

5 b5 n% c' _6 N! c( C+ x+ u

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

Y. N0 G9 J$ t- C, N# Z( g

if err != nil <{p> return err

5 m9 M% @4 R- d) p: S# w. N3 Z

}

8 T* g# s; Y2 |0 P

tp := tracesdk.NewTracerProvider(

9 J" d0 q* X3 m# Y$ H2 f# o

tracesdk.WithBatcher(exp),

, d) r+ X, K9 |: V

tracesdk.WithResource(resource.NewWithAttributes(

" @& q6 i$ T4 C1 a

semconv.SchemaURL,

% U p0 I! j$ g, Q* r. ^3 E8 b0 w! u

semconv.ServiceNameKey.String(serviceName),

" }3 a+ Y/ d( y* p

attribute.String("environment", environment),

7 Q2 ^0 I: h$ [* }% m

)),

5 G6 h2 R6 K5 T$ E

)

e' }' K" y0 o+ _

otel.SetTracerProvider(tp)

3 W1 g# U" r Z+ Y# T% g

return nil

9 l8 D0 a& t7 t

}

6 H) T) w+ ?& R" F2 e: r

结构化日志

0 s5 Y% J6 @6 ^5 q# J

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

7 n: C* }5 ^! r

cfg := zap.NewProductionConfig()

3 k8 a9 u1 O& v/ U

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

' x$ M% L: f4 K+ Z8 Q7 S" K

logger, _ := cfg.Build()

& O l8 b% w' {% Q3 W# B2 p

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

6 }- I, D3 }( R

使用 OTel Collector 进行 metric、trace 收集

" F L1 O+ y: t5 Z$ U' m5 K

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

" p8 [( M6 ^0 T

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

7 L5 j8 o5 m5 Y2 W# }7 O

receivers:

8 o& O$ ^+ _0 x* g9 H9 a

otlp:

$ L+ X" K3 N$ @9 Y% o) S+ R; k. o

protocols:

2 g5 F' }- Q# }1 W+ ^& q

grpc:

6 L% I" g. @6 S( `- a* t# U) S

http:

: A9 L( L1 Q: ^( ~! H+ h

prometheus:

& S. @8 J' S2 G1 x+ R/ z+ \+ g

config:

, _; a% M% ?' N9 G) |6 }+ H+ S

scrape_configs:

7 Y1 \9 o) `0 ~% K6 v* h3 q

- job_name: app

( g3 }# s, J; T/ u, a+ O

scrape_interval: 10s

, o& o7 I; c* l1 g0 V2 w3 e

static_configs:

* ]* i& Q4 H: ~

- targets: [app:8080]

, [- m" r1 _- J+ t v7 n

exporters:

: y0 Z# P- P7 ^; D4 e8 Q

otlp:

) \7 ~* r8 F; D, d, ?$ U

endpoint: tempo:4317

# W( r8 C" v4 c" I

tls:

& i1 u0 J- l* t" E* O# {

insecure: true

- n! r- Z: f+ ]7 M4 b! w6 k J, V

prometheusremotewrite:

9 I, f" U1 `/ p. [

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

" ~9 z( {0 t; W M

tls:

! g% ^7 A6 c( s" Z

insecure: true

5 K% ~" u L' W: U" J- C6 w o

headers:

3 @! ^. F4 z1 R9 G! E" ?+ i

X-Scope-OrgID: demo

5 z( w2 }% }3 q9 }+ r% P

processors:

9 k0 P0 P- p. t

batch:

" K/ I! ~' r5 r2 C: v z# H; [% b

service:

" j" S7 V9 B( e4 N

pipelines:

' m: j0 }, R2 K4 A

traces:

1 b6 F$ N3 n( G, K( ^

receivers: [otlp]

% x) ~4 x8 L1 t- g8 f$ o- |# Q9 ?

processors: [batch]

+ g- t! X$ q4 v# E" {6 V

exporters: [otlp]

6 g1 i. y" W# s* o% m. `3 O2 Z$ P

metrics:

" @3 @* w$ V0 |1 u0 @2 R* E& v/ M* c

receivers: [prometheus]

; S( e+ b. P" E, w) t4 w

processors: [batch]

& B! V; \4 k0 f( v; e6 ]4 S

exporters: [prometheusremotewrite]

1 \3 h+ k* X# M

使用 OTel Collector Contrib 进行 log 收集

$ U: R/ x% r4 Z$ B

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

$ I1 U; ?, _' a

receivers:

2 M9 d0 |& j$ I$ `1 n6 @1 g1 p9 T

filelog:

' @" k4 c0 z5 g# p7 A9 `3 A

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

# ]" f r, r, S3 ?- u2 E

exporters:

$ S6 q* G" {- n' e& V

loki:

; q. x: z7 H9 m; [* T4 |

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

( l! [. I6 f) F% \1 o% O2 e

tenant_id: demo

: a/ \" S: _ w! e

labels:

; P' p! E( x! h

attributes:

# ^" h4 T3 q2 T

log.file.name: "filename"

- ~# \" ~& E1 M: h/ T, p

processors:

) d6 h3 b8 A" f* L/ V8 R8 z7 c4 d

batch:

9 X1 L, G3 ^3 ]& F- k7 C8 `' ?

service:

_/ s( j, d% i9 m- p2 @7 d% i ]

pipelines:

, b" ^1 e7 }( D8 t% u! N

logs:

: l, d; g7 v4 U7 H" L

receivers: [filelog]

h2 ^# q$ A8 E5 H

processors: [batch]

; F0 d( W0 D: k9 `

exporters: [loki]

* n5 Q$ P! h; X0 g& G# M, N+ Y

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

7 B* }8 u0 Y$ `5 m% b) f, w

总结

! u. i( S* Z: Y

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

* F K2 z& |5 q

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

0 G$ l0 s$ m0 b! W% L% ] + e6 l% ^! N F0 t: S

责任编辑:

/ ` R: ?6 j/ f0 ~8 i ' K; u* Y5 n: f- e3 `1 }7 `- B8 i! n( c& d! P7 V: ]/ ` 5 E' Z1 d+ T6 `# K3 f- T* e- f5 E7 u
回复

举报 使用道具

相关帖子

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