|
0 ] F. J9 _) S1 Z" }
原标题:基于 Grafana LGTM 可观测平台的构建 + M; z _/ z" j- |
* \/ v6 F; p- R$ g' b
可观测性目前属于云原生一个比较火的话题,它涉及的内容较多,不仅涉及多种遥测数据(信号),例如日志(log)、指标(metric)、分布式追踪(trace)、连续分析(continuous profiling)、 事件(event);还涉及遥测数据各生命周期管理,比如暴露、采集、存储、计算查询、统一看板。
& |3 Q. ^: d; F5 F* w8 Z 目前社区相关开源产品较多,各有各的优势,今天我们就来看看如何使用 Grafana LGTM 技术栈(Grafana、Loki、Tempo、Mimir)快速构建一个自己的可观测性平台。
* z1 r+ h% W! e( \& h1 h. c 通过本文你将了解:
8 u) i& ^$ W ]. @/ y 如何在 Go 程序中导出 metric、trace、log、以及它们之间的关联 TraceID
5 g/ V4 b" Z6 ]2 a6 A! r+ ]; } |( s 如何使用 OTel Collector 进行 metric、trace 收集
# J- o$ F* z8 Y( c 如何使用 OTel Collector Contrib 进行日志收集 1 h& ` I5 P2 `4 w: e o8 w
如何部署 Grafana Mimir、Loki、Tempo 进行 metric、trace、log 数据存储 ) ^, Z/ |) Y6 I! U0 Y1 R0 s
如何使用 Grafana 制作统一可观测性大盘
& j! f. n; }/ y" U# A: C0 d 为了本次的教学内容,我们提前编写了一个 Go Web 程序,它提供 /v1/books 和 /v1/books/1 两个 HTTP 接口。 : V2 W& Z# F" w5 Q- m7 q! r1 ?3 D
当请求接口时,会先访问 Redis 缓存,如果未命中将继续访问 MySQL;整个请求会详细记录相关日志、整个链路各阶段调用情况以及整体请求延迟,当请求延迟 >200ms 时,会通过 Prometheus examplar 记录本次请求的 TraceID,用于该请求的日志、调用链关联。
* V2 a/ a6 s, R; @9 x* s# N5 {# d 下载并体验样例 ' z/ F0 S5 }( r
我们已经提前将样例程序上传到 github,所以您可以使用 git 进行下载: 8 y% i/ |0 h6 ]+ s6 U' _* r
git clone https://github.com/grafanafans/prometheus-exemplar.git A! K$ x" g% Z0 X% i
cd prometheus-exemplar
! M1 \) c" j4 r 使用 docker-compose 启动样例程序:
" b$ U; U7 [' q docker-compose up -d 2 M, t" M4 y7 g0 }
这个命令会启动以下程序: + ], q1 q |& s. z. b+ \/ h
使用单节点模式分别启动一个 Mimir、Loki、Tempo 0 V. g- i. M. o- ~% A2 f5 Z) ^
启动一个 Nginx 作为统一可观测平台查询入口,后端对接 Mimir、Loki、Tempo $ X; c! C3 c5 ]1 J5 Z) d
启动 demo app, 并启动其依赖的 MySQL 和 Redis, demo app 可以使用 http://localhost:8080 访问
; Q; O/ u' ^' @ 启动 Grafana 并导入预设的数据源和 demo app 统一看板,可以使用 http://localhost:3000 访问 ( Q* c- T- v! \
整个部署架构如下: . d/ W9 C/ X2 S1 @7 J# x
 5 ?4 |. T: D' H4 {0 w+ |
