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

[复制链接]
/ L* D+ }. z: M( L3 N n

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

% q; w( m+ L) C: z9 g( C% z! a3 n" F) H! c* }' l0 Y2 J7 k

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

$ w/ p7 u( }! D3 j

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

# `, K/ N$ y- M6 V. k: j0 a& |

通过本文你将了解:

! J% p! _6 q7 t4 g5 ]" ]

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

' f' O/ _" k% p) v( |

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

/ J1 V+ Z7 Q. |1 y$ y6 N9 |) g

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

) G9 |' O3 }) H, ?) W

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

& x/ d. z( t( ~1 D% L: X+ q& U

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

; o+ l( w( {8 n" c! N

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

! g) h. D& Y" Q+ i3 X! T

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

+ r9 p& H# [& Y T+ K" R" X

下载并体验样例

8 Q" \% \" R0 ]3 c

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

7 y3 y5 J! \* F! n

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

9 {. I2 y; {+ e$ L+ ~

cd prometheus-exemplar

& b4 ^, S' Q' x+ s7 @# H W

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

+ }9 g7 N X" i4 }: }0 {- h

docker-compose up -d

( U S. ?6 r+ ~" X

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

2 D" n; k1 N n

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

% p5 }) t4 u0 J. x( `

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

* R8 C/ [ C/ z2 @# k# g, ^

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

Y0 S3 k; y( P" ?+ P

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

" a0 ? g* L; a8 }/ K

整个部署架构如下:

8 Z8 n' A2 P5 i" i0 w' |3 z

2 X+ Q% I+ w* G5 X y3 U

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

5 B2 f% @. }$ S/ b+ F4 F# c. g

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

! s7 l' ~# y; B: ?7 Y# V: k+ _

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

) h3 Y4 v9 ]$ d& y1 ^5 k4 W

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

$ L! w( P/ O, j' J- j- _) b4 W

( h6 s V4 V, P2 i9 B5 L

细节说明

, \% k( l5 X# k5 j/ |+ @* G' Q1 r

使用 Promethues Go SDK 导出 metrics

% x* r0 V# d" y9 J* ~2 l; { A @

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

/ e& m8 }4 _2 ?# F$ l; D! Y

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

/ B3 C1 J2 D0 d1 r H& ]/ [

Help: "Http latency distributions.",

$ z# o# O$ M3 J8 ~! E

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

. f8 c7 E% D! ^

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

2 P& \3 j" E9 a3 F6 V# Y

prometheus.MustRegister(httpDurationsHistogram)

8 O' Y+ p r/ n5 Y$ I+ E7 M6 B

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

: h- S" u* X4 J. C" G5 N0 |5 g

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

( _0 f- i+ Z. C" b( G

observer.Observe(elapsed)

B0 C% D+ k0 U) o) e6 B1 l) A

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

: W: X1 m e$ W( _

})

# |5 m! w" q' f, ]" H/ L

}

% H+ _) `. E' c1 {$ }+ q

}

* U4 J. G( c9 W& e

}

% U- x3 n. D- ? U5 |1 \ g' J# s, Z

使用 OTLP HTTP 导出 traces

2 O5 ]) q, T& p' _( h

使用 OTel SDK 进行 trace 埋点:

# @5 W. {' S/ ~, C

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

: B* k' { A- C4 X

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

% Q1 j; d6 V: H ^# V0 ^

defer span.End()

& Y, p! M# K+ W6 T g

// mysql qury random time duration

: U- P" x" v9 s3 y

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

h& V2 I. W+ g1 k/ J2 i

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

# y+ h# \8 w/ m$ E. W1 i1 ?" k

return

1 I7 a( O$ @! [; H; @

}

8 v+ F) s, `/ u' c6 x- D

使用 OLTP HTTP 进行导出:

0 F0 I5 i+ {9 y- T! }6 s

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

0 N+ l0 \2 R; D( F

client := otlptracehttp.NewClient(

( Q# s: V+ Z- l% z. P3 L8 e, y u

otlptracehttp.WithEndpoint(endpoint),

+ n: N O$ ?# s9 q

otlptracehttp.WithInsecure(),

( E! l8 x4 o: y1 N4 N

)

. l0 m2 Z& q" S7 s$ b2 s

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

5 o5 }* z2 M6 A) U$ C

if err != nil <{p> return err

7 l l" y4 {* w0 j( m. g6 Z

}

9 T) c2 O# a8 t, @3 S

tp := tracesdk.NewTracerProvider(

6 |& C7 }& ^* V" p

tracesdk.WithBatcher(exp),

) Y0 I) h( d+ i( ?

tracesdk.WithResource(resource.NewWithAttributes(

% N: l8 f1 g/ W. c; R5 \

semconv.SchemaURL,

6 x; T+ U( E" A' Q8 L

semconv.ServiceNameKey.String(serviceName),

! u$ i4 X* m" _% O9 l* ^9 E$ c

attribute.String("environment", environment),

; v, ? g9 }, c& y' ]* P) X

)),

& M: {1 e" m+ {# }: N

)

. v" t0 z/ t% e" [( a( q# D

otel.SetTracerProvider(tp)

. m' q# M# Q- k1 R

return nil

: S# O4 w& M! |& W( g9 r3 y# Y( }

}

7 q! G$ o) `8 T& u3 ~4 k

结构化日志

) i. |# g6 G1 m$ L" {

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

( r0 T/ o- R* u4 [0 a

cfg := zap.NewProductionConfig()

8 y, n0 w+ i f3 G/ t' p5 P% P

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

- l" L% A% C9 a k! O7 g

logger, _ := cfg.Build()

" F/ y) `7 g; l( j! N/ z- d

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

5 c8 `2 L2 a) t4 \1 A9 `

使用 OTel Collector 进行 metric、trace 收集

& o5 O( G/ P) T% z% Z

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

3 Z7 W9 m! T4 H0 j- l

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

" z% @; F. @5 Z1 S

receivers:

* O* W, N8 J g9 C3 u6 H4 A0 d

otlp:

2 z/ h" J2 I' j1 U3 Y( ?

protocols:

9 j( ?- d8 P+ Y1 y |! _7 ~' ]

grpc:

+ q" X3 u' `/ \

http:

0 t- i% O, D; G/ Q

prometheus:

- k' l7 ^- N4 d* V) k; P; w

config:

/ e6 N9 b& x. q2 J0 c

scrape_configs:

. `) j% H; Y0 J0 n3 X0 R8 T

- job_name: app

% z2 W: D$ m ^1 O

scrape_interval: 10s

5 m5 r6 e S8 s& l. f$ S: k1 ~9 ]6 [

static_configs:

3 h5 l5 ?/ q; }* g! X2 L. V

- targets: [app:8080]

8 @. j `- j- e, D7 f' w

exporters:

* f* c. o q4 K( H# \7 F. |

otlp:

3 J) Y5 P" I8 {* \3 z) ]. ~6 m& y

endpoint: tempo:4317

/ e Y8 C& A: m3 c' W: \+ \

tls:

0 r. t/ \, K0 r- o: T

insecure: true

U. M S O- B0 g- e8 |

prometheusremotewrite:

# \3 A3 }' }8 c8 @9 ~: |( U4 S

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

$ Q9 M( `: v7 o% }

tls:

; Y! p) D7 {, I: l7 r R

insecure: true

2 p$ k& Z4 w* x( P0 G& _$ V

headers:

: o( S9 `+ E) Y4 j

X-Scope-OrgID: demo

3 ~. q s1 Y% z* p' e/ V

processors:

' ~0 G r B5 e' |2 \( t9 v

batch:

6 \; G! V! \" n: i" f' d* \

service:

2 G C+ ?/ h( T+ m; K% J# r9 }

pipelines:

/ E6 `" j+ x* O# ~/ S8 A

traces:

( H+ a; h! J/ L/ j0 v

receivers: [otlp]

; \; K+ x$ y8 ~1 G9 c

processors: [batch]

5 S* D$ _% O, J8 U$ z

exporters: [otlp]

3 L) l* O+ i) E8 ^- Q7 M$ e

metrics:

4 a" P0 `8 g p

receivers: [prometheus]

! {5 [- {- \) n; X. d

processors: [batch]

! l- b$ ?: V. b$ h0 ]

exporters: [prometheusremotewrite]

1 F. k- ^8 b. M

使用 OTel Collector Contrib 进行 log 收集

2 B4 A2 h: C3 l4 Y. a0 r/ V

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

8 R! w# V! g5 s( D7 p# @

receivers:

% W8 p! i& B& _8 ~) j, U

filelog:

4 U0 i/ m* G+ V9 j1 e, a2 i

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

1 w% z& n1 {1 N& y

exporters:

( ~4 g% k E1 k! k4 O0 U$ u1 r( F

loki:

6 y( i: k' f% f6 X) M8 J5 x' {" Y

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

" @6 ~0 u1 S% W

tenant_id: demo

5 ]% ~2 g# B0 W

labels:

$ p4 |5 {: \8 k+ {% J

attributes:

6 e! A) e9 {2 h1 ^' p1 o

log.file.name: "filename"

; w- d6 @4 L6 I4 W) Q" }2 i' L$ ~; W

processors:

3 _# o3 D& B! S$ ]6 R8 n' i5 k

batch:

# M' ]8 \! Y( V0 E1 a! c* M

service:

! \" X0 ]; }( o K1 M# V6 H

pipelines:

- y* `1 v1 M, E$ T- O" C, ]

logs:

3 @* ? z, l4 l) F9 P4 U' v8 f

receivers: [filelog]

0 k# A8 f# ~4 r0 B

processors: [batch]

+ @7 b9 [3 T/ a$ k- \) O

exporters: [loki]

) o' V' ~% S; X2 i+ {4 u: S

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

; z7 p ~) v0 N$ N7 p# Y0 W& W6 ?

总结

I. P8 e$ L0 v

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

0 P2 S$ n* E$ j% A2 `* q) m

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

, _; H( q# j0 K: |% D" K, Z' o4 X - i. h1 i. E5 u0 T! r4 x# i( N. i8 [

责任编辑:

) `- y1 A* j7 Z( Q: ]' V 0 S- a8 X0 P, v* ~ 7 A" y) s6 N5 l; w, v ; P) ]2 }2 q; `/ ~- e: Z. S) T/ i8 [. r
回复

举报 使用道具

相关帖子

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