在 Kubernetes 中使用 DataKit Operator 实现 Java 包级 APM 监控

文章介绍了如何在 Kubernetes 环境中通过 DataKit Operator 自动注入 Java Agent,并开启包级 APM 监控,无需修改业务代码即可追踪 Service 层方法调用与耗时,补全 Controller 到数据库之间的链路盲区,实现更细粒度的性能分析与问题定位。

最佳实践
banner.png

前言

Java Agent 对 Spring MVC、Servlet、HTTP Client、JDBC 等常见组件进行自动插桩后,通常已经能够展示接口入口、跨服务调用和数据库访问。但当一条请求的大部分时间消耗在业务 Service 中时,默认链路往往只能告诉我们“Controller 到 SQL 之间很慢”,无法继续回答是哪一个业务方法慢、方法之间如何嵌套,以及耗时发生在哪个业务阶段。如下图所示,trace详情中只能看到SysUserController.list,无法看到具体的业务方法。

包级插桩可以在不修改应用代码的情况下,将指定 Java 包下的业务方法自动转换成 span。本文以 Kubernetes 中运行的 RuoYi-Cloud 为例,通过 DataKit Operator 批量注入观测云扩展版 DDTrace Java Agent,并对 com.ruoyi.system.service.impl 开启包级监控,最终实现业务方法的监控,如下图所示,可以看到自定义方法c.r.s.s.i.SysUserServiceImpl.selectUserList的耗时:

本文假定 RuoYi 或其他 Spring Boot 应用已经部署到 Kubernetes,重点介绍 APM 接入和包级插桩,不展开数据库、注册中心和前端的安装过程。

1. 包级监控解决什么问题

包级监控适合以下情况:

  • 默认链路只有 Controller 和数据库 span,中间业务逻辑仍然是黑盒。
  • 不方便修改源码,却希望定位 Service 层慢方法或异常热点。
  • 希望建立 HTTP、Controller、Service、缓存和数据库的完整调用关系。

例如,用户列表接口调用 SysUserController.list 后会进入 SysUserServiceImpl.selectUserList。如果只看到 Controller 总耗时和 SQL 耗时,就无法判断分页初始化、权限范围处理还是 Service 自身逻辑消耗了时间。包级插桩生成 trace.annotation span 后,这段时间会在链路中单独展示。

自动插桩能够识别 Servlet、Spring MVC、JDBC 等框架边界,却不知道哪些普通 Java 方法具有业务意义。DD_TRACE_METHOD_PACKAGES 会在类加载时增强目标包,并把命中的方法作为 trace.annotation span 放入当前 Trace。

2. 测试环境说明

实施前确认:

  • Kubernetes 中已有可正常访问的 Spring Boot 应用(本文档以开源项目 RuoYi-Cloud 为例进行演示)。
  • DataKit 与应用位于同一集群,应用 Pod 能访问 DataKit Service。
  • 集群 DNS 能解析 DataWay 域名。
  • 具有创建 ConfigMap、Secret、Service、Operator 和重启 Deployment 的权限。
  • 已确认需要增强的包,本例为 com.ruoyi.system.service.impl

本案例验证通过的版本如下:

组件 示例版本 说明
DataKit 2.8.0 启用 ddtrace input
DataKit Operator 1.8.10 使用 admission_inject_v2
DDTrace Java Agent 1.63.7-ext 固定已验证的扩展版镜像
RuoYi-Cloud 3.6.8 案例业务版本,用于标识 DD_VERSION

本案例包含 ruoyi-gatewayruoyi-authruoyi-systemruoyi-genruoyi-jobruoyi-file 六个 Java 服务。观测云DataKit 以 DaemonSet 运行,DataKit Operator 通过 admission webhook 在 Pod 创建时注入 Java Agent。

RuoYi Java Pod
  ├── datakit-lib-init:复制 dd-java-agent.jar
  ├── JAVA_TOOL_OPTIONS:加载 -javaagent
          │
          ▼
datakit-service.datakit.svc.cluster.local:9529
          │
          ▼
DataKit ddtrace input → DataWay → 观测云 APM

Operator 只在 Pod 创建时修改 PodSpec,负责注入 initContainer、共享卷和 Agent 环境变量;Java Agent 则把 span 发送到 DataKit 的 9529/TCP,再经 DataWay 上报。六个服务都可获得基础 APM 能力,只有加载 com.ruoyi.system.service.implruoyi-system 会额外生成该包的方法 span。

3. 安装并配置 DataKit Operator

3.1 安装 Operator,确保链路数据已经成功采集

