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

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

[复制链接]
: w0 a) A- P8 D F# k2 }

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

" R5 f; N8 p+ y' P& R$ Z' A; I3 s$ O/ [1 }# u! k. s% J

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

5 Y z, q% O- j4 N/ e& _! e/ y

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

" t, m9 Z5 g i

通过本文你将了解:

" A; O2 G( g D7 |( q+ L6 d8 H- j

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

$ C1 q w9 s! J: [$ J

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

1 {% C2 M0 O$ F: v* t

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

* Y q6 y5 [' r

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

; r) t. B9 x* t4 _

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

) j4 }, ~0 s7 t, v* T' n- _

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

8 I0 P( |- \5 M

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

4 d# i" a1 A; }- ^7 {% ]+ [

下载并体验样例

1 S7 f/ P O9 x3 ]1 l2 J! \3 s ^: M8 s

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

# U& k4 W. H! ^& ^% j* Y$ [

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

4 |2 A3 L, a2 e+ p- ]3 v

cd prometheus-exemplar

6 x2 [5 {) W# D# e7 |( ]2 R% i

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

9 Y7 C# p Z# {+ |

docker-compose up -d

4 ]# ^2 Q" Q+ e8 J) D

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

5 \. m$ h& o0 V; |4 D5 A

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

7 ^& l8 [+ k4 `+ M! ^2 F8 p/ p

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

* P9 |7 ~( m. L. X! f" A4 h

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

& ~& I+ A- s/ U0 j2 V `1 j# f

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

, b* N Y& Q, X" L ^

整个部署架构如下:

, |- y' e: g0 X6 `4 ^2 Z

& g4 c0 ^& M4 F

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

9 E0 I: r* l" @5 b

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

" \! N) I2 ]3 }' Q

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

/ n# Z$ s$ l: b' S6 i: C# v ?, i

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

; n& d, z0 O7 `

5 b3 A9 c4 E0 a* c0 w

细节说明

: q% [/ V q1 V

使用 Promethues Go SDK 导出 metrics

" M3 s: e# e% {7 z2 y- _

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

) ?. f _: [1 m6 h+ j

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

: L" g, ? C1 P$ b3 s( V. P! s

Help: "Http latency distributions.",

6 @% z" B" A4 a4 y

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

4 c# ?# a& o2 x% K5 m+ e

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

/ q2 l" z7 ~, F/ {1 l$ z% M2 q) O

prometheus.MustRegister(httpDurationsHistogram)

7 E: w- u M8 B/ C9 q

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

& _% A+ @+ {" y! I6 [' k

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

- V* V5 S, z& B; U% `4 }

observer.Observe(elapsed)

9 T% P, D" Y( l4 e* W

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

4 \$ @* t- N/ ^, ?7 g( F

})

1 g' w) B- j. A! [9 a; W) X

}

( L h4 b6 D+ n: D

}

. {5 q2 K! ^* t

}

: l6 J6 ?0 n+ j

使用 OTLP HTTP 导出 traces

1 L9 k" n, [5 o1 e, ]: Q' u) Y1 H8 l

使用 OTel SDK 进行 trace 埋点:

; r0 I' F2 ^9 k% r3 L$ _

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

' H: K5 W) C" O+ ]

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

7 ?) v# Q# m- ~

defer span.End()

