|
0 {7 g8 ]- i; `6 G" V0 y3 I
原标题:基于 Grafana LGTM 可观测平台的构建
: ^9 ]- Q$ G! w M1 ]: r
9 x' x2 s5 S7 R4 ~ 可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。 ! q2 R2 [$ F2 N
目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。 7 u: {: L5 e9 u# m% u# B
通过本文你将了解:
! H q% O1 R: ~( \! q8 p1 g; | 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
) T) \6 ]; R7 _! n! ?- A- p 如何使用 OTel Collector 进行 metric、trace 收集 5 ?/ t# J4 W8 U
如何使用 OTel Collector Contrib 进行日志收集 & W/ _- v2 p5 l1 q. c4 a
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 0 Q6 h: K5 c% c) B
如何使用 Grafana 制作统一可观测性大盘
" {: W8 P0 r1 t- ^) B( W 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。
2 ^: T& i" L# ?5 ~7 l8 Z$ Y4 R 当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
9 @& \ T* V2 T9 {& x2 o* Z6 \ |2 D 下载并体验样例
0 t1 h9 P- [% T" S. D 我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: ! S. W0 f" ]. G* E. r8 w- G' {, J
git clone https://github.com/grafanafans/prometheus-exemplar.git
2 s8 \) h" y- J* v4 t2 y+ z cd prometheus-exemplar
{& w4 E5 Q+ O; T% h( F( d 使用 docker-compose 启动样例程序:
* G: q& M" q# [" f5 C* J3 j9 O docker-compose up -d 4 D, }* y; B& B) b3 ?' Q- S
这个命令会启动以下程序: - W0 K9 f2 h( Q* x# s& `! C+ ~0 c8 ^
使用单节点模式分别启动一个 Mimir、Loki、Tempo 3 B& w. |6 ~1 y
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo
! V, X' J# P6 {1 V# }8 I/ |. H 启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
! i+ r6 |) r( g+ d4 C6 Y4 E0 K% W 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问
* h6 f2 ? A; v1 `' @5 _ 整个部署架构如下:
% Z) ]" q0 {5 I* R8 \9 y* M* [  : H3 E% G' k% x% B5 v1 x6 Q3 J, X# {
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求:
- x! ~" ~3 ~/ U5 Y' D w6 ]; s8 ] wrk http://localhost:8080/v1/books 9 [6 m: f b3 t; a8 l
wrk http://localhost:8080/v1/books/1 $ P: w: d1 F( \/ D
最后通过 http://localhost:3000 页面访问对应的看板:
+ ]# Q) L5 z d: w" ^. c3 I 
5 B' O# o/ j+ A6 D 细节说明
) i! b. D2 Y* l% q1 s; v& v 使用 Promethues Go SDK 导出 metrics
& ?9 k& v1 h0 c7 S; \0 ? 在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
6 `" ^$ S- I& d4 `: p- U* f func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds",
. s2 x% f' o V+ q r: E Help: "Http latency distributions.",
( F7 T6 e" n4 U& o' b! E" X4 N Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2},
* C1 |$ k O# } }, []string{"method", "path", "code"}) % H( |+ c% s9 `2 K+ R5 s
prometheus.MustRegister(httpDurationsHistogram)
- B8 q, ]6 @9 [4 y5 r$ U: u7 D return func(c *gin.Context) <{p> ..... % p: m6 f A4 }" h0 g4 \
observer := httpDurationsHistogram.WithLabelValues(method, url, status) , T# U, S$ l8 S' {6 X& \1 j' V2 |
observer.Observe(elapsed) 2 k$ n' c9 o2 T( f" f( y
if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID),
6 F {, p1 ^; L0 E })
4 O3 `- n6 `4 i } : O, J: h; \/ x) s6 n# g
} - e* F5 O7 R f( w1 j$ C
} 5 P; Q2 ]/ E6 Y0 h, y; t
使用 OTLP HTTP 导出 traces
3 C- \9 p- _$ n7 @ 使用 OTel SDK 进行 trace 埋点:
" y3 m: {/ M& Z) B func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") " g, v ]& s9 J$ A |
span.SetAttributes(attribute.String("id", id))
3 f8 a. n2 i+ L$ Q; P defer span.End()
. Q- g1 c3 u0 P# G8 F# m) `+ s // mysql qury random time duration + M' e3 z. f5 @* a1 I# a
time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond) / \. @$ \+ `1 q- Q+ r) [4 T
err = db.Where(Book{Id: id}).Find(&item).Error : ~5 ?1 b; T( y9 y
return
& u) Z' ~+ B/ p0 z5 Z; F }
$ q- O5 v5 E" ]: S2 J4 g& A 使用 OLTP HTTP 进行导出:
# ~$ q" A0 e/ r$ K# F" m func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name & p3 R7 Z# a3 E) f9 i; x) A
client := otlptracehttp.NewClient( x; u& B5 W0 h% E! `, f
otlptracehttp.WithEndpoint(endpoint), ' d" i9 r/ @" {# v0 U
otlptracehttp.WithInsecure(), 5 |7 j$ p9 G! D8 F; u# L# o$ j
) 3 i' ]5 q* }7 F
exp, err := otlptrace.New(context.Background(), client) . e" m7 s/ u$ P' Q# x# p$ U2 F* s
if err != nil <{p> return err
' C# E2 [4 n8 k }
5 Q h. x4 r2 A3 l5 r( j tp := tracesdk.NewTracerProvider( - ~- z" f4 e' Z: _- b- X/ j ^( ?5 o
tracesdk.WithBatcher(exp),
) M* P+ P9 s* |) e+ O tracesdk.WithResource(resource.NewWithAttributes(
" _+ v, @3 ^5 H- T9 O' G) A' s semconv.SchemaURL,
# N: q# o$ ^: `9 q- q' M5 \ semconv.ServiceNameKey.String(serviceName),
2 T* u% H2 l3 i' s7 d4 b attribute.String("environment", environment), , {; r9 v9 q0 Q3 T t1 x g
)),
# a$ W( Z9 g6 x+ ?# L+ D! ~1 S )
; J! t% A$ [6 j otel.SetTracerProvider(tp) , I7 q- Y4 N& g2 F, `4 G8 ~
return nil
4 }5 L; X* F* Y8 R B } 1 [* w( w$ o0 p& B% n
结构化日志 ) z) N1 B+ m0 E; V; d
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID:
+ j* \1 k! A; i# V: X cfg := zap.NewProductionConfig() ! D) X" O# @; o5 G7 x
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
6 Z5 i: s( Y/ M% i( n logger, _ := cfg.Build()
; F; a3 e, m( l$ O' F' k logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
. ~% v/ ^- j! Y4 ]. R! X 使用 OTel Collector 进行 metric、trace 收集 % d7 s$ t, T6 D
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 % w3 S5 t0 z$ D% u
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: ' F# Q- ?' f+ L$ e% |4 F" V
receivers:
! R$ ^. W Y! e otlp: 7 i5 O5 Z9 \# E; o# c- y
protocols: " {, G5 z' B. v3 d' i5 w
grpc: + u s: L% y* w s+ _2 @7 u/ r
http:
) T0 @, p2 N$ N% ]$ N! h8 R, \5 l prometheus:
& Y; l# X1 ]# a$ W7 K" A config:
$ e2 @" m, F `+ g" F scrape_configs: 5 w8 F2 n# K B" N6 p# K
- job_name: app 0 X4 z% S4 K* M; q1 U" Z
scrape_interval: 10s 7 i- z) E$ ^& s8 W. |* v5 l
static_configs:
7 v6 E7 Y! k( `% a1 w - targets: [app:8080] : m& E0 b3 s; Q. }% b
exporters: 6 w, Q& Y! V/ B( }, t4 v
otlp:
2 A5 V- f& {2 k: N$ Z$ g endpoint: tempo:4317 : }' R" `* o& V/ F- w {
tls: * i' X9 K" w3 H3 `% k
insecure: true
( b; j3 R/ `' ?4 y/ @. Q- D! h. S prometheusremotewrite:
- H+ S+ h/ R: V: @& C endpoint: http://mimir:8080/api/v1/push 1 c d B1 n- i$ J- Y
tls:
6 l3 M2 Q6 A/ i' B7 l! e6 A. A I! Q" U insecure: true ( F# M( ?) t+ p2 a9 E- ]
headers:
& l1 ]9 r0 l6 w X-Scope-OrgID: demo 0 y" Z. j! @+ S0 j- @3 I; @% {+ P! Y. R
processors: 1 b$ g& U! j9 U2 A* x
batch:
- f. j6 V! I+ T: K: Y7 y" j9 k service: ! ~) w y9 t8 L( U' Q
pipelines: ! s. m8 `2 t, `8 J: c- j
traces: ) J& [' \3 G! U$ D
receivers: [otlp] # u# f) K( B; e, O+ Y9 [6 G
processors: [batch] # A( I( D0 O9 @$ c/ g- Q( H, ^
exporters: [otlp] & o+ r5 l+ h! `- o) p1 y
metrics: 9 |" I0 r$ Z; V3 U: W) {
receivers: [prometheus]
5 L% J) R, |* o a( M# r processors: [batch]
: m5 {- I Y- R exporters: [prometheusremotewrite] 9 M: }/ t- m/ x8 @, D
使用 OTel Collector Contrib 进行 log 收集
5 I/ C, S# H- j5 v& } 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
: F0 {7 C3 n' V1 y1 x' G% I, l receivers: 6 B# t2 Q' X, U
filelog: , p _$ s8 Y T: E0 f, [' }
include: [/var/log/app.log]
# [+ Y1 G0 t5 b% m% U ~( G* o exporters:
" P. J# p. ]" ?* b( M loki: 5 d& r; ^: t0 J3 t# A' V+ z
endpoint: http://loki:3100/loki/api/v1/push
4 g4 F4 @5 ^# k1 d8 d) z/ ~! A7 V- X# A/ n tenant_id: demo
9 K9 v* l% u& S labels: 7 N3 Y1 a* l3 j+ t7 `
attributes: % k) U, N- W8 L: ], {: o4 c% c
log.file.name: "filename" ' ^6 M0 M, X; P" @0 Q
processors:
9 a8 _0 s' i$ V4 b* V( Y batch: & i6 B, \, F7 _( h! `7 N
service: - t! Y$ U+ ^6 k
pipelines:
: `- Z0 V4 G& ]3 H+ v logs: + w6 y$ O; r- p9 @
receivers: [filelog] ! [: B% @6 i; P; I
processors: [batch]
0 t/ }8 q9 t$ _/ [0 x exporters: [loki]
. K/ V8 Y2 l e0 v3 N( v+ j 以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。 * x2 `- ^4 k: g: ~+ Q
总结 ; l* q; C; g7 c
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
( n/ I' D' h* b3 j; E! ?3 D 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多 5 l) _2 _9 ^0 ^2 H! p
! C- a. l$ D A0 ?1 t" @0 T
责任编辑: 1 Q4 F6 _: @1 v2 ]* K; b7 x( K
9 r5 Z2 Y$ a& P1 x% `1 h" z9 q5 i9 U0 }: T- E/ _5 c
& j% ?8 M9 H% s9 m q
( m4 u' p) t% d& `3 z8 j( i6 T8 _ |