|
' T$ j! O( ^ t
原标题:基于 Grafana LGTM 可观测平台的构建 0 x2 a+ P% H- P4 J; x4 f5 O
' P) `; ^# a! A9 l" q0 w
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
/ q3 Y& G9 Q% {9 n* L+ j 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
" g! d5 R6 Y; S7 z) ?- p 通过本文你将了解:
, O0 ]4 v7 g4 g$ i a 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID a3 R1 ~" ^/ Z. ]" X
如何使用 OTel Collector 进行 metric、trace 收集
* j' z' ^. u- B) d6 z! U; P 如何使用 OTel Collector Contrib 进行日志收集 ( g; H: K5 C; U0 G3 f7 }% Y- _8 y
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
8 O+ A" H2 V6 V' a2 B% t 如何使用 Grafana 制作统一可观测性大盘
. \2 y# \8 I! C2 m# r! \ 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
* [/ k. {# _" z) \' r4 }7 M: J3 l 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
2 S5 }+ M. @, C0 y* E9 @/ N$ m 下载并体验样例
2 }" [6 V1 p |7 m8 [ 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: 0 O5 m i2 B" j9 z/ h. x
git clone https://github.com/grafanafans/prometheus-exemplar.git % r9 O; }9 \) {
cd prometheus-exemplar
+ A7 f+ }7 c% {( M+ f 使用 docker-compose 启动样例程序:
\- F3 @0 b$ @, G) \% [ docker-compose up -d
' U e* c- a( B- b 这个命令会启动以下程序:
1 E# S5 G# }5 A 使用单节点模式分别启动一个 Mimir、Loki、Tempo
9 C" X8 x# I& f6 {+ h! |5 T 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
0 m9 o3 j! a J+ u$ f2 ~2 Z& @% {2 c 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 / ?* R: a& N, i k6 h8 ~! B/ k
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
8 i! y3 c) q6 g9 t6 D 整个部署架构如下: 0 Y3 ^/ }- r ~! o( P3 Z

! G9 X$ B$ w! c8 p+ o 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 3 O I. b+ `% j- M( L" b, b
wrk http://localhost:8080/v1/books
+ j; `' j a3 w+ {) r" L wrk http://localhost:8080/v1/books/1 & }+ {# d, y$ m3 G$ k
最后通过 http://localhost:3000 页面访问对应的看板: 0 n8 E, b/ T Q$ V" C' e/ @5 G- _

$ b1 t0 Y$ O7 V' p' F 细节说明 3 |0 L: I1 f1 L& ]* s5 s Z
使用 Promethues Go SDK 导出 metrics
5 x1 y Y5 v5 M1 R& b4 Q, B 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: ?) y9 I3 X% S+ r. O
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
3 r- {& q, o3 K6 }* F Help: "Http latency distributions.", 3 q8 @! T( r* P; Z
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, ( C, k* b0 @' Z0 V b9 j2 Z
}, []string{"method", "path", "code"})
$ v7 x+ p4 A. I) s$ t. g" ^6 a9 N/ X4 i prometheus.MustRegister(httpDurationsHistogram)
$ C l [7 r9 E+ |( Y% O return func(c *gin.Context) <{p> .....
6 k4 h+ v# K$ |: C8 \9 L" v& _ observer := httpDurationsHistogram.WithLabelValues(method, url, status) . o( @$ w5 O! T; f3 r7 J
observer.Observe(elapsed)
# v) V! x* ]. B0 `% c7 ?/ l0 k if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
5 x$ R2 l6 W( n( a }) : h0 X- m T* O5 X7 c \/ T
} ; E+ s5 \3 h/ c6 K
}
/ I3 r$ Z; o# Q } o. a" `7 C8 q9 }+ g, W
使用 OTLP HTTP 导出 traces 5 s* k( s6 T7 C" h
使用 OTel SDK 进行 trace 埋点:
3 C8 m8 j; p/ I+ S* @0 _$ D func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
6 I' D& t5 k. p0 O6 L" r span.SetAttributes(attribute.String("id", id))
( G1 R: @1 {' H/ F5 Q3 ~" s defer span.End() $ O% b1 I& o7 S6 x: x6 z/ l
// mysql qury random time duration
% ~& ^* p& h% L; f/ {% y* p9 S1 m( Y time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 2 M, z1 P5 v! u. `
err = db.Where(Book{Id: id}).Find(&item).Error
2 h; L& K, Q2 B$ P" O0 L return - J% Q4 H5 S/ e/ Y8 J
} ; w5 A0 u5 c1 @/ }4 G
使用 OLTP HTTP 进行导出: ( P$ Y$ r$ I) Y' I
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
4 M, u$ ^9 h, X2 v* y client := otlptracehttp.NewClient(
) t. K6 C/ s% @2 e otlptracehttp.WithEndpoint(endpoint),
) @" F' M( `5 |: |3 _ otlptracehttp.WithInsecure(),
. _9 ~2 n- H3 Y$ \0 v, j2 j6 W ) 4 f/ [$ Y! l% R3 K5 y$ W
exp, err := otlptrace.New(context.Background(), client) : M) h/ |( o$ f
if err != nil <{p> return err 3 D) b0 I( ?2 |; I0 ^
}
8 u* i, ^% F3 h' K tp := tracesdk.NewTracerProvider(
& b# a' i4 b; x6 k7 u tracesdk.WithBatcher(exp), # o* L% g7 z+ n% N* h
tracesdk.WithResource(resource.NewWithAttributes(
; r9 E! i6 {0 D' K$ \/ m) Z! B% r) c( G+ L semconv.SchemaURL,
- f+ t$ {, h2 e( O( [- v! m semconv.ServiceNameKey.String(serviceName),
, B8 B( a/ t1 C% \" q( Z6 Z8 ] attribute.String("environment", environment),
8 ?0 K1 f, \" }! Q% e$ ?: U )), 8 h# Y* V; u/ @/ {& q
)
/ J1 K" s3 V+ Y! t otel.SetTracerProvider(tp) : \* b! h0 \: e) e R6 ~& |
return nil ) `6 S7 C X; f; `* p. x6 u" A" N: \/ i
}
7 v4 J7 V6 C9 I3 ^2 r) M: \ 结构化日志
. ]+ l# W) e3 y5 B* r. o- m9 s6 e 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
1 a ?3 G5 x& }& g cfg := zap.NewProductionConfig()
- Q s/ T# M) ]0 m! z+ L7 X9 }6 ]( L cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} 0 _: T# v" }: R. _8 W: D: `' O
logger, _ := cfg.Build() 6 M3 P! ~& D6 c$ X! c1 t0 [
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
- a$ g* n0 ?. V5 U3 M7 A. \ 使用 OTel Collector 进行 metric、trace 收集
$ e7 V7 e9 s3 s# t1 o' Y4 L: D 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
. c4 a% I6 r: g' n 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
: s) J+ I# m2 P) o6 Q receivers: " V6 X. m+ D; i
otlp:
) Z" y% [, u' B: y# s5 r6 f protocols: % I/ D) \+ C" z- G2 b, q) e; X# F) d
grpc: ) W$ Z* c2 b+ ^
http:
0 Q- e4 ~& m& v" y8 L prometheus:
7 h% Q0 t1 @+ }8 a9 V3 p+ E/ t config: 2 z: U) |, u5 U/ ^; Z; U
scrape_configs:
; W9 M- w. ]2 }% Q" w8 @1 u - job_name: app
) r; E+ B/ k: z9 A scrape_interval: 10s
0 ^% j1 `3 N2 j. m) Q static_configs: , ?* K9 V& e$ A! i+ D$ O0 M# ~$ ]* {$ H
- targets: [app:8080] / n# Z, f/ |( T! G7 b% V
exporters: 7 V3 k: j$ e; z8 d0 x
otlp:
- G+ \$ T6 c* c; h' X! @ endpoint: tempo:4317
7 {* s/ ]; j8 P5 U3 s) \0 ] tls:
( R8 n9 z( ~6 z0 ^# w. I; f insecure: true / L _. V9 ?9 z6 @
prometheusremotewrite:
) ^+ ^' A4 _7 Y' K! w- P endpoint: http://mimir:8080/api/v1/push ]( f: H- |+ c2 u$ N1 O# ~
tls:
M4 R& i+ r- \" G" o# @% ~ insecure: true 2 D v( f3 `+ }3 z: W, A
headers: ; A8 ?0 @4 p5 l6 S3 |, u
X-Scope-OrgID: demo `- B8 P! S* X; g" W% r
processors: $ u$ k+ j2 Q |" g; v2 D6 ~
batch:
* u) R0 X' G7 S2 h9 K! L- h service:
! S6 v* z% `. D% J% J; O5 H pipelines:
/ b& g( z$ H$ E- T traces: 1 d" k. Y+ D* `
receivers: [otlp]
. H9 z9 g1 T! O; H- u7 R! q9 r processors: [batch]
; Q) N' c# I* M exporters: [otlp] $ d: O! \# Q. S' L) E
metrics: 0 I8 P$ y8 S) G
receivers: [prometheus] " E0 O0 I2 }7 W6 N7 @8 @
processors: [batch] 5 ] A- q$ I. \8 ?9 k4 t" Y$ u. c
exporters: [prometheusremotewrite]
+ x) x! ^' D# p0 f) a 使用 OTel Collector Contrib 进行 log 收集 1 H1 o/ r+ ?) X
因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
7 I- `1 s# f u- U+ L receivers: : ], u9 z" B L( A
filelog:
9 q, ]% _1 O) Z4 w include: [/var/log/app.log]
2 O- j, X9 w/ y* L" n exporters:
3 v K) K1 N) s6 {# a. Y* w loki:
+ s; J0 @% e3 P6 r) t: e endpoint: http://loki:3100/loki/api/v1/push 7 K5 e- |, u7 O4 h+ y5 k+ E2 e" ]; w
tenant_id: demo
+ N9 c# O! U7 z7 y+ q9 A- Y labels: # A9 w" M# ~1 p' s% n# k% @, i
attributes:
6 I) M" F5 H k9 `" W: m* u0 S% N log.file.name: "filename"
4 A. F& c/ E' A3 |: b$ t8 } processors: t$ y1 _3 H4 ?8 _) |) h9 |, N+ N
batch: - D; d+ a/ l8 o/ p; t* d6 J& I
service: ) s, M, X( E& k1 }, c" e
pipelines:
5 t( q0 _4 }. w1 X logs: 2 n' E0 w" R& O+ n
receivers: [filelog]
( y. F7 K/ W' Z" K& O processors: [batch]
M$ d' G% g- s* k: N1 `, J- |( }% v9 w exporters: [loki] 7 D- J, J" ?9 I% E, U2 O; X
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
2 Q% k3 |3 `$ T, t8 D3 W9 ` 总结 ! T* a; C7 b4 {* F% e
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
1 F: S$ P" S7 @; l/ D3 k& c: H 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 2 J4 u6 _6 o( r! l0 e T
* L# h; Q! S2 f- }/ P
责任编辑: # D2 V! D- B& ~9 y9 \# c
# `9 X+ ^& c( I7 ?6 ] r" K! T1 m
- g' l o# {; }+ M* p# U* C, M1 C6 i, o$ { B* x7 {
|