[alibaba/arthas]为使用Alpine Linux作为Docker基础镜像的Java应用提供profiler命令支持

2024-07-17 284 views
2
问题背景

在 arthas cli 命令行,可使用 profiler 命令支持生成应用热点的火焰图,背后使用了 async-profiler 提供支持。在 Linux 环境下,async-profiler 默认只对 x86-64、aarch64 这两种操作系统架构提供了 glibc 版的动态链接库文件:

libasyncProfiler-linux-x64.so
libasyncProfiler-linux-arm64.so

但是在基于 musl libc 实现的 Alpine Linux 环境下,执行 profiler 命令会误加载到 libasyncProfiler-linux-x64.so 或者 libasyncProfiler-linux-arm64.so ,首先会提示缺少 libstdc++、libgcc 库文件,手工执行下面的命令安装后,再次执行 profiler 命令会造成应用 jvm 进程 crash : hs_err_pid17.log

Stack: [0x0000ffff60bf0000,0x0000ffff60dffac8],  sp=0x0000ffff60dfe520,  free space=2105k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [libstdc++.so.6+0x1683dc]
C  [libstdc++.so.6+0xd9b9c]  std::locale::operator=(std::locale const&)+0x54
C  [libstdc++.so.6+0xd9094]  std::ios_base::_M_init()+0x4c
C  [libstdc++.so.6+0x1153c0]  std::basic_ios<char, std::char_traits<char> >::init(std::basic_streambuf<char, std::char_traits<char> >*)+0x18
C  [ArthasJniLibrary5656461273075369895.tmp+0x273ac]  Symbols::parseLibraries(NativeCodeCache**, int volatile&, int, bool)+0xf4
C  [ArthasJniLibrary5656461273075369895.tmp+0x20208]  VM::ready()+0x28
C  [ArthasJniLibrary5656461273075369895.tmp+0x20c48]  VM::init(JavaVM_*, bool)+0x430
C  [ArthasJniLibrary5656461273075369895.tmp+0x210e0]  JNI_OnLoad+0x14
C  [libjava.so+0xecc4]  Java_java_lang_ClassLoader_00024NativeLibrary_load+0xb4
j  java.lang.ClassLoader$NativeLibrary.load(Ljava/lang/String;Z)V+0
j  java.lang.ClassLoader.loadLibrary0(Ljava/lang/Class;Ljava/io/File;)Z+328
j  java.lang.ClassLoader.loadLibrary(Ljava/lang/Class;Ljava/lang/String;Z)V+48
j  java.lang.Runtime.load0(Ljava/lang/Class;Ljava/lang/String;)V+57
j  java.lang.System.load(Ljava/lang/String;)V+7
j  one.profiler.AsyncProfiler.getInstance(Ljava/lang/String;)Lone/profiler/AsyncProfiler;+23
j  com.taobao.arthas.core.command.monitor200.ProfilerCommand.profilerInstance()Lone/profiler/AsyncProfiler;+165
j  com.taobao.arthas.core.command.monitor200.ProfilerCommand.process(Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+43
j  com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+34
j  com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(Lcom/taobao/arthas/core/shell/command/impl/AnnotatedCommandImpl;Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+2
j  com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+5
j  com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(Ljava/lang/Object;)V+5
j  com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run()V+11
..........................
.............

然后在 Alpine Linux 环境下查看基于 Alpine Linux 环境手工编译的 libasyncProfiler 和原始的基于 glibc + xxx Linux 环境编译的 libasyncProfiler,可以看到如下区别: image

我提了一个 issue #741给 async-profiler ,async-profiler 的开发者建议我根据需要自行编译 libasyncProfiler

考虑到在 K8s 集群环境下,使用 Alpine Linux 作为 Java 应用基础镜像的用户比较多,所以这里考虑增加对 Alpine Linux x86-64 和 aarch64 这两种操作系统环境下使用 profiler 进行支持。

主要改动代码

基于 async-profiler 源码和Alpine Linux x86-64 和 aarch64 这两种操作系统的 docker 镜像构建环境打包 libasyncProfiler so

FROM --platform=$TARGETPLATFORM base-registry.cn-hangzhou.cr.aliyuncs.com/cnstack/alpine-linux-gcc-builder-env:gcc-openjdk11

ARG TARGETOS
ARG TARGETARCH
ARG OSS_ENDPOINT
ARG ALIYUN_USER_AK
ARG ALIYUN_USER_SK

WORKDIR /root

RUN wget https://github.com/async-profiler/async-profiler/archive/refs/tags/v2.9.tar.gz -O async-profiler-2.9.tar.gz && \
    tar zxvf async-profiler-2.9.tar.gz && cd async-profiler-2.9 && \
    sed -i 's/CXXFLAGS=/CXXFLAGS=-static-libgcc -static-libstdc++ /' Makefile && make && \
    curl https://gosspublic.alicdn.com/ossutil/install.sh | bash && \
    ossutil -e ${OSS_ENDPOINT} -i ${ALIYUN_USER_AK} -k ${ALIYUN_USER_SK} \
    cp build/libasyncProfiler.so oss://async-profiler/alpine/libasyncProfiler-${TARGETOS}-musl-${TARGETARCH}.so -u

Note: 这里默认将 libstdc++ 和 libgcc 作为静态依赖打进 libasyncProfiler so 文件,使客户在 Alpine Linux 环境下为了使用 arthas 的 profiler 命令时,不再需要单独安装 libstdc++ 和 libgcc.

生成的两个 libasyncProfiler so 文件: libasyncProfiler-linux-musl-amd64.so libasyncProfiler-linux-musl-arm64.so

libasyncProfiler so 文件加载入口处代码: core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java

if (OSUtils.isLinux()) {
    if (OSUtils.isX86_64() && OSUtils.isMuslLibc()) {
       profierSoPath = "async-profiler/libasyncProfiler-linux-musl-amd64.so";
    } else if(OSUtils.isX86_64()){
        profierSoPath = "async-profiler/libasyncProfiler-linux-x64.so";
    } else if (OSUtils.isArm64() && OSUtils.isMuslLibc()) {
       profierSoPath = "async-profiler/libasyncProfiler-linux-musl-arm64.so";
    } else if (OSUtils.isArm64()) {
       profierSoPath = "async-profiler/libasyncProfiler-linux-arm64.so";
    }
}
测试情况

image

image

image

image

回答

8

通过 build async-profiler 工作流打包的 libasyncProfiler-linux-musl-x64.so 和 libasyncProfiler-linux-musl-arm64.so 测试情况:

Alpine Linux x86-64(=x64=amd64) docker: image

Alpine Linux aarch64(=arm64) docker: image

7
1. 使用build-async-profiler.yml workflow 构建出的 async-profiler 2.10 版本动态链接库文件(.so)测试情况 image 2.使用build-async-profiler.yml workflow 构建时的已知问题

使用build-async-profiler.yml workflow 构建时,偶尔会遇到 invalid flag: --source 的错误:

.....
................
/usr/lib/jvm/java-11-openjdk/bin/javac --source 7 -target 7 -Xlint:-options -g:none src/helper/one/profiler/Server.java
error: invalid flag: --source
Usage: javac <options> <source files>
use --help for a list of possible options
make: *** [Makefile:145: src/helper/one/profiler/Server.class] Error 2
Error: Process completed with exit
................
.....

Action: 实际上是 -source 7,async-profiler 的作者已经在近期的一个 commit 中改掉了。实际上遇到这个问题时,终止当前构建任务,重试即可。

3.使用build-async-profiler.yml workflow 构建出的 libasyncProfiler-xxx.so 使用时已知问题

在 arthas 命令行执行 profiler start -d 60 命令时, 目标Java进程标准输出日志显示:

[WARN] Unknown argument: framebuf
[WARN] Unknown argument: framebuf

这是 async-profiler 2.7 版本引入的 change: - Do not accept unknown agent arguments

Action: 需要 arthas 侧修改 framebuf 参数,让 libasyncProfiler-xxx.so 能识别出来,当前不影响 profiler 命令的使用,可稍后修改 arthas profiler 发出的命令进行优化。