背景
OpenTelemetry 探针
OpenTelemetry(简称 Otel,最新的版本是 1.27) 是一个用于观察性的开源项目,提供了一套工具、APIs 和 SDKs,用于收集、处理和导出遥测数据(如指标、日志和追踪信息)。应用程序遥测数据(如追踪、指标和日志)的收集是通过探针来完成的,探针通常以库的形式集成到应用程序中,自动捕获重要信息协助监控和调试。OpenTelemetry 探针支持市面上大多数的编程语言,探针的安装(通常被称为插桩,Instrumentation)分为手动和自动两种方式。
- 手动插桩:指开发者直接在其应用程序代码中显式地添加遥测数据收集的代码,需要手动完成 SDK 初始化、插入追踪点、添加上下文信息等一系列操作。
- 自动插桩:利用 OpenTelemetry 提供的库自动捕获应用程序的遥测数据,无需或只需很少的代码更改。比如,Java 通过
javaagent
实现探针的自动安装。
二者各有优劣:手动插桩适用于需要高度定制和精确控制遥测数据收集的场景;自动插桩适合快速启动和简化集成,特别是在使用标准框架和库的应用程序中。
OpenTelemetry Operator 介绍
OpenTelemetry Operator 是一个为了简化 OpenTelemetry 组件在 Kubernetes 环境中的部署和管理而设计的 Kubernetes Operator。
OpenTelemetry Operator 通过 CRD(OpenTelemetryCollector、Instrumentation、OpAMPBridge) 实现在 Kubernetes 集群中自动部署和管理 OpenTelemetry Collector;在工作负载中自动安装 OpenTelemetry 探针。
今天我们就将体验如何使用 OpenTelemetry Operator 自动安装探针,实现链路跟踪。
演示
架构
这是演示的架构,Otel 提供了 多种语言的 instrumentation SDK,这篇文章中我们将使用 Java 和 Go 两种语言的应用。这两种语言会使用全自动和半自动的注入安装:
- Java 全自动注入安装,Otel Operator 通过使用 init container 引入 sdk ,并通过
JAVA_TOOL_OPTIONS
来指定javaagent
来插桩。这里将使用pinakispecial/spring-boot-rest
镜像来运行一个简单的 Spring Boot REST 服务。 - Go 半自动注入安装,为什么是半自动?Go 的全自动是通过 eBPF 的方式实现的:在 Pod 注入独立的容器,加载 BPF 程序。但是 eBPF 的实现对内核要求十分苛刻 5.4 - 5.14。这里演示半自动的方式:手动引入 Go instrumentation SDK,自动注入配置。
Jaeger
为了便于演示这里使用 jaegertracing/all-in-one
镜像来部署 Jaeger,这个镜像包含了 Jaeger 收集器、内存存储、查询服务和 UI 等组件,非常适合开发和测试使用。
通过环境变量 COLLECTOR_OTLP_ENABLED
启动对 OTLP(OpenTelemetry Protocol) 的支持。
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
spec:
replicas: 1
selector:
matchLabels:
app: jaeger
template:
metadata:
labels:
app: jaeger
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one:latest
env:
- name: COLLECTOR_OTLP_ENABLED
value: "true"
ports:
- containerPort: 16686
- containerPort: 14268
---
apiVersion: v1
kind: Service
metadata:
name: jaeger
spec:
selector:
app: jaeger
type: ClusterIP
ports:
- name: ui
port: 16686
targetPort: 16686
- name: collector
port: 14268
targetPort: 14268
- name: http
protocol: TCP
port: 4318
targetPort: 4318
- name: grpc
protocol: TCP
port: 4317
targetPort: 4317
EOF
安装 cert-manager
Otel Operator 依赖 cert-manager 进行证书的管理,安装 operator 之前需要安装 cert-manager。
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
安装 OpenTelemetry Operator
执行下面命令安装 Otel Operator
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
配置 OpenTelemetry Collector
通过创建 CR OpenTelemetryCollector 来配置 Otel 的采集器,这里我们配置了:
-
otel
接收器:支持 grpc(端口4317
)和 http(端口4318
) -
memory_limiter
和batch
处理器,但是为了方便快速查看数据,这两个并没有启用,仅作展示用。 -
debug
和otlp/jaeger
的输出器,分别用于在标准输出中打印信息和使用 otlp 协议输出到 Jaeger。 -
pipeline
服务,用于配置跟踪数据的处理流程:接收、处理和输出。
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
spec:
config: |
receivers:
otlp:
protocols:
grpc:
http:
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 75
spike_limit_percentage: 15
batch:
send_batch_size: 10000
timeout: 10s
exporters:
debug:
otlp/jaeger:
endpoint: "jaeger.default:4317"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: []
exporters: [debug,otlp/jaeger]
EOF
创建 CR OpenTelemetryCollector 后,Otel Operator 会创建一个 deployment 和 多个 service。
kubectl get deployment,service -l app.kubernetes.io/component=opentelemetry-collector
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/otel-collector 1/1 1 1 12h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/otel-collector ClusterIP 10.43.152.81 <none> 4317/TCP,4318/TCP,8889/TCP,9411/TCP 12h
service/otel-collector-headless ClusterIP None <none> 4317/TCP,4318/TCP,8889/TCP,9411/TCP 12h
service/otel-collector-monitoring ClusterIP 10.43.115.103 <none> 8888/TCP 12h
Collector 部署的四种部署模型 Deployment、DaemonSet、StatefulSet、Sidecar,默认为 Deployment。
配置 Instrumentation
Instrumentation 是 Otel Operator 的另一个 CRD,用于自动安装 Otel 探针和配置:
-
propagators
用于配置跟踪信息在上下文的传递方式。 -
sampler
采样器 -
env
和[language].env
添加到容器的环境变量
更多配置说明,请参考 Instrumentation API 文档。
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: instrumentation-sample
spec:
propagators:
- tracecontext
- baggage
- b3
sampler:
type: parentbased_traceidratio
argument: "1"
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: otel-collector.default:4318
java:
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: http://otel-collector.default:4317
EOF
Java 示例应用
为 Pod 添加注解 instrumentation.opentelemetry.io/inject-java: "true"
通知 Otel Operator 该应用的类型以便注入正确的探针。
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-sample
spec:
replicas: 1
selector:
matchLabels:
app: java-sample
template:
metadata:
labels:
app: java-sample
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: java-sample
image: pinakispecial/spring-boot-rest
ports:
- containerPort: 8080
EOF
可以看到 Otel Operator 向 Pod 中注入了一个 otel 的初始化容器。
以及在 java 容器中注入了一系列的环境变量进行配置。
Go 示例应用
前面提到 Go 语言的自动注入演示使用半自动的方式,与本文的标题不符,属于嵌入式的。我写了一个 简单的 Go 应用,使用手动的方式来安装 Otel 探针,有兴趣的可以查看源码。
kubectl apply -f https://raw.githubusercontent.com/addozhang/http-sample/main/manifests/service-v1.yaml
查看 Pod 同样可以看到通过环境变量的方式注入的 Otel 配置。
测试
pod_name="$(kubectl get pod -n default -l app=service-a -o jsonpath='{.items[0].metadata.name}')"
kubectl port-forward $pod_name 8080:8080 &
curl localhost:8080
service-a(version: v1, ip: 10.42.0.68, hostname: service-a-5bf98748f5-l9pjw) -> service-b(version: v1, ip: 10.42.0.70, hostname: service-b-676c56fb98-rjbwv) -> service-c(version: v1, ip: 10.42.0.69, hostname: service-c-79985dc75d-bh68k)
发送请求后,打开 Jaeger UI。
jaeger_pod="$(kubectl get pod -l app=jaeger -o jsonpath='{.items[0].metadata.name}')"
kubectl port-forward $jaeger_pod 16686:16686 &
Bingo!
访问 Jaeger UI 就可以看到这个访问的链路信息了。