|
5 Q: n$ C6 L+ o7 B
原标题:基于 Grafana LGTM 可观测平台的构建 6 S% [4 r e7 x9 b: x5 i7 j9 e
% q* ?4 n; k! W7 C6 [: n 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
' `6 O( d; g# u# y0 v 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 4 p H' A4 N, I* N; g& O t
通过本文你将了解: * J. Z2 q' _/ N
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
5 \" P, Z- o Z: b 如何使用 OTel Collector 进行 metric、trace 收集 0 g" A; C$ E+ u. N' F
如何使用 OTel Collector Contrib 进行日志收集
' B/ }. t. @' d' r/ W 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储
$ D/ A' l0 z* K6 U 如何使用 Grafana 制作统一可观测性大盘 " {. r8 y7 D4 L2 O" ]
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
4 P" K5 Y, Q+ H# W 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
. X" u S1 y& W6 p5 z 下载并体验样例 / ^" Z) @ }9 |& ^8 @( R* [
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
( x6 \8 l7 Q8 }6 T6 ^% t7 N0 } git clone https://github.com/grafanafans/prometheus-exemplar.git
7 ] T3 O8 |4 m# Q; y5 b6 j cd prometheus-exemplar
( e K+ q* ^) d- p C. G D+ r 使用 docker-compose 启动样例程序: 3 u: r: C7 g% x+ |; }
docker-compose up -d 8 k& a( W( y) [
这个命令会启动以下程序: " s6 e: ~6 h% ~: r* E2 X
使用单节点模式分别启动一个 Mimir、Loki、Tempo 9 W0 j4 q) x) u+ N) K8 w7 r! g
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo 1 a/ r& Z& b3 W* @- w$ y; m
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
: W+ a- z7 N+ c 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 3 v3 {; m+ J; |* N+ t2 s& J
整个部署架构如下:
6 ?; a' I! e1 H  4 A, E5 m# P/ m% u* i3 l
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: 1 m& C$ O+ E" t) Q8 j5 F( `7 m; l
wrk http://localhost:8080/v1/books
9 [/ f ~, T5 i5 w- N; y0 r) } wrk http://localhost:8080/v1/books/1 [6 q, l$ @) u( U5 f
最后通过 http://localhost:3000 页面访问对应的看板:
+ E) N$ K' V- H, U* Z( F  ! L7 w+ K# o2 D; i+ r3 g
细节说明
/ n p0 n( o2 ~: b& D 使用 Promethues Go SDK 导出 metrics ( a/ k1 p2 I; e9 E' b
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
. g2 }* e9 h$ @) O$ k func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
+ n6 P! X% m( Q, T Help: "Http latency distributions.",
/ k" M/ q3 O; v @5 d Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, # [& N4 Z' M$ M& z+ e
}, []string{"method", "path", "code"}) $ @ W' J& Z1 M$ s' L6 H
prometheus.MustRegister(httpDurationsHistogram)
, s8 V0 l. }/ [ return func(c *gin.Context) <{p> .....
7 Q0 r* w" A# {' o; ] observer := httpDurationsHistogram.WithLabelValues(method, url, status) ( t- B- U( r* ~. h6 q, z
observer.Observe(elapsed)
, U( S; h0 [, C3 [& r if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
6 J9 k7 F, C4 y3 G3 k })
, x, V t2 b; ^/ h( Q1 V, @$ i% H } # q1 |- b0 E* O) l3 [" n9 [: d5 A
}
! t! B; n- ?- Y } ! _* e; P' i2 M( W$ p8 }8 M
使用 OTLP HTTP 导出 traces
7 Z/ R# @4 u: b5 d: T' S 使用 OTel SDK 进行 trace 埋点: + |: i7 g% I4 T
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
b$ N1 f6 g% Q3 p span.SetAttributes(attribute.String("id", id)) 1 @# q+ J7 @+ x9 A; y
defer span.End() 4 x* n( ?' y& o% l4 N3 y
// mysql qury random time duration S7 f$ Z c9 \6 z! C2 E
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) ) i. r r; p( K+ F3 U0 _
err = db.Where(Book{Id: id}).Find(&item).Error
% _, K: f4 A$ t: S0 n" N return
4 J- R" f j" V1 m }
& `$ k( Z0 h: h7 M j* F! u( B 使用 OLTP HTTP 进行导出:
9 ` K, Y' y B8 K func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 0 B; K" h$ D$ `* f' [: q. A
client := otlptracehttp.NewClient( ! T+ M9 C: h- q2 T/ n3 T
otlptracehttp.WithEndpoint(endpoint), - f$ K& b' X' l+ y9 `4 j9 S" E$ I% E. \
otlptracehttp.WithInsecure(), - L: U- w: l& v
)
# m6 B \# D! ^1 e O% ? exp, err := otlptrace.New(context.Background(), client) 5 Y& Z9 D, X6 t. e2 S* g& f5 O
if err != nil <{p> return err . c8 p$ }1 a8 i! N5 p, x" O
} ' `5 I' `, i% R1 h3 J X
tp := tracesdk.NewTracerProvider( 9 \! W! k% V% f# c* [
tracesdk.WithBatcher(exp),
: f+ z8 I8 A6 {7 i tracesdk.WithResource(resource.NewWithAttributes(
/ {7 E. [ G6 k9 @! e semconv.SchemaURL, % Y! c& i, m# ~- m9 w
semconv.ServiceNameKey.String(serviceName), 4 g" q" w7 S4 r
attribute.String("environment", environment),
8 r$ G. e$ O3 A4 ]+ C2 l6 w )),
, g0 R; w; ~4 W2 v+ q0 w )
- E/ Z" x: c/ y3 N' J otel.SetTracerProvider(tp) $ b% i5 N& N& W1 @% N" J
return nil
# v6 S8 D H. e1 E% L } , d" W1 H5 N6 j6 s
结构化日志
7 j. ]3 h# g) r7 q 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: ( f# n" |; ]- Z7 S
cfg := zap.NewProductionConfig() 3 e, O$ i- K9 a/ ?
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
& U/ E$ y2 V: k% y2 |% Z logger, _ := cfg.Build()
& L( ?3 J! f/ h4 u+ k logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
* ]9 Z3 l( R2 x4 ~6 I) q$ U 使用 OTel Collector 进行 metric、trace 收集
+ @, u/ b- z5 Y! L* c% A 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 . f/ W+ J0 q; `- k5 w9 g
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: % p0 T5 V7 M% U" L
receivers: / ~+ z) \. q' ?0 i4 [ v
otlp:
& q+ b/ e" q6 A protocols: 0 h3 m3 E* b! b+ `7 P3 r
grpc:
! j' t6 d$ O) O http: ; s, {) P0 N. w1 _8 F F; g- E8 x6 ?
prometheus:
! u: Q9 N6 J4 x5 W3 Z$ x8 W+ m" F! w config:
& z$ F( b4 v# g2 o+ U scrape_configs:
5 L: t1 N, _/ N - job_name: app
3 y, o+ W o6 {2 B; d scrape_interval: 10s
1 Q6 W v% g+ B S static_configs:
" d/ b/ K2 ?) e( c6 w - targets: [app:8080]
) N- W& `5 a9 R! Y" T, O exporters: 5 ?6 w4 x* E4 i0 f
otlp: $ D) M$ R, A5 P9 u$ \
endpoint: tempo:4317 , `+ N$ |8 U8 Z! b
tls: : U! |0 ?2 f- D+ L
insecure: true ( p/ D' _1 |. B! b% w+ v
prometheusremotewrite:
6 J' Y, N& `! p3 e) F$ w: ? endpoint: http://mimir:8080/api/v1/push 9 c9 n4 O% K3 \
tls:
2 ^6 p) o7 G: G5 w insecure: true
/ x; Z ~: [# ~$ e3 c6 s+ f: t% U headers: $ v2 U, S _' b
X-Scope-OrgID: demo
% _. [. c: x6 V processors: ! A; U2 z J' P) w
batch:
/ D3 Z9 B3 i4 B! Q; K4 U service:
+ f* n; M2 y- \1 t) R pipelines:
* q; f- z& @3 q* A4 T/ U) T traces:
u6 }0 \/ X# `* ]8 j receivers: [otlp] , L4 q+ A. c, i8 w
processors: [batch]
7 a) E a! q8 R M5 f- U; J' } exporters: [otlp]
3 }, n7 F& C* K8 T0 B4 s metrics:
$ n% C! Z. _0 i5 C, z% I0 R% V receivers: [prometheus]
* ~4 Z( P. p' K F processors: [batch] % h# j3 s2 f1 y; c+ Y+ R7 N
exporters: [prometheusremotewrite]
9 l- q5 z- m. n' _0 g9 C 使用 OTel Collector Contrib 进行 log 收集
9 O7 g9 f! h6 o- }$ O* w0 ~ 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
" f- ]1 {$ H% z: F& h; ^ receivers: & x' y6 E. e" ] P" k, C/ @! Z
filelog:
7 T8 h7 G+ t3 A( g9 b ^ include: [/var/log/app.log] _; N% ]4 y Q1 ]4 E3 i
exporters: ; t/ K% r+ S! j0 F* M4 L
loki: / W) Q7 i$ w" B9 e! G
endpoint: http://loki:3100/loki/api/v1/push
a+ X. r% E: k3 {1 @ tenant_id: demo - d8 P# i1 h |: H1 _+ N
labels:
: f Z6 i# }% l) _; \ attributes: & p- ~* p8 M& J& J& {
log.file.name: "filename"
7 `% g8 c* f* L/ F) u' w4 r6 Y9 j processors: 5 ~8 x9 b8 ?) m3 D
batch: 8 Y0 D( Z& E$ \, _# L1 S: J8 x: M$ V
service: 9 m2 v' Z( W2 M9 s5 y
pipelines: 8 P( z3 r+ X7 D! ?# `
logs: ; e# U( O, j1 l, N/ t7 J5 U, c! g
receivers: [filelog]
# O( `9 E8 Z( [3 F) C; u+ @4 ~ processors: [batch] L& e0 x" n% h- V3 f+ Z& x
exporters: [loki]
: }* i: r) n1 N0 t; |' z! h& a2 T* { 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
. Z h- S6 _3 r6 U$ | 总结
9 b6 q5 y( k3 G: ` 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 7 v9 |. G |3 k; D
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
" J! C4 R, r4 q: d; V& T. C
* a j5 \4 V) ~3 y. [8 P7 E 责任编辑: 1 s9 S4 B1 o' \/ v; P6 r
9 k0 S5 A" w: \( ? e6 f+ E J; a$ m! a8 |
3 N; p8 c X' F4 _/ k9 w* C
+ C3 C! O! l" V: f" E, X, M
|