Homelab OTel 实践:从日志采集到双集群全链路追踪
目录
背景#
在 Homelab 的可观测性建设中,我经历了几个阶段:
- 最初:kube-prometheus-stack 提供 Metrics + Grafana,Promtail 采集日志到 Loki
- OTel 日志迁移:用 OTel Collector DaemonSet 替换 Promtail,统一到 OTLP 协议
- 多集群扩展:oracle-k3s 集群通过 OTel Collector 将 logs + metrics 推送到 homelab 的 Loki + Prometheus
- 本次改进:打通 Traces 管道,实现完整的 LGTM(Loki/Grafana/Tempo/Mimir)三信号可观测性
本文重点记录第 4 步——如何在双集群场景下实现 OpenTelemetry 全链路追踪。
部署环境#
| 集群 | 位置 | 节点 IP | Tailscale IP |
|---|---|---|---|
| k3s-homelab | Proxmox VM | 10.10.10.10 | 100.107.254.112 |
| oracle-k3s | Oracle Cloud ARM | 10.0.0.26 | 100.107.166.37 |
两个集群通过 Tailscale 互联,RTT ~80ms。所有可观测性后端(Loki、Prometheus、Tempo、Grafana)运行在 homelab 集群的 monitoring 命名空间。
改进前的状态#
在动手之前,先梳理了现有的 OTel 体系:
| 组件 | homelab | oracle-k3s |
|---|---|---|
| OTel Collector 版本 | v0.146.1 (Helm chart) | v0.120.0 (手动 DaemonSet) |
| 日志采集 | ✅ filelog → OTLP HTTP → Loki | ✅ filelog → OTLP HTTP → Loki |
| 指标采集 | ✅ Prometheus 本地 scrape | ✅ prometheusremotewrite → Prometheus |
| 追踪采集 | ❌ 无 OTLP receiver | ❌ 无 OTLP receiver |
| 内存保护 | ❌ 无 memory_limiter | ❌ 无 memory_limiter |
| 健康检查 | ❌ 无 health_check | ❌ 无 health_check |
| Tempo | 已部署但无数据流入 | N/A |
几个关键问题:
- 没有 OTLP receiver:OTel Collector 只有
filelogreceiver,应用无法推送 traces - 没有 memory_limiter:高负载下 Collector 可能 OOM
- Grafana Tempo 数据源 URL 错误:配置了
:3100(Loki 端口),Tempo 实际使用:3200 - 没有 Tempo NodePort:oracle-k3s 的 traces 无法发送到 homelab 的 Tempo
架构设计#
改进后的追踪架构:
┌──────── homelab Cluster ─────────┐ ┌────── oracle-k3s Cluster ──────┐
│ │ │ │
│ App Pod │ │ App Pod │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ OTel SDK │ │ │ │ OTel SDK │ │
│ │ (Go/Java/ │ │ │ │ (any lang) │ │
│ │ Node/Rust) │ │ │ └──────┬──────┘ │
│ └──────┬──────┘ │ │ │ OTLP gRPC :4317 │
│ │ OTLP gRPC :4317 │ │ ▼ │
│ ▼ │ │ ┌─────────────────────┐ │
│ ┌─────────────────────┐ │ │ │ OTel Collector │ │
│ │ OTel Collector │ │ │ │ (DaemonSet) │ │
│ │ (DaemonSet) │ │ │ │ │ │
│ │ │ │ │ │ resource attrs: │ │
│ │ resource attrs: │ │ │ │ cluster=oracle-k3s│ │
│ │ cluster=homelab │ │ │ └──────┬──────────────┘ │
│ └──────┬──────────────┘ │ │ │ │
│ │ OTLP gRPC │ └─────────┼──────────────────────┘
│ ▼ │ │ OTLP gRPC
│ ┌─────────────┐ │ │ via Tailscale
│ │ Tempo 2.8.2 │ ◄───────────────┼──────────────┘ :31317 NodePort
│ │ (5Gi NFS) │ │
│ └──────┬──────┘ │
│ │ TraceQL │
│ ▼ │
│ ┌─────────────┐ │
│ │ Grafana │ │
│ │ 12.3.3 │ │
│ └─────────────┘ │
└───────────────────────────────────┘
核心设计决策:
- 统一走 OTel Collector 中转:应用不直接连 Tempo,而是发送到本集群的 OTel Collector ClusterIP Service,由 Collector 添加
cluster标签后转发。这样可以统一做采样、限流和资源标注。 - gRPC 传输:Traces 数据量大,gRPC 的双向流和压缩优于 HTTP。
- Tailscale NodePort 跨集群:oracle-k3s 的 Collector 通过 Tailscale IP
100.107.254.112:31317发送到 homelab Tempo,复用已有的跨集群网络。 - Head-based 采样(暂缓):当前 traces 量很小,全量采集;后续自研服务上量后再在 Collector 层加
probabilistic_sampler。
实施步骤#
1. Homelab OTel Collector — 添加 Traces 管道#
Homelab 集群使用 Helm chart 部署 OTel Collector,修改 values/opentelemetry-collector.yaml:
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
limit_mib: 200
spike_limit_mib: 50
check_interval: 5s
exporters:
otlp/tempo:
endpoint: tempo.monitoring.svc.cluster.local:4317
tls:
insecure: true
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, resource, batch]
exporters: [otlp/tempo]
logs:
receivers: [filelog, otlp] # 同时接受 SDK 推送的日志
processors: [memory_limiter, k8sattributes, resource, batch]
exporters: [otlphttp]
关键配置说明:
otlpreceiver:开启 gRPC (4317) 和 HTTP (4318) 两个端口,接收应用推送的 traces 和 logsmemory_limiter:200MiB 硬限制,50MiB spike buffer,5 秒检查一次。必须放在 pipeline 的第一个 processorotlp/tempoexporter:gRPC 连接到集群内的 Tempo Servicehealth_check:暴露:13133端点,供 K8s liveness/readiness 探针使用resourceprocessor:注入cluster=homelab属性,标识数据来源
同时需要开启 Helm chart 的 ClusterIP Service:
service:
enabled: true # DaemonSet 模式默认不创建 Service
部署命令:
cd k8s/helm && just deploy-otel-collector
2. Oracle-k3s OTel Collector — 添加 Traces 管道#
Oracle 集群使用手动 DaemonSet manifest,需要在 ConfigMap 中添加 traces 管道配置:
exporters:
otlp/tempo:
endpoint: "100.107.254.112:31317" # Tempo NodePort via Tailscale
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, resource, batch]
exporters: [otlp/tempo]
同时添加 ClusterIP Service,让 oracle-k3s 上的应用也能通过 otel-collector.monitoring.svc:4317 推送 traces:
apiVersion: v1
kind: Service
metadata:
name: otel-collector
namespace: monitoring
spec:
selector:
app: otel-collector
ports:
- name: otlp-grpc
port: 4317
targetPort: 4317
- name: otlp-http
port: 4318
targetPort: 4318
部署命令:
kubectl --context oracle-k3s apply -f cloud/oracle/manifests/monitoring/otel-collector.yaml
kubectl --context oracle-k3s rollout restart daemonset/otel-collector -n monitoring
3. Tempo NodePort — 跨集群入口#
为了让 oracle-k3s 的 traces 能发送到 homelab 的 Tempo,需要创建一个 NodePort Service:
apiVersion: v1
kind: Service
metadata:
name: tempo-otlp-external
namespace: monitoring
spec:
type: NodePort
selector:
app.kubernetes.io/name: tempo
ports:
- name: otlp-grpc
port: 4317
targetPort: 4317
nodePort: 31317
这样 oracle-k3s 的 OTel Collector 通过 100.107.254.112:31317(Tailscale IP + NodePort)即可发送 gRPC traces 到 Tempo。
这与 Kopia 使用 NodePort :31515 的模式一致——对于需要跨集群直连的 gRPC 服务,Cloudflare Tunnel 不适合(双向流 + 524 超时),NodePort + Tailscale 是更可靠的方案。
4. Tempo 存储扩容#
将 Tempo 的 PVC 从 2Gi 扩容到 5Gi,为 traces 数据增长预留空间:
# values/tempo.yaml
persistence:
enabled: true
storageClassName: nfs-client
size: 5Gi
5. Grafana 数据源联动#
这是让追踪体验真正好用的关键——配置 Tempo 数据源与 Loki、Prometheus 的联动:
# kube-prometheus-stack additionalDataSources
- name: Tempo
type: tempo
url: http://tempo.monitoring.svc.cluster.local:3200
jsonData:
tracesToLogsV2:
datasourceUid: loki
filterByTraceID: true
filterBySpanID: true
tags:
- key: k8s.namespace.name
value: k8s_namespace_name
- key: k8s.pod.name
value: k8s_pod_name
- key: service.name
value: service_name
tracesToMetrics:
datasourceUid: prometheus
nodeGraph:
enabled: true
serviceMap:
datasourceUid: prometheus
配置效果:
- tracesToLogs:在 Tempo 查看 trace 时,可以一键跳转到 Loki 查看该请求的关联日志,自动根据 traceID 和 spanID 过滤
- tracesToMetrics:从 trace 跳转到 Prometheus 查看对应服务的 RED 指标
- nodeGraph:可视化展示服务间的调用拓扑
- serviceMap:基于 Prometheus 的
traces_spanmetrics_*指标绘制服务地图
踩坑:之前 Tempo 数据源 URL 配置的是
:3100,这其实是 Loki 的端口。Tempo 的查询端口是:3200。花了一些时间才发现这个小错误。
验证#
部署完成后,直接用 curl 发送一个测试 trace 来验证:
# 通过 port-forward 访问 OTel Collector
kubectl port-forward svc/opentelemetry-collector-agent -n monitoring 4318:4318
# 发送测试 trace
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d '{
"resourceSpans": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "demo-app"}},
{"key": "cluster", "value": {"stringValue": "homelab"}}
]
},
"scopeSpans": [{
"spans": [{
"traceId": "5b8aa5a2d2c872e8321cf37308d69df2",
"spanId": "051581bf3cb55c13",
"name": "GET /api/test",
"kind": 2,
"startTimeUnixNano": "1740000000000000000",
"endTimeUnixNano": "1740000000500000000",
"status": {"code": 1}
}]
}]
}]
}'
收到 HTTP 200 响应后,通过 Tempo API 验证 trace 已持久化:
kubectl port-forward svc/tempo -n monitoring 3200:3200
curl -s "http://localhost:3200/api/traces/5b8aa5a2d2c872e8321cf37308d69df2" | jq '.batches[0].resource'
返回了完整的 trace 数据,包含 service.name=demo-app 和 cluster=homelab 属性。
应用接入指南#
基础设施已就绪,接下来就是在自研服务中接入 OTel SDK。对于所有语言,K8s Deployment 中只需要添加以下环境变量:
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector.monitoring.svc:4317"
- name: OTEL_SERVICE_NAME
value: "my-service"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "cluster=homelab,k8s.namespace.name=my-namespace"
Go#
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
exp, err := otlptracegrpc.New(ctx) // 自动读取 OTEL_EXPORTER_OTLP_ENDPOINT
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp))
otel.SetTracerProvider(tp)
return tp, nil
}
HTTP 中间件使用 otelhttp:
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
handler := otelhttp.NewHandler(mux, "server")
Java + Spring Boot(零代码修改)#
Spring Boot 可以通过 Java Agent 实现零代码自动埋点:
# 下载 OTel Java Agent
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar /otel/
ENV JAVA_TOOL_OPTIONS="-javaagent:/otel/opentelemetry-javaagent.jar"
Agent 自动 instrument Spring MVC、JDBC、Redis、gRPC 等常用库,无需修改任何业务代码。
Node.js#
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(), // 读取 OTEL_EXPORTER_OTLP_ENDPOINT
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
auto-instrumentations-node 包含 Express、Koa、HTTP、MySQL、Redis 等 30+ 库的自动埋点。
Rust#
use opentelemetry::global;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::trace::TracerProvider;
fn init_tracer() -> TracerProvider {
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.build()
.expect("failed to create exporter");
let provider = TracerProvider::builder()
.with_batch_exporter(exporter)
.build();
global::set_tracer_provider(provider.clone());
provider
}
配合 tracing-opentelemetry crate 可以将 Rust 的 tracing 生态与 OTel 打通。
跨集群 NodePort 汇总#
完成本次改进后,homelab 目前使用的 NodePort 端口:
| NodePort | 服务 | 用途 |
|---|---|---|
| 31317 | tempo-otlp-external | oracle-k3s traces → Tempo gRPC |
| 31515 | kopia-server-external | Kopia gRPC 备份客户端 |
| 31100 | loki-external | oracle-k3s logs → Loki HTTP |
| 31090 | prometheus-external | oracle-k3s metrics → Prometheus remote write |
这些端口都仅通过 Tailscale 内网暴露,不经过 Cloudflare Tunnel。
总结#
经过这次改进,Homelab 的可观测性体系从"两个信号"(Logs + Metrics)升级为完整的"三个信号"(Logs + Metrics + Traces):
| 信号 | 采集 | 传输 | 存储 | 查询 |
|---|---|---|---|---|
| Logs | OTel filelog receiver | OTLP HTTP | Loki 3.x | LogQL |
| Metrics | Prometheus scrape / OTel prometheusremotewrite | Remote Write | Prometheus | PromQL |
| Traces | OTel OTLP receiver | OTLP gRPC | Tempo 2.8.2 | TraceQL |
所有数据在 Grafana 中实现了联动:Tempo trace → 关联 Loki 日志 → 关联 Prometheus 指标。这正是 Grafana Labs 推崇的 LGTM 栈最佳实践。
后续计划#
- 在第一个自研 Go 服务中实际接入 OTel SDK
- 添加
probabilistic_samplerprocessor,按比例采样(当 trace 量增大时) - 部署 Grafana Trace Dashboard(服务拓扑图、延迟分布)
- 升级 oracle-k3s 的 OTel Collector 从 v0.120.0 到最新版本
相关文章: