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

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

[复制链接]
0 ] F. J9 _) S1 Z" }

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

+ M; z _/ z" j- | * \/ v6 F; p- R$ g' b

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

& |3 Q. ^: d; F5 F* w8 Z

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

* z1 r+ h% W! e( \& h1 h. c

通过本文你将了解:

8 u) i& ^$ W ]. @/ y

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

5 g/ V4 b" Z6 ]2 a6 A! r+ ]; } |( s

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

# J- o$ F* z8 Y( c

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

1 h& ` I5 P2 `4 w: e o8 w

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

) ^, Z/ |) Y6 I! U0 Y1 R0 s

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

& j! f. n; }/ y" U# A: C0 d

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

: V2 W& Z# F" w5 Q- m7 q! r1 ?3 D

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

* V2 a/ a6 s, R; @9 x* s# N5 {# d

下载并体验样例

' z/ F0 S5 }( r

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

8 y% i/ |0 h6 ]+ s6 U' _* r

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

A! K$ x" g% Z0 X% i

cd prometheus-exemplar

! M1 \) c" j4 r

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

" b$ U; U7 [' q

docker-compose up -d

2 M, t" M4 y7 g0 }

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

+ ], q1 q |& s. z. b+ \/ h

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

0 V. g- i. M. o- ~% A2 f5 Z) ^

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

$ X; c! C3 c5 ]1 J5 Z) d

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

; Q; O/ u' ^' @

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

( Q* c- T- v! \

整个部署架构如下:

. d/ W9 C/ X2 S1 @7 J# x

5 ?4 |. T: D' H4 {0 w+ |

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

/ i* l+ h" _; C4 D. o

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

$ O4 ?! X9 g8 |' z3 k' t( d

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

4 u3 Q+ n- D6 i' \

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

& y- T. K" k* L9 F

: Y) x. `' C; K; \3 s

细节说明

# K2 K. O# f" A% ]& b5 S# l: `

使用 Promethues Go SDK 导出 metrics

# D# |/ v7 s( l7 e2 }7 |

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

! ?( C7 W2 P" w

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

: H3 z8 Q; m. P$ M5 E- X. B

Help: "Http latency distributions.",

- [$ N/ z5 B; }5 {* Z/ s1 w/ _

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

* m2 ?2 Y3 o3 v! W/ m

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

# Q" r) T& x. r3 D

prometheus.MustRegister(httpDurationsHistogram)

% F" s2 Z7 Z8 x& N, u w. @: |' Y4 Y% n

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

1 j3 V1 V, F7 G' G1 i0 r

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

1 Q- ?/ C5 P5 [) o3 b

observer.Observe(elapsed)

$ l; l* A5 i) v

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

6 j z, A, b+ e* }9 w& e

})

: {5 F4 Q9 C+ ~% Z4 n# c

}

# L5 U% t+ u( p( V

}

0 u8 b/ K( I4 O- t

}

+ C; K4 B0 ]5 X" c# p& X; ?5 y- i3 j

使用 OTLP HTTP 导出 traces

7 a* j/ }' j! Z( n! x. s

使用 OTel SDK 进行 trace 埋点:

: j% k, R* S2 L) E

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

4 E1 c- m7 z3 D) P1 S6 h1 S

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

$ `* C: P. ?5 \2 E8 @6 m6 N6 S. s

defer span.End()

5 m- o q6 Q( S# S* R7 h

// mysql qury random time duration

$ \9 b3 n7 V; F4 K# S9 A

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

0 u# c ?. u/ W5 h

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

2 @8 c$ g, f) l4 ]

return

+ \6 P" X* g1 M& p0 `5 \

}

3 m0 i% p0 D/ x6 A4 w7 f5 |4 ~

使用 OLTP HTTP 进行导出:

2 V9 W" P1 a: U% l) Q, E. K/ F, k+ c

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

+ t2 y# s& j( `' J! k

client := otlptracehttp.NewClient(

3 b! x1 g5 Q1 B+ T! A* W

otlptracehttp.WithEndpoint(endpoint),

2 L6 s$ K% D9 D, I; U, b

otlptracehttp.WithInsecure(),

$ P4 O) i* Q+ n! M

)

, \6 t0 ^% d% O/ R, F$ m6 b

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