参考DataKit安装文档和DataKit Operator安装文档,确保在观测云上可以看到应用的链路数据。

3.2 对比基础 APM 与包级 APM 配置

Operator 已经完成 Java Agent 注入,开启包级监控不需要改变注入方式。核心差异是在原有 envs 中增加 DD_TRACE_METHOD_PACKAGES,将需要添加的包名填写进去:

 "envs": {
   "DD_AGENT_HOST": "datakit-service.datakit.svc.cluster.local",
   "DD_TRACE_AGENT_PORT": "9529",
   "DD_SERVICE": "{fieldRef:metadata.labels['app']}",
+  "DD_TRACE_METHOD_PACKAGES": "com.ruoyi.system.service.impl",
+  "DD_TRACE_STARTUP_LOGS": "true"
 }

其中 DD_TRACE_METHOD_PACKAGES 是启用包级插桩的必要配置;DD_TRACE_STARTUP_LOGS 只用于接入阶段输出 Agent 配置,方便确认规则是否加载。下面是可直接应用的完整 ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: datakit-operator-config
  namespace: datakit
data:
  jsonconfig: |-
    {
      "server_listen": "0.0.0.0:9543",
      "log_level": "info",
      "admission_inject_v2": {
        "ddtraces": [
          {
            "namespace_selectors": ["^ruoyi$"],
            "label_selectors": ["apm=enabled"],
            "check_annotation": false,
            "image": "pubrepo.guance.com/datakit-operator/dd-lib-java-init:v1.63.7-ext",
            "language": "java",
            "envs": {
              "DD_AGENT_HOST": "datakit-service.datakit.svc.cluster.local",
              "DD_TRACE_AGENT_PORT": "9529",
              "DD_SERVICE": "{fieldRef:metadata.labels['app']}",
              "DD_ENV": "demo",
              "DD_VERSION": "3.6.8",
              "DD_TRACE_SAMPLE_RATE": "1",
              "DD_TRACE_METHOD_PACKAGES": "com.ruoyi.system.service.impl",
              "DD_TRACE_STARTUP_LOGS": "true",
              "POD_NAME": "{fieldRef:metadata.name}",
              "POD_NAMESPACE": "{fieldRef:metadata.namespace}",
              "NODE_NAME": "{fieldRef:spec.nodeName}",
              "DD_TAGS": "pod_name:$(POD_NAME),pod_namespace:$(POD_NAMESPACE),host:$(NODE_NAME)"
            }
          }
        ]
      },
      "admission_inject": {
        "ddtrace": {}
      }
    }

Operator Deployment 从 datakit-operator-configjsonconfig 键读取配置。若使用 Helm 或平台模板安装,应先确认 Deployment 实际引用的 ConfigMap 名称和键名。命名空间与标签 selector 需要同时匹配。

关键配置说明:

配置 作用
namespace_selectors 使用正则限定命名空间,避免全局注入
label_selectors 只注入明确标记的业务 Pod
image 固定扩展版 Java Agent 镜像
DD_SERVICE 从 Pod 的 app 标签生成 APM 服务名
DD_TRACE_AGENT_PORT 显式使用 DataKit DDTrace 端口 9529
DD_TRACE_METHOD_PACKAGES 对指定业务包生成方法 span
DD_TRACE_STARTUP_LOGS 输出 Agent 启动配置,便于验证

应用 ConfigMap 后重启 Operator。Operator 从 ConfigMap 读取启动配置,仅更新 ConfigMap 不能保证运行中的 Operator 已加载新值。

kubectl apply -f datakit-operator-config.yaml
kubectl rollout restart deployment/datakit-operator -n datakit
kubectl rollout status deployment/datakit-operator -n datakit --timeout=180s

4. 标记并重建 Java Pod

Operator webhook 只在 Pod 创建时 注入 initContainer、共享卷和环境变量,不会修改已经运行的 Pod。因此修改 Operator 配置或包名后,必须重新创建目标 Pod。

为六个 Java Deployment 的 Pod 模板增加标签:

for service in gateway auth system gen job file; do
  kubectl patch deployment "ruoyi-${service}" -n ruoyi --type=merge \
    -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"app\":\"ruoyi-${service}\",\"apm\":\"enabled\"}}}}}"
done

标签必须写入 spec.template.metadata.labels。如果 app 已用于 Deployment selector,应保留与 selector 一致的值。

等待滚动发布完成:

for service in gateway auth system gen job file; do
  kubectl rollout status deployment/"ruoyi-${service}" \
    -n ruoyi --timeout=300s
