基于 OpenTelemetry:Kubernetes 环境下 Node.js 应用无侵入接入落地实践

本文介绍如何通过 DataKit Operator,为 Kubernetes 中的 Node.js 应用自动注入 OpenTelemetry 探针。

最佳实践
banner.png

背景:为什么 Node.js 上了 Kubernetes,问题反而更难定位

Node.js 应用运行在 Kubernetes 中以后,Pod 会随着发布、扩容和故障迁移不断变化。接口一旦变慢或报错,容器日志和 Kubernetes 指标通常只能回答“哪个 Pod 有异常”,却很难继续说明时间消耗在应用内部、数据库、Redis,还是下游服务。

真正的难点不是缺少数据,而是缺少一次请求从入口到下游的完整上下文。运维人员需要看到请求经过了哪些服务、每一步耗时多久、错误发生在哪个 Span,以及新版本发布前后的调用链是否发生变化。这正是 Trace 要解决的问题。

OpenTelemetry 能够自动采集 Node.js 调用链,但传统接入方式需要每个项目分别安装依赖、调整启动参数、配置上报地址。服务数量增加后,探针升级和配置一致性很快会成为新的维护负担。

这次实践选择 DataKit Operator,把探针接入从“每个项目单独改造”转为“平台统一配置、应用按标签启用”。业务镜像和业务代码保持不变,平台团队则可以集中管理探针版本、上报地址、资源属性和采样策略。

观测云如何解决这个问题

这套方案把职责分成三层。DataKit Operator 负责在新 Pod 创建时注入 OpenTelemetry 探针;OpenTelemetry 负责生成 Node.js Trace;DataKit 接收 OTLP 数据并发送到观测云。应用原有的容器日志和 Kubernetes 指标仍由 DataKit 采集,不会与 Trace 重复。

1.png

Node.js 应用
    │ OpenTelemetry 自动生成 Trace
    ▼
DataKit Service
    │ 接收并转发 OTLP 数据
    ▼
观测云应用性能监测
    │
    ├── 服务与接口性能
    ├── 完整调用链和错误 Span
    └── Pod、Namespace、Node 等资源属性

这种分工的价值在于,应用团队只需通过 Pod 标签声明“这个应用需要 Node.js 探针”,而平台团队只维护一套 Operator 规则。新 Pod 创建时,Operator 会加入 init Container、共享探针目录、NODE_OPTIONS 和 OTEL_* 环境变量,Node.js 进程启动后便能自动生成 Span。

实践:使用 DataKit Operator 自动注入 OpenTelemetry

前置条件

本文方案要求 DataKit Operator 不低于 v1.9.0,Node.js 不低于 20.6,并以 CommonJS 方式运行。普通 Linux 和 Alpine 镜像均可使用;ESM、bundler 打包、自定义 loader,或已经初始化另一套 OpenTelemetry SDK 的应用,应先在测试环境验证兼容性。

集群需要启用 MutatingAdmissionWebhook,节点能够拉取 Operator 和探针镜像,操作账号具备创建 Deployment、Service、RBAC 与 Admission Webhook 的权限。可以先检查 Node.js 版本和 Operator 是否已经存在:

kubectl -n production exec deploy/order-service -- node --version
kubectl -n datakit get deployment datakit-operator

安装 DataKit Operator

DataKit 与 DataKit Operator 是两个不同的组件:DataKit 负责采集和转发数据,Operator 负责监听新 Pod 并注入探针。因此,集群已经安装 DataKit 并不代表 Operator 已经存在。

使用 Helm 安装时,执行:

helm install datakit-operator datakit-operator \
  --repo https://pubrepo.guance.com/chartrepo/datakit-operator \
  -n datakit \
  --create-namespace

如果需要下载 YAML 后审查和纳管配置,官方文件地址为:

