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

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

[复制链接]
; P" v/ v7 e) W" j1 Y! b5 T! v0 M

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

0 [5 w$ E* u$ j! q4 Q# K [9 j; N) D# S b" Q5 F+ m) R

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

: u7 V7 T& K1 E+ z4 [9 i& b& f

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

) g6 d* i8 b) i

通过本文你将了解:

$ B3 t* z2 X6 e8 R3 w! T

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

9 e# M% f2 U- B6 P

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

$ }0 B3 B3 v6 D$ w# T7 c& u* C. ]

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

& ^3 f5 R9 F+ ]; ~# y" w

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

1 F" l$ s1 f- ], L' e

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

% n0 M' U1 ]" O/ |4 t( [+ \. e+ q

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

4 _, H( ^" h* O5 F8 \

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

% Q1 [0 m& Q& W, D9 a$ Q

下载并体验样例

0 K1 K. e: c, U8 t4 Y" j7 l

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

# R$ h" N( H( Z) J) b

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

5 W3 [& V# M; i

cd prometheus-exemplar

# {+ `3 D% F$ h' v" z

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

: V) B, z m6 j, _

docker-compose up -d

* z7 U& `5 u; m1 f% F

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

* n! r7 g! V& z' `1 m

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

* K. K' a$ W7 n3 b

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

' A( N2 [/ S; H: s) E

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

$ `4 v& }8 m+ I n+ v& I

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

% `! m) W3 w9 v) t

整个部署架构如下:

1 a6 L- V! f3 y0 [- z9 N

$ Y' c+ M7 \8 ` r: b6 Q

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

( X2 |) t$ s P8 d

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

5 V+ X8 q! c. P) ~ h( \

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

# H9 h$ V5 b" \' m, z6 ?, j4 l

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

6 t: P R% Q/ U( O4 X2 a% C# l% i

: A# `- e* _9 K4 m p$ y5 W

细节说明

* h( U" b+ z( X J% _( x3 r( [

使用 Promethues Go SDK 导出 metrics

/ p, j+ g; m$ A+ Q

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

! N9 U7 z$ p* T4 d2 Q8 U

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

5 j0 S( |9 t! c! O

Help: "Http latency distributions.",

- p$ {4 X* u! J0 H& o

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

% i1 R8 F+ ?) v. l

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

7 i1 F) g; k) B) N2 y

prometheus.MustRegister(httpDurationsHistogram)

) ?6 c5 D" q; i5 [+ `; {! d

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

' R$ B5 K6 u' |% V; Q& Z" i

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

3 N) ?& `0 [+ Y/ ?8 E7 v4 j1 `0 t1 e# ]

observer.Observe(elapsed)

6 [$ I8 f, j3 p# P( r+ N, W

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

G0 _" ?% d- F. c

})

7 U7 J- `( ~+ y: X- J

}

% _4 T& y+ `4 u6 c' i

}

$ I( S# a- D. K/ w" U$ b8 E, l

}

+ ~: D o0 X/ d4 a1 ^# B

使用 OTLP HTTP 导出 traces

V( S8 n. L4 @, ~! f$ c; {& t

使用 OTel SDK 进行 trace 埋点:

* d5 r. {* o: J8 [1 @

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

1 I8 j+ J) x! y0 J0 ]5 S

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

8 Z+ \/ f' M) n4 R

defer span.End()

$ i: }$ ~- T# } v; T

// mysql qury random time duration

) A8 L0 n# _+ \# d

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

5 m- l9 L6 k! h' n% ]

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

# K0 Z% Z. e% Q! l3 b# T

return

1 @+ t) a g' `! n" L

}

% n( Z; C$ n+ v! I2 ?1 d

使用 OLTP HTTP 进行导出:

$ g$ m" Y# r* D

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

3 L- U2 O3 ]( l0 X4 w

client := otlptracehttp.NewClient(

" c4 R, y) P; D( \

otlptracehttp.WithEndpoint(endpoint),

3 Q+ ~2 |$ c, P, d- B/ s

otlptracehttp.WithInsecure(),

' ^% {8 W2 G/ E

)

* ?* k6 {3 ?4 v+ M# l0 u

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

6 ?( {$ ?" ^4 U% `+ x0 d+ R

if err != nil <{p> return err

; O) j0 K5 v+ \4 N% C

}

$ F4 C$ [- Q; j, G5 v# H1 n

tp := tracesdk.NewTracerProvider(

; x: b$ S7 `- T% R8 J- I- x

tracesdk.WithBatcher(exp),

8 m8 d+ y1 _2 h: D w

tracesdk.WithResource(resource.NewWithAttributes(

$ {, A9 {# l; s6 }

semconv.SchemaURL,

2 ~+ {; n: }. `+ I% h, Q

semconv.ServiceNameKey.String(serviceName),

7 x6 n0 o8 u8 H5 t3 N5 z

attribute.String("environment", environment),

3 s8 S6 w5 v. U& U

)),

% i1 [6 y5 A: |, A. x

)

. w' W: s0 C0 Q, \8 I8 Z

otel.SetTracerProvider(tp)

' A5 ^; T5 I9 r

return nil

. ~& Z' @9 }! \5 Y7 }) ~

}

7 z$ O! \. r6 P \

结构化日志

s6 i2 z, Z3 k+ k) I! E! F

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

8 r0 |' C: ]5 B* t& I' D+ P5 j

cfg := zap.NewProductionConfig()

3 o5 L V# e0 V+ j- Y

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

0 f9 N& L5 O) |

