|
( p! w$ R8 T' _) x; n
原标题:基于 Grafana LGTM 可观测平台的构建
) ?0 _0 Y% y- v! S7 c7 U
- H- f5 |. I: N& \ W" V 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
/ a- p' |! U, b+ g2 c 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 , O8 u: h' N4 J o) J
通过本文你将了解:
3 {# z0 D9 f9 O: F 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID ( |% r- m$ G3 B- O5 V
如何使用 OTel Collector 进行 metric、trace 收集 7 Q' H5 C8 X1 u. M3 H
如何使用 OTel Collector Contrib 进行日志收集
: b( }2 C' C) C. ^5 c2 r7 ^+ o 如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 * N: e. d: v, V7 u8 o
如何使用 Grafana 制作统一可观测性大盘 1 W3 T' ^1 A W& q1 \& K
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
T+ n0 Y( B0 u2 k8 l( i 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
L8 n/ |; L7 \: L4 H3 X1 I2 [ 下载并体验样例 : L7 E v" h5 O* Y
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
, [6 n. w8 j& e7 ]( s git clone https://github.com/grafanafans/prometheus-exemplar.git ; L. {4 w0 D6 a1 ?
cd prometheus-exemplar 2 K4 i3 E& R, H' U! U
使用 docker-compose 启动样例程序:
# j4 P+ a1 X2 e docker-compose up -d 2 C' q& c2 [- Z' j
这个命令会启动以下程序: . @1 ]' W/ K7 Q, d d6 l" V
使用单节点模式分别启动一个 Mimir、Loki、Tempo
/ X; S8 n* O& ]9 J* ?3 R( @8 k: P 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
7 ], \4 M! d8 k3 v 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
1 j3 V+ P+ F# Z P( s- v1 @0 J4 t4 s 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
X# u1 M$ c3 |1 H4 h( u2 T# S 整个部署架构如下:
' s$ o8 U, J f; W% c# z& [ 
# p9 ?+ m! E7 Y% w 当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
; R0 b' k4 U5 G v7 T wrk http://localhost:8080/v1/books
- r. D( R% S! k; a wrk http://localhost:8080/v1/books/1
/ C" L* l9 l$ j) a5 @' Q' o) j- M* \0 L! S! @ 最后通过 http://localhost:3000 页面访问对应的看板: & A7 O& S2 c! M: f8 N
 % O* [& z: A; b% c" z
细节说明 : r2 P2 O5 l: f# P, ]
使用 Promethues Go SDK 导出 metrics
7 A, s0 a4 I$ L8 W 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: / x: n! \! n; N' \# E. x) T3 I9 L
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
* G0 h& L2 ]$ x/ i2 J, K Help: "Http latency distributions.", . w* U. w K6 X$ z/ i( j
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, * K8 S; k, B) Z7 v% n; U
}, []string{"method", "path", "code"}) ) e* n E% O4 w; E! l9 s- j% K
prometheus.MustRegister(httpDurationsHistogram) ' l, Y# M; I& ^% s
return func(c *gin.Context) <{p> ..... ; v6 F9 H& ]! x
observer := httpDurationsHistogram.WithLabelValues(method, url, status) ; O$ o/ ~ u6 p" L8 w4 a9 ]
observer.Observe(elapsed) % S$ K) i/ v' r$ w3 {
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), 0 Z9 B# v2 s* K0 T: q) y
})
8 U* I7 M0 |6 {" L2 V1 M4 z) S. \ } ( y* d+ r7 J8 o8 { R- G! l, ~
} # s) v z/ C% Z
}
' _* }0 M) ]$ x7 { 使用 OTLP HTTP 导出 traces
) O7 ]2 i/ R9 z) }1 D 使用 OTel SDK 进行 trace 埋点: 8 n5 c6 P) f' P5 C# v, N$ W
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show")
& D. S8 a" U* j J& k span.SetAttributes(attribute.String("id", id))
& C. f) `5 w0 s4 o+ a defer span.End()
/ u7 R8 ]* W' W/ S // mysql qury random time duration ! j) {3 Y; K/ k' s# Q
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 9 c7 ~$ g! t' G
err = db.Where(Book{Id: id}).Find(&item).Error 7 H9 E# G) I8 |
return - ?0 q& }0 ]/ m
} " Z6 x1 H. p7 N- S- Z [7 r
使用 OLTP HTTP 进行导出: 4 l( C$ m! T1 Z8 z% { @6 W
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name $ |3 X! K: E% A
client := otlptracehttp.NewClient( $ [$ i8 g/ S2 \
otlptracehttp.WithEndpoint(endpoint),
9 l, u. a) R: b) U8 q$ ~/ U0 J otlptracehttp.WithInsecure(),
; l. M s2 L) \5 g ) - _/ C6 q+ r2 j% N( i
exp, err := otlptrace.New(context.Background(), client) 8 _4 [( |' F/ F) `, `1 f5 A; |
if err != nil <{p> return err
" w c% T! J) X( [& w) } }
" h" {/ z+ [( @ tp := tracesdk.NewTracerProvider( ; }' n$ L) ?. S) S6 A( w
tracesdk.WithBatcher(exp),
4 T! V5 a8 T" v0 f! q, V, V4 D tracesdk.WithResource(resource.NewWithAttributes(
' P7 j- t* k& b% o semconv.SchemaURL, 2 D# E+ g/ [* }( P
semconv.ServiceNameKey.String(serviceName),
! o/ Q4 t+ N) a7 R0 \ attribute.String("environment", environment),
% Q" k; j2 V+ u! S1 z* P )),
& n. R0 O. ~' c# h8 B9 O' u1 U ) $ c( f$ S9 Q, x8 k# {
otel.SetTracerProvider(tp) 8 r% k, S, B, E: c7 u' L" q& \
return nil 9 v+ L8 j6 a+ p, r( m, {# j
}
" V T( H) @$ W 结构化日志 # t1 ^) @# f7 b# [* ~! L
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
0 L0 k' \; a% e$ _: t, l1 \& T: v cfg := zap.NewProductionConfig() 9 h4 D6 A$ \# z: `( y* r; O9 }
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
' I. ~% C# k; G+ E/ _# u' Z7 { logger, _ := cfg.Build()
, ]. O# R+ W6 W0 { logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
, t. ~% e# d! \9 R. N) S2 p0 t 使用 OTel Collector 进行 metric、trace 收集 % v: {4 r- R: @5 Y0 w
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。
: T* p, f; k$ a6 J: e" S2 }+ I 针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: 1 E Z1 |) B+ h; `: _. T
receivers:
o- Q7 J. b" r' T4 P otlp:
4 Y6 w- [$ E( y2 Y! V% Z protocols: $ X1 S7 \+ D9 v, N9 R
grpc:
4 p: H; e- O* H+ Z http:
& i0 L- B/ l. m4 C prometheus: ' `. p3 ^2 o- B4 I
config: 4 f4 {& z$ o0 w( F6 K7 t
scrape_configs: # j% L$ g# M r" {" q
- job_name: app 8 m, S+ T' O2 \+ L; X. a7 u( B
scrape_interval: 10s 2 w. z4 t7 G. w8 [/ U4 f0 B
static_configs:
. C, b8 Z3 d; @ - targets: [app:8080] & v. l( x/ m& L+ p& n! O2 Y" j
exporters: % }. [" d7 \; ~2 f. E
otlp: 9 l( j! A/ k, q% W# B' M+ R
endpoint: tempo:4317 0 k( t$ S7 z! c0 o
tls:
) C' p8 l. A z+ X1 L insecure: true
# x$ i- i- n. ?2 e4 o prometheusremotewrite: ( z) q, w0 z, P& _/ T& i/ I
endpoint: http://mimir:8080/api/v1/push ' o# O2 o5 p1 n2 n3 I6 F' q
tls:
4 N5 v% o% t( r insecure: true
. ~7 ^. [! i6 X: j9 S headers:
. o1 t* t0 b% n1 N' v* t X-Scope-OrgID: demo + U/ x; V) J" p+ U
processors: & [3 [0 T7 N' V
batch: 5 L2 p* ~9 i# m
service:
$ b/ b5 ~" n5 a5 i4 e) U+ \) ? pipelines: ( S) z5 Z" `( E5 T4 m# g7 R
traces:
0 {: a% N/ x0 v5 W. ] receivers: [otlp] + p+ S) @3 I$ {* x/ ]( I
processors: [batch] # n" C' x) l. }7 n* j
exporters: [otlp] + l8 {# b; ?1 U+ r; j$ r" Y
metrics:
# D: L# k6 p) I8 ^$ Y1 L receivers: [prometheus]
% L W: V$ P: J: Z processors: [batch] & O1 r" K" c+ R- N* v
exporters: [prometheusremotewrite]
) o7 W+ Q8 ?: s1 n( l 使用 OTel Collector Contrib 进行 log 收集
; Q7 o/ A% @+ | t! |( c 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
3 m# v1 [8 O1 P0 }! {- I receivers: ) X3 e, X+ N! R: A D" j
filelog: 7 |7 v6 ~5 }, O# s' v6 n, \
include: [/var/log/app.log] ' H7 m9 {% Y; }
exporters:
$ e1 q: [' b, t loki: $ F' a! D/ e+ m9 b; [
endpoint: http://loki:3100/loki/api/v1/push
9 C' f8 l) U% f tenant_id: demo
: m) p: z8 ]; S5 Q- Y+ ~ labels: : m3 [1 n: O3 S
attributes: ! k% L7 F/ f6 B1 I# L; m
log.file.name: "filename"
% E! J( a! [+ l8 J* c t processors: 2 N4 m9 }3 S: p
batch: ; E- G' Q+ b8 Q# e7 u" \( L: j
service: $ M/ h" y6 q5 ^' l$ b: ~! D* c( k
pipelines: ( S X* c+ t( `
logs: & G5 k. e8 X# C
receivers: [filelog]
) a) M3 l h* J y8 o$ Q$ _ processors: [batch]
+ W; G) j3 q- u' I exporters: [loki]
* i O; _' U8 f8 t 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
1 |/ K) ^/ ~9 ]3 q! T& N; ` 总结
* R5 l5 c6 A, U) E' _ 本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 : @" k% r- E! O: s
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 + L2 O/ a. \1 Z% R
- ]' P; i9 g. m' x( V* {1 k3 j
责任编辑:
T& r a" X8 ]+ X
& W% w) d1 P3 V3 I$ D
; N$ G, a' ^; \5 L
1 ^' N$ o1 C7 \
2 F, J" ^% ~9 h3 ^5 o( \# V; V& C4 I |