https://static.guance.com/datakit-operator/datakit-operator.yaml
curl -LO https://static.guance.com/datakit-operator/datakit-operator.yaml
kubectl create namespace datakit
kubectl apply -f datakit-operator.yaml
kubectl -n datakit rollout status deployment/datakit-operator

datakit Namespace 已存在时,可以跳过创建命令。安装完成后,还应查看 Operator 镜像版本和最近日志,确认没有证书、RBAC 或 Webhook 注册错误:

kubectl -n datakit get deployment datakit-operator \
  -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

kubectl -n datakit logs deployment/datakit-operator --tail=100

必须手动配置:新下载的 datakit-operator.yaml 默认没有 OpenTelemetry 注入规则

安装文件中的 admission_inject_v2.otels 默认是空数组 []。直接执行 kubectl apply -f datakit-operator.yaml 只会安装 Operator,不会自动启用 Node.js OpenTelemetry 注入。必须把 "otels": [] 替换为下文的 Node.js 规则,再重新应用 YAML 并重启 Operator。

2.jpg

开启 OpenTelemetry 采集器

DataKit 需要启用 opentelemetry 采集器,才能接收应用发送的 Trace。在 DaemonSet 的 ENV_DEFAULT_ENABLED_INPUTS 中加入 opentelemetry:

env:
  - name: ENV_DEFAULT_ENABLED_INPUTS
    value: >-
      cpu,disk,diskio,mem,system,container,opentelemetry

本文使用的 OTLP Trace 地址为:

http://datakit-service.datakit.svc.cluster.local:9529/otel/v1/traces

如果 DataKit 的 Namespace 或 Service 名称不同,需要同步调整后面的 Endpoint。接着在 datakit-operator.yaml 中搜索:

"otels": []

将其整体替换为下面的数组,不要重复粘贴第二个 admission_inject_v2,也不要删除同级的 ddtraces、logfwds 等配置:

"otels": [
  {
    "name": "otel-nodejs",
    "language": "nodejs",
    "namespace_selectors": ["*"],
    "label_selectors": [
      "admission.datakit/otel-language=nodejs"
    ],
    "check_annotation": false,
    "image": "ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:0.78.0",
    "envs": {
      "POD_NAME": "{fieldRef:metadata.name}",
      "POD_NAMESPACE": "{fieldRef:metadata.namespace}",
      "NODE_NAME": "{fieldRef:spec.nodeName}",
      "OTEL_SERVICE_NAME": "{fieldRef:metadata.labels['app']}",
      "OTEL_RESOURCE_ATTRIBUTES": "k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(POD_NAMESPACE),k8s.node.name=$(NODE_NAME)",
      "OTEL_TRACES_EXPORTER": "otlp",
      "OTEL_LOGS_EXPORTER": "none",
      "OTEL_METRICS_EXPORTER": "none",
      "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
      "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "http://datakit-service.datakit.svc.cluster.local:9529/otel/v1/traces"
    },
    "resources": {
      "requests": {
        "cpu": "100m",
        "memory": "64Mi"
      },
      "limits": {
        "cpu": "500m",
        "memory": "512Mi"
      }
    }
  }
]

这里有三个真正决定接入结果的配置:label_selectors 决定哪些 Pod 会被注入,OTEL_SERVICE_NAME 决定观测云中显示的服务名,OTEL_EXPORTER_OTLP_TRACES_ENDPOINT 决定 Trace 发送到哪个 DataKit。如果集群无法访问 ghcr.io,应先把自动探针镜像同步到企业私有仓库,再替换 image。

保存文件后重新应用并检查配置:

kubectl apply -f datakit-operator.yaml
kubectl -n datakit rollout restart deployment/datakit-operator
kubectl -n datakit rollout status deployment/datakit-operator
kubectl -n datakit get configmap -o yaml | grep -A 15 '"otels"'

应用启用探针