! O$ d6 @% `1 L3 }3 W4 f# C; h" l- o; X2 v

// mysql qury random time duration

, ?9 a3 o% o; A" ^( z4 a

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

8 k3 o |/ T% s$ t

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

" l. d- I% m# A2 e

return

' J4 J* [( ]4 i( p

}

3 |3 _* `+ D, [9 ?: P( e

使用 OLTP HTTP 进行导出:

y7 z% I# T6 z% Q

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

' }# W) D% f- U; a9 U7 t

client := otlptracehttp.NewClient(

% d, i( Q ]: X$ [) P! `% I* ~' j

otlptracehttp.WithEndpoint(endpoint),

+ V8 N1 y4 y. _+ O

otlptracehttp.WithInsecure(),

( t, `7 V1 b3 q/ R3 M

)

) z. s! p3 f& c; F9 R

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

# ^ Q% v( _/ {

if err != nil <{p> return err

5 u1 C9 K5 l* t$ \

}

& l3 q, _: ?# }2 O. E$ S6 O

tp := tracesdk.NewTracerProvider(

- Y6 {$ l- f- Y( b

tracesdk.WithBatcher(exp),

( F7 }) m1 `" s" _6 {2 y6 _

tracesdk.WithResource(resource.NewWithAttributes(

0 M9 i/ g3 A9 }( V+ O& d. k

semconv.SchemaURL,

, u. h, r7 y f0 M

semconv.ServiceNameKey.String(serviceName),

) ]+ z; {; h& U& u1 Z8 E

attribute.String("environment", environment),

, P' w& S0 a, ]0 s9 j2 D% y& I

)),

% q) k' Y8 I2 s' i& j; U

)

0 c, L. k" M( f |- C

otel.SetTracerProvider(tp)

8 I3 D# m6 Q- ~& H$ p& l1 z

return nil

m1 W! @2 u7 h1 _' J, @9 B

}

' Y% s, o& T4 q% h( Y

结构化日志

5 b9 i# k( c! h! x4 E3 i

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

7 F6 ~' P2 j Q* S& c

cfg := zap.NewProductionConfig()

2 C; b/ E# [: F9 e9 \

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

: Q7 l; D' g# U* Y) A5 T5 p! }9 h

logger, _ := cfg.Build()

/ p2 Q" R3 R/ c: S7 y

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

4 V' G0 B+ B8 s8 u

使用 OTel Collector 进行 metric、trace 收集

! k ~* F: S* S% `3 [" V

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

& R3 i! n/ P7 z' W0 y5 k

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

" ~7 E+ r5 I# x. g2 s1 N

receivers:

' e% N0 }* ^" D. W

otlp:

5 K1 [2 L L# Z. b7 S

protocols:

9 ?: a+ a# E8 V, S! \1 k

grpc:

- i: ?$ }& j$ Y

http:

" ^1 b4 i* i, r1 r' ]

prometheus:

. j! Y6 c8 m+ N o

config:

# t" G: D, _$ g, a* b! d5 G4 u6 ?. @

scrape_configs:

2 z! l% v# H) U6 E8 r: R

- job_name: app

+ `- m7 e' _1 @/ u' Z7 C8 @+ @

scrape_interval: 10s

/ P/ N" b- G! d% m/ o; _

static_configs:

- n! L9 l+ @; G

- targets: [app:8080]

3 _8 ^ \0 N1 ?; k2 y5 P1 o$ t+ I

exporters:

% {/ c! W L" t& n

otlp:

& ?; ^6 a: ~% k7 u0 k) t

endpoint: tempo:4317

0 i S0 R; m' {, H1 I

tls:

; h4 W; y5 F( {9 a8 w

insecure: true

( G. E7 W9 X" w

prometheusremotewrite:

1 @- R7 Y4 W3 l% |

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

( R" i/ @" Q7 C

tls:

; Q3 V% J: v& x% @9 C2 [6 o

insecure: true

6 m+ V7 p" @- W% B, _+ ~

headers:

: }" }0 D1 a+ ?. X! t: x5 Y% D

X-Scope-OrgID: demo

! @% _- }7 P4 p1 x3 s C

processors:

; r; L+ g# \+ ~1 t

batch:

: J, r8 A6 t9 A2 v' w- Y

service:

D X: M) P! v! a; ?

pipelines:

. t: y7 W& R, W& R3 y+ U i! F

traces:

/ [6 L% W0 }3 S0 F0 A. _# K

receivers: [otlp]

3 r- |7 X' s; z4 p2 x8 }

processors: [batch]

- @/ c& l; Y1 J

exporters: [otlp]

/ M, x1 ?4 \" s* p5 [$ m) v5 X! Q. T

metrics:

; [6 Z j U- v, W+ ]) ?

receivers: [prometheus]

1 p* s/ _$ I7 M0 s6 \/ i: E) d

processors: [batch]

1 e3 D& ]5 }4 n% Q+ f7 ]. y5 a

exporters: [prometheusremotewrite]

% V$ t$ Y8 Y# {& i

使用 OTel Collector Contrib 进行 log 收集

$ Q2 o0 }. N' R3 z9 O& g

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

% _% m/ N9 c! s- `+ r$ `7 O

receivers:

; ?( Y8 j! m1 a7 s m; q( n/ f. q

filelog:

8 X. o. U* o- i, n# d

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

8 b" W2 x( ^7 Z6 N7 B

exporters:

0 L" o( H7 D' v$ W$ @; i

loki:

- F8 u' t5 X2 h6 E4 {! c7 B

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

/ s/ a- o1 i, i$ w9 L: b

tenant_id: demo

/ D9 n, ~/ J/ Y- C! g1 _

labels:

6 f* L/ o, g( M! ]2 |

attributes:

6 O( ^% t, M7 n, C+ |/ L

log.file.name: "filename"

6 a9 x1 t% q- K G' S) Z7 b/ j

processors:

0 `3 @( J9 D# I X6 p

batch:

0 `1 ?/ v! F' O6 G

service:

5 Q5 W( |, ^- H% {

pipelines:

4 d" C$ @2 Q8 M$ q9 R

logs:

4 |3 A4 ?3 V! B6 `" h8 P

receivers: [filelog]

$ ?; d" D' _$ q, m

processors: [batch]

# s$ u" [& f0 N6 y" c. U

exporters: [loki]

7 H, j% X% k- V: F7 r0 c; y& w! E

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

( `5 g5 K7 v1 n8 a+ a! d

总结

- o: G9 `8 X: f _( ~9 s

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

# d# E A) y; v

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

. H" e3 p, i/ f1 c) c! P+ V' i 2 U" p6 R( h6 L: y. Z* y6 e

责任编辑:

! N4 x4 x& x3 V+ v/ k! R' Y ( M8 K) N) r8 [4 D+ }8 y7 L. H1 ^, Y8 k" } * u2 q4 n8 K2 e' L8 i6 T& u" A/ ?7 y( s5 ]3 v! ]
回复

举报 使用道具

相关帖子

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