logger, _ := cfg.Build()

1 P$ M- K# K) _0 M# \/ ^% ^# I) H

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

$ \9 K- s6 ^4 U y

使用 OTel Collector 进行 metric、trace 收集

) @. r/ d$ C s- i* K& m+ a

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

0 m0 H" o' O: q+ b6 n) |

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

( E" i {+ ~3 f; r6 m

receivers:

5 j# |. o' w% M# `' z1 V; a5 u; |

otlp:

- A. U5 l2 I4 I' H

protocols:

9 b7 Y2 j+ }: K, _$ {/ N# Q

grpc:

6 I! y0 v- a3 P2 S, S5 H Y& O

http:

- U+ n. w2 c" E3 L

prometheus:

1 d, [- v# M2 @0 d& l; p/ i

config:

, D* |9 C% _) }8 l. I% i. |" `

scrape_configs:

% B1 H: v+ }9 v6 c

- job_name: app

' T: C5 C2 J& l3 q$ C) B. o# F

scrape_interval: 10s

9 Y; _! l( A/ a2 @7 {( U

static_configs:

2 M2 j" c+ x$ n9 u$ e5 ]

- targets: [app:8080]

$ Y9 o! {' a; i5 i" G6 l

exporters:

2 w2 x. J) o. v. [7 X5 P

otlp:

7 [& c2 r0 y8 u3 v% P0 B

endpoint: tempo:4317

3 h9 ^/ K4 ?) @2 m' b; X

tls:

- T: x R5 \7 n; P. c

insecure: true

" G; A! e1 m- Z, o f$ b

prometheusremotewrite:

6 j h1 H" O+ C3 h6 E

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

, y$ ?# W% M- Y3 }1 N9 r

tls:

+ I6 j& u7 h7 `0 S

insecure: true

" p- W6 [( |& N/ W# N

headers:

: q8 g5 i: O* ^$ K

X-Scope-OrgID: demo

3 u( g( G$ _3 w! q

processors:

1 i7 R: J+ b- q, X# S

batch:

) |1 ?6 m$ G; Z1 t7 o4 K

service:

( G$ y S8 c, }- C4 I5 f; C

pipelines:

5 R# j7 B6 K$ m6 a: T5 g

traces:

% a) S3 _4 F* j. M/ g* G) Z

receivers: [otlp]

' i6 q5 e+ b) ~8 v' L c' X

processors: [batch]

. `* Y- n$ g k$ ]" a0 W) n

exporters: [otlp]

. g9 P6 {/ K0 j5 D6 i8 R

metrics:

5 E1 m; a1 k6 w3 Z3 p

receivers: [prometheus]

7 A' O$ P; ^1 v$ {

processors: [batch]

5 H& T" `; l# S2 x2 @$ A

exporters: [prometheusremotewrite]

, ~9 C( w' K! f2 B5 h1 |* R

使用 OTel Collector Contrib 进行 log 收集

# Z/ a1 z! v# p+ Y9 z: a% e# h. ]

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

- y% g( g5 H3 r) i3 \

receivers:

* a% T7 P4 n+ X

filelog:

6 `. ?. @0 ]) L# I% H5 d+ v

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

( x1 T2 Y' u0 T

exporters:

/ A6 }4 g$ e" U9 F" i. C

loki:

% v- s6 ?; d9 U0 |" V% a2 U8 r5 @

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

^* {# F% d( ~: p# v

tenant_id: demo

, O' {% R+ B$ r- b* L9 q: A+ ]2 ]

labels:

& Y, h+ L' T/ a8 X$ I; D7 ]* s

attributes:

7 m5 g# O' B* A5 ?% Q

log.file.name: "filename"

9 r0 c; }5 `. N4 W { E, i' p

processors:

+ j' w0 Z9 n* |/ I9 u6 e* s+ E5 ~2 C

batch:

8 j) d; k$ b6 q# O

service:

+ V) Z n% H" H

pipelines:

1 _' n" Y- B$ ?6 A: A

logs:

- D+ h; ~# H# P; u3 ?( \

receivers: [filelog]

. O. I" o! W) X! z+ N

processors: [batch]

! i; z8 o' O& f6 W+ J$ L5 x

exporters: [loki]

- |: P! y8 C) [ x

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

6 K, I) Q4 p9 l9 E: d4 p J/ Y2 G

总结

, U$ `1 l; N s4 E" `) [6 ]

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

0 N9 s2 o, R. ~* b: h

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

2 ?0 j; N% @1 y7 i. \& F 6 S, H/ z! w# k7 T. _

责任编辑:

# c! L7 j& o8 w, N0 C * p n0 S6 ^, x8 N0 @, d* d! `! v + j$ N- Q. P% e: x' _& e. f @! _3 { i! P: G. N
回复

举报 使用道具

相关帖子

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