当程序部署完成后,我们可以使用 wrk 进行 demo app 接口批量请求: / i* l+ h" _; C4 D. o
wrk http://localhost:8080/v1/books
$ O4 ?! X9 g8 |' z3 k' t( d wrk http://localhost:8080/v1/books/1 4 u3 Q+ n- D6 i' \
最后通过 http://localhost:3000 页面访问对应的看板: & y- T. K" k* L9 F

: Y) x. `' C; K; \3 s 细节说明 # K2 K. O# f" A% ]& b5 S# l: `
使用 Promethues Go SDK 导出 metrics # D# |/ v7 s( l7 e2 }7 |
在 demo app 中,我们使用 Prometheus Go SDK 作为 metrics 导出,这里没有使用 OpenTelmetry SDK 主要因为当前版本(v0.33.0)还不支持 exemplar, 代码逻辑大致为:
! ?( C7 W2 P" w func Metrics(metricPath string, urlMapping func(string) string) gin.HandlerFunc <{p> httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts<{p> Name: "http_durations_histogram_seconds", : H3 z8 Q; m. P$ M5 E- X. B
Help: "Http latency distributions.",
- [$ N/ z5 B; }5 {* Z/ s1 w/ _ Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2}, * m2 ?2 Y3 o3 v! W/ m
}, []string{"method", "path", "code"})
# Q" r) T& x. r3 D prometheus.MustRegister(httpDurationsHistogram) % F" s2 Z7 Z8 x& N, u w. @: |' Y4 Y% n
return func(c *gin.Context) <{p> .....
1 j3 V1 V, F7 G' G1 i0 r observer := httpDurationsHistogram.WithLabelValues(method, url, status)
1 Q- ?/ C5 P5 [) o3 b observer.Observe(elapsed)
$ l; l* A5 i) v if elapsed > 0.2 <{p> observer.(prometheus.ExemplarObserver).ObserveWithExemplar(elapsed, prometheus.Labels<{p> "traceID": c.GetHeader(api.XRequestID), 6 j z, A, b+ e* }9 w& e
}) : {5 F4 Q9 C+ ~% Z4 n# c
} # L5 U% t+ u( p( V
}
0 u8 b/ K( I4 O- t } + C; K4 B0 ]5 X" c# p& X; ?5 y- i3 j
使用 OTLP HTTP 导出 traces
7 a* j/ }' j! Z( n! x. s 使用 OTel SDK 进行 trace 埋点:
: j% k, R* S2 L) E func (*MysqlBookService) Show(id string, ctx context.Context) (item *Book, err error) <{p> _, span := otel.Tracer().Start(ctx, "MysqlBookService.Show") 4 E1 c- m7 z3 D) P1 S6 h1 S
span.SetAttributes(attribute.String("id", id))
$ `* C: P. ?5 \2 E8 @6 m6 N6 S. s defer span.End() 5 m- o q6 Q( S# S* R7 h
// mysql qury random time duration
$ \9 b3 n7 V; F4 K# S9 A time.Sleep(time.Duration(rand.Intn(250)) * time.Millisecond)
0 u# c ?. u/ W5 h err = db.Where(Book{Id: id}).Find(&item).Error 2 @8 c$ g, f) l4 ]
return + \6 P" X* g1 M& p0 `5 \
} 3 m0 i% p0 D/ x6 A4 w7 f5 |4 ~
使用 OLTP HTTP 进行导出: 2 V9 W" P1 a: U% l) Q, E. K/ F, k+ c
func SetTracerProvider(name, environment, endpoint string) error <{p> serviceName = name
+ t2 y# s& j( `' J! k client := otlptracehttp.NewClient( 3 b! x1 g5 Q1 B+ T! A* W
otlptracehttp.WithEndpoint(endpoint),
2 L6 s$ K% D9 D, I; U, b otlptracehttp.WithInsecure(), $ P4 O) i* Q+ n! M
) , \6 t0 ^% d% O/ R, F$ m6 b
exp, err := otlptrace.New(context.Background(), client)
9 [- h q+ B( ~+ z if err != nil <{p> return err
! ?& P5 t0 j, x3 c5 E% U! B; c4 v } . T3 W6 O( M# {
tp := tracesdk.NewTracerProvider(
6 t3 G% u' {* c+ }8 {$ x tracesdk.WithBatcher(exp), & r$ O; }% Q6 ?' j
tracesdk.WithResource(resource.NewWithAttributes(
4 ?6 [" r5 I( b semconv.SchemaURL, ( o Z) |( H8 b
semconv.ServiceNameKey.String(serviceName),
5 a, F9 ~, }' z6 F0 s attribute.String("environment", environment), 5 J0 {7 b5 }5 [4 z; X: I& d4 @
)),
2 s& Q6 q: c6 `0 D" T- ^; B )
. I4 t& W/ P. G: r+ k9 N I; c otel.SetTracerProvider(tp) ) b6 L) S4 r1 U* z ?8 O( u( u
return nil
$ A( M' v' e6 k: p6 i6 k, } }
: x) e% v/ V8 K5 b* z 结构化日志 T7 h4 }: K5 l1 H/ {
这里我们使用 go.uber.org/zap 包进行结构化日志输出,并输出到 /var/log/app.log 文件,每个请求开始时,注入 traceID: 3 B5 c3 ?% B# }$ s- [3 j
cfg := zap.NewProductionConfig() 7 B0 g$ [- Q4 Z+ g
cfg.OutputPaths = []string{"stderr", "/var/log/app.log"}
4 h0 J7 R# L, @! s! v- u) _ logger, _ := cfg.Build() + K9 g! d# \: T4 b. N1 T7 F
logger.With(zap.String("traceID", ctx.GetHeader(XRequestID)))
8 x: K: r# N% @% |% R6 |1 d 使用 OTel Collector 进行 metric、trace 收集 0 a) K$ l1 a/ ]$ \# Y9 L
因为 demo app 的 metrics 使用 Prometheus SDK 导出,所以 OTel Collector 需要使用 Prometheus recevier 进行抓取,然后我们再通过 Prometheus remotewrite 将数据 push 到 Mimir。 ! u6 w% @" ]2 _
针对 traces,demo app 使用 OTLP HTTP 进行了导出,所有 Collector 需要用 OTP HTTP recevier 进行接收,最后再使用 OTLP gRPC 将数据 push 到 Tempo,对应配置如下: - b9 T# W. ?8 I3 B. i
receivers:
$ y7 G7 M0 f! q1 x# n otlp:
$ T6 D3 i1 X) Q' ^9 y, D+ X% W/ V# v protocols: ! z7 S. W) s' h( e5 G
grpc:
& j' {" m: Z9 w, m7 V' B* w http:
4 T; T x! }! o, Z; k prometheus: & v: \8 K, m9 r/ u: z" `. W$ ^
config: : ?$ `6 b0 |; U0 x" k
scrape_configs: 7 r8 q6 Q4 m0 |) w2 C" S% k9 U$ U
- job_name: app
3 ]* b7 y# j) Y scrape_interval: 10s
" v$ }- v; k, l* p static_configs: - A, u A) l6 m+ x) l
- targets: [app:8080]
* @+ v0 k/ Y) T: \. z, i! X7 d1 q/ C: G exporters:
; r! q6 `5 P' H otlp:
$ S- Q& p! |( W) w5 W endpoint: tempo:4317 / s# y1 d7 c: r$ c% d' {
tls:
" h( w* C; e% x insecure: true 8 o! L; Q: Y! }( J6 Q
prometheusremotewrite:
7 q, N$ g# w% D endpoint: http://mimir:8080/api/v1/push 1 Q9 N3 f: q7 F6 H% p% q" H& g- R" n/ v
tls:
4 v; V, P6 t3 `) M insecure: true 6 {4 j$ F+ ~: J% D- ^" c
headers: 4 k# j7 g5 e9 i+ C( t2 v( q% A9 |
X-Scope-OrgID: demo
9 E3 G7 n- X) k. i- a7 `+ s processors: ) X$ I y4 |' |0 } j
batch: 6 U) u" q! t9 f s" T" S
service:
+ P# }; K$ o- E% v pipelines: 6 l- J4 P- b9 Z5 W' F/ ~
traces:
- G3 F7 s2 H+ Z( { receivers: [otlp]
+ E" U3 X7 o C0 o5 a processors: [batch]
, M. Y1 T9 f) K M exporters: [otlp] 4 ?! n+ Q3 g0 N
metrics: ' h" ^5 T# a8 T5 v3 V+ `" B
receivers: [prometheus] 3 x) }; Q/ s0 ^2 P
processors: [batch]
" B/ P* F! K+ l [/ j* h4 a exporters: [prometheusremotewrite]
- U9 L4 i( U5 ]: u4 } 使用 OTel Collector Contrib 进行 log 收集
|4 A9 C. ~% c+ } l 因为我们结构化日志输出到了/var/log/app.log 文件,所以这里使用 filelog receiver 进行最新日志读取,最后再经过loki exporter 进行导出,配置如下:
" q9 O, i/ v9 E6 h5 N receivers: 5 [. S% G" \ v6 f! [
filelog:
3 z% W5 r9 G& j* F0 \; u$ v% f include: [/var/log/app.log]
8 n# z+ {1 c2 x$ @ exporters:
% J3 L) |- C" w9 p) V. e loki:
/ e6 v7 F& r( B2 v, v3 b endpoint: http://loki:3100/loki/api/v1/push
' ?7 X% W. ~* T, T; ^ tenant_id: demo
2 ?- U) m2 ]' X) O% k5 S: v- v labels:
7 p# Y# Q5 |" z3 N- T$ B( E( N attributes: + K6 b( W9 z" k ^
log.file.name: "filename" 2 R8 G; P5 D2 ?7 F7 H$ B$ J) n* R
processors: ! V' B0 S" @; U7 q3 N1 A5 A
batch: ' ^, i5 L* P* Q1 `' [, y
service: 0 i7 @5 T+ _9 i3 w1 k
pipelines: 3 q6 k F: ]" q( T8 W$ S. L
logs: 4 g/ @/ I: k. o' B
receivers: [filelog] % N6 ~3 E) i. i3 ]
processors: [batch]
6 a9 ]; b; z( r' m1 W% G; A exporters: [loki] " U. f4 D4 ^6 R" g' H8 ]
以上就是有关 demo app 可观测性与 Grafana LGTM 技术栈集成的核心代码与配置,全部配置请参考 https://github.com/grafanafans/prometheus-exemplar 。
; e4 N* t4 G' c& f 总结 ( |- [4 y: g/ h3 @( U
本文我们通过一个简单的 Go 程序,导出了可观测性相关的遥测数据,其中包括 metrics、traces、logs, 然后统一由 OTel Collector 进行抓取,分别将三种遥测数据推送到 Grafana 的 Mimir、 Tempo、Loki 进行存储,最后再通过 Grafana 统一看板并进行 metrics、traces、logs 关联查询。
# T9 g R1 o1 P5 y 这里关联的逻辑为使用 Prometheus 的 exemplar 记录采样对应的 traceID,然后通过该 traceID 进行相关日志和 trace 查询。返回搜狐,查看更多
0 I, H/ }1 q% D
1 O7 N2 I' v1 }) l 责任编辑:
+ c, v b# v* y% q# t
7 k' j1 Y9 T- {( \* L# w" u8 ?
3 ]& l. v/ n! c0 L+ a4 w1 P# B. C! z
{7 b; a F% b2 q |