9 [- h q+ B( ~+ z

if err != nil <{p> return err

! ?& P5 t0 j, x3 c5 E% U! B; c4 v

}

. T3 W6 O( M# {

tp := tracesdk.NewTracerProvider(

6 t3 G% u' {* c+ }8 {$ x

tracesdk.WithBatcher(exp),

& r$ O; }% Q6 ?' j

tracesdk.WithResource(resource.NewWithAttributes(

4 ?6 [" r5 I( b

semconv.SchemaURL,

( o Z) |( H8 b

semconv.ServiceNameKey.String(serviceName),

5 a, F9 ~, }' z6 F0 s

attribute.String("environment", environment),

5 J0 {7 b5 }5 [4 z; X: I& d4 @

)),

2 s& Q6 q: c6 `0 D" T- ^; B

)

. I4 t& W/ P. G: r+ k9 N I; c

otel.SetTracerProvider(tp)

) b6 L) S4 r1 U* z ?8 O( u( u

return nil

$ A( M' v' e6 k: p6 i6 k, }

}

: x) e% v/ V8 K5 b* z

结构化日志

T7 h4 }: K5 l1 H/ {

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

3 B5 c3 ?% B# }$ s- [3 j

cfg := zap.NewProductionConfig()

7 B0 g$ [- Q4 Z+ g

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

4 h0 J7 R# L, @! s! v- u) _

logger, _ := cfg.Build()

+ K9 g! d# \: T4 b. N1 T7 F

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

8 x: K: r# N% @% |% R6 |1 d

使用 OTel Collector 进行 metric、trace 收集

0 a) K$ l1 a/ ]$ \# Y9 L

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

! u6 w% @" ]2 _

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

- b9 T# W. ?8 I3 B. i

receivers:

$ y7 G7 M0 f! q1 x# n

otlp:

$ T6 D3 i1 X) Q' ^9 y, D+ X% W/ V# v

protocols:

! z7 S. W) s' h( e5 G

grpc:

& j' {" m: Z9 w, m7 V' B* w

http:

4 T; T x! }! o, Z; k

prometheus:

& v: \8 K, m9 r/ u: z" `. W$ ^

config:

: ?$ `6 b0 |; U0 x" k

scrape_configs:

7 r8 q6 Q4 m0 |) w2 C" S% k9 U$ U

- job_name: app

3 ]* b7 y# j) Y

scrape_interval: 10s

" v$ }- v; k, l* p

static_configs:

- A, u A) l6 m+ x) l

- targets: [app:8080]

* @+ v0 k/ Y) T: \. z, i! X7 d1 q/ C: G

exporters:

; r! q6 `5 P' H

otlp:

$ S- Q& p! |( W) w5 W

endpoint: tempo:4317

/ s# y1 d7 c: r$ c% d' {

tls:

" h( w* C; e% x

insecure: true

8 o! L; Q: Y! }( J6 Q

prometheusremotewrite:

7 q, N$ g# w% D

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

1 Q9 N3 f: q7 F6 H% p% q" H& g- R" n/ v

tls:

4 v; V, P6 t3 `) M

insecure: true

6 {4 j$ F+ ~: J% D- ^" c

headers:

4 k# j7 g5 e9 i+ C( t2 v( q% A9 |

X-Scope-OrgID: demo

9 E3 G7 n- X) k. i- a7 `+ s

processors:

) X$ I y4 |' |0 } j

batch:

6 U) u" q! t9 f s" T" S

service:

+ P# }; K$ o- E% v

pipelines:

6 l- J4 P- b9 Z5 W' F/ ~

traces:

- G3 F7 s2 H+ Z( {

receivers: [otlp]

+ E" U3 X7 o C0 o5 a

processors: [batch]

, M. Y1 T9 f) K M

exporters: [otlp]

4 ?! n+ Q3 g0 N

metrics:

' h" ^5 T# a8 T5 v3 V+ `" B

receivers: [prometheus]

3 x) }; Q/ s0 ^2 P

processors: [batch]

" B/ P* F! K+ l [/ j* h4 a

exporters: [prometheusremotewrite]

- U9 L4 i( U5 ]: u4 }

使用 OTel Collector Contrib 进行 log 收集

|4 A9 C. ~% c+ } l

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

" q9 O, i/ v9 E6 h5 N

receivers:

5 [. S% G" \ v6 f! [

filelog:

3 z% W5 r9 G& j* F0 \; u$ v% f

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

8 n# z+ {1 c2 x$ @

exporters:

% J3 L) |- C" w9 p) V. e

loki:

/ e6 v7 F& r( B2 v, v3 b

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

' ?7 X% W. ~* T, T; ^

tenant_id: demo

2 ?- U) m2 ]' X) O% k5 S: v- v

labels:

7 p# Y# Q5 |" z3 N- T$ B( E( N

attributes:

+ K6 b( W9 z" k ^

log.file.name: "filename"

2 R8 G; P5 D2 ?7 F7 H$ B$ J) n* R

processors:

! V' B0 S" @; U7 q3 N1 A5 A

batch:

' ^, i5 L* P* Q1 `' [, y

service:

0 i7 @5 T+ _9 i3 w1 k

pipelines:

3 q6 k F: ]" q( T8 W$ S. L

logs:

4 g/ @/ I: k. o' B

receivers: [filelog]

% N6 ~3 E) i. i3 ]

processors: [batch]

6 a9 ]; b; z( r' m1 W% G; A

exporters: [loki]

" U. f4 D4 ^6 R" g' H8 ]

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

; e4 N* t4 G' c& f

总结

( |- [4 y: g/ h3 @( U

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

# T9 g R1 o1 P5 y

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

0 I, H/ }1 q% D 1 O7 N2 I' v1 }) l

责任编辑:

+ c, v b# v* y% q# t 7 k' j1 Y9 T- {( \* L# w" u8 ? 3 ]& l. v/ n! c0 L+ a4 w1 P# B. C! z {7 b; a F% b2 q
回复

举报 使用道具

相关帖子

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