docker 的 Java 容器的远程调试

docker 的 Java 容器的远程调试

前提

Java 应用容器中必须安装 JDK 才能支持远程调试,仅安装 JRE 是不支持远程调试的,因为 JRE 中不包含了 JPDA

具体分析,请看《IDEA 远程调试.docx》

修改 Dockerfile

参考文档:IDEA 远程调试 Docker 容器中的 Spring Boot 程序 | 程序员技术之旅

Dockerfile 文件:制定了 JAVA_OPTS 的默认值

FROM openjdk:11-oraclelinux8
WORKDIR /app
COPY target/property-env-0.0.1-SNAPSHOT.jar /app/app.jar
ENV JAVA_OPTS="-Xms10m -Xmx20m"
CMD ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]

设置 JAVA_OPTS 为 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

docker run -d \
-p 8080:8080 -p 5005:5005 \
-e JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" \
myapp

直接在 jib-maven-plugin 中配置

参考博客:jib-maven-plugin java_tool_options-掘金

<jvmFlag> 中添加 JAVA_TOOL_OPTIONS 参数,同时在 <port> 中指定端口。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot.custom</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <encryptJarPrefixs>${encryptJarPrefixs}</encryptJarPrefixs>
            </configuration>
        </plugin>

        <!-- 通过正则替换配置文件里的spring.profiles.active,从而实现配置文件切换 -->
        <!-- 由于采用的外置config,maven的resource替换不好用,正则替换也更为强大 -->
        <plugin>
            <groupId>com.google.code.maven-replacer-plugin</groupId>
            <artifactId>replacer</artifactId>
        </plugin>

        <!-- 自定义打包 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
        </plugin>


        <!-- 打包docker镜像  -->
        <plugin>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>jib-maven-plugin</artifactId>
            <version>3.2.1</version>
            <configuration>
                <!--from节点用来设置镜像的基础镜像,相当于Docerkfile中的FROM关键字-->
                <from>
                    <!-- 使用的公司仓库,可以替换,必须添加host 192.168.115.208 harbor.dameng.io -->
<!--                        <image>openjdk:8u342-jre@sha256:46a298905a037f46e5bc93aae3b061e0e148beab5098e6d0c26d5e7981ac36e3</image>-->
<!--                        为了使用远程调试功能,开始使用jdk-->
<!--                        86e863cc57215cfb181bd319736d0baf625fe8f150577f9eb58bd937f5452cb8-->
<!--                        <image>openjdk:8u342-jdk@sha256:53ff4b6f85a89d88a34a0e8a00f1df940d15aee8cc1c717f919cc368ece0218e</image>-->
                    <image>openjdk:8u342-jdk</image>
<!--                        <image>azul/zulu-openjdk-alpine:8u362-8.68.0.21-arm64</image>-->
                    <!-- <image>azul/zulu-openjdk-alpine:8u362-8.68.0.21-jre-arm64</image>-->
                    <!-- <image>azul/zulu-openjdk-alpine:8u362-8.68.0.21</image>-->
                    <auth>
                        <username>${docker.auth.username}</username>
                        <password>${docker.auth.password}</password>
                    </auth>
                </from>
                <to>
                    <!--镜像名称和tag,使用了mvn内置变量${project.version},表示当前工程的version-->
                    <image>dms/dms-dataaggregation-all:1.0</image>
