ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Dockerfile 配置 SkyWalking Java Agent 完整指南:从环境变量到避坑实践

Dockerfile 配置 SkyWalking Java Agent 完整指南:从环境变量到避坑实践 如果你正在做 Java 服务的容器化改造大概率会碰到这样一个场景应用镜像已经能跑起来了健康检查也过了但打开 SkyWalking 的 Web UI服务列表里始终看不到这个新服务。查了一圈问题往往不是出在监控系统本身而是 Agent 压根没被打进镜像或者打进去了没生效。在 Dockerfile 里配置 SkyWalking是 Java 服务以容器方式接入 APM 时绕不开的一步。这篇就完整讲清楚怎么配、为什么这样配、哪些地方容易阴沟里翻船。1. 为什么是 DockerfileAgent 接入的三种姿势与最终取舍1.1 三种接入方式对比Java Agent 的接入原理并不复杂JVM 在启动时通过-javaagent:xxx.jar参数加载探针 jar探针用 Instrumentation API 改写字节码从而实现链路数据采集。所以核心问题只有一个——怎么让 Agent 出现在每个运行环境里。我见过身边团队常见的做法有三类改启动脚本在应用原有的start.sh里加一行JAVA_OPTS-javaagent:/path/to/skywalking-agent.jar $JAVA_OPTS。这种方式在一两台物理机上没问题可一旦服务拆成十几个 Docker 容器脚本分散在各处环境变量传递、Agent 版本同步全都靠自觉迟早出乱子。重新打包镜像把 Agent 和配置都打进业务镜像里。这才是 Dockerfile 方式的核心思路能让用什么版本的 Agent、连哪个 OAP 后端、服务叫什么名字这些信息完完全全固化下来跟着镜像走做到构建一次、到处运行。K8s 场景下的 InitContainer 方式在 Pod 里用apache/skywalking-java-agent镜像把 Agent 文件拷贝到共享目录。这个思路其实和 Dockerfile 殊途同归本质都是解决运行容器里得有 Agent 文件这件事。但离开 K8s 的裸 Docker 环境你还是得回到 Dockerfile。1.2 为什么建议把 Agent 固化进镜像把 Agent 放进镜像的最大好处是可追溯。镜像有版本、有构建记录、有哈希Agent 版本跟着镜像走出问题的时候能明确知道这一版跑的是哪个 Agent如果选择运行时挂载或脚本注入环境一变就可能出现开发环境好好的预发环境没数据这种排查起来极其痛苦的问题。其次这种方式对团队协作最友好。后端研发不需要关心 APM 怎么接运维只需要知道镜像里有这么个 Agent前端甚至无感知。Dockerfile 是构建产物的直接载体在 CI/CD 流水线里天然排得上用场。2. 动工前的避坑功课版本匹配、JDK 与基础镜像2.1 版本匹配Agent 和 OAP 后端不是随便搭的很多新手挂在第一步不是不会写 Dockerfile而是版本没对上。SkyWalking Java Agent 和 OAP 后端之间走的是 gRPC 协议大版本不匹配时经常出现握手失败、数据上报异常等问题。我的建议是Agent 版本跟 OAP 后端尽量保持同一个大版本。比如后端是 9.xAgent 也用 9.x 的最新小版本后端是 8.x就用 8.x 的 Agent。别图省事拿 8.x 的 Agent 配 9.x 的后端不是说一定跑不起来而是排查问题时你会多一个变量。版本选型时去 SkyWalking 官方发布页看一眼 Release Notes 里标注的兼容范围比什么事后猜都强。另外要留意 JDK 版本约束。SkyWalking Java Agent 9.x 对 JDK 8 以上的环境基本都支持但如果你的服务还在 JDK 6/7 上那就只能用老版本的 Agent。这个约束在公共镜像上选基础镜像时候就要确定下来。2.2 基础镜像选型JRE 够用别背着 JDK 跑Agent 是运行期探针它只需要目标 JVM 存在不需要编译能力。所以运行镜像用 JRE 版本就可以没必要把整套 JDK 塞进去能省下几百 MB。这里我把几种常见基础镜像放在一起对比一下基础镜像体积兼容性实际体验eclipse-temurin:17-jre中等高社区维护活跃优先推荐glibc 环境兼容性最好openjdk:17-jre中等高但维护已逐渐转移老牌选择新项目不太建议alpine 系列小中需注意 musl 库兼容性省体积但踩坑时排查更费劲distroless小中无 shell 难调试不适合刚开始接 APM 的团队如果没特殊体积要求直接选eclipse-temurin对应版本的 JRE 镜像最稳妥。它基于 glibc兼容性比 Alpine 好有 shell出问题还能docker exec进去看环境。用 Alpine 省那几十 MB等 Agent 日志写不进去或者动态库加载异常时你会怀念 shell 的。2.3 下载源别把 GitHub 当唯一依赖构建环境不总在外网通联。官方 Agent 包发布在 GitHub Releases 上很多企业内网的构建机访问不了。几种可行方案把 Agent zip 包提前下载到公司内部制品库Nexus/AritfactoryDockerfile 里直接用内部地址。构建机配置代理让curl和unzip可以访问外网。用多阶段构建里的 Builder 阶段做下载运行阶段不依赖任何下载工具。这块要提前设计好不然写完 Dockerfile构建那一步就会被网络卡住。后面给的示例就是内部制品库 多阶段构建的写法生产环境会更顺手。3. Dockerfile 完整示例与逐行拆解3.1 一份可以直接抄的 Dockerfile下面这份 Dockerfile 是我在实际项目里调整过的通用写法适用于 Spring Boot 这类可执行 Jar 的 Java 服务# 阶段一Builder只负责准备 SkyWalking Agent FROM eclipse-temurin:17-jdk AS builder ARG SW_AGENT_VERSION9.2.0 ARG SW_AGENT_DOWNLOAD_URLhttps://github.com/apache/skywalking-java/releases/download/v${SW_AGENT_VERSION}/skywalking-agent-${SW_AGENT_VERSION}.zip WORKDIR /build RUN curl -L -o skywalking-agent.zip ${SW_AGENT_DOWNLOAD_URL} \ unzip skywalking-agent.zip \ ls -la /build/skywalking-agent # 阶段二运行镜像 FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuilder /build/skywalking-agent /opt/skywalking-agent COPY your-app.jar /app/app.jar # SkyWalking 核心配置 ENV JAVA_TOOL_OPTIONS-javaagent:/opt/skywalking-agent/skywalking-agent.jar ENV SW_AGENT_SERVICE_NAMEdemo-service ENV SW_AGENT_COLLECTOR_BACKEND_SERVICESoap:11800 ENV SW_AGENT_LOGGING_OUTPUTstdout EXPOSE 8080 ENTRYPOINT [java, -jar, /app/app.jar]构建和启动命令对应如下# 构建 docker build --build-arg SW_AGENT_VERSION9.2.0 -t demo-service:1.0 . # 如果后端在 docker-compose 网络里用服务名访问 docker run --network skywalking_demo \ -e SW_AGENT_SERVICE_NAMEdemo-service \ -e SW_AGENT_COLLECTOR_BACKEND_SERVICESoap:11800 \ demo-service:1.03.2 关键行拆解为什么这样写先看JAVA_TOOL_OPTIONS这一行。这是很多人看不懂的地方——为什么不用直接在ENTRYPOINT里写-javaagentJAVA_TOOL_OPTIONS是 JVM 启动时自动读取的环境变量。只要环境变量里存在它Java 会在启动时把它当 JVM 参数解析并加载。这样做的最大好处是不管ENTRYPOINT里怎么拼 Java 命令哪怕是应用内部用 ProcessBuilder 拉起的子 JVM只要继承了环境变量Agent 都会被挂上。当然这个特性也意味着容器内如果有多个 Java 进程它们都会加载 Agent。一般业务容器里只有一个 Java 进程问题不大但心里要清楚这个机制。再强调一个表面细节但实际很重要的点-javaagent必须写在-jar的前面。JVM 解析启动参数是有顺序的Agent 需要在 main 类加载之前完成Instrumentation。写成java -jar app.jar -javaagent:xxx.jar是没用的那个参数会被当成应用参数传进去。至于COPY --frombuilder这是多阶段构建的核心。Builder 阶段用了带 JDK、带 curl、带 unzip 的大镜像但运行阶段只把/build/skywalking-agent这个目录拿过来。这样运行镜像里不会残留下载工具、解压工具和源码包镜像体积和攻击面都能控制住。3.3 关于 Agent 目录的裁剪与权限skywalking-agent目录里除了核心 jar还有plugins、optional-plugins、bootstrap-plugins、config等目录。如果业务只用了常见框架optional-plugins里的插件可以不拷贝能省不少空间。比如只保留核心组件COPY --frombuilder /build/skywalking-agent/skywalking-agent.jar /opt/skywalking-agent/ COPY --frombuilder /build/skywalking-agent/config /opt/skywalking-agent/config COPY --frombuilder /build/skywalking-agent/plugins /opt/skywalking-agent/plugins但有个前提插件裁剪需要了解业务用了哪些中间件、什么框架一次性全删掉容易导致部分调用链采集不到。我的习惯是先完整拷贝跑通再按需裁剪。权限方面也是个容易漏的坑。如果镜像里用非 root 用户启动应用Agent 默认日志目录/opt/skywalking-agent/logs可能没写权限Agent 日志写不进去表面看没报错实际采集已经悄悄失败。要么显式创建并授权RUN mkdir -p /opt/skywalking-agent/logs \ chown -R 1001:1001 /opt/skywalking-agent \ chmod -R 775 /opt/skywalking-agent USER 1001要么在环境变量里把 Agent 日志指向可写目录ENV SW_AGENT_LOGGING_DIR/tmp/agentlogs。Docker 里我倾向用后者少一个授权环节日志也能通过挂载目录统一收集。4. 这些 SW_AGENT_ 环境变量建议背下来4.1 环境变量映射规则一眼看懂配置项SkyWalking Java Agent 的配置体系很有意思。它有一个agent.config文件作为默认配置里面每一行形如agent.service_name${SW_AGENT_SERVICE_NAME:default}。也就是说很多配置项在定义时就已经预留了环境变量入口入口命名规则是SW_AGENT_ 配置项路径点号转下划线。比如agent.service_name对应SW_AGENT_SERVICE_NAMEagent.sample_rate对应SW_AGENT_SAMPLE_RATEcollector.backend_service对应SW_AGENT_COLLECTOR_BACKEND_SERVICESlogging.output对应SW_AGENT_LOGGING_OUTPUT如果你记不住具体变量名直接去看agent.config文件每个配置项后面都写好了变量名。这是一个很重要的排查技巧很多问题扫一眼配置文件就能明白。配置项的优先级也要清楚系统属性-Dskywalking.xxx最高环境变量次之agent.config文件是兜底默认值。理解了这条规则你就知道为什么 Dockerfile 里写了ENV SW_AGENT_SERVICE_NAME运行时加-e SW_AGENT_SERVICE_NAMExxx还能覆盖镜像里的默认值。4.2 高频配置项总表环境变量对应配置项默认值说明SW_AGENT_SERVICE_NAMEagent.service_name空服务在 SkyWalking UI 里显示的名字SW_AGENT_NAMEagent.service_name空老版本兼容写法同样表示服务名SW_AGENT_NAMESPACEagent.namespace空命名空间隔离多个环境共用 OAP 时常用SW_AGENT_COLLECTOR_BACKEND_SERVICEScollector.backend_service127.0.0.1:11800OAP 的 gRPC 地址支持逗号分隔多个SW_AGENT_SAMPLE_RATEagent.sample_rate-1全量采样率配置整数比例SW_AGENT_LOGGING_OUTPUTlogging.output文件stdout 是控制台输出容器推荐SW_AGENT_LOGGING_DIRlogging.dir当前目录/logsAgent 日志目录SW_AGENT_LOGGING_LEVELlogging.levelINFO调成 DEBUG 可查更详细的问题如果你在agent.config里看到SW_AGENT_TRACE_IGNORE_PATH这类变量那也是能直接通过环境变量操控的想忽略某些健康检查接口的链路采集直接给容器加环境变量即可不用改 Dockerfile 重新构建。4.3 JAVA_TOOL_OPTIONS 的两个隐藏坑坑一多个 Agent 或多个 JAVA_TOOL_OPTIONS 拼接问题。JAVA_TOOL_OPTIONS是一个整体字符串容器平台、基础镜像脚本、应用启动脚本如果都往里塞东西后设置的会覆盖之前的。比如某些云厂商的基础镜像会往JAVA_TOOL_OPTIONS注入额外参数你 Dockerfile 里写的ENV JAVA_TOOL_OPTIONS-javaagent:...就可能被覆盖掉。遇到这种情况优先检查镜像里是不是有对应的启动脚本或者改用JDK_JAVA_OPTIONSJDK 9 支持语义类似优先级稍低。不过JDK_JAVA_OPTIONS会多打印一行提示有的人会觉得日志不干净。坑二看到Picked up JAVA_TOOL_OPTIONS就以为出错了。这是认识问题不是 bug。JVM 只要用了JAVA_TOOL_OPTIONS环境变量启动时就会往 stderr 输出一行Picked up JAVA_TOOL_OPTIONS: -javaagent:...很多同事第一次看到这行日志吓一跳以为是异常。它是正常现象恰恰说明环境变量被正确读取了。5. OAP 地址写错是整个接入失败的头号原因5.1 单机 Docker容器里没有宿主机 localhost这是我在答疑里碰到最多的问题。Dockerfile 里写SW_AGENT_COLLECTOR_BACKEND_SERVICES127.0.0.1:11800然后 Agent 一直连不上 OAP。原因很简单容器的 localhost 是容器自己不是宿主机。如果 OAP 部署在宿主机上而容器使用默认 bridge 网络有几种写法可选使用 Docker Desktop 或新版 Docker Engine 提供的host.docker.internal域名。Linux 环境下给容器加启动参数docker run --add-hosthost.docker.internal:host-gateway。直接用宿主机的局域网 IP。这块的判断标准就一句话Agent 进程所在的网络空间里哪个地址能到达 OAP。写之前先在容器里用curl或nc探一下端口docker run --rm --network skywalking_demo \ -e SW_AGENT_COLLECTOR_BACKEND_SERVICESoap:11800 \ --entrypoint bash demo-service:1.0 \ -c cat /dev/tcp/oap/11800 echo OK能通再启动应用别让 Agent 在启动阶段就做无用功。5.2 Docker Compose 和 K8s 下的地址写法Docker Compose 里最自然的写法是用服务名services: skywalking-oap: image: apache/skywalking-oap-server:9.2.0 ports: - 11800:11800 - 12800:12800 demo-service: build: . environment: SW_AGENT_COLLECTOR_BACKEND_SERVICES: skywalking-oap:11800注意 Compose 里服务名就是网络里可用的 DNS 名不需要写 IP也不建议写 IP因为容器重建后 IP 可能变。depends_on只保证启动顺序不保证 OAP 就绪稳妥做法是给 OAP 加健康检查或者让应用启动逻辑本身有重试容错不然会出现 Agent 启动时连不上后端过一会儿才恢复的情况。K8s 里则写成 Service DNSSW_AGENT_COLLECTOR_BACKEND_SERVICESskywalking-oap.skywalking.svc.cluster.local:118005.3 端口选择11800 还是 12800这里有个很经典的误用SkyWalking OAP 提供两个端口11800是 Agent gRPC 上报端口12800是 Web UI 查询后端用的 HTTP 端口。Agent 连的是11800。我见过不止一次有人把12800填到SW_AGENT_COLLECTOR_BACKEND_SERVICES里结果链路数据一条都没有。判断方法很简单打开 SkyWalking UI能看 UI 说明 12800 是通的但 Agent 上报必须走 11800。排查的时候两个端口都确认一遍少走弯路。6. 验证接入是否成功别等 UI 出问题才回头查6.1 启动日志三板斧接入完成后第一件事不是打开 UI而是看容器日志。容器启动后先确认有没有输出Picked up JAVA_TOOL_OPTIONS。有说明 JVM 读到了环境变量。再看 SkyWalking Agent 自己的日志走stdout时正常启动会看到类似SkyWalking agent start的信息如果SW_AGENT_LOGGING_OUTPUT没设成stdout就进容器看日志文件docker exec -it 容器ID tail -f /tmp/agentlogs/skywalking-api.log如果 Agent 启动报错第一优先去看这几个方面路径是否存在、jar 是否有读权限、OAP 端口是否可达、版本是否匹配。这四件事占掉九成故障。6.2 UI 显示指标链路数据不是瞬间出现的Agent 成功上报后SkyWalking UI 的 General Service 页面需要一点时间才能刷出数据。链路数据一般是异步批量上报通常十几秒到一分钟内会看到服务出现。如果你调用了某个接口隔一会儿还没出现在 Topology 里再按下面的表格排查现象可能原因处理方式日志没有 Agent 启动信息JAVA_TOOL_OPTIONS被覆盖或路径错误检查 ENV、确认 jar 路径报错Connection refusedOAP 端口不通或地址错误容器内探端口确认 11800报错No available collectorgRPC 地址不可达或负载均衡探测失败检查网络、OAP 健康状态UI 一直不显示服务namespace 不一致或版本不匹配检查 SW_AGENT_NAMESPACE核对版本链路数据偶发缺失采样率配置或插件没覆盖对应框架调整采样率检查插件列表6.3 采样率与插件调优接入成功后下一步往往是控制成本。采样率用SW_AGENT_SAMPLE_RATE调整默认全量采样对于高流量服务可以按比例采样降低 Agent 对业务的性能影响和 OAP 的写入压力。需要忽略健康检查、探活类的请求链路可以用SW_AGENT_TRACE_IGNORE_PATH也通过环境变量直接配置不需要重新改 Dockerfile。插件方面SkyWalking Agent 加载的插件越多探针做过字节码增强的类越多启动阶段的影响就越大。在镜像里精确控制plugins目录只保留业务真正用到的中间件插件是我实际优化启动速度时验证过有效的办法。注意公众号常见的spring-cloud-gateway、dubbo、mysql这几个插件如果服务里没用到对应组件直接删掉对应 jar 就好。接入 SkyWalking 这件事本身不复杂但容器化环境把问题放大了网络空间、环境变量继承、版本协议、日志输出位置每一环都可能导致看起来没生效。我个人的习惯是在任何一次 APM 接入改造里都先写一个最小验证版本把 Agent 日志调到 stdout、OAP 地址探通、UI 出现第一个服务再往 Dockerfile 里叠加业务侧的复杂配置。一个小技巧是给所有以 SkyWalking 相关变量命名的 ENV 打上SW_前缀注释比如在 Dockerfile 里相邻放两行# SkyWalking trace settings这样后续接手的人一眼就能找到需要改的配置而不是翻遍整个构建文件。毕竟 Dockerfile 一旦多起来能让队友少花五分钟的注释就是值得写的注释。
返回列表