使用 Knative、OpenTelemetry 和 Jaeger 进行分布式追踪 ¶
发布时间:2021-08-30, 修订时间:2024-01-17
使用 Knative、OpenTelemetry 和 Jaeger 进行分布式追踪¶
在尝试理解和诊断系统时,我们学习的首批基本工具之一就是堆栈跟踪。堆栈跟踪为我们提供了程序执行逻辑流的结构化视图,以帮助我们了解如何进入某个状态。分布式追踪是我们行业试图将这种理念应用到下一个更高抽象级别并为我们提供程序之间消息流方式的视图的尝试。
Knative Eventing 是用于连接分布式架构的构建块集,这种架构如今受到许多人的青睐。它为我们提供了一种用于描述和组装程序之间连接的语言,通过代理、触发器、通道和流,但这种能力也带来了风险,即创建难以确定事件触发方式的意大利面条式代码堆。在这篇文章中,我们将逐步介绍如何使用 Eventing 设置分布式追踪,并了解它如何帮助我们更好地理解程序以及 Eventing 在幕后工作方式的一些信息。
追踪领域的布局¶
尝试学习如何进行追踪时出现的第一个问题之一就是了解生态系统:Zipkin、Jaeger、OpenTelemetry、OpenCensus、OpenTracing 以及无数其他工具,你应该使用哪一个?好消息是,这最后三个“Open”库试图创建指标和追踪的标准,这样我们就不需要立即决定将使用哪些存储和可视化工具,并且在它们之间切换应该(大部分)是无痛的。OpenCensus 和 OpenTracing 最初都是为了统一围绕追踪和指标的碎片化领域而创建的,导致了一组令人悲痛/滑稽的新的分歧和竞争标准。OpenTelemetry 是最新的尝试,它本身就是 OpenCensus 和 OpenTracing 的统一。
Knative 的追踪支持目前 仅适用于 OpenCensus,但 OpenTelemetry 社区为我们提供了工具来弥合系统中的这种差距。在这篇文章中,我们将重点介绍如何通过 OpenCensus 和 OpenTelemetry 的组合使用 Jaeger,但更广泛的经验教训应该适用于你使用的任何工具。
入门¶
我们将假设你拥有已安装 Knative Serving 和 Eventing 的集群。如果你还没有集群,我建议尝试一下 Knative 快速入门,但理论上任何设置都应该可以正常工作。
安装 Knative 后,我们将向集群添加 OpenTelemetry 运算符,它依赖于 cert-manager。在安装这两个组件时需要注意的是,你需要等待 cert-manager 的 webhook pod 启动,然后才能安装运算符,否则你会看到许多创建证书时的“连接拒绝”错误。运行 kubectl -n cert-manager wait --for=condition=Ready pods --all
将阻塞,直到 cert-manager 准备好运行。kubectl wait
默认超时时间为 30 秒,因此在你的集群上可能需要更长时间,具体取决于镜像下载速度。
kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml &&
kubectl -n cert-manager wait --for=condition=Ready pods --all &&
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.40.0/opentelemetry-operator.yaml
接下来,我们将设置 Jaeger 运算符(是的,又是另一个运算符,我发誓这是最后一个)。
kubectl create namespace observability &&
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/crds/jaegertracing.io_jaegers_crd.yaml &&
kubectl create -n observability \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/service_account.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/role.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/role_binding.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/operator.yaml
启动后,我们可以通过运行以下命令创建 Jaeger 实例:
kubectl apply -n observability -f - <<EOF
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: simplest
EOF
这可能需要一段时间才能启动,因为我们再次等待运算符 pod 以及 Jaeger pod 本身启动。启动后,Jaeger 运算符将为我们的 Jaeger 创建 Kubernetes Ingress,但由于我们正在 Kind 上运行,因此没有安装任何 Ingress。没关系,端口转发就足够了:运行 kubectl -n observability port-forward service/simplest-query 16686
将使我们的 Jaeger 仪表板可以通过 https://#:16686 访问。
接下来,我们将创建 OpenTelemetry 收集器,它将负责接收来自我们程序的追踪并将它们转发到 Jaeger。收集器是一个抽象层,它让我们可以连接使用不同协议的系统。即使我们只导出 Zipkin 追踪,我们也可以依靠收集器将它们转换为 Jaeger 可以使用的形式。 此收集器定义 将告诉 OpenTelemetry 运算符创建一个收集器,该收集器将监听追踪,就像它是一个 Zipkin 实例一样,但将它们导出到日志(用于调试)以及我们的 Jaeger 实例。
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
namespace: observability
spec:
config: |
receivers:
zipkin:
exporters:
logging:
jaeger:
endpoint: "simplest-collector.observability:14250"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [zipkin]
processors: []
exporters: [logging, jaeger]
EOF
如果一切顺利,我们现在应该看到 observability
命名空间中正在运行 3 个 pod:我们的 Jaeger 运算符、我们的 Jaeger 实例以及 OpenTelemetry 收集器。
最后,我们可以配置 Eventing 和 Serving 以将其所有追踪指向我们的收集器
for ns in knative-eventing knative-serving; do
kubectl patch --namespace "$ns" configmap/config-tracing \
--type merge \
--patch '{"data":{"backend":"zipkin","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans", "debug": "true"}}'
done
此处的 debug
标志告诉 Knative 将所有追踪发送到我们的收集器,而在实际部署中,你可能希望设置采样率以仅获取追踪的代表性子集。
你好,世界?¶
现在我们的追踪基础设施已全部部署和配置,我们可以通过部署一些服务来开始利用它。我们可以将 心跳镜像 部署为 ContainerSource 以进行测试并确保一切连接正常
kubectl apply -f - <<EOF
apiVersion: sources.knative.dev/v1
kind: ContainerSource
metadata:
name: heartbeats
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/heartbeats:latest
name: heartbeats
args:
- --period=1
env:
- name: POD_NAME
value: "heartbeats"
- name: POD_NAMESPACE
value: "default"
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","sample-rate":"1","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
sink:
uri: http://dev.null
EOF
目前,此容器只会将其心跳发送到不存在的域 http://dev.null,因此如果我们查看此 pod 的日志,我们会看到许多 DNS 解析错误。但是,如果我们检查 otel-collector
pod 的日志,应该会看到它已成功接收来自我们服务的追踪。这很好地确认了我们的配置有效,但从追踪的角度来看并不令人兴奋!让我们通过添加 Knative 服务来接收我们的心跳使其更具现实意义
kubectl apply -f - <<EOF
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: event-display
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/event_display:latest
env:
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
EOF
我们将更新心跳服务以开始向这里发送心跳
kubectl apply -f - <<EOF
apiVersion: sources.knative.dev/v1
kind: ContainerSource
metadata:
name: heartbeats
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/heartbeats:latest
name: heartbeats
args:
- --period=1
env:
- name: POD_NAME
value: "heartbeats"
- name: POD_NAMESPACE
value: "default"
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
sink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-display
EOF
部署这些服务后,我们可以返回到 Jaeger 仪表板,应该会看到一些更有趣的追踪
在 Jaeger 的“系统架构”选项卡上,我们还可以看到我们拓扑的漂亮图表,包括你可能知道或可能不知道的一个组件,即 激活器
这是一个 Knative Serving 添加到 Knative 服务网络路径的组件,用于在我们的服务无法处理请求时缓冲请求,以及向自动缩放器报告请求指标。您也可以看到它会增加很小的开销,在我的集群上大约为 2 毫秒。可以通过 配置 Knative 使激活器在不同情况下脱离路径,但这将是另一篇博文的主题:)。
变得花哨¶
让我们通过添加一些 Knative 的花里胡哨,使我们的拓扑更加有趣。首先,让我们开始通过 代理 和 触发器 发送我们的消息,而不是直接从我们的心跳服务发送。我们将创建一个代理和触发器,将所有消息转发到事件显示服务,并重新配置我们的心跳服务以指向代理。
kubectl apply -f - <<EOF
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: heartbeat-to-eventdisplay
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-display
---
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: default
---
apiVersion: sources.knative.dev/v1
kind: ContainerSource
metadata:
name: heartbeats
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/heartbeats:latest
name: heartbeats
args:
- --period=1
env:
- name: POD_NAME
value: "heartbeats"
- name: POD_NAMESPACE
value: "default"
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
sink:
ref:
apiVersion: eventing.knative.dev/v1
kind: Broker
name: default
EOF
如果我们现在回到 Jaeger,我们应该会看到一个更加复杂的跟踪,其中包含更多从事件的内存中代理到我们的消息在心跳和事件显示之间传递的路径中的跳跃。如果您使用的是其他代理实现,您的跟踪将会不同,但在所有情况下,我们都为了提高系统的灵活性和功能而增加了复杂性。
从这里,我们可以为我们的部署添加另一个折皱:而不是让每个心跳都直接进入我们的事件显示服务,让我们抛一枚硬币,只有当我们得到“正面”时才发送它。幸运的是,我精通数字理论,并且已经 编写了这个掷硬币的微服务,所以我们只需要把它部署为一个新的 Knative 服务。
kubectl apply -f - <<EOF
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: coinflip
spec:
template:
spec:
containers:
- image: benmoss/coinflip:latest
env:
- name: OTLP_TRACE_ENDPOINT
value: otel-collector.observability:4317
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: heartbeat-to-coinflip
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: coinflip
filter:
attributes:
type: dev.knative.eventing.samples.heartbeat
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: heartbeat-to-eventdisplay
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-display
filter:
attributes:
flip: heads
EOF
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
namespace: observability
spec:
config: |
receivers:
zipkin:
otlp:
protocols:
grpc:
exporters:
logging:
jaeger:
endpoint: "simplest-collector.observability.svc.cluster.local:14250"
insecure: true
service:
pipelines:
traces:
receivers: [zipkin, otlp]
processors: []
exporters: [logging, jaeger]
EOF
如果我们查看 新的触发器配置,我们可以看到我们现在有两个触发器,一个将所有心跳类型事件发送到掷硬币器,另一个将所有扩展名为“flip: heads”的事件发送到事件显示。掷硬币服务克隆传入的心跳事件,抛一枚硬币并将结果添加为 CloudEvents 扩展,并将事件类型更改,以便我们不会意外地生成无限的掷硬币循环。然后它将此事件发送回代理以重新排队,然后该事件要么在正面时被分派到事件显示,要么在结果为反面时被丢弃。
如果我们回到 Jaeger 界面,我们会看到不同长度的心跳跟踪,有时在不走运的反面终止,有时赢得大奖并转发到事件显示。检查事件显示的日志,我们应该会看到事件仍在进入,尽管比以前慢,并且都带有“flip: heads”扩展。我们还将看到我们使用 自定义检测 从掷硬币服务内部发送的这些自定义跨度。
我们可以从 Jaeger 的架构图中了解发生了什么。我们的事件从心跳服务流入,经过代理,并流向我们的每个触发器。我们触发器上的过滤器意味着最初事件只会继续流向我们的掷硬币服务。掷硬币服务回复一个新的事件,该事件然后通过代理和过滤器流回,这一次被我们的掷硬币触发器拒绝,但被事件显示触发器接受。
总结¶
希望通过这一切,我们都能了解一些关于 Knative 和良好可观察性工具价值的信息。我们看到了如何利用 OpenTelemetry 收集器来集成使用不同协议的系统,并将它们全部导入到一个共享的 Jaeger 实例中。我们创建的拓扑从某种意义上来说很简单,但希望足够有趣和复杂,可以指明如何构建真实的事件驱动系统。可观察性和指标生态系统很大,有时可能让人感到不知所措,但是一旦配置好,它就可以成为理解和解决系统问题的救星。