<!--                        <auth>-->
<!--                            <username>${docker.auth.username}</username>-->
<!--                            <password>${docker.auth.password}</password>-->
<!--                        </auth>-->
                </to>
                <outputPaths>
                    <tar>${project.build.directory}/${docker.image.name}-image.tar</tar>
                    <digest>${project.build.directory}/${docker.image.name}-image.digest</digest>
                    <imageId>${project.build.directory}/${docker.image.name}-image.id</imageId>
                    <imageJson>${project.build.directory}/${docker.image.name}-image.json</imageJson>
                </outputPaths>
                <!--容器相关的属性-->
                <container>
                    <mainClass>com.dameng.dms.dataaggregation.DataAggregationAllApplication</mainClass>
                    <!--jvm内存参数-->
                    <jvmFlags>
                        <jvmFlag>-XX:+UseContainerSupport</jvmFlag>
                        <jvmFlag>-XX:InitialRAMPercentage=50.0</jvmFlag>
                        <jvmFlag>-XX:MinRAMPercentage=50.0</jvmFlag>
                        <jvmFlag>-XX:MaxRAMPercentage=75.0</jvmFlag>
                        <jvmFlag>-Xss512K</jvmFlag>
                        <jvmFlag>-XX:MetaspaceSize=256m</jvmFlag>
                        <jvmFlag>-XX:MaxMetaspaceSize=512m</jvmFlag>
                        <jvmFlag>-Djava.awt.headless=true</jvmFlag>
                        <jvmFlag>-Dfile.encoding=utf-8</jvmFlag>
                        <jvmFlag>-Djava.security.egd=file:/dev/./urandom</jvmFlag>
                        <jvmFlag>-XX:+DisableExplicitGC</jvmFlag>
                        <jvmFlag>-XX:-UseAdaptiveSizePolicy</jvmFlag>
                        <jvmFlag>-Duser.timezone=GMT+08</jvmFlag>
                        <!-- 添加远程调试参数 -->
                        <!-- -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=10009-->
                        <jvmFlag>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=10009</jvmFlag>
                    </jvmFlags>
                    <!--要暴露的端口-->
                    <ports>
                        <port>8080</port>
                        <!-- 远程调试接口 -->
                        <port>10009</port>
                    </ports>
                    <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                </container>
                <!--开启允许上传镜像到仓库中-->
                <allowInsecureRegistries>true</allowInsecureRegistries>
                <!--外部文件-->
                <extraDirectories>
                    <paths>
                        <path>
                        <from>${project.build.directory}/config</from>
                        <into>/config</into>
                        </path>
                    </paths>
                </extraDirectories>
            </configuration>
        </plugin>

        <!-- 复制docker镜像需要的文件  -->
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-resource</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/config</outputDirectory>
                        <resources>
                            <resource>
                                <directory>${project.build.directory}</directory>
                                <includes>
                                    <include>*.yml</include>
                                </includes>
                            </resource>
                            <resource>
                                <directory>${project.basedir}/config</directory>
                                <includes>
                                    <include>application-${profile.active}.yml</include>
                                    <include>bootstrap.yml</include>
                                    <include>*.xml</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <!-- 打包docker镜像 end -->

    </plugins>

    <!-- 会影响 Spring dataflow 相关配置文的加载 不要放开-->
<!--        <resources>-->
<!--            <resource>-->
<!--                <directory>${basedir}/config</directory>-->
<!--                <includes>-->
<!--                    <include>**/*.xml</include>-->
<!--                    <include>**/*.yml</include>-->
<!--                    <include>**/*.yaml</include>-->
<!--                    <include>**/*.properties</include>-->
<!--                </includes>-->
<!--            </resource>-->
<!--        </resources>-->

</build>

指定环境变量 JAVA_TOOL_OPTIONS - 推荐

参考博客:冷知识!如何远程调试在K8S POD中的Java应用程序!-腾讯云开发者社区-腾讯云

直接指定环境变量即可,不需要修改打包的过程,比上面的方法更加灵活和方便,推荐此方法

这种做法好像只能跟 openjdk 的基础镜像配合使用,比如 openjdk:8u342-jdk

docker run -d --name dms --network host -m 4096M -p 8080:8080  -e JAVA_TOOL_OPTIONS='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=10009' -v /data/dms_docker/application-prod.yml:/config/application-prod.yml -v /data/dms_docker/data:/data -v /data/dms_docker/logs:/logs dms/dms-dataaggregation-all:1.0

ps: 其实本质上,以上两种方式是同一种方式,在 <jvmFlag> 中指定 JVM 参数的效果等同于在环境变量中指定。

感觉本质上,跟直接 Jar 启动的时候的远程调试的方式没有啥区别

参考《IDEA 远程调试.docx》