done

如果环境变量无法由平台注入,也可以在 JVM 启动参数中使用等价配置:

-Ddd.trace.method.packages=com.ruoyi.system.service.impl

环境变量和 JVM 参数二选一即可。必须重建 Pod,是因为 webhook 只处理新建 Pod,Agent 也只在 JVM 启动时读取包级参数并增强目标类。

5. 验证 Agent 和包级规则

5.1 检查 Pod 注入结果

可以通过kubectl describe pod命令来检查应用Pod的环境变量,如下是本实验环境的输出

kubectl -n ruoyi describe  pod ruoyi-system-7fd88b644d-4j8hm
...省略配置...
    Readiness:  tcp-socket :8080 delay=20s timeout=1s period=5s #success=1 #failure=60
    Environment:
      SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR:  ruoyi-nacos:8848
      SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR:     ruoyi-nacos:8848
      DD_SERVICE:                                ruoyi-gateway
      JAVA_TOOL_OPTIONS:                          -javaagent:/datadog-lib/dd-java-agent.jar
      DD_AGENT_HOST:                             datakit-service.datakit.svc.cluster.local
      DD_TRACE_AGENT_PORT:                       9529
      DD_JMXFETCH_STATSD_HOST:                   datakit-service.datakit.svc.cluster.local
      DD_JMXFETCH_STATSD_PORT:                   8125
      DD_ENV:                                    demo
      DD_VERSION:                                3.6.8
      DD_TRACE_SAMPLE_RATE:                      1
      DD_TRACE_METHOD_PACKAGES:                  com.ruoyi.system.service.impl
      DD_TRACE_STARTUP_LOGS:                     true
      POD_NAME:                                  ruoyi-gateway-67c8d6cd84-85b6v (v1:metadata.name)
      POD_NAMESPACE:                             ruoyi (v1:metadata.namespace)
      NODE_NAME:                                  (v1:spec.nodeName)
      DD_TAGS:                                   pod_name:$(POD_NAME),pod_namespace:$(POD_NAMESPACE),host:$(NODE_NAME) 
...省略配置...   

本次修改最重要的配置为:

  • DD_TRACE_METHOD_PACKAGES 值需要与源码包声明完全一致。

6. 触发业务请求并在观测云验证

6.1 对比启用前后的链路

包级监控前后的变化集中在 Controller 与下游组件之间:

阶段 Operator 环境变量 用户列表链路
基础 APM 未配置 DD_TRACE_METHOD_PACKAGES servlet.request → spring.handler → mysql.query
包级 APM DD_TRACE_METHOD_PACKAGES=com.ruoyi.system.service.impl servlet.request → spring.handler → trace.annotation → mysql.query

基础 APM 能证明请求进入了 Controller 并执行了 SQL;包级 APM 进一步展示 SysUserServiceImpl.selectUserList,从而把 Controller 与数据库之间的业务耗时单独拆分出来。

6.2 触发请求并确认结果

登录 RuoYi 后进入“系统管理 → 用户管理”,打开或刷新用户列表。也可以使用已有访问令牌调用接口:

curl -sS -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  'https://<RUOYI_HOST>/prod-api/system/user/list?pageNum=1&pageSize=10'

记录请求时间,在观测云进入“应用性能监测 → 链路”,筛选 service=ruoyi-system ,打开 GET /user/list 链路并确认其中包含 SysUserServiceImpl.selectUserList

本案例的脱敏验收结果如下:

operation resource span 类型 状态
servlet.request GET /user/list entry ok
spring.handler SysUserController.list local ok
trace.annotation c.r.s.s.i.SysUserServiceImpl.selectUserList local ok

c.r.s.s.i 是探针对 com.ruoyi.system.service.impl 的缩写。同一批业务请求还可观察到:

  • SysUserServiceImpl.selectUserByUserName
  • SysUserServiceImpl.updateLoginInfo
  • SysPermissionServiceImpl.getRolePermission

最终验收应确认入口请求、Controller 和自定义方法使用同一 Trace ID,父子关系正确。截图时需遮盖真实 Trace ID、用户标识、访问令牌和业务参数。

写在最后

包级插桩会增加 span 数量和字节码增强范围。不建议一开始就配置 com.ruoyi、公司根包或第三方框架包,也应避免工具包、实体类包和高频 getter/setter。对于流量较大的生产服务,应先选择一个具体的 Service 实现包,在小流量或预发布环境验证后再扩大范围。

参考文档

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

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

立即开始

选择观测云版本

代码托管平台