Opentelemetry 實踐分享 - Golang 篇
OpenTelemetry
Opentelemetry 是一個 CNCF 社區下一個開源的可觀測性框架,或者也可以說是一組工具、API 和 SDK 的集合,來檢測、生成、收集和導出可觀測性數據(指標、日誌和鏈路),以幫助我們分析軟件的性能和行爲。
優點
過去,檢測代碼的方式會有所不同,因爲每個可觀測性後端都有自己的檢測庫和代理,用於向工具發送數據。
這意味着沒有用於將數據發送到可觀察性後端的標準化數據格式,由於缺乏標準化,最終結果是缺乏數據可移植性和用戶維護儀器庫的負擔。
Opentelemetry 因此而生,擁有來自雲提供商、 供應商和最終用戶的廣泛行業支持和採用,提供了:
-
每種語言都有一個獨立於供應商的 instrumentation library ,支持自動和手動。
-
可以以多種方式部署的單個供應商中立的收集器二進制文件。
-
生成、發出、收集、處理和導出遙測數據的端到端實現。
-
完全控制您的數據,能夠通過配置將數據並行發送到多個目的地。
-
開放標準語義約定以確保與供應商無關的數據收集
-
能夠並行支持多種 上下文傳播 格式,以協助隨着標準的發展進行遷移。
缺點
有別於 Istio ,它並不是一個開箱即用的工具,也是更有侵入性的,但是根據我們的經驗:
越不具侵入性的工具,就越無法做出更深更廣的觀測
我們爲了獲取更深、更廣的指標,勢必要侵入性地進行觀測,因此,採用 Istio envoy 提供的指標是不夠的。而此時,Opentelemetry 正在逐漸形成行業標準,受到許多供應商支持,是我們一個很好的選擇。
OpenTelemetry 架構
如上圖所示,整體的組織架構實際可以理解爲兩部分:
- 將可觀測性數據 (trace, metric, log) 全部導出(push)到
otel collector
,無論你是通過什麼形式,來自什麼組件,如:
-
從項目代碼通過
otlp
協議導出 -
語言:go, java, python...
-
集成方式: auto/manual instrumentation, api, sdk
# example config for otel collector's receivers
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
-
通過基礎設施 (本質上還是通過應用程序導出)
-
k8s
-
aws
-
others...
-
通過其他服務,直接將一些服務數據導出到
otel collector
,如 -
prometheus
-
jarger
-
others...
- 將不同類型的數據按需求導出 (push or pull) 到具體的可觀測性工具,如
-
metrics 指標可以導出至監控服務 (如通過 prometheues)
-
trace 指標可以導出至鏈路追蹤服務 (如 jaeger)
-
log 指標可以導出至日誌服務 (如 loki)
# example config for otel collector's exporters
exporters:
jaeger:
endpoint: jaeger-operator-jaeger-collector.observability:14250
tls:
insecure: true
loki:
endpoint: http://localhost:3100/loki/api/v1/push
prometheus:
endpoint: 0.0.0.0:8889
resource_to_telemetry_conversion:
enabled: true
項目組織結構
Opentelemetry 項目組織結構繁多而複雜,官方共有 59 個 repo,但我可以大致按以下結構進行梳理:
首先,Opentelemetry 提供了官方的opentelemetry-collector
,作爲整個項目的核心倉庫,用以整和所有可觀測性指標,也整合了opentelemetry-collector-contrib
提供的第三方服務,這兩個項目統一構成collector
,但是作爲開發者,我們不需要過多關心。
然後,針對不同的語言,基本每種語言都提供了三個倉庫作以下用途:
-
核心倉庫 (黃色): 提供該語言的基礎 SDK,爲
instrumentation
和contrib
倉庫提供接入的統一標準,通過這個倉庫,你也可以在不使用以下兩個庫的情況下接入 opentelemetry。 -
instrumentation(綠色): 特定的語言實現,通過它,你可以在不甚瞭解 otel 的情況下,實現一體化、開箱即用地、一鍵地爲你的工程引入 opentelemetry。
如
opentelemetry-java-instrumentation
可以直接以java -javaagent:path/to/opentelemetry-javaagent.jar \ -jar myapp.jar
的形式接入 opentelemetry。
-
contrib(藍色): 提供一些爲第三方庫以相對便捷的形式接入 Opentelemetry 的庫。
如
opentelemetry-go-contrib
提供了針對gin
,beego
框架等第三方庫接入 opentelemetry 的便捷方法。
Golang 實踐指南
Trace(stable)
初始化
我們需要構造一個全局的TraceProvider
,下面的例子構造的 provider 採用的 http exporter
,即將 traces 通過 http 協議發送給指定的opentelemetry-collector
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
exp, err := otlptracehttp.New(ctx)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exp),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
return tp, nil
}
注意:
- 全局
TraceProvider
通過otel.SetTracerProvider()
設置,獲取時,也可直接調otel.GetTracerProvider()
。
我建議大家直接設置爲全局的,而不是作爲局部變量傳來傳去的一個好處是,當我們引用了第三方庫,它通常也會默認使用全局的 provider,這樣就能簡單的保證我們一個程序只有一個 provider,也就是說,只會把數據發送到一個 collector。
- 初始化的過程中,不需要指定
opentelemetry-collector endpoint
等配置,我們統一通過環境變量注入。如:
-
otlptracehttp.WithEndpoint()
=>OTEL_EXPORTER_OTLP_ENDPOINT
-
otlptracehttp.WithInsecure
=>OTEL_EXPORTER_OTLP_INSECURE
支持的環境變量:
-
基礎配置: https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/
-
導出器: https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
採樣器
Go SDK 提供了幾個基本的採樣器:
-
AlwaysSample()
: 全部採樣 -
NeverSample()
: 全部丟棄 -
TraceIDRatioBased(fraction float64)
: 設置採樣率 -
ParentBased(root Sampler, samplers ...ParentBasedSamplerOption)
: 基於 parent span 設置採樣策略
除此之外,根據Sampler
接口:
// Sampler decides whether a trace should be sampled and exported.
type Sampler interface {
// DO NOT CHANGE: any modification will not be backwards compatible and
// must never be done outside of a new major release.
// ShouldSample returns a SamplingResult based on a decision made from the
// passed parameters.
ShouldSample(parameters SamplingParameters) SamplingResult
// DO NOT CHANGE: any modification will not be backwards compatible and
// must never be done outside of a new major release.
// Description returns information describing the Sampler.
Description() string
// DO NOT CHANGE: any modification will not be backwards compatible and
// must never be done outside of a new major release.
}
我們可以編寫自己的採樣器,eg:
import (
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
// kubegems sampler, ignore samples whitch contains "kubegems.ignore" attrbute.
type kubegemsSampler struct{}
func (as kubegemsSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
result := sdktrace.SamplingResult{
Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(),
}
shouldSample := true
for _, att := range p.Attributes {
if att.Key == "kubegems.ignore" && att.Value.AsBool() == true {
shouldSample = false
break
}
}
if shouldSample {
result.Decision = sdktrace.RecordAndSample
} else {
result.Decision = sdktrace.Drop
}
return result
}
func (as kubegemsSampler) Description() string {
return "KubegemsSampler"
}
使用採樣器時,我們需要注意以下問題:
假如有兩個服務爲 A,B, 調用關係爲 A -> B, 我們想要爲其設置採樣率爲 50%,怎麼設?
直接爲兩個服務都設置
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.5))
這樣設置後,A 的採樣率自然是 50%,但 B 的採樣率並不會成了 25%,測試發現它仍然是 50%。我們可以查閱設計文檔:
The
TraceIdRatioBased
MUST ignore the parentSampledFlag
. To respect the parentSampledFlag
, theTraceIdRatioBased
should be used as a delegate of theParentBased
sampler specified below.
也就是說,它只會根據 parent span 來決定是否被採樣
-
使用
ParentBased
採樣器(最好的方法)sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.5))),
ParentBased Sampler
顯式地配置有parent span
情況下地採樣策略,默認情況下使用如下策略:func configureSamplersForParentBased(samplers []ParentBasedSamplerOption) samplerConfig { c := samplerConfig{ remoteParentSampled: AlwaysSample(), remoteParentNotSampled: NeverSample(), localParentSampled: AlwaysSample(), localParentNotSampled: NeverSample(), } for _, so := range samplers { c = so.apply(c) } return c }
以
remoteParentSampled: AlwaysSample()
爲例:它是說,默認情況下,如果這個 span 來自遠程的parent span
,而且parent spane
已經被採樣了,那麼,這個 span 也會被採樣。我們也可以調整
ParentBasedSamplerOption
參數,eg:sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.5), sdktrace.WithRemoteParentSampled(sdktrace.NeverSample()))),
它表示,當
parent span
被採樣時,自己不採樣,當然,這是不合理的。
埋點
我們可以在想要記錄 trace 的地方,通過tracer.Start()
創建一個新 span 來埋點。
當然,在 span 中,我可以主要可以添加以下幾類信息:
-
SetAttributes
: 設置一些屬性 (記錄爲 tag) -
AddEvent
: 添加事件 (記錄爲 log), 通常用來記錄一些重要操作 -
SetStatus
: 設置 span 狀態。
// get user name by user id
func getUser(ctx context.Context, id string) (string, error) {
// start a new span from context.
newCtx, span := tracer.Start(ctx, "getUser", trace.WithAttributes(attribute.String("user.id", id)))
defer span.End()
// add start event
span.AddEvent("start to get user",
trace.WithTimestamp(time.Now()),
)
var username string
// get user name from db, if you want to trace it, `WithContext` is necessary.
result := getDB().WithContext(newCtx).Raw(`select username from users where id = ?`, id).Scan(&username)
if result.Error != nil || result.RowsAffected == 0 {
err := fmt.Errorf("user %s not found", id)
span.SetStatus(codes.Error, err.Error())
return "", err
}
// set user info in span's attributes
span.SetAttributes(attribute.String("user.name", username))
// add end event
span.AddEvent("end to get user",
trace.WithTimestamp(time.Now()),
trace.WithAttributes(attribute.String("user.name", username)),
)
span.SetStatus(codes.Ok, "")
return username, nil
}
屆時,span 大概長這個樣子:
另外,關於 span 的父子關係,是通過 context 上下文來傳遞的。
在tracer.Start(ctx context.Context, ...)
中,如果傳入的 ctx 中沒有 span,那麼返回的就是root span
;如果有,那返回的就是該 span 的子 span。
因此,我們能通過 context 串聯起清晰的鏈路調用,但也因此,我們需要非常關注 context 的使用。
跨進程傳播
Openletemetry 提供 propagator
在進程間交換的消息中讀取和寫入上下文數據的對象,詳見 https://opentelemetry.io/docs/reference/specification/context/api-propagators/
Openletemetry 實現了兩種 propagator API:
-
TraceContext
: 用以傳播traceparent
和tracestate
信息來保證一條 trace 的調用信息不會因爲跨進程而中斷 -
Baggage
: 用以傳播用戶自定義信息
propagator
實現兩個方法:
-
Inject(ctx context.Context, carrier TextMapCarrier)
: Injects the value into a carrier. For example, into the headers of an HTTP request. -
Extract(ctx context.Context, carrier TextMapCarrier) context.Context
: Extracts the value from an incoming request. For example, from the headers of an HTTP request.
TraceContext
使用 TraceContext 在下游Inject
和上游Extract
來打通服務間調用鏈路, eg:
- 設置 propagater:
otel.SetTextMapPropagator(propagation.TraceContext{})
- client:
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
func DoRequest(){
...
req, err := http.NewRequestWithContext(ctx, method, addr, body)
// inject to http.Request by propagator to do distribute tracing
otel.GetTextMapPropagator().Inject(req.Context(), propagation.HeaderCarrier(req.Header))
http.DefaultClient.Do(req)
...
}
- server:
import (
"go.opentelemetry.io/otel/propagation"
)
func HandleRequest(){
...
// extract from http.Request by propagator to do distribute tracing
ctx := cfg.Propagators.Extract(req.Context(), propagation.HeaderCarrier(req.Header))
ctx, span := tracer.Start(ctx, spanName, opts...)
defer span.End()
req = req.WithContext(ctx)
...
}
如果你想了解更多關於TraceContext
的信息,可以閱讀文檔:https://www.w3.org/TR/trace-context/,因爲它遵從W3C Trace Context format
標準。
Baggage
使用 Baggage 在進程間傳遞信息,在使用它之前,我們需要弄清楚兩個問題:
- 爲什麼我們需要 Baggage?
-
在整條 trace 中傳播信息
-
假如我們希望將應用程序中的信息附加到一個 span, 並在稍後檢索該信息,然後將其用於另一個 span。由於 span 一經創建就不能修改,而 Baggage 允許通過提供一個存儲和檢索信息的地方來解決這個問題。
-
Baggage 應該用來做什麼?
Baggage 應該用於我們可以向第三方公開的非敏感數據,因爲它與當前上下文一起存儲在 HTTP 標頭中。
建議用來傳播包括 ** 帳戶標識、用戶 ID、產品 ID 和原始 IP ** 等內容。將它們向下傳遞之後,我們就可以將它們添加到下游服務中的 Span 中,以便在在可觀察性後端中進行搜索時更輕鬆地進行過濾。
比如說,在 kubegems 中有兩個服務:api
和agent
,以一次用戶請求獲取 k8s 資源爲例:
-
api: 解析用戶 token,校驗用戶信息,再交給
agent
獲取對應集羣的 k8s 資源 -
agent: 不再處理用戶信息,直接調用 k8s api 並返回
在這種情況下,假如我們想要在 agent 的 trace 信息中,知道這個請求時哪個用戶發起的,就可以藉助 baggage 來實現:
首先,初始化TextMapPropagator
時,需要加上Baggage Propagator
:
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
然後,在api
向agent
發起請求時,注入user name
的baggage
:
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/baggage"
)
func DoRequest(){
...
userBaggage, err := baggage.Parse(fmt.Sprintf("user.id=%d,user., user.ID, user.Username))
if err != nil {
otel.Handle(err)
}
req, err := http.NewRequestWithContext(baggage.ContextWithBaggage(ctx, userBaggage), clientreq.Method, addr, body)
if err != nil {
return nil, err
}
otel.GetTextMapPropagator().Inject(req.Context(), propagation.HeaderCarrier(req.Header))
http.DefaultClient.Do(req)
...
}
最後,在agent
解析 baggage 並設置爲 attributes:
import (
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/baggage"
)
func HandleRequest(){
...
// extract from http.Request by propagator to do distribute tracing
ctx := cfg.Propagators.Extract(req.Context(), propagation.HeaderCarrier(req.Header))
ctx, span := tracer.Start(ctx, spanName, opts...)
defer span.End()
reqBaggage := baggage.FromContext(ctx)
span.SetAttributes(
attribute.String("user.id", reqBaggage.Member("user.id").Value()),
attribute.String("user.name", reqBaggage.Member("user.name").Value()),
)
req = req.WithContext(ctx)
...
}
如果你想了解更多關於Baggage
的信息,可以閱讀文檔:https://www.w3.org/TR/baggage/,因爲它遵從W3C Baggage format
標準。
理解 propagator
無論是TraceContext
還是Baggage
,在我們選用的TextMapPropagator
中,都是採用TextMapCarrier
來實現
// TextMapCarrier is the storage medium used by a TextMapPropagator.
type TextMapCarrier interface {
...
}
而TextMapCarrier
,目前的唯一實現是HeaderCarrier
:
// HeaderCarrier adapts http.Header to satisfy the TextMapCarrier interface.
type HeaderCarrier http.Header
也就是說,不管我們採用http
還是grpc
協議,只要我們採用TextMapPropagator
,實現信息傳播的,是 http 協議 header。
我們可以通過 Debug 來追蹤這一過程,首先, 在client
端的Inject
方法打上斷點,觀察它是怎麼把要傳播的信息注入進去的:
可以看到,注入前 context 已經帶有了user.id
和user.name
信息,然後下一步:
通過把 ctx 帶的信息注入進headr
, 此時請求的Header
中已經帶有了Traceparent
和Baggage
信息。
然後我們在server
端的Extract
方法打上斷點,觀察它是怎麼解析出傳播的信息的。
很顯然,它通過從client
請求的 header 中提取Traceparent
來獲取traceID
和spanID
, 來關聯上下游,再提取Baggage
來獲取來自client
的信息。
其他形式的 propagator
對基於 http 協議的進程間通信,我們使用TextMapPropagator
完全足夠,但如果說要針對沒有HeaderCarrier
實現的通信協議,官方有計劃開發binary propagator
來實現, 詳見 https://github.com/open-telemetry/opentelemetry-specification/issues/437
Metrics(alpha)
由於 opentelemety go 標準庫的 metric 實現還是 alpha,極不穩定,文檔幾乎沒有,請謹慎使用。
初始化
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/metric/global"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
)
func initMeter(ctx context.Context) (*sdkmetric.MeterProvider, error) {
exp, err := otlpmetrichttp.New(ctx)
if err != nil {
return nil, err
}
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp, sdkmetric.WithInterval(15*time.Second))))
global.SetMeterProvider(mp)
return mp, nil
}
要注意的配置主要是NewPeriodicReader()
, 它用來設置我們收集並向opentelemetry collector
發送指標的時間間隔。
在 kubegems 上,我們的opentelemetry collector
使用的是pometheus exporter
來導出監控指標,並設置有30s
的scrape_interval
,因此,我們這裏的WithInterval()
最好是小於30s
以保證監控數據的及時性。
使用
以下的示例是 kubegems 爲gin
框架添加的metrics
實現,參照了net/http
的opentelemetry
實現(https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/net/http/otelhttp),記錄了兩個指標:
-
http.server.request_count
: 請求總量 -
http.server.duration
:請求耗時(ms)
import (
"time"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric/global"
"go.opentelemetry.io/otel/metric/instrument/syncfloat64"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
)
// Server HTTP metrics.
const (
RequestCount = "http.server.request_count" // Incoming request count total
ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds
)
const (
instrumentationName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
var (
counters map[string]syncint64.Counter
valueRecorders map[string]syncfloat64.Histogram
)
func MeterMiddleware(service string) gin.HandlerFunc {
counters = make(map[string]syncint64.Counter)
valueRecorders = make(map[string]syncfloat64.Histogram)
meter := global.MeterProvider().Meter(instrumentationName)
requestCounter, _ := meter.SyncInt64().Counter(RequestCount)
serverLatencyMeasure, _ := meter.SyncFloat64().Histogram(ServerLatency)
counters[RequestCount] = requestCounter
valueRecorders[ServerLatency] = serverLatencyMeasure
return func(c *gin.Context) {
requestStartTime := time.Now()
attributes := semconv.HTTPServerMetricAttributesFromHTTPRequest(service, c.Request)
ctx := otel.GetTextMapPropagator().Extract(c.Request.Context(), propagation.HeaderCarrier(c.Request.Header))
c.Next()
// Use floating point division here for higher precision (instead of Millisecond method).
// 由於Bucket分辨率的問題,這裏只能記錄爲millseconds而不是seconds
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
counters[RequestCount].Add(ctx, 1, attributes...)
valueRecorders[ServerLatency].Record(ctx, elapsedTime, attributes...)
}
}
Log (not implemented yet)
opentelemetry 目前還未針對 go 有相關的實現。
但是,假如我們的應用運行在kubegems
上,其中的日誌收集、查詢功能本身就提供了相關的能力,所以在官方的標準推出之前,我們也可以先通過span.SpanContext().TraceID()
獲取trace-id
,自行在日誌中打印trace-id
,來實現trace-log
關聯。
下面以 gin 和 beego 框架爲例,簡單講解一下:
gin 可以添加個打印日誌的middleware
:
func logMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
ctx := otel.GetTextMapPropagator().Extract(c.Request.Context(), propagation.HeaderCarrier(c.Request.Header))
span := trace.SpanFromContext(ctx)
c.Next()
statusCode := c.Writer.Status()
logrus.WithFields(logrus.Fields{
"method": c.Request.Method,
"path": c.Request.URL.Path,
"trace-id": span.SpanContext().TraceID(),
"code": statusCode,
"latency": time.Since(start).String(),
"sampled": span.SpanContext().IsSampled(),
}).Info(http.StatusText(statusCode))
}
}
beego 可以添加個filter
:
beego.InsertFilter("*", beego.BeforeRouter, func(c *bcontext.Context) {
ctx := otel.GetTextMapPropagator().Extract(c.Request.Context(), propagation.HeaderCarrier(c.Request.Header))
newctx, span := tracer.Start(ctx, "getUserFromBaggage")
defer span.End()
logrus.WithFields(logrus.Fields{
"method": c.Request.Method,
"path": c.Request.URL.Path,
"trace-id": span.SpanContext().TraceID(),
"sampled": span.SpanContext().IsSampled(),
}).Info("handle request")
reqBaggage := baggage.FromContext(newctx)
span.SetAttributes(
attribute.String("user.id", reqBaggage.Member("user.id").Value()),
attribute.String("user.name", reqBaggage.Member("user.name").Value()),
)
c.Request = c.Request.WithContext(newctx)
})
Kubegems 接入 Opentelemetry
假如我們的應用程序,已經在代碼層面接入了 opentelemetry,我們只需要爲其添加幾個環境變量(爲統一 kubegems 上應用程序的接入,不建議修改):
- name: OTEL_K8S_NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
- name: OTEL_K8S_POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: OTEL_SERVICE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.labels['app']
- name: OTEL_K8S_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: OTEL_RESOURCE_ATTRIBUTES
value: service.name=$(OTEL_SERVICE_NAME),namespace=$(OTEL_K8S_NAMESPACE),node=$(OTEL_K8S_NODE_NAME),pod=$(OTEL_K8S_POD_NAME)
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: http://opentelemetry-collector.observability:4318 # grpc change to 4317 port
- name: OTEL_EXPORTER_OTLP_INSECURE
value: "true"
示例程序
我們通過示例程序 otel-demo 來演示、使用 opentelemetry 基本功能,該 demo 功能如下:
代碼演示
獲取代碼並部署:
$ git clone https://github.com/jojotong/otel-demo.git
$ cd otel-demo
$ make build docker-build docker-push deploy
重點:sampler, propagator, baggage 使用,gorm 接入
kubegems 功能演示
重點:trace, metric, log 聯動查詢
應用性能
trace 詳情
trace -> log
log -> monitor
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/wiAT4GHaeitn2o6Byr_kGA