尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从零搭建一个单节点 K8S 可观测实验室(六):安装 Tempo + OpenTelemetry,构建链路追踪链路

从零搭建一个单节点 K8S 可观测实验室(六):安装 Tempo + OpenTelemetry,构建链路追踪链路 在前面的文章中我们已经完成了 Kubernetes 可观测体系中的两个部分Metrics 指标 Prometheus Logs 日志 LokiPrometheus 可以帮助我们发现Node CPU 是否过高Memory 是否不足Pod 是否频繁重启服务错误率是否突然升高Loki 则可以帮助我们进一步查看数据库连接是否失败配置文件是否缺失应用是否出现异常某个 Pod 在故障前输出了什么日志但是当系统由多个服务组成时仅仅查看 Metrics 和 Logs 仍然不够。假设一次用户请求需要经过下面几个服务用户请求 │ ▼ Frontend │ ▼ Backend │ ▼ Database现在请求耗时突然从200 ms上升到2 sPrometheus 可以发现接口延迟上升Loki 也可能找到一些相关日志。但是我们仍然需要回答这 2 秒究竟消耗在哪个服务上是 Frontend 处理缓慢还是 Backend 调用了一个很慢的接口或者 Database 查询耗费了大量时间这正是分布式链路追踪需要解决的问题。这一篇我们继续搭建可观测体系的第三部分Tracing 链路追踪 Tempo本篇将在单节点 Kubernetes 实验环境中部署Tempo存储和查询 TraceOpenTelemetry Collector接收、处理和转发 TracePython Demo生成最简单的测试 TraceGrafana查询和展示完整调用链最终形成下面这条链路Demo Application │ │ OTLP/gRPC 4317 ▼ OpenTelemetry Collector │ │ OTLP/gRPC 4317 ▼ Tempo │ │ HTTP 3200 ▼ Grafana Explore一、什么是 Trace一次请求进入系统后可能会经过多个处理步骤。例如HTTP Request │ ├── Validate Request │ ├── Query Database │ └── Build Response在链路追踪系统中一次完整请求通常称为TraceTrace 内部的每个处理步骤称为Span例如Trace: 用户请求 │ ├── Span: HTTP Request 320 ms │ ├── Span: Validate Request 10 ms │ ├── Span: Query Database 250 ms │ └── Span: Build Response 60 ms通过这些 Span我们可以直观看出Query Database占用了大部分时间。因此Tracing 主要回答一次请求经过了哪些步骤以及时间究竟消耗在哪里二、OpenTelemetry 和 Tempo 分别负责什么在本实验中OpenTelemetry 和 Tempo 承担不同的角色。OpenTelemetryOpenTelemetry 是一套开放的可观测标准和工具体系。它可以处理Metrics Logs Traces本篇主要使用其中两个部分OpenTelemetry SDK OpenTelemetry Collector应用通过 OpenTelemetry SDK 创建 Trace 和 Span然后将数据发送给 OpenTelemetry Collector。Collector 主要负责接收应用发送的遥测数据批量处理 Trace添加或修改属性将 Trace 转发给后端存储系统将应用和具体存储后端解耦TempoTempo 是 Grafana 生态中的分布式链路追踪后端。它主要负责接收 Trace存储 Trace根据 Trace ID 查询根据服务名、Span 名称和属性搜索 Trace将查询结果提供给 GrafanaTempo 支持单体和微服务两种部署模式。对于入门、开发和小规模环境Grafana 官方建议使用单体模式微服务模式更加复杂适合需要独立扩展各组件的场景。本实验室的目标仍然是单节点 低资源占用 便于学习因此采用Tempo Monolithic也就是单体部署模式。三、整体架构本篇完整架构如下Kubernetes Cluster Demo Trace Application │ │ OpenTelemetry SDK │ OTLP/gRPC :4317 ▼ OpenTelemetry Collector │ │ Batch Processor │ OTLP/gRPC :4317 ▼ Tempo │ │ Query API :3200 ▼ Grafana这里为什么不让应用直接连接 Tempo理论上可以Application │ ▼ Tempo但是在真实系统中更常见的结构是Application │ ▼ OpenTelemetry Collector │ ▼ Tracing BackendCollector 相当于应用和后端之间的统一中转站。以后即使将 Tempo 替换成其他 Trace 后端应用端也不一定需要修改。Collector 还可以统一完成数据批处理数据过滤属性补充采样多后端转发重试和队列缓冲因此本实验采用更加标准的结构Application ↓ Collector ↓ Tempo ↓ Grafana四、安装 Tempo前面的 Prometheus 和 Grafana 已经安装在monitoringNamespace 中。为了方便 Grafana 访问这一篇也将 Tempo 安装在monitoringNamespace 中。1. 添加 Grafana Helm Repository如果前面安装 Loki 时已经添加过可以直接执行更新helm repo add grafana https://grafana.github.io/helm-charts helm repo update查看 Tempo Charthelm search repo grafana/tempoGrafana 提供了单体和分布式 Tempo Helm 部署方式对于本实验使用单体grafana/tempoChart 即可。2. 准备 tempo-values.yaml创建配置文件vim tempo-values.yaml内容如下tempo: # 关闭匿名使用情况上报 reportingEnabled: false # 开启 OTLP 接收端口 receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 # 单节点实验环境不使用持久化存储 persistence: enabled: false service: type: ClusterIP这里开启了两个 OTLP 接收协议4317 OTLP/gRPC 4318 OTLP/HTTP本篇主要使用4317 OTLP/gRPCTempo 的 OTLP Receiver 默认可能只监听本地地址因此在 Kubernetes 中需要明确配置为0.0.0.0:4317 0.0.0.0:4318这样其他 Pod 才能通过 Kubernetes Service 访问。Tempo 官方配置文档也说明Receiver 默认可能监听 localhost若需要接收外部容器发送的数据应配置监听地址。3. 关于临时存储这里设置了persistence: enabled: false这意味着 Tempo 数据只用于实验。如果 Tempo Pod 被删除或重新创建已经存储的 Trace 可能丢失。因此这套配置适用于学习 Tempo验证 OTLP 链路测试 Grafana Trace 查询单节点可观测实验室不适合生产环境。生产环境通常应该使用PVCAmazon S3Google Cloud StorageAzure Blob Storage其他对象存储Tempo 官方也建议生产环境优先使用对象存储本地文件系统更加适合单体测试和开发场景。4. 安装 Tempo执行helm install tempo grafana/tempo \ -n monitoring \ -f tempo-values.yaml查看 Helm Releasehelm list -n monitoring查看 Podkubectl get pods -n monitoring正常情况下应该看到类似结果tempo-0 1/1 Running不同版本的 Helm ChartPod 名称和容器数量可能略有差异。5. 查看 Tempo Service执行kubectl get svc -n monitoring应该可以看到tempo进一步查看端口kubectl get svc tempo -n monitoring结果中通常可以看到3200/TCP 4317/TCP 4318/TCP它们分别对应3200 Tempo HTTP 查询接口 4317 OTLP/gRPC 4318 OTLP/HTTPTempo 的完整 Kubernetes Service DNS 为tempo.monitoring.svc.cluster.local后续 OpenTelemetry Collector 将数据发送到tempo.monitoring.svc.cluster.local:43176. 查看 Tempo 日志执行kubectl logs -n monitoring statefulset/tempo --tail100如果当前 Chart 创建的不是 StatefulSet也可以先查看资源kubectl get deployment,statefulset -n monitoring然后根据实际资源名称查看日志。如果 Tempo 正常启动日志中不应该持续出现address already in use或者connection refused等错误。五、安装 OpenTelemetry CollectorTempo 已经能够接收 Trace接下来部署 OpenTelemetry Collector。Collector 官方 Helm Chart 要求显式设置运行模式可选值包括daemonset deployment statefulset本篇只需要一个集中接收 Trace 的 Collector因此使用deployment官方当前安装示例推荐使用otel/opentelemetry-collector-k8s镜像。1. 添加 OpenTelemetry Helm Repository执行helm repo add open-telemetry \ https://open-telemetry.github.io/opentelemetry-helm-charts helm repo update查看 Charthelm search repo open-telemetry/opentelemetry-collector2. 创建 observability Namespace创建专门用于 Collector 和测试应用的 Namespacekubectl create namespace observability如果 Namespace 已经存在会提示AlreadyExists也可以使用更加幂等的写法kubectl create namespace observability \ --dry-runclient -o yaml | kubectl apply -f -检查kubectl get namespace应该可以看到observability六、准备 Collector 配置创建配置文件vim otel-collector-values.yaml内容如下mode: deployment image: repository: otel/opentelemetry-collector-k8s alternateConfig: extensions: health_check: endpoint: ${env:MY_POD_IP}:13133 receivers: otlp: protocols: grpc: endpoint: ${env:MY_POD_IP}:4317 http: endpoint: ${env:MY_POD_IP}:4318 processors: memory_limiter: check_interval: 5s limit_percentage: 80 spike_limit_percentage: 25 batch: {} exporters: otlp/tempo: endpoint: tempo.monitoring.svc.cluster.local:4317 tls: insecure: true service: extensions: - health_check pipelines: traces: receivers: - otlp processors: - memory_limiter - batch exporters: - otlp/tempo ports: otlp: enabled: true otlp-http: enabled: true jaeger-compact: enabled: false jaeger-thrift: enabled: false jaeger-grpc: enabled: false zipkin: enabled: false service: type: ClusterIP resources: limits: memory: 256Mi requests: cpu: 50m memory: 128Mi这里使用了alternateConfig:而不是直接使用config:原因是 OpenTelemetry Collector Chart 自带默认配置其中包括Logs PipelineMetrics PipelineDebug ExporterJaeger ReceiverZipkin Receiver如果直接覆盖部分configHelm 会将自定义内容与默认内容合并。对于本篇只处理 Trace 的最小环境来说可能会引入不必要的配置。alternateConfig不与默认配置合并可以提供一份完全独立的 Collector 配置。但使用这种方式时必须保留健康检查扩展否则 Chart 的 Readiness 和 Liveness Probe 可能失败。1. Receiver下面的配置表示 Collector 接收 OTLP 数据receivers: otlp: protocols: grpc: endpoint: ${env:MY_POD_IP}:4317 http: endpoint: ${env:MY_POD_IP}:4318支持两种协议OTLP/gRPC 4317 OTLP/HTTP 43182. Processor这里配置了两个 Processormemory_limiter: batch:memory_limiter用于限制 Collector 内存占用。batch会将多个 Span 组成批次后再发送从而减少网络请求次数。因此处理过程大致是接收 Span │ ▼ 检查内存限制 │ ▼ 批量处理 │ ▼ 发送给 Tempo3. Exporter下面的配置表示将 Trace 发送到 Tempoexporters: otlp/tempo: endpoint: tempo.monitoring.svc.cluster.local:4317 tls: insecure: true由于 Collector 和 Tempo 都运行在同一个 Kubernetes Cluster 内部因此可以使用 Kubernetes Service DNStempo.monitoring.svc.cluster.local本实验没有配置 TLS所以设置insecure: true4. PipelineTrace Pipeline 如下pipelines: traces: receivers: - otlp processors: - memory_limiter - batch exporters: - otlp/tempo完整的数据流为OTLP Receiver │ ▼ Memory Limiter │ ▼ Batch Processor │ ▼ OTLP Tempo Exporter七、安装 OpenTelemetry Collector执行helm install otel-collector \ open-telemetry/opentelemetry-collector \ -n observability \ -f otel-collector-values.yaml查看 Helm Releasehelm list -n observability1. 查看 Pod执行kubectl get pods -n observability正常情况下可以看到类似结果otel-collector-opentelemetry-collector-xxxxxxxxxx-xxxxx 1/1 Running2. 查看 Deployment执行kubectl get deployment -n observability应该可以看到otel-collector-opentelemetry-collector3. 查看 Service执行kubectl get svc -n observability应该可以看到otel-collector-opentelemetry-collector查看具体端口kubectl get svc \ otel-collector-opentelemetry-collector \ -n observability应该包含4317/TCP 4318/TCP完整 Service DNS 为otel-collector-opentelemetry-collector.observability.svc.cluster.local4. 查看 Collector 日志执行kubectl logs \ -n observability \ deployment/otel-collector-opentelemetry-collector \ --tail100不同版本的 Collector 日志格式可能略有差异。正常情况下应该能够看到 OTLP Receiver 和健康检查扩展启动并且不应该持续出现connection refused或者failed to export等错误。此时链路的基础设施部分已经完成OpenTelemetry Collector │ ▼ Tempo │ ▼ Grafana但是目前还没有应用产生 Trace。接下来部署一个最小 Python 应用。八、创建最小 Trace Demo为了避免一次部署复杂的 OpenTelemetry Demo 系统本篇只创建一个最简单的 HTTP 应用。每次访问/应用会生成一个 Trace其中包含三个 Spanhttp-request │ ├── step-1 └── step-2两个子步骤分别模拟step-1 100 ms step-2 200 ms通过这个简单例子可以直观看到TraceRoot SpanChild SpanSpan DurationSpan AttributeService Name1. 创建 demo-config.yaml创建文件vim demo-config.yaml内容如下apiVersion: v1 kind: ConfigMap metadata: name: demo-trace-app namespace: observability data: app.py: | import os import time from flask import Flask from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( OTLPSpanExporter, ) app Flask(__name__) service_name os.getenv( OTEL_SERVICE_NAME, demo-trace ) otlp_endpoint os.getenv( OTEL_EXPORTER_OTLP_ENDPOINT, otel-collector-opentelemetry-collector. observability.svc.cluster.local:4317 ) resource Resource.create({ service.name: service_name, deployment.environment: k8s-lab, service.version: 1.0.0 }) provider TracerProvider( resourceresource ) exporter OTLPSpanExporter( endpointotlp_endpoint, insecureTrue ) processor BatchSpanProcessor( exporter ) provider.add_span_processor( processor ) trace.set_tracer_provider( provider ) tracer trace.get_tracer( demo-trace ) app.route(/) def hello(): with tracer.start_as_current_span( http-request ) as span: span.set_attribute( demo.tag, v1 ) span.set_attribute( http.route, / ) with tracer.start_as_current_span( step-1 ): time.sleep(0.1) with tracer.start_as_current_span( step-2 ): time.sleep(0.2) return hello otel trace\n app.route(/slow) def slow(): with tracer.start_as_current_span( slow-request ) as span: span.set_attribute( demo.slow, True ) with tracer.start_as_current_span( slow-operation ): time.sleep(1) return slow trace generated\n if __name__ __main__: app.run( host0.0.0.0, port8080 )这个程序显式设置了几个 Resource Attributeservice.name deployment.environment service.version其中最重要的是service.name demo-trace后续 Grafana 将根据这个属性查询服务。应用 ConfigMapkubectl apply -f demo-config.yaml检查kubectl get configmap -n observability应该可以看到demo-trace-app九、部署 Demo Application创建vim demo-app.yaml内容如下apiVersion: apps/v1 kind: Deployment metadata: name: demo-trace namespace: observability spec: replicas: 1 selector: matchLabels: app: demo-trace template: metadata: labels: app: demo-trace spec: containers: - name: demo-trace image: python:3.11-slim command: - sh - -c args: - | pip install --no-cache-dir \ flask \ opentelemetry-api \ opentelemetry-sdk \ opentelemetry-exporter-otlp echo Starting demo trace application python /app/app.py env: - name: OTEL_SERVICE_NAME value: demo-trace - name: OTEL_EXPORTER_OTLP_ENDPOINT value: - otel-collector-opentelemetry-collector.observability.svc.cluster.local:4317 ports: - name: http containerPort: 8080 readinessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 3 periodSeconds: 5 resources: requests: cpu: 50m memory: 64Mi limits: memory: 256Mi volumeMounts: - name: app mountPath: /app volumes: - name: app configMap: name: demo-trace-app应用kubectl apply -f demo-app.yaml查看 Deploymentkubectl get deployment -n observability查看 Podkubectl get pods -n observability刚开始可能会看到ContainerCreating随后进入Running由于容器启动时需要执行pip install第一次启动可能需要等待一段时间。查看应用日志kubectl logs \ -n observability \ deployment/demo-trace \ -f正常情况下应该可以看到Starting demo trace application Running on http://0.0.0.0:8080十、访问 Demo 并生成 Trace为了从 Kubernetes Node 访问 Demo先安装socat执行sudo apt update sudo apt install -y socat验证command -v socat应该输出/usr/bin/socat然后使用kubectl port-forward \ -n observability \ deployment/demo-trace \ 8080:8080这里直接使用deployment/demo-trace而不是填写具体 Pod 名称。因为 Pod 名称包含随机字符串例如demo-trace-5ffd95c654-hs448Pod 重新创建后名称会发生变化而 Deployment 名称保持稳定。端口转发成功后会看到类似输出Forwarding from 127.0.0.1:8080 - 8080 Forwarding from [::1]:8080 - 8080保持这个终端不关闭。重新打开另一个终端多访问几次curl http://localhost:8080 curl http://localhost:8080 curl http://localhost:8080输出hello otel trace再访问慢请求curl http://localhost:8080/slow输出slow trace generated每执行一次请求应用都会生成一条新的 Trace。此时完整链路为curl │ ▼ Demo Application │ │ OpenTelemetry SDK ▼ OpenTelemetry Collector │ │ OTLP/gRPC ▼ Tempo正常使用kubectl port-forward时一般不需要额外安装socat。只有在特定旧环境或特殊转发工具明确报告缺少socat时才需要单独处理。十一、在 Grafana 中配置 Tempo进入 Grafana。依次打开Connections ↓ Add new connection ↓ Tempo ↓ Add new data source填写 URLhttp://tempo.monitoring.svc.cluster.local:3200这里使用的是Tempo HTTP Query API而不是 OTLP 数据接收端口。两者的作用不同4317 Collector 向 Tempo 写入 Trace 3200 Grafana 从 Tempo 查询 Trace本实验中的 Grafana 和 Tempo 都运行在 Kubernetes Cluster 内部因此 Grafana 可以通过 Kubernetes Service DNS 访问 Tempo。点击Save Test如果看到类似提示Data source is working说明 Grafana 已经能够查询 Tempo。十二、在 Grafana 中查询 Trace进入Explore选择数据源Tempo1. 使用 Search 页面查询可以在查询界面中选择Service Name然后选择demo-trace点击Run query应该可以看到刚才生成的 Trace。注意这里的Limit默认是20查询的Trace可能较少可以自行改大。2. 使用 TraceQL 查询也可以切换到 TraceQL 模式输入{ resource.service.name demo-trace }这里需要注意TraceQL 的完整查询需要使用花括号。不是service.name demo-trace而是{ resource.service.name demo-trace }3. 查询普通请求查询 Root Span{ name http-request }4. 查询慢请求查询{ name slow-request }或者根据自定义属性查询{ span.demo.slow true }5. 根据环境查询查询{ resource.deployment.environment k8s-lab }6. 根据版本查询查询{ resource.service.version 1.0.0 }十三、查看完整 Trace点击其中一条 Trace可以看到类似结构demo-trace: http-request │ ├── http-request 300 ms │ ├── step-1 100 ms │ └── step-2 200 ms其中http-request是 Root Span。下面两个是 Child Spanstep-1 step-2可以看到step-1 大约 100 ms step-2 大约 200 ms对于慢请求则可以看到slow-request └── slow-operation 大约 1 s这就是链路追踪最核心的能力将一次请求拆分成多个步骤并显示每个步骤的耗时。如果真实系统中出现延迟就可以判断时间主要消耗在HTTP 请求处理数据库查询外部接口调用消息队列缓存访问业务计算十四、Trace、Span 和上下文传播在本篇 Demo 中我们创建了一个父 Spanwith tracer.start_as_current_span( http-request ):然后在父 Span 内创建两个子 Spanwith tracer.start_as_current_span( step-1 ):以及with tracer.start_as_current_span( step-2 ):OpenTelemetry 会自动识别当前上下文因此形成http-request │ ├── step-1 └── step-2这就是 Span 上下文传播。如果没有正确传播上下文可能会变成三条互不相关的 TraceTrace A http-request Trace B step-1 Trace C step-2而不是一条完整调用链。在真正的分布式系统中上下文还需要通过 HTTP Header 在服务之间传播。常见 Header 为traceparent例如Frontend │ │ traceparent ▼ Backend │ │ traceparent ▼ Database Service这样不同服务产生的 Span 才能被组合成同一条 Trace。本篇 Demo 只在一个进程内部创建父子 Span因此 OpenTelemetry SDK 可以自动处理上下文。十五、为什么使用 Collector而不是直接写入 Tempo现在的链路是Application │ ▼ Collector │ ▼ Tempo看起来比直接连接 Tempo 多了一层。但是 Collector 带来了几个重要好处。1. 应用与存储后端解耦应用只需要知道OTLP Endpoint不需要了解 Tempo 的具体实现。以后后端变更时可以只修改 Collector。2. 批量发送Collector 使用batch processor将多个 Span 合并后发送减少网络请求次数。3. 数据处理Collector 可以在发送前删除敏感字段添加 Kubernetes Metadata修改属性丢弃无用数据对 Trace 进行采样4. 多后端输出同一份 Trace 可以同时发送到多个系统Application │ ▼ Collector │ │ ▼ ▼ Tempo Another Backend5. 统一入口多个应用可以统一发送到 CollectorFrontend ──┐ Backend ──┼──► Collector ──► Tempo Worker ──┘应用不需要分别维护后端连接。十六、清理测试资源本篇创建的demo-trace ConfigMap demo-trace Deployment主要用于生成测试 Trace。确认已经能够在 Grafana 中查询 Trace 后可以删除kubectl delete -f demo-app.yaml kubectl delete -f demo-config.yaml检查kubectl get pods -n observability此时应该只保留 OpenTelemetry Collector。Collector 和 Tempo 可以继续保留因为后续还可以用于接入真实应用演示跨服务 Trace将 Trace 与 Logs 关联将 Trace 与 Metrics 关联进行故障注入实验如果希望删除整个 Collector 和 Demo 环境可以执行helm uninstall otel-collector \ -n observability kubectl delete namespace observability如果还需要删除 Tempohelm uninstall tempo \ -n monitoring不过本系列后续还会继续使用 Tempo因此暂时建议保留Tempo OpenTelemetry Collector Grafana本系列采用的资源管理原则仍然是基础设施组件继续保留 临时测试业务验证完成后删除这样既能保持实验连续性也可以避免无用 Pod 不断堆积。十七、小结Metrics、Logs 和 Tracing到这里单节点 Kubernetes 可观测实验室已经完成了三项核心能力。MetricsKubernetes Metrics │ ▼ Prometheus │ ▼ GrafanaMetrics 主要回答系统发生了什么例如CPU 是否过高Memory 是否不足Pod 是否频繁重启请求延迟是否上升LogsPod stdout / stderr │ ▼ Fluent Bit │ ▼ Loki │ ▼ GrafanaLogs 主要回答为什么会出现问题例如数据库连接失败配置文件缺失权限错误应用异常退出TracingApplication │ ▼ OpenTelemetry Collector │ ▼ Tempo │ ▼ GrafanaTracing 主要回答问题发生在哪个调用步骤例如哪个服务最慢哪次数据库查询耗时过长请求经过了哪些服务某个步骤花费了多长时间将三者放在一起Metrics Logs Tracing就形成了一个相对完整的可观测体系。可以简单理解为Metrics 发现问题 Logs 解释问题 Tracing 定位问题现在我们的单节点 Kubernetes 实验室已经具备Prometheus Grafana Loki Fluent Bit Tempo OpenTelemetry Collector完整链路为Metrics ──► Prometheus ──┐ │ Logs ─────► Loki ────────┼──► Grafana │ Traces ───► Tempo ───────┘当然目前三种数据仍然相对独立。真正更有价值的可观测体验是将它们关联起来从 Metrics 发现延迟升高 │ ▼ 进入对应 Trace │ ▼ 定位最慢 Span │ ▼ 查看相关 Pod 日志这也是后续可以继续完善的方向。
返回列表