文章目錄
- 云原生之開源遙測框架OpenTelemetry
- 背景
- 什么是可觀測性?
- 什么是 OpenTelemetry?
- Opentelemetry的主要優勢有以下幾點:
- 理解分布式鏈路
- 日志
- Spans
- 分布式鏈路
- 在 Gin 框架中使用 OpenTelemetry 進行分布式追蹤和監控
- 0. 整體思路
- 1. 初始化 OpenTelemetry
- 工作流程
- 2. 集成 Gin 和 OpenTelemetry
- otelgin 中間件的能力
- 關于otel-go-contrib-tracer
- 3. 手動創建 Span
- 4. 導出數據到
- 關于路徑:/api/v1/traces
- 其他參考
云原生之開源遙測框架OpenTelemetry
背景
在云原生時代,隨著系統應用的不斷復雜化和分布式,傳統的監控手段已經難以滿足需求。因此,OpenTelemetry這一開源遙測框架應運而生,它旨在提供一套統一的解決方案,幫助開發者和運維團隊全面了解系統性能狀況,迅速定位和解決問題。
什么是可觀測性?
可觀測性是通過檢查系統輸出來理解系統內部狀態的能力。 在軟件的背景下,這意味著能夠通過檢查遙測數據(包括鏈路、指標和日志)來理解系統的內部狀態。
要使系統可觀測,必須對其進行儀表化。也就是說,代碼必須發出鏈路、指標或日志。 然后,儀表化的數據必須發送到可觀測性后端。
什么是 OpenTelemetry?
官方文檔:https://opentelemetry.io/zh/docs/what-is-opentelemetry/
OpenTelemetry 是一個可觀測性框架和工具包, 旨在創建和管理遙測數據,如鏈路、 指標和日志。 重要的是,OpenTelemetry 是供應商和工具無關的,這意味著它可以與各種可觀測性后端一起使用, 包括 Jaeger 和 Prometheus 這類開源工具以及商業化產品。
OpenTelemetry 是云原生計算基金會 (CNCF)的一個項目,是由 OpenTracing 和 OpenCensus 項目合并而成的。原來這兩個項目都是為解決同樣的問題而創建的: 缺乏一種標準的方法來為代碼進行儀表化并將遙測數據發送到可觀測性后端。 由于這兩個項目都無法獨立解決這個問題,所以將其合并成立了 OpenTelemetry, 吸收了雙方的優勢,提供了統一的解決方案。
OpenTelemetry 不是像 Jaeger、Prometheus 或其他商業供應商那樣的可觀測性后端。 OpenTelemetry 專注于遙測數據的生成、采集、管理和導出。 OpenTelemetry 的一個主要目標是, 無論應用程序或系統采用何種編程語言、基礎設施或運行時環境,你都可以輕松地將其儀表化。 重要的是,遙測數據的存儲和可視化是有意留給其他工具處理的。
Opentelemetry的主要優勢有以下幾點:
支持開源通用標準:Opentelemetry使用開放式協議,使得開發人員可以更加靈活地根據需求選擇適合的實現方法,如Jaeger、Zipkin等。這有助于確保數據的一致性和互通性。
跨語言支持:Opentelemetry支持眾多編程語言,因此可以應用于任何一種混合語言的架構中。這使得多語言環境下的監控和觀測變得更加容易,降低了跨語言集成的復雜性。
統一的SDK和自動化埋點方案:Opentelemetry提供了一套統一的SDK,簡化了開發人員的工作。此外,它還支持自動化埋點方案,可以自動收集關鍵的性能指標,減少了手動配置的工作量。
數據采集和Traces/Metrics/Logs互通:Opentelemetry不僅關注跟蹤數據,還關注度量和日志數據。這使得開發人員可以從多個維度全面了解系統性能狀況,提高了問題的定位和解決效率。
理解分布式鏈路
分布式鏈路讓你能夠觀察請求如何在復雜的分布式系統中傳播。它提高了應用程序或系統健康狀況的可見性,并讓你能夠調試那些難以在本地重現的行為。對于分布式系統來說,分布式鏈路是必不可少的,因為這些系統通常存在不確定性問題,或者過于復雜而無法在本地重現。
要理解分布式鏈路,你需要了解其各個組成部分的角色:日志、span(跨度)和 trace(鏈路)。
日志
日志 日志是由服務或其他組件發出的帶時間戳的消息。與鏈路不同,它們不一定與特定的用戶請求或事務相關聯。在軟件中幾乎到處都能找到日志。長期以來,開發人員和運維人員一直依靠日志來洞察系統行為。
日志雖然有用,但僅靠它們來追蹤代碼執行還不夠,因為日志通常缺乏上下文信息,比如它們是從哪里被調用的。
當日志作為 span(跨度)的一部分,或者與 trace(鏈路)和 span 關聯起來時,它們的價值就會大大增加。
要深入了解日志以及它們與OpenTelemetry的關系,請參閱日志章節。
Spans
Spans
Span(跨度)是分布式鏈路中的基本構建塊,它代表了一個具體的操作或工作單元。每個 span 都記錄了請求中的特定動作,幫助我們了解操作執行過程中發生的詳細情況。
一個 span 包含名稱、時間相關的數據、結構化的日志消息,以及其他元數據(屬性),這些信息共同描繪了該操作的完整畫面。
分布式鏈路
分布式鏈路,通常簡稱為鏈路,記錄了請求(無論是來自應用程序還是終端用戶)在多服務架構(如微服務和無服務器應用)中傳播的路徑。
**一個鏈路由一個或多個 span 組成。第一個 span 被稱為根 span,它代表了一個請求從開始到結束的全過程。**根 span 下的子 span 則提供了請求過程中更詳細的上下文信息(或者說,構成了請求的各個步驟)。
如果沒有鏈路,在分布式系統中找出性能問題的根源將會非常具有挑戰性。鏈路通過分解請求在分布式系統中的流轉過程,使得調試和理解分布式系統變得不那么令人生畏。
瀑布圖清晰地展示了根 span 與其子 span 之間的父子關系。當一個 span 包含另一個 span 時,這種關系就表現為嵌套結構。
Trace:表示一個完整的請求路徑,包含多個 Span。
Span:表示請求中的一個操作(如 HTTP 請求、數據庫查詢)。
在 Gin 框架中使用 OpenTelemetry 進行分布式追蹤和監控
通過 OpenTelemetry,可以輕松實現 Gin 應用的分布式追蹤和監控,提升系統的可觀測性。
0. 整體思路
- 初始化 OpenTelemetry 的 TracerProvider 和 Exporter。
- 使用 otelgin.Middleware 自動追蹤 Gin 請求。
- 可選:手動創建 Span 以增強追蹤信息。
- 將數據導出到 Jaeger 或其他后端系統
運行和測試
- 啟動 Jaeger。
- 編寫和運行 Go 程序。
- 訪問 Jaeger UI(
http://localhost:16686
),查看追蹤數據。
1. 初始化 OpenTelemetry
在 Gin 應用啟動時,初始化 OpenTelemetry 的 TracerProvider 和 Exporter。
package mainimport ("context""log""time""go.opentelemetry.io/otel""go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp""go.opentelemetry.io/otel/propagation""go.opentelemetry.io/otel/sdk/resource"sdktrace "go.opentelemetry.io/otel/sdk/trace"semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)func initTracer() (*sdktrace.TracerProvider, error) {// 創建 OTLP HTTP Exporterctx := context.Background()exporter, err := otlptracehttp.New(ctx,otlptracehttp.WithInsecure(),otlptracehttp.WithEndpoint("localhost:4318"), // OpenTelemetry Collector 的地址)if err != nil {return nil, err}// 設置 TracerProvidertp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter),sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL,semconv.ServiceName("gin-server"), // 服務名稱)),)otel.SetTracerProvider(tp)// 設置上下文傳播otel.SetTextMapPropagator(propagation.TraceContext{})return tp, nil
}
關鍵字:otlptracehttp.New
- TracerProvider 負責配置 Tracer 的行為,例如設置采樣策略、資源信息(如服務名稱)等。TracerProvider創建和管理 Tracer 實例。
- Exporter 是 OpenTelemetry 中用于將遙測數據(如 Span、Metric)導出到外部系統的組件。它負責將數據發送到后端系統(如 Jaeger、Prometheus、OpenTelemetry Collector 等)。Exporter將 Span 或 Metric 數據導出到指定的后端系統。
工作流程
- TracerProvider 創建 Tracer。
- Tracer 創建 Span。
- Span 完成后,TracerProvider 將 Span 數據傳遞給注冊的 SpanProcessor。
- SpanProcessor 將 Span 數據傳遞給 Exporter。
- Exporter 將 Span 數據導出到外部系統(如 Jaeger)。
SpanProcessor 是連接 TracerProvider 和 Exporter 的橋梁。它負責將 Span 數據從 TracerProvider 傳遞到 Exporter。
- TracerProvider 是追蹤數據的生產者,負責生成和管理 Span。
- Exporter 是追蹤數據的消費者,負責將數據發送到外部系統。
- 兩者通過 SpanProcessor 連接,共同實現分布式追蹤和監控。
2. 集成 Gin 和 OpenTelemetry
使用 otelgin 中間件自動追蹤 Gin 的請求。
package mainimport ("log""net/http""time""github.com/gin-gonic/gin""go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)func main() {// 初始化 OpenTelemetrytp, err := initTracer()if err != nil {log.Fatalf("Failed to initialize OpenTelemetry: %v", err)}defer func() {if err := tp.Shutdown(context.Background()); err != nil {log.Printf("Error shutting down tracer provider: %v", err)}}()// 創建 Gin 應用r := gin.Default()// 添加 OpenTelemetry 中間件r.Use(otelgin.Middleware("gin-server"))// 定義路由r.GET("/hello", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "Hello, OpenTelemetry!",})})// 啟動服務器if err := r.Run(":8080"); err != nil {log.Fatalf("Failed to start server: %v", err)}
}
關鍵字:otelgin.Middleware
在 Gin 應用中,通過 r.Use(otelgin.Middleware("service-name"))
注冊 otelgin 中間件。
TracerProvider 是生成 Tracer 的工廠,但 Tracer 實例 由中間件在需要時通過 TracerProvider.Tracer() 自動創建
otelgin 中間件 是 OpenTelemetry 提供的一個專門用于 Gin 框架 的 Instrumentation 工具。它的主要能力是自動追蹤 Gin 應用的 HTTP 請求,并生成相應的 Span 數據。
otelgin.WithTracerProvider(provider) // 將 TracerProvider 注入中間件 即 gin注冊 Provider
Tracer 由 TracerProvider 在需要時動態創建,中間件會自動處理這些細節。
中間件行為:otelgin.Middleware 會自動使用 TracerProvider 創建 Tracer.
otelgin 中間件的能力
中間件行為:otelgin.Middleware 會自動使用 TracerProvider 創建 Tracer,并在請求處理過程中通過 Span 上下文傳遞追蹤信息。
-
自動追蹤 HTTP 請求
功能:otelgin 中間件會自動為每個進入 Gin 應用的 HTTP 請求創建 Span。
追蹤內容:
請求的路徑(如 /hello)。
HTTP 方法(如 GET、POST)。
請求的狀態碼(如 200、404)。
請求的耗時(從開始到結束的時間)。
示例:
對于 GET /hello 請求,otelgin 會生成一個 Span,記錄請求的詳細信息。 -
上下文傳播
功能:otelgin 中間件會自動處理分布式追蹤中的上下文傳播。
場景:
如果請求中包含了 Trace ID 和 Span ID(例如通過 HTTP 頭 traceparent 傳遞),otelgin 會提取這些信息并繼續追蹤。
如果請求中沒有上下文信息,otelgin 會創建一個新的 Trace ID 和 Span ID。
作用:確保在分布式系統中,請求的追蹤信息能夠跨服務傳遞。 -
錯誤記錄
功能:otelgin 中間件會自動記錄請求中的錯誤信息。
場景:
如果請求返回了錯誤狀態碼(如 500),otelgin 會將錯誤信息附加到 Span 中。
如果請求中發生了 panic,otelgin 會捕獲 panic 并記錄到 Span 中。 -
與 OpenTelemetry 集成
功能:otelgin 中間件與 OpenTelemetry 的全局 TracerProvider 集成,使用其配置的 Tracer 和 Exporter。
作用:確保生成的 Span 數據能夠被 OpenTelemetry 的 Exporter 導出到后端系統(如 Jaeger、Prometheus)。
otelgin中間件在請求開始時調用TracerProvider的Tracer方法獲取Tracer實例。
關于otel-go-contrib-tracer
gin注冊otelgin.Middleware中間件時,會自動給 gin context的 key 為otel-go-contrib-tracer
存儲一個tracker實例,
value := ctx.Value("otel-go-contrib-tracer")
tracer, ok := value.(trace.Tracer)
如上,這個就可以直接拿到 tracker實例,然后可以調spanctx, span = tracer.Start(c.Request.Context(), spanName)
返回 一個包含span的context,以及span
3. 手動創建 Span
如果需要手動創建 Span,可以通過 otel.Tracer 實現。
r.GET("/manual", func(c *gin.Context) {// 獲取 Tracertracer := otel.Tracer("gin-server")// 創建 Spanctx, span := tracer.Start(c.Request.Context(), "manual-span")defer span.End()// 模擬一些操作time.Sleep(100 * time.Millisecond)c.JSON(http.StatusOK, gin.H{"message": "Manual Span Created!",})
})
總結:
通過ProviderOption注入的TracerProvider會在請求處理時自動創建Tracer實例并存入Context。
4. 導出數據到
japer 官方文檔:https://www.jaegertracing.io/docs/1.66/apis/
jaeger的本地搭建和快速可以參考本人文章Jaeger安裝和簡單使用
可以選擇將鏈路追蹤的數據發送至 Jaeger,通過 Jaeger UI 查看
使用 OTLP(OpenTelemetry Protocol) 導出器將追蹤數據發送到 Jaeger。
自 v1.35 以來,Jaeger 后端可以通過原生的 OpenTelemetry 協議(OTLP)接收來自 OpenTelemetry SDK 的跟蹤數據。現在不再需要為 OpenTelemetry SDK 配置 Jaeger 導出程序,也不需要在 OpenTelemetry SDK 和 Jaeger 后端之間部署 OpenTelemetry 收集器。
如果你希望使用 OTLP(OpenTelemetry Protocol) 導出器將追蹤數據發送到 Jaeger 或其他支持 OTLP 的后端(如 OpenTelemetry Collector),可以使用 go.opentelemetry.io/otel/exporters/otlp/otlptrace
包。OTLP 是 OpenTelemetry 的通用協議,支持通過 HTTP 或 gRPC 傳輸數據。
以下是使用 OTLP 導出器(支持 gRPC 和 HTTP)的示例代碼。
將 OpenTelemetry 的追蹤功能集成到 Gin 框架中,并使用 OTLP HTTP 導出器將追蹤數據發送到 Jaeger 或 OpenTelemetry Collector:
- traceotel 包實現
package traceotelimport ("context""log""github.com/gin-gonic/gin""go.opentelemetry.io/otel""go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp""go.opentelemetry.io/otel/propagation""go.opentelemetry.io/otel/sdk/resource"sdktrace "go.opentelemetry.io/otel/sdk/trace"semconv "go.opentelemetry.io/otel/semconv/v1.17.0""go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)// Register 創建一個 Gin 中間件,用于集成 OpenTelemetry 追蹤功能
func Register(appname string, endpoint string) gin.HandlerFunc {opts := []otelgin.Option{ProviderOption(appname, endpoint),PropagationExtractOption(),}return otelgin.Middleware(appname, opts...)
}// ProviderOption 初始化并返回一個 TracerProvider
func ProviderOption(appname string, endpoint string) otelgin.Option {return otelgin.WithTracerProvider(func() (*sdktrace.TracerProvider, error) {// 創建 OTLP HTTP Exporterctx := context.Background()exporter, err := otlptracehttp.New(ctx,otlptracehttp.WithInsecure(), // 使用非安全連接(無 TLS)otlptracehttp.WithEndpoint(endpoint), // 追蹤數據收集端點)if err != nil {return nil, err}// 設置 TracerProvidertp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter), // 使用批處理導出器sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL,semconv.ServiceName(appname), // 服務名稱)),)return tp, nil})
}// PropagationExtractOption 配置上下文傳播器
func PropagationExtractOption() otelgin.Option {return otelgin.WithPropagators(propagation.TraceContext{})
}
- 在 Gin 應用中使用 traceotel 包,并啟動一個簡單的 HTTP 服務器。
package mainimport ("log""net/http""github.com/gin-gonic/gin""your-project/traceotel" // 替換為實際的包路徑
)func main() {// 創建 Gin 應用r := gin.Default()// 注冊追蹤中間件r.Use(traceotel.Register("gin-server", "localhost:4318"))// 定義路由r.GET("/hello", func(c *gin.Context) {// 模擬業務邏輯c.JSON(http.StatusOK, gin.H{"message": "Hello, OpenTelemetry!",})})// 啟動服務器if err := r.Run(":8080"); err != nil {log.Fatalf("Failed to start server: %v", err)}
}
- Register 函數通過 otelgin.Middleware 將 OpenTelemetry 的追蹤功能集成到 Gin 框架中。
- ProviderOption 函數用于初始化 TracerProvider,并配置 OTLP 導出器。
- PropagationExtractOption 函數用于配置上下文傳播器。
- 為什么不需要指定路徑?
OTLP 是 OpenTelemetry 的通用協議,用于傳輸遙測數據(如 Trace、Metric、Log)。
它已經定義了默認的路徑:
gRPC:默認路徑是 /opentelemetry.proto.collector.trace.v1.TraceService/Export。
HTTP:默認路徑是 /v1/traces。
客戶端只需要指定 endpoint 的主機和端口(如 localhost:4318),路徑由 OTLP 協議自動處理。
- 使用 OTLP 導出器時,客戶端只需要指定 endpoint 的主機和端口,無需包含路徑。
- 為什么我使用 /api/v1/traces Jaeger可以收到
你使用 /api/v1/traces 時,Jaeger 仍然可以收到數據,是因為 Jaeger 的 HTTP 接收器支持多種路徑,包括 /api/v1/traces 和 /v1/traces。
Jaeger 的 HTTP 接收器默認支持以下路徑:
- /api/v1/traces:這是 Jaeger 的傳統路徑,用于接收追蹤數據。
- /v1/traces:這是 OpenTelemetry 的 OTLP 協議默認路徑。
當 Jaeger 啟動時,它的 HTTP 接收器會監聽多個路徑,因此無論你使用 /api/v1/traces 還是 /v1/traces,數據都能被正確接收。
- 為什么 /api/v1/traces 可以工作?
- Jaeger 的兼容性設計:
Jaeger 為了兼容不同客戶端(如 OpenTelemetry、Jaeger 原生客戶端等),支持多種路徑。
即使你顯式指定 /api/v1/traces,Jaeger 仍然能夠識別并處理數據。 - OpenTelemetry 的靈活性:
OpenTelemetry 的 OTLP 導出器允許你指定完整的 URL(包括路徑),例如 http://localhost:4318/api/v1/traces。
當你指定路徑時,OTLP 導出器會直接將數據發送到該路徑,而不會覆蓋默認路徑。
關于路徑:/api/v1/traces
不同的可觀測性后端如 Jaeger、Zipkin、Prometheus 都支持這一標準端點,實現了遙測數據的統一收集。
/api/v1/traces
是 Jaeger 的 HTTP 接收器的默認路徑,通常用于直接使用 Jaeger 客戶端的項目。/v1/traces
是 OTLP 接收器的默認路徑,是 OpenTelemetry 的通用協議。
推薦使用 OTLP,OpenTelemetry 正在逐步成為可觀測性領域的事實標準。因為它更通用且支持多種后端。如果你看到/api/v1/traces
,可能是因為項目直接使用了 Jaeger 客戶端或舊版配置。
Jaeger 提供了兩種主要的接收器來接收追蹤數據:
-
Jaeger 的 HTTP 接收器:
默認路徑為 /api/v1/traces。
這是一種專為 Jaeger 設計的接收器,主要用于接收 Jaeger 客戶端發送的 JSON 或 Thrift 格式的追蹤數據。
例如,Jaeger 的 Go 客戶端會通過 HTTP POST 請求將數據發送到 http:///api/v1/traces。 -
OTLP 接收器:
默認路徑為 /v1/traces。
這是 OpenTelemetry 的通用協議(OTLP),支持通過 gRPC 或 HTTP 傳輸數據。
使用 OTLP 時,客戶端只需要指定 endpoint 的主機和端口,路徑由協議自動處理。
我們可以參考阿里日志服務文檔 https://help.aliyun.com/zh/sls/user-guide/import-trace-data-from-opentelemetry-to-log-service
,可以看到使用的 /v1/traces
路徑。
接入點信息
-
HTTPS協議的接入點為${endpoint}/opentelemetry/v1/traces,例如https://test-project.cn-hangzhou-intranet.log.aliyuncs.com/opentelemetry/v1/traces。
-
gRPC協議的接入點為${endpoint}:10010,例如test-project.cn-hangzhou-intranet.log.aliyuncs.com:10010。
其他參考
通過OpenTelemetry監控gRPC調用
Monitor gRPC calls with OpenTelemetry - explained with a Golang example
https://dev.to/signoz/monitor-grpc-calls-with-opentelemetry-explained-with-a-golang-example-350o