|
$ K0 S4 G( [0 W& F% N% W 原标题:基于 Grafana LGTM 可观测平台的构建 ! ~8 z# l) m7 O* I8 b
# p$ |# U+ `3 {+ T5 j$ L- p- D. e
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 % |$ S( V0 ^; X, T, T2 t2 c/ p( v
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 2 d0 p( u- j% I
通过本文你将了解: 6 b( @4 G; W' O, d
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID 3 A0 y* B/ [' E, B( D/ N' u2 y
如何使用 OTel Collector 进行 metric、trace 收集 / ^! ~1 T. O r3 ^5 g4 j. N& B
如何使用 OTel Collector Contrib 进行日志收集 - K: u7 V. P4 f1 B# y
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
( v0 s3 j1 B4 e! H% q. u 如何使用 Grafana 制作统一可观测性大盘
3 r {) t7 `* i$ l( f" A+ p! Y 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
6 j m7 R# o* ]; W7 _ 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
; E' y# G# i% s( } 下载并体验样例 ) z3 F9 O4 D9 J8 ~$ q; ~
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
7 Y2 j2 a+ P. x% F8 Q git clone https://github.com/grafanafans/prometheus-exemplar.git * p2 L8 Y3 V4 V. f5 ~
cd prometheus-exemplar & C2 \+ _; [0 O: o* F7 W: @
使用 docker-compose 启动样例程序: 8 X3 ?& v$ ], ~2 T) v \
docker-compose up -d * a6 |$ C/ j" {+ f ]4 v
这个命令会启动以下程序: * F9 J1 u; ^+ U* a
使用单节点模式分别启动一个 Mimir、Loki、Tempo a& X% M5 \/ g0 t
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo 3 n1 ?8 @: }. U* H6 U, z
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问 2 J% W8 A G2 A; ]6 n$ A8 g
启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
1 z5 [( a' Z0 J3 a0 f) P 整个部署架构如下:
! a7 B% w" y c# _* Z( o6 o; Z 
' k3 n7 p4 {# [0 l# \5 y 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
# m2 g- |+ o! I9 z6 e$ ` wrk http://localhost:8080/v1/books " Q P; R& p9 C6 @% `: K( T+ I2 ~
wrk http://localhost:8080/v1/books/1
) o0 | H( C/ X% h. Z 最后通过 http://localhost:3000 页面访问对应的看板:
' j$ ^! @+ V# g& v 
9 T2 ^& Y. ?- \, x 细节说明
: |+ T# J' u! x: M 使用 Promethues Go SDK 导出 metrics
' ~) d1 Z* X* }) j) } q 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
u, ~& `. G4 | func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
) d( e( m( ^* K/ d Help: "Http latency distributions.", : _1 E$ p4 N# I2 S. \: M; K' I5 `
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
$ l; Q* i! [/ c1 t7 i$ A$ P: } }, []string{"method", "path", "code"}) ( v5 E& \, {9 L
prometheus.MustRegister(httpDurationsHistogram) 2 G% W/ o V$ K$ M! J
return func(c *gin.Context) <{p> ..... 4 h+ Z2 H2 S H/ Z5 c+ S
observer := httpDurationsHistogram.WithLabelValues(method, url, status)
6 R# a: B3 T1 m3 P5 }9 P- I- [ observer.Observe(elapsed) - Q: S5 _, N0 A P: I
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
" q2 J. a- H, _5 \ r; r: P8 E })
8 A+ v9 x- v& G- h( b$ Z% F }
: }9 m h/ u& n# Q } 2 M4 }* T( K l- J3 {( u
} 1 A! a0 C/ C7 W6 L8 v# Y8 g
使用 OTLP HTTP 导出 traces 8 @6 X2 r3 p$ G/ X
使用 OTel SDK 进行 trace 埋点: + ^2 u; n' d W1 [& u
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
3 D* M3 _8 n6 g span.SetAttributes(attribute.String("id", id)) 5 A$ ~- |& [6 f9 J3 _: ?; b
defer span.End() 2 n& {! P, h$ M; W7 |) m
// mysql qury random time duration + D; G9 `/ A1 b( W8 \1 l
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 6 ?; I; Z+ y! ?% D( r
err = db.Where(Book{Id: id}).Find(&item).Error
' p9 H; K L6 d& E& N8 c$ b return
, G9 b2 B8 C% W' W; |/ I }
; F% Y" E9 b- f 使用 OLTP HTTP 进行导出:
9 }4 j; A5 Z/ _0 Z; ~1 n0 h; T5 Z1 r func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name % B4 {; x% W8 h5 [* m' n
client := otlptracehttp.NewClient( 2 e8 l5 `& u6 Y8 E
otlptracehttp.WithEndpoint(endpoint), 9 h- K0 [9 }% ` P1 D( Z, j
otlptracehttp.WithInsecure(),
" O8 J t1 a/ q* H( C" _& ~ u ) + p5 z5 q; [8 w
exp, err := otlptrace.New(context.Background(), client) ! ]; c+ ~6 m* `0 w0 {7 N' _* e
if err != nil <{p> return err ' i8 I: k7 ? `" n
} # N0 @ P* g( J0 [' J \
tp := tracesdk.NewTracerProvider(
4 X9 g5 K9 t" I- \' U8 c tracesdk.WithBatcher(exp), * \7 v3 x& o* Z+ b
tracesdk.WithResource(resource.NewWithAttributes( / k9 p J* Y7 j/ @) g# i; W8 Y
semconv.SchemaURL, % m& P' c* k" I! n) p
semconv.ServiceNameKey.String(serviceName), 0 L. E! e! N3 d) a7 }; F
attribute.String("environment", environment), 6 ^8 f. B O" C0 Y! q, ~
)), 1 \$ W2 @; Y7 C- [, Q0 C3 |
)
. A. p9 m' \, s% a A* N4 u" D( f, i otel.SetTracerProvider(tp)
* E! k5 A0 P0 j, y3 \. ], i return nil 9 {/ K! C1 J8 z/ U! {8 D7 p1 |
}
+ o( y& l9 j& h3 n 结构化日志 % n% h) s% S- E. O; H4 g3 j# m
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
7 [ B( t" ]! e: y0 | cfg := zap.NewProductionConfig()
! Y3 i2 j+ u' ] cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
2 r! }: a6 p5 b& Y4 h logger, _ := cfg.Build()
; E. @6 F1 Y0 Z; E logger.With(zap.String("traceID", ctx.GetHeader(XRequestID))) , r! u/ k! a3 ?
使用 OTel Collector 进行 metric、trace 收集
* {' Y6 N! h# c+ p% e: Y 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
* ]. v( e' \- ]9 _( } 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: 8 z. {6 S4 l$ b5 ?# Y
receivers:
; M( C4 l% e# m/ |+ @6 Z, M/ g; {/ M: I& O otlp:
0 a b6 b# N; N7 D protocols:
$ w7 b# Y0 B$ Y. ~; S6 p; Y grpc: + E. m* O5 b+ |4 t; N1 B
http:
; X7 S8 J2 _2 l* f8 F5 B' y prometheus:
9 ?/ e* i, P/ @1 R/ C; |3 R config:
& L- R: |& @! D& S9 b7 ] scrape_configs:
$ U7 [: e, i5 ~) @6 t! c$ n - job_name: app 2 S1 @8 \! `; A0 k. @9 T4 P0 O
scrape_interval: 10s / ~% _0 K1 M y& ]
static_configs: ! ]5 _: w! i3 I! G% I
- targets: [app:8080]
# h: h, `, t% w3 t. s) \1 P* p exporters:
Q, v+ J- x3 H; A4 O2 j' G. j7 t otlp: " c' k8 }2 g: B. o Y- r3 G; z
endpoint: tempo:4317 % n/ z. b2 W6 V
tls: ! X3 r6 |& }- y% r0 V% ~6 |4 k
insecure: true 1 z, _1 V) V: L* F7 W z
prometheusremotewrite: . b; J" C5 |! c
endpoint: http://mimir:8080/api/v1/push J1 N; R& O+ B% o8 r+ s# ^0 l+ @
tls:
; c0 \: S2 z8 x6 ^% h( Q+ y insecure: true
( C0 r+ g L" d* R/ i' | headers:
/ ]( r; f2 [+ r8 k1 g9 g X-Scope-OrgID: demo
0 f9 U6 z5 m1 p2 |6 u processors:
1 U: d. _0 G2 e6 t9 g batch: 5 T0 v3 z. z3 ?
service:
; ]- E4 H1 S: y9 r$ X' ]- i$ r e pipelines:
+ a8 x; a; |9 l( N traces: * n( s; D7 {( u$ h# I) k
receivers: [otlp]
: v% A( Z( T( T5 G processors: [batch]
$ f5 ]) U% t& g( E' O' x' Q" E" g exporters: [otlp] # w+ _+ f! {" b" _% D! X+ u
metrics: 8 T4 w# N: G% m3 u
receivers: [prometheus]
% z2 u: N4 m" y2 y processors: [batch] 3 [, Z2 [ a" E7 ~% U4 Z6 v2 j7 b
exporters: [prometheusremotewrite]
; F$ f- }# L6 _. f 使用 OTel Collector Contrib 进行 log 收集
7 R: T, n, k: j2 k' Z* ~ 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
5 C- X4 k( Q7 [- ~* Q receivers: ( i6 Z3 y* k$ ~6 ]+ t, ]3 P
filelog:
8 j- O3 p' I7 N( B include: [/var/log/app.log] . R9 M; X& L; ^: G1 Y
exporters:
- T O4 R: i" ]* H; r, f loki: ( J0 {3 a% u* n; U+ D/ t
endpoint: http://loki:3100/loki/api/v1/push
0 A! w/ s; z; z [2 A% `4 c# z2 P tenant_id: demo ! A, r! t% T8 l$ O+ f6 w a6 ?* y4 w
labels:
/ k: I/ ~8 C' ]6 l$ [ attributes:
8 m$ K" G. I, d9 P log.file.name: "filename"
/ n7 d7 J" `2 @ processors:
$ h2 A" z2 B! ?7 b" O6 m9 _ batch:
" _: _0 S L, r$ N) s6 P service: * \- v. n3 R8 @/ g0 I" F1 [. f# c
pipelines:
8 W# d- O) H3 P8 O logs:
; |+ R1 x3 I, o( _$ L receivers: [filelog] 8 `' r2 @. D. a; `
processors: [batch]
9 _ y1 M% U' j- |% \ exporters: [loki]
6 E3 o6 V9 x [ f* A/ f% I 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 , l7 R$ ]* }/ I
总结
4 o5 z7 F; e# N. G 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 % N |/ O/ [$ c: t# t+ C
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 3 j" a N" ~6 b! G& w
6 z, [" M! ^. e; e6 Z
责任编辑:
+ Y" J# w- r7 i: S m3 A* u
% T0 z- W6 H+ j) E: R0 U$ h( V$ ?4 ?, ?- M% p0 F
% U/ F- _- ~' M( P( U2 _: |4 b5 {" G' i& x# U, `1 D! A; R
|