OpenTelemetry

OpenTelemetry 是一个开源项目,旨在通过统一的工具集、API和SDK,简化在多样化的技术栈中集成可观测性功能,确保一致地收集、处理及导出应用性能数据。

OpenTelemetry Golang观测方案一览


背景

在云原生概念愈发深入人心的今天,Golang编程语言也越来越成为软件开发人员的首选,OpenTelemetry作为可观测领域目前的事实标准,对Golang编程语言也提供了对应的观测方案,本文将分别介绍这些观测方案。

SDK手动插桩

SDK手动插桩方案可以说是适配所有编程语言的最基础的一种观测方案了,通过在代码中引入opentelemetry-go SDK依赖,就可以以一种侵入式的方式来为Golang的应用程序提供可观测的能力,具体地,使用opentelemetry-go SDK进行手动插桩的流程主要分为以下几步:

引入依赖

首先需要在工程的go.mod文件里引入opentelemetry-go SDK的相关依赖。

require (
go.opentelemetry.io/otel v1.20.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0
go.opentelemetry.io/otel/sdk v1.20.0
)

SDK初始化

之后,需要初始化SDK,并进行一些关键的配置操作,例如采样率配置,上报端点的配置等等,配置操作一般在程序的主逻辑执行之前进行:

package otel_util
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"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.15.0"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding/gzip"
"log"
"os"
"time"
)
const (
SERVICE_NAME = ""
SERVICE_VERSION = ""
DEPLOY_ENVIRONMENT = ""
HTTP_ENDPOINT = "tracing-analysis-dc-hz-internal.aliyuncs.com"
HTTP_URL_PATH = "adapt_ggxw4lnjuz@7323a5caae30263_ggxw4lnjuz@53df7ad2afe8301/api/otlp/traces"
)
// 设置应用资源
func newResource(ctx context.Context) *resource.Resource {
hostName, _ := os.Hostname()
r, err := resource.New(
ctx,
resource.WithFromEnv(),
resource.WithProcess(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithAttributes(
semconv.ServiceNameKey.String(SERVICE_NAME), // 应用名
semconv.ServiceVersionKey.String(SERVICE_VERSION), // 应用版本
semconv.DeploymentEnvironmentKey.String(DEPLOY_ENVIRONMENT), // 部署环境
semconv.HostNameKey.String(hostName), // 主机名
),
)
if err != nil {
log.Fatalf("%s: %v", "Failed to create OpenTelemetry resource", err)
}
return r
}
func newHTTPExporterAndSpanProcessor(ctx context.Context) (*otlptrace.Exporter, sdktrace.SpanProcessor) {
traceExporter, err := otlptrace.New(ctx, otlptracehttp.NewClient(
otlptracehttp.WithEndpoint(HTTP_ENDPOINT),
otlptracehttp.WithURLPath(HTTP_URL_PATH),
otlptracehttp.WithInsecure(),
otlptracehttp.WithCompression(1)))
if err != nil {
log.Fatalf("%s: %v", "Failed to create the OpenTelemetry trace exporter", err)
}
batchSpanProcessor := sdktrace.NewBatchSpanProcessor(traceExporter)
return traceExporter, batchSpanProcessor
}
// InitOpenTelemetry OpenTelemetry 初始化方法
func InitOpenTelemetry() func() {
ctx := context.Background()
var traceExporter *otlptrace.Exporter
var batchSpanProcessor sdktrace.SpanProcessor
traceExporter, batchSpanProcessor = newHTTPExporterAndSpanProcessor(ctx)
otelResource := newResource(ctx)
traceProvider := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithResource(otelResource),
sdktrace.WithSpanProcessor(batchSpanProcessor))
otel.SetTracerProvider(traceProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return func() {
cxt, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
if err := traceExporter.Shutdown(cxt); err != nil {
otel.Handle(err)
}
}
}

代码插桩

最后,在需要观测的关键函数处进行埋点插桩,以获取关键函数的调用耗时,调用次数等关键信息:

// 手动埋点插桩前的方法
func unInstrumentedMethod(ctx context.Context) {
childMethod(ctx)
}
// 手动埋点插桩后的方法
func instrumentedMethod(ctx context.Context) {
tracer := otel.Tracer("otel-go-tracer")
ctx, span := tracer.Start(ctx, "parent span")
fmt.Println(span.SpanContext().TraceID()) // 打印 TraceId
span.SetAttributes(attribute.String("key", "value"))
span.SetStatus(codes.Ok, "Success")
childMethod(ctx)
span.End()
}

eBPF自动插桩

对于Golang编程语言,第二种对齐进行观测的方式是通过eBPF对齐进行自动埋点插桩。在OpenTelemetry中,eBPF自动埋点插桩主要由下面三个模块组成:

  1. 进程分析器Analyzer:Analyzer用来探测可以使用eBPF进行自动插桩的进程和库。
  2. 控制器Controller:Controller使用opentelemetry-go SDK来导出观测数据。
  3. 插桩管理器Manager:Manager用以将eBPF程序发送来的事件转发至Controller,充当协调者的作用。

eBPF自动插桩首先会在宿主机上找到需要进行eBPF自动插桩的进程,并通过/proc文件系统获取该进程的相关信息。在获取到进程的相关信息后,eBPF自动插桩会去根据进程的详细信息去找到需要插桩的位置,同时为这些插桩行为分配需要的资源(如eBPF map等内存资源)。在做完了以上这些初始化操作后,eBPF自动插桩会去加载预先定义好的eBPF程序,这些eBPF程序会通过uProbe的方式挂载到此前分析出的各个插桩点上,在相关的插桩点被触发后,挂载的eBPF程序会相应的执行,同时产生相应的eBPF事件,这些eBPF事件将被发送到Controller侧,由Controller将通过opentelemetry-go SDK上报这些事件。

在实际进行使用时,这种观测方案的使用成本非常的低,用户只需要在集群的每台机器中运行一个特权级的进程即可,比如在K8s中运行一个如下所示的DaemonSet:

- name: autoinstrumentation-go
image: otel/autoinstrumentation-go
imagePullPolicy: IfNotPresent
env:
- name: OTEL_GO_AUTO_TARGET_EXE
value: <location_of_target_application_binary>
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://<address_in_network>:4318"
- name: OTEL_SERVICE_NAME
value: "<name_of_service>"
securityContext:
runAsUser: 0
privileged: true

之后,Golang程序运行期间产生的观测数据将会被自动地收集。

编译期自动插桩

最后一种对Golang编程语言进行观测的方案是基于Golang编程语言提供的编译器扩展-toolexec来实现的,代表实现有阿里巴巴的opentelemetry-go-auto-instrumentation,以及OpenTelemetry提供的InstrGen扩展。

通常Golang程序在进行编译时,需要经过下面几个步骤将源代码编译成二进制程序:

1.源码解析: Golang 编译器会先解析源代码文件,将其转化为抽象语法树(AST)。

2.类型检查: 解析后会进行类型检查,确保代码符合 Golang 的类型系统。

3.语义分析: 对程序的语义进行分析,包括变量的定义和使用、包导入等。

4.编译优化:将语法树转化为中间表示, 进行各种优化,提高代码执行效率。

5.代码生成: 生成目标平台的机器代码。

6.链接: 将不同包和库链接成一个单一的可执行文件。

而在使用编译器的自动注入方案后,通过Golang编程语言的toolexec机制,编译器的自动插桩可以在编译器的前端解析出语法树之后,对语法树进行相应的改写,根据用户定义的插桩规则匹配到相应的规则之后更改语法树,从而将应用观测的相关逻辑插桩到关键函数的前后。

在实际使用编译器自动插桩时,通常需要执行如下两步操作:

编译/下载编译工具

以阿里巴巴开源的opentelemetry-go-auto-instrumentation 为例,首先需要下载编译工具的二进制程序,或者进入开源代码的根目录执行

make clean && make build

以获取到可以使用的Golang编译工具。

执行编译工具

传统的Golang应用会通过

go build .

来将Go源代码编译成可以运行的二进制程序,而在拥有了第一步中的编译工具之后,用户需要使用编译工具替代go build来执行Golang应用程序的编译,用户可以将编译工具放在自身业务源代码的根目录下,之后在业务源代码的根目录下执行编译工具:

./otelbuild

此后在用户业务源代码的根目录中,便会产生对应的二进制文件,此二进制文件将会包含Golang应用的可观测能力。


observability.cn Authors 2024 | Documentation Distributed under CC-BY-4.0
Copyright © 2017-2024, Alibaba. All rights reserved. Alibaba has registered trademarks and uses trademarks.
浙ICP备2021005855号-32