平台规则准备完成后,应用 Deployment 只需修改 Pod Template。下面示例中的 app 标签会成为观测云中的服务名:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        admission.datakit/otel-language: nodejs
      annotations:
        admission.datakit/ddtrace.enabled: "false"
        admission.datakit/otel.enabled: "true"
    spec:
      containers:
        - name: app
          image: registry.example.com/order-service:1.0.0
          ports:
            - name: http
              containerPort: 3000

标签和注解必须位于 spec.template.metadata,而不是 Deployment 最外层的 metadata。其中 ddtrace.enabled: "false" 用于避免同时加载 DDTrace 与 OpenTelemetry,otel.enabled: "true" 明确启用本次注入。

3.png

Operator 只处理新创建的 Pod,因此修改规则或 Deployment 后必须重新创建 Pod:

kubectl apply -f deployment.yaml
kubectl -n production rollout restart deployment/order-service
kubectl -n production rollout status deployment/order-service

检查

Operator 采用 fail-open 方式,注入失败时业务 Pod 仍可能正常创建。验证时应直接检查 NODE_OPTIONS 和 OTEL_* 环境变量:

kubectl -n production exec deploy/order-service -- \
  sh -c 'echo "$NODE_OPTIONS"; env | grep "^OTEL_" | sort'

正常情况下,Pod 中还能看到 datakit-otel-lib-init、datakit-otel-auto-instrument 共享卷和 /otel-auto-instrumentation-nodejs 挂载目录。探针最终会通过下面的启动参数先于业务代码加载:

--require /otel-auto-instrumentation-nodejs/autoinstrumentation.js

如果这些内容不存在,优先检查 Pod 标签位置、Operator 中的 otels 是否仍为空、是否同时匹配 DDTrace 规则,以及 Operator 日志中的 Warning。使用 runAsNonRoot: true 时还要显式设置非零 runAsUser;探针镜像拉取失败时则需要检查 GHCR 网络或私有镜像地址。

效果

接入完成后,访问一个会经过数据库、Redis 或下游 HTTP 服务的真实接口:

curl http://<业务访问地址>/api/orders

随后进入观测云“应用性能监测”,使用下面的条件定位数据:

service:order-service
source:opentelemetry

此时不仅能看到入口 HTTP Span,还能沿着火焰图查看数据库、缓存和下游服务调用。Span 的耗时、状态、错误信息以及 Pod、Namespace、Node 等 Kubernetes 资源属性,会被放在同一条 Trace 上下文中。排障人员可以从“接口慢”继续下钻到“哪一个调用慢、慢了多久、运行在哪个 Pod”。

4.jpg

上图中已经出现 sdk_language=nodejs 与 sdk_name=opentelemetry,说明数据确实来自 Node.js OpenTelemetry 探针。火焰图还能直观看到各 Span 的时间占比,错误 Span 会被单独标识,便于定位失败发生在哪一层。

生产推广时,建议先选一个非核心服务验证应用启动、CPU、内存和 Trace 完整性,再逐步灰度。低流量验证阶段可以暂时使用 100% 采样;数据稳定后,再根据请求量和成本配置 parentbased_traceidratio 等采样策略。

总结

DataKit Operator 将 Node.js OpenTelemetry 接入从项目级改造变成平台级能力。应用团队无需安装 npm 探针包或维护启动参数,只需在 Pod Template 中加入标签和注解;平台团队则统一管理探针版本、DataKit 地址、资源属性和采样策略。

落地时最容易遗漏的不是业务代码,而是 Operator 配置本身:新下载的 datakit-operator.yaml 中 "otels": [] 默认为空,必须手动补齐 Node.js 注入规则。完成配置后,还要重新创建 Pod,并通过 NODE_OPTIONS、OTEL_* 环境变量和观测云中的真实 Trace 三层验证接入结果。

当 Trace 与 Kubernetes 资源上下文进入观测云后,团队不仅能够定位一次慢请求,还可以让 AI 对多条 Trace、错误 Span 和版本差异做辅助归纳。这使可观测数据从“能看见”进一步走向“能解释、能验证、能形成排查闭环”。

参考资料

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台