|
0 e" X6 d* W9 i8 P
原标题:基于 Grafana LGTM 可观测平台的构建
\1 T& c8 W% j2 {4 b; ?
5 e# G+ A h. t 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 ; Q1 ?: M( j% a
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
3 l* F7 N6 r6 j y: B8 _ 通过本文你将了解:
, V/ t- O. [- h( M8 [. p. S 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
1 W; } P( |, K5 w; U: | 如何使用 OTel Collector 进行 metric、trace 收集 0 e5 n+ {/ o( p8 A
如何使用 OTel Collector Contrib 进行日志收集 % F$ r8 s2 g0 L( X" C6 R
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 1 c9 E3 ^3 j6 p( i6 A- u. C6 b
如何使用 Grafana 制作统一可观测性大盘 " m; p1 X' B: A) s; ^) Q
为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
" G1 J7 V% ~8 {# S0 m8 k2 u 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
7 F& W# L2 L3 a" r9 c; @ 下载并体验样例
6 ^& |' B7 N4 F& O 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载:
" J! {% g E8 v, u( t0 {5 b git clone https://github.com/grafanafans/prometheus-exemplar.git / I1 g& F1 S" P {* o% q& t7 R, Z
cd prometheus-exemplar
1 u0 M' N- W1 ]) N$ w2 ~ 使用 docker-compose 启动样例程序: : s7 Z' ]: z* v
docker-compose up -d # m1 F9 S# X! i$ h9 L" u, H
这个命令会启动以下程序: 7 A; l0 K, Q% n; h$ L3 U
使用单节点模式分别启动一个 Mimir、Loki、Tempo
8 r) w8 y" x3 {$ X7 H0 N 启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
" D# P: J6 M+ F+ ? 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
# I/ u) h3 C# _: q 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
# j2 Q f" `5 V 整个部署架构如下: 6 u& J( k" n: a) q2 c7 F: f
 ) z; s2 l7 V. g6 j/ ~8 _; ^) t4 \
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
# E: H5 `+ G C9 F wrk http://localhost:8080/v1/books 4 [7 s# j, N! a I5 b
wrk http://localhost:8080/v1/books/1 % ^! @% q1 X( d7 U: A; S- H
最后通过 http://localhost:3000 页面访问对应的看板: & x; Y, h* I! x" V( P
 : c1 v1 S& O$ B u9 _) L
细节说明 3 D/ g& n% \% O3 e, q# w. U
使用 Promethues Go SDK 导出 metrics
6 s8 J1 p7 i1 g! Z; z7 V 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为: 9 q2 Y! p' e( ^- i
func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
* U- w) w+ r& ?& m8 h Help: "Http latency distributions.", 3 ^, y( `: Y, u( g
Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, " i) x& p) W# a( a6 B
}, []string{"method", "path", "code"})
/ \: v/ t- d8 g% c# B1 h: q; } prometheus.MustRegister(httpDurationsHistogram)
! C3 S8 b( h$ j$ \ n! b2 f @: ^ return func(c *gin.Context) <{p> ..... + ^1 S# v9 I/ j; g
observer := httpDurationsHistogram.WithLabelValues(method, url, status) ( M2 o& c0 q& j3 c) Q8 H
observer.Observe(elapsed)
& P. W; _" t" M: v if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
7 P$ k* ] B/ z9 y/ V6 `1 v }) ; T3 b% l8 g( g; D( ^
}
4 D; ?- G, S# V7 v! [ }
1 M' Q6 d1 Z2 {2 f6 A; o/ Q } & ^ c9 n' _' ?3 e$ v! S
使用 OTLP HTTP 导出 traces
7 P4 H! _/ k7 _5 C; t! v 使用 OTel SDK 进行 trace 埋点: 4 b3 @. `* v# Z$ Y6 ^) L
func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") $ S$ A3 g8 A# ^* l& s
span.SetAttributes(attribute.String("id", id))
( O1 k7 | b2 |$ a) L; F$ j defer span.End()
$ l8 V/ k2 _4 O5 }; ?6 \$ m // mysql qury random time duration # s1 ~; t! ]3 R7 |/ e: j' z) g
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) 7 l3 n8 l) }0 V: H6 T8 T
err = db.Where(Book{Id: id}).Find(&item).Error . y N! }1 d" `" b& P
return
; D# \9 I, z# a6 ] N }
# \4 f) t- C$ T+ `; A 使用 OLTP HTTP 进行导出:
( d& [5 ~$ ~, t# r8 P func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name + d# M; Q: a g* i2 k4 {" m
client := otlptracehttp.NewClient(
& X6 `* B6 q( w) r) T otlptracehttp.WithEndpoint(endpoint),
+ C5 { G. a0 y5 x C! n otlptracehttp.WithInsecure(),
' j5 o: e) ^/ L: w( G; a( u4 ] )
" }+ _+ g @0 p. ?' o# y exp, err := otlptrace.New(context.Background(), client) 3 G8 [8 M; V8 g! y% s
if err != nil <{p> return err
* s9 h' B( z) I U2 t2 l3 t i* Y+ w } 4 N0 Z! _- k4 O% P) t/ R; v1 f
tp := tracesdk.NewTracerProvider(
- ~3 b. T7 A3 V% t7 w tracesdk.WithBatcher(exp), 9 } @# `! ~! s
tracesdk.WithResource(resource.NewWithAttributes(
2 d1 T ~; J( ^& M2 L semconv.SchemaURL, ' N- c8 X: T j( R
semconv.ServiceNameKey.String(serviceName), ! x% T( ?: t( {3 K, i$ e
attribute.String("environment", environment),
3 |3 N/ E( X* I9 Z2 Z. j& R) e0 [ )),
/ E9 h( M3 c4 }: ^: c ) - F' P5 D' _5 }" W( Y( _; Z
otel.SetTracerProvider(tp)
7 Y- T$ o2 A' x# P3 |4 B return nil ; [! A2 `% Y( u+ J2 Z6 ]5 R
} ! w! p& E" {% I8 q
结构化日志 - n$ W6 W7 t0 u
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: 8 ~) _0 P1 E4 h% |4 k; K4 C* [
cfg := zap.NewProductionConfig()
% G# E, ?) }7 W" ]2 Y% P( C& ? cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
8 G! j d j# L) c1 h# g logger, _ := cfg.Build() & B1 M9 J& ` S" ?( m; K7 B* w
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
4 O( P% J' j' @( h: d3 h: f2 n 使用 OTel Collector 进行 metric、trace 收集 ' f( f+ a8 l9 g! [* o
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 : \. y# R( q- u: d- n# U/ V/ H
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: $ J1 G0 v; _5 T& \5 I/ Z' ^" S
receivers: 7 }1 _8 y/ H$ f# @
otlp:
6 G4 m# z1 n9 q- f# d protocols:
. |2 G1 X# W- B/ T grpc:
8 X; s" l3 i3 u1 S0 e6 B2 l http: % u/ c5 c9 O* a
prometheus:
2 p) j* O6 |8 L: q1 m3 N3 E8 v config: % W8 _% z9 o5 K" v) N+ |' N
scrape_configs: ; x( |8 W6 T% {2 f: o4 o
- job_name: app 1 G E+ v4 E% P8 v6 z! t+ @' P% q
scrape_interval: 10s 1 L" G) l2 J" w% S
static_configs:
4 X% b% Q% J7 `( _7 V - targets: [app:8080]
1 o8 p2 k, m& s. z8 O3 L' } exporters: ' N; |2 ?9 W. D! U& P. r4 R& M* p
otlp: k1 h( U3 Y: U( f" ^: u
endpoint: tempo:4317
q: l8 p. z6 [5 p3 f6 j tls: 4 {9 }* \$ l3 d
insecure: true ! t6 B$ L! ]$ L; _! w2 Q
prometheusremotewrite: 5 k/ y- ^/ F7 n
endpoint: http://mimir:8080/api/v1/push / l+ ?% w. w5 N1 Z
tls:
# X* c) p% [2 U insecure: true % E, B' B/ I/ n! |2 |- o
headers: ' r9 t& j g Z: C& p; B! y
X-Scope-OrgID: demo
. r) I' i5 V4 G processors: : n: E1 K' q7 r2 y! h! w* R( }! q
batch:
( _3 r# e9 V9 R6 O service:
7 U' ?' B' @2 `2 ^ pipelines:
0 g- s1 C& x$ r+ B4 P traces: " V, j. E L2 }. A! @+ f% [
receivers: [otlp]
1 H' l- h0 ^. c% a% d* N/ R processors: [batch] 1 H+ ^7 p* O. K+ k, D& V
exporters: [otlp] $ @- Z! l1 v* ^% g/ ^/ |
metrics: 6 v6 r8 e% X& W V/ C
receivers: [prometheus]
) p; A* `) `: }' x0 P processors: [batch] / V( {1 f5 K! v0 M8 T. d+ U( s
exporters: [prometheusremotewrite] * r% |# H1 M# \6 s
使用 OTel Collector Contrib 进行 log 收集
6 I# b' g# Z" l* G$ B1 M" D 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下: C) o3 ?- Z# X1 x, i* j8 i. C( S
receivers: / q- V5 C1 ]* q/ z9 I3 k4 c
filelog: - C; b8 O5 \3 z
include: [/var/log/app.log]
9 `* Y& \8 g. B3 L [ exporters:
' k) X% t |5 b( ~( y& R# Q0 E loki:
& G ]" ^ a8 W8 T endpoint: http://loki:3100/loki/api/v1/push
4 X ~( ] g1 I7 x+ r( k& |% | tenant_id: demo
. ^- @0 I$ c- o% Z labels:
9 r5 `6 }$ z; U' U attributes:
# [7 |0 L5 B) \9 f/ h! T+ y4 s log.file.name: "filename" 4 n: i3 X8 t( S5 n; ^; L
processors:
; l' n( t+ M. o; o" L2 g0 t0 u batch: " e0 G% Y. r. a& G, R, o+ s$ b
service: + a$ `1 G8 I4 _1 J2 W6 m; K" _7 G
pipelines: % G. F {# z- D6 d& x
logs:
9 A) s! G# W q3 D( J% N2 f receivers: [filelog] b# @5 B1 G9 r; @+ l0 O8 d) E: L
processors: [batch] ( O$ D) M2 m" N. |* o# d6 Z
exporters: [loki] $ l/ b8 ^ b5 Z1 d; S
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 8 v. S. ~+ c. p5 \# c% d
总结 ) k; q$ G; o2 ~
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。 ! b7 T, d) J" [& x+ q% z Y8 r
这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
4 d3 Z4 T; r4 r G9 n1 C! j
8 j! d2 o& P& ^1 q9 H( P, ~ 责任编辑: 7 M! B! t! Z0 i) x5 r7 ]
2 p7 }5 t3 W" b' j7 j' }$ c0 c, z# V `; j" h& P% {1 ^8 z" h# Q
- R% i) U# L1 h- L0 ~- Q+ Q, q9 y$ M* f) N& z" c5 d( c" R7 q
|