|
( W4 z" x3 \8 W5 q 原标题:基于 Grafana LGTM 可观测平台的构建
* ]. f6 T8 x5 x' O, {" l+ X9 n6 e; e* {9 S# P( Q
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
& P/ r" V# f# P. T0 @ 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 , Q: ^% [3 {' `2 X& r! m p; S
通过本文你将了解: 5 K3 }9 M i6 p+ @
如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID : U/ i7 E2 i2 |$ }- y1 e$ U5 }
如何使用 OTel Collector 进行 metric、trace 收集 # N# r% j8 {" L$ Z! O9 h$ K
如何使用 OTel Collector Contrib 进行日志收集
1 q. w! \3 x) |/ z" i( _; p 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 # E1 w" g; n, V5 h3 x# l
如何使用 Grafana 制作统一可观测性大盘 & \/ \0 N4 _; V
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
4 A6 P6 s( t2 t3 ^6 T7 z6 S 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
5 H# w4 n* Q+ H2 T 下载并体验样例
' [4 G q8 ]% b5 k 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
6 k) h7 h$ n% u9 } n git clone https://github.com/grafanafans/prometheus-exemplar.git
# Q/ z5 J- y6 W cd prometheus-exemplar
7 r9 H8 R1 T% n9 ~4 F; P 使用 docker-compose 启动样例程序:
% W% h( {$ ~- Y+ v docker-compose up -d 0 _! S. X/ V g! D" r: N# G
这个命令会启动以下程序: * I! ]# z# E1 `( D2 H
使用单节点模式分别启动一个 Mimir、Loki、Tempo
5 D& r: V+ A+ N' q5 H: \8 M 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo : K( M3 }0 b: {' r8 u+ K
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
4 b& L7 P* l$ U8 ?0 ` 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
$ J; J* I% M* d4 o 整个部署架构如下: $ X& |2 p+ u8 `* i* Z
 2 p- H" _9 S$ C5 }
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: * X$ G8 }6 B+ S) L+ j F
wrk http://localhost:8080/v1/books ; X3 K- r# W) ?! J/ _
wrk http://localhost:8080/v1/books/1
* K1 K' b% a* k4 J3 D" Q* p! [ 最后通过 http://localhost:3000 页面访问对应的看板: ) z' E; C- a& r# f3 E

) [. {$ L P& W* N6 i1 b6 x 细节说明 5 J' p4 d+ A# H) {% ?
使用 Promethues Go SDK 导出 metrics # O& k5 C/ K" C/ r. v9 d& m
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: * ^7 ~! S: l# d- J) O8 }6 ?2 R
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
_9 ?8 h; Y! x* G' D0 D Help: "Http latency distributions.",
8 Z, J3 h; q% H0 d3 F. r) p Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, & _* n* x6 p9 E0 S8 v7 p& C
}, []string{"method", "path", "code"}) # P# I( R( a8 x( f
prometheus.MustRegister(httpDurationsHistogram) ' e- r q, J4 }; @
return func(c *gin.Context) <{p> ..... ' }4 L/ F& {( X, [; k
observer := httpDurationsHistogram.WithLabelValues(method, url, status)
' {/ M. N1 a0 r) Q observer.Observe(elapsed) _8 q6 K8 w1 e5 S8 N2 M+ s" M
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), 7 q! |* r$ {* S1 D1 S- M
}) 6 z/ h& H1 W5 G$ m" d4 k+ `0 b! W6 c G
} : U7 ^9 ?4 K, I! S; H6 t! |! n
} 2 [# o- s, a/ u+ s4 U
}
- w4 A8 u" c+ z 使用 OTLP HTTP 导出 traces
5 O4 g. T* c0 D! t 使用 OTel SDK 进行 trace 埋点: , x' P& k& ]* J2 I; @( x
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") ( [: c8 e$ {7 ]5 p2 J, i7 ?
span.SetAttributes(attribute.String("id", id)) 7 S: W$ [# _$ u4 `# Z1 U7 N3 O
defer span.End()
; \" s' ?9 j2 a5 i8 P // mysql qury random time duration 5 y0 Q. C3 s8 ]9 w5 |
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 8 t# s3 o3 f# T' X" L* \* I
err = db.Where(Book{Id: id}).Find(&item).Error # ]6 u+ W) r. s& R" \
return
. M. f4 c( Z# G1 T7 ^$ }$ u }
* }& @5 `7 f( i+ L4 g 使用 OLTP HTTP 进行导出:
# \% Y2 i( D4 ?( b/ p! x7 Q, R func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name 9 i, X) w# O0 E, ^6 k
client := otlptracehttp.NewClient(
/ S- f( w* y" E2 A otlptracehttp.WithEndpoint(endpoint),
; a4 Q1 J7 ?& V, h+ R5 D/ ~ otlptracehttp.WithInsecure(), 0 r1 q! }# E5 t7 g" f0 B
)
3 X- @4 d! U) @) S% ^ exp, err := otlptrace.New(context.Background(), client) # k4 F3 }& D$ q
if err != nil <{p> return err
- r# r1 G/ y; h5 } } 5 x: [: A( S7 g% H2 M% @$ R
tp := tracesdk.NewTracerProvider( # O0 O- s0 v) ^: D' h& m! p
tracesdk.WithBatcher(exp),
3 Q% P9 y5 H; t* w3 B6 M: N* I tracesdk.WithResource(resource.NewWithAttributes(
4 `/ I* B! |& f& m- q6 z semconv.SchemaURL,
4 K& C, Y5 V( v" B3 @0 p$ i semconv.ServiceNameKey.String(serviceName), 7 A& y) v4 B0 G1 y( h& B9 y3 p
attribute.String("environment", environment),
# Q7 a% `4 h; `/ u; t' q )),
& m% o* T. x$ l )
: n8 U* g8 f' q1 v5 m otel.SetTracerProvider(tp) . D9 |0 H# N' V! B5 o/ @
return nil ' M7 v9 H* D( C$ E. G
} ( m* k/ M$ }+ _& s( s. l
结构化日志
, |6 O8 U: _( \3 I9 G% N 这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
/ p$ u, A3 {7 }- z, Q7 ` cfg := zap.NewProductionConfig() * G6 E1 @9 D) L
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"} 3 L6 M0 S7 e) A+ b
logger, _ := cfg.Build()
% {1 ]& H" f/ @# u5 Y. y logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
' R' s/ E- R" f W 使用 OTel Collector 进行 metric、trace 收集
! y9 [1 f: T8 B- B5 n- w' D" s 因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 ; ~( ^. {& x; r
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下:
9 ~, {+ s% ?5 u receivers:
' L4 K; _; H g5 r otlp: 3 M5 s+ M& F0 c' }! g
protocols: 1 d7 z, m v+ w9 [" u: t" k$ H$ ?
grpc:
$ l5 b+ l' d5 M" A http: $ z5 `3 W8 N6 c) h0 D$ y
prometheus: + _; K0 j1 g) g/ ?5 I# q( V/ p
config:
o# {0 T4 X1 B, b8 P5 k! ?3 X scrape_configs: 9 R9 O) S# C7 n5 L; b8 m
- job_name: app
`' w! X9 \) ? }4 ^ scrape_interval: 10s
- Y$ A8 A/ s3 p0 h% A static_configs: : B" p$ Q. a: N( g( y
- targets: [app:8080]
/ C4 U& \' a ?/ [9 F exporters:
b( {$ b# D: a8 P5 m% H9 p" f B0 x otlp:
) ~8 e0 R$ O2 ]. w6 X8 } endpoint: tempo:4317 + l) g; E; @4 a
tls:
( j% }) ~) n+ U! ]- |2 B3 b insecure: true + h/ z, r& S) w) d0 I& N
prometheusremotewrite:
N+ M/ T3 }8 }. ^7 ] endpoint: http://mimir:8080/api/v1/push 4 m! n% U2 w n6 ]) n/ s
tls: , W8 Z3 J' D" W2 J
insecure: true 3 T* x; |2 t! B
headers:
- I" H0 w( w( h X-Scope-OrgID: demo
6 S8 u9 ?, n2 S' w) ~/ v+ m$ j processors: ( b5 ]3 G. {$ j+ x
batch: 8 P' v! F" i0 u9 s8 b
service:
3 Q8 n3 d) G1 ]" y: g; g1 M. N( e" I pipelines: # _; R/ R& V# Y0 W- w
traces:
* v3 |' g. D' g4 C8 K& J" o& Y receivers: [otlp]
7 l7 D5 z1 g4 R3 H% i# z; u processors: [batch] ' r& L! J, j# m O: s
exporters: [otlp]
) A0 j4 i5 m1 C2 _4 V# e metrics: 1 N2 V2 v- B7 `' x% k7 [9 f
receivers: [prometheus] 0 O# q1 u8 g* F. Y! j
processors: [batch]
! [4 _* e W. Q1 e! n! b% f/ ] exporters: [prometheusremotewrite] 0 n) M. R6 P1 ]6 a, c
使用 OTel Collector Contrib 进行 log 收集
$ \! @# ] T, i: G, K 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: * v/ z# I, {, h- Y+ P
receivers: & D% s+ g) q. @2 g
filelog: 2 N+ ^; _- Q" R. A8 m$ J
include: [/var/log/app.log]
2 M7 S( E- o( O" j. Q1 ~3 k exporters: * E- U) c) Q E: v. ^! S3 \/ X
loki: * ` b& B- D1 a+ M/ R) N+ m9 u
endpoint: http://loki:3100/loki/api/v1/push
s: v# A+ Q& e E" I- y tenant_id: demo # g" ^2 U* Y9 f
labels: " k' j7 D6 v7 P4 b
attributes:
2 `5 z# F6 ~& a& g6 U log.file.name: "filename"
* |1 F: I: e" y) c: ?6 {) X processors:
( H. m/ e& o; O" R1 s batch: ! ?2 n$ ?% Y0 b8 c( O+ R( K
service:
: m1 p' v! j$ k pipelines:
p% C' |& K# A% } logs:
% p( L% \5 l* s/ W& {: G receivers: [filelog] * @, Q) ^0 Y# x' a [2 V- Y
processors: [batch] . L* w; m) \% V, W
exporters: [loki]
' F( r/ V+ l! E6 b! x1 H- o3 @% h 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 + X; \8 ]9 ^+ _ w4 q) O
总结 ) Q7 ~7 a0 y( C
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
& c- K$ @ T- l1 ~9 s 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 - k+ \: I( ]' h: q
; m# }% b) f v" S* e1 `) G+ B 责任编辑:
( D( }- C' C. |* F1 U$ d2 e ?$ C
0 Y2 k3 E: Q& M6 r( g( C+ }% G b, i2 I5 e+ b1 d
& A: m( n$ ^7 w7 s4 q1 @/ j$ F& }
|