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

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

[复制链接]
0 e" X6 d* W9 i8 P

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

\1 T& c8 W% j2 {4 b; ? 5 e# G+ A h. t

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

; Q1 ?: M( j% a

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

3 l* F7 N6 r6 j y: B8 _

通过本文你将了解:

, V/ t- O. [- h( M8 [. p. S

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

1 W; } P( |, K5 w; U: |

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

0 e5 n+ {/ o( p8 A

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

% F$ r8 s2 g0 L( X" C6 R

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

1 c9 E3 ^3 j6 p( i6 A- u. C6 b

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

" m; p1 X' B: A) s; ^) Q

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

" G1 J7 V% ~8 {# S0 m8 k2 u

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

7 F& W# L2 L3 a" r9 c; @

下载并体验样例

6 ^& |' B7 N4 F& O

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

" J! {% g E8 v, u( t0 {5 b

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

/ I1 g& F1 S" P {* o% q& t7 R, Z

cd prometheus-exemplar

1 u0 M' N- W1 ]) N$ w2 ~

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

: s7 Z' ]: z* v

docker-compose up -d

# m1 F9 S# X! i$ h9 L" u, H

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

7 A; l0 K, Q% n; h$ L3 U

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

8 r) w8 y" x3 {$ X7 H0 N

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

" D# P: J6 M+ F+ ?

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

# I/ u) h3 C# _: q

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

# j2 Q f" `5 V

整个部署架构如下:

6 u& J( k" n: a) q2 c7 F: f

) z; s2 l7 V. g6 j/ ~8 _; ^) t4 \

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

# E: H5 `+ G C9 F

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

4 [7 s# j, N! a I5 b

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

% ^! @% q1 X( d7 U: A; S- H

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

& x; Y, h* I! x" V( P

: c1 v1 S& O$ B u9 _) L

细节说明

3 D/ g& n% \% O3 e, q# w. U

使用 Promethues Go SDK 导出 metrics

6 s8 J1 p7 i1 g! Z; z7 V

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

9 q2 Y! p' e( ^- i

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

* U- w) w+ r& ?& m8 h

Help: "Http latency distributions.",

3 ^, y( `: Y, u( g

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

" i) x& p) W# a( a6 B

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

/ \: v/ t- d8 g% c# B1 h: q; }

prometheus.MustRegister(httpDurationsHistogram)

! C3 S8 b( h$ j$ \ n! b2 f @: ^

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

+ ^1 S# v9 I/ j; g

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

( M2 o& c0 q& j3 c) Q8 H

observer.Observe(elapsed)

& P. W; _" t" M: v

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

7 P$ k* ] B/ z9 y/ V6 `1 v

})

; T3 b% l8 g( g; D( ^

}

4 D; ?- G, S# V7 v! [

}

1 M' Q6 d1 Z2 {2 f6 A; o/ Q

}

& ^ c9 n' _' ?3 e$ v! S

使用 OTLP HTTP 导出 traces

7 P4 H! _/ k7 _5 C; t! v

使用 OTel SDK 进行 trace 埋点:

4 b3 @. `* v# Z$ Y6 ^) L

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

$ S$ A3 g8 A# ^* l& s

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

( O1 k7 | b2 |$ a) L; F$ j

defer span.End()

$ l8 V/ k2 _4 O5 }; ?6 \$ m

// mysql qury random time duration

# s1 ~; t! ]3 R7 |/ e: j' z) g

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

7 l3 n8 l) }0 V: H6 T8 T

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

. y N! }1 d" `" b& P

return

; D# \9 I, z# a6 ] N

}

# \4 f) t- C$ T+ `; A

使用 OLTP HTTP 进行导出:

( d& [5 ~$ ~, t# r8 P

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

+ d# M; Q: a g* i2 k4 {" m

client := otlptracehttp.NewClient(

& X6 `* B6 q( w) r) T

otlptracehttp.WithEndpoint(endpoint),

+ C5 { G. a0 y5 x C! n

otlptracehttp.WithInsecure(),

' j5 o: e) ^/ L: w( G; a( u4 ]

)

" }+ _+ g @0 p. ?' o# y

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

3 G8 [8 M; V8 g! y% s

if err != nil <{p> return err

* s9 h' B( z) I U2 t2 l3 t i* Y+ w

}

4 N0 Z! _- k4 O% P) t/ R; v1 f

tp := tracesdk.NewTracerProvider(

- ~3 b. T7 A3 V% t7 w

tracesdk.WithBatcher(exp),

9 } @# `! ~! s

tracesdk.WithResource(resource.NewWithAttributes(

2 d1 T ~; J( ^& M2 L

semconv.SchemaURL,

' N- c8 X: T j( R

semconv.ServiceNameKey.String(serviceName),

! x% T( ?: t( {3 K, i$ e

attribute.String("environment", environment),

3 |3 N/ E( X* I9 Z2 Z. j& R) e0 [

)),

/ E9 h( M3 c4 }: ^: c

)

- F' P5 D' _5 }" W( Y( _; Z

otel.SetTracerProvider(tp)

7 Y- T$ o2 A' x# P3 |4 B

return nil

; [! A2 `% Y( u+ J2 Z6 ]5 R

}

! w! p& E" {% I8 q

结构化日志

- n$ W6 W7 t0 u

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

8 ~) _0 P1 E4 h% |4 k; K4 C* [

cfg := zap.NewProductionConfig()

% G# E, ?) }7 W" ]2 Y% P( C& ?

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

8 G! j d j# L) c1 h# g

logger, _ := cfg.Build()

& B1 M9 J& ` S" ?( m; K7 B* w

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

4 O( P% J' j' @( h: d3 h: f2 n

使用 OTel Collector 进行 metric、trace 收集

' f( f+ a8 l9 g! [* o

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

: \. y# R( q- u: d- n# U/ V/ H

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

$ J1 G0 v; _5 T& \5 I/ Z' ^" S

receivers:

7 }1 _8 y/ H$ f# @

otlp:

6 G4 m# z1 n9 q- f# d

protocols:

. |2 G1 X# W- B/ T

grpc:

8 X; s" l3 i3 u1 S0 e6 B2 l

http:

% u/ c5 c9 O* a

prometheus:

2 p) j* O6 |8 L: q1 m3 N3 E8 v

config:

% W8 _% z9 o5 K" v) N+ |' N

scrape_configs:

; x( |8 W6 T% {2 f: o4 o

- job_name: app

1 G E+ v4 E% P8 v6 z! t+ @' P% q

scrape_interval: 10s

1 L" G) l2 J" w% S

static_configs:

4 X% b% Q% J7 `( _7 V

- targets: [app:8080]

1 o8 p2 k, m& s. z8 O3 L' }

exporters:

' N; |2 ?9 W. D! U& P. r4 R& M* p

otlp:

k1 h( U3 Y: U( f" ^: u

endpoint: tempo:4317

q: l8 p. z6 [5 p3 f6 j

tls:

4 {9 }* \$ l3 d

insecure: true

! t6 B$ L! ]$ L; _! w2 Q

prometheusremotewrite:

5 k/ y- ^/ F7 n

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

/ l+ ?% w. w5 N1 Z

tls:

# X* c) p% [2 U

insecure: true

% E, B' B/ I/ n! |2 |- o

headers:

' r9 t& j g Z: C& p; B! y

X-Scope-OrgID: demo

. r) I' i5 V4 G

processors:

: n: E1 K' q7 r2 y! h! w* R( }! q

batch:

( _3 r# e9 V9 R6 O

service:

7 U' ?' B' @2 `2 ^

pipelines:

0 g- s1 C& x$ r+ B4 P

traces:

" V, j. E L2 }. A! @+ f% [

receivers: [otlp]

1 H' l- h0 ^. c% a% d* N/ R

processors: [batch]

1 H+ ^7 p* O. K+ k, D& V

exporters: [otlp]

$ @- Z! l1 v* ^% g/ ^/ |

metrics:

6 v6 r8 e% X& W V/ C

receivers: [prometheus]

) p; A* `) `: }' x0 P

processors: [batch]

/ V( {1 f5 K! v0 M8 T. d+ U( s

exporters: [prometheusremotewrite]

* r% |# H1 M# \6 s

使用 OTel Collector Contrib 进行 log 收集

6 I# b' g# Z" l* G$ B1 M" D

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

C) o3 ?- Z# X1 x, i* j8 i. C( S

receivers:

/ q- V5 C1 ]* q/ z9 I3 k4 c

filelog:

- C; b8 O5 \3 z

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

9 `* Y& \8 g. B3 L [

exporters:

' k) X% t |5 b( ~( y& R# Q0 E

loki:

& G ]" ^ a8 W8 T

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

4 X ~( ] g1 I7 x+ r( k& |% |

tenant_id: demo

. ^- @0 I$ c- o% Z

labels:

9 r5 `6 }$ z; U' U

attributes:

# [7 |0 L5 B) \9 f/ h! T+ y4 s

log.file.name: "filename"

4 n: i3 X8 t( S5 n; ^; L

processors:

; l' n( t+ M. o; o" L2 g0 t0 u

batch:

" e0 G% Y. r. a& G, R, o+ s$ b

service:

+ a$ `1 G8 I4 _1 J2 W6 m; K" _7 G

pipelines:

% G. F {# z- D6 d& x

logs:

9 A) s! G# W q3 D( J% N2 f

receivers: [filelog]

b# @5 B1 G9 r; @+ l0 O8 d) E: L

processors: [batch]

( O$ D) M2 m" N. |* o# d6 Z

exporters: [loki]

$ l/ b8 ^ b5 Z1 d; S

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

8 v. S. ~+ c. p5 \# c% d

总结

) k; q$ G; o2 ~

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

! b7 T, d) J" [& x+ q% z Y8 r

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

4 d3 Z4 T; r4 r G9 n1 C! j 8 j! d2 o& P& ^1 q9 H( P, ~

责任编辑:

7 M! B! t! Z0 i) x5 r7 ] 2 p7 }5 t3 W" b' j7 j' }$ c0 c, z# V `; j" h& P% {1 ^8 z" h# Q - R% i) U# L1 h- L0 ~- Q+ Q, q9 y$ M* f) N& z" c5 d( c" R7 q
回复

举报 使用道具

相关帖子

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