jvm groovy程序作为kubernetes作业运行的可能原因在执行期间转储线程

tjrkku2a  于 2022-11-07  发布在  Kubernetes
关注(0)|答案(1)|浏览(171)

我有一个简单的groovy脚本,它利用GPars库的withPool功能向两个内部API端点并行地发出HTTPGET请求。
该脚本在本地运行良好,无论是直接运行还是作为docker容器运行。
当我将其部署为Kubernetes Job时(在我们的内部EKS集群中:1.20),它也在那里运行,但在它遇到第一个withPool调用时,我看到了一个巨大的线程转储,但执行继续,并成功完成。

注意:集群中的容器使用以下Pod安全上下文运行:

securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 1000

环境


# From the k8s job container

groovy@app-271df1d7-15848624-mzhhj:/app$ groovy --version
WARNING: Using incubator modules: jdk.incubator.foreign, jdk.incubator.vector
Groovy Version: 4.0.0 JVM: 17.0.2 Vendor: Eclipse Adoptium OS: Linux

groovy@app-271df1d7-15848624-mzhhj:/app$ ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
groovy       1     0  0 21:04 ?        00:00:00 /bin/bash bin/run-script.sh
groovy      12     1 42 21:04 ?        00:00:17 /opt/java/openjdk/bin/java -Xms3g -Xmx3g --add-modules=ALL-SYSTEM -classpath /opt/groovy/lib/groovy-4.0.0.jar -Dscript.name=/usr/bin/groovy -Dprogram.name=groovy -Dgroovy.starter.conf=/opt/groovy/conf/groovy-starter.conf -Dgroovy.home=/opt/groovy -Dtools.jar=/opt/java/openjdk/lib/tools.jar org.codehaus.groovy.tools.GroovyStarter --main groovy.ui.GroovyMain --conf /opt/groovy/conf/groovy-starter.conf --classpath . /tmp/script.groovy
groovy     116     0  0 21:05 pts/0    00:00:00 bash
groovy     160   116  0 21:05 pts/0    00:00:00 ps -ef

脚本(相关部分)

@Grab('org.codehaus.gpars:gpars:1.2.1')
import static groovyx.gpars.GParsPool.withPool

import groovy.json.JsonSlurper
final def jsl = new JsonSlurper()

//...

while (!(nextBatch = getBatch(batchSize)).isEmpty()) {
    def devThread = Thread.start {
        withPool(poolSize) {
            nextBatch.eachParallel { kw ->
                String url = dev + "&" + "query=$kw"
                try {
                    def response = jsl.parseText(url.toURL().getText(connectTimeout: 10.seconds, readTimeout: 10.seconds,
                            useCaches: true, allowUserInteraction: false))
                    devResponses[kw] = response
                } catch (e) {
                    println("\tFailed to fetch: $url | error: $e")
                }
            }
        }
    }

    def stgThread = Thread.start {
        withPool(poolSize) {
            nextBatch.eachParallel { kw ->
                String url = stg + "&" + "query=$kw"
                try {
                    def response = jsl.parseText(url.toURL().getText(connectTimeout: 10.seconds, readTimeout: 10.seconds,
                            useCaches: true, allowUserInteraction: false))
                    stgResponses[kw] = response
                } catch (e) {
                    println("\tFailed to fetch: $url | error: $e")
                }
            }
        }
    }
    devThread.join()
    stgThread.join()
}

停靠文件

FROM groovy:4.0.0-jdk17 as builder

USER root
RUN apt-get update && apt-get install -yq bash curl wget jq

WORKDIR /app

COPY bin /app/bin

RUN chmod +x /app/bin/*

USER groovy
ENTRYPOINT ["/bin/bash"]
CMD ["bin/run-script.sh"]

bin/run-script.sh只是在运行时下载上面的groovy脚本并执行它。

wget "$GROOVY_SCRIPT" -O "$LOCAL_FILE"

...

groovy "$LOCAL_FILE"

一旦执行命中对withPool(poolSize)的 * 第一个 * 调用,就会有一个巨大的线程转储,但执行仍在继续。
我想弄清楚 * 什么 * 可能导致这种行为。任何想法🤷🏽‍♂️?
Thread dump

wn9m85ua

wn9m85ua1#

为了子孙后代,在这里回答我自己的问题。
这个问题原来是this log4j2 JVM hot-patch的问题,我们目前正在利用它来修复最近的log4j 2漏洞。这个代理(作为DaemonSet运行)修补我们所有k8s集群中所有正在运行的JVM。
不知何故,这会导致我的基于OpenJDK 17的应用程序发生线程转储。(也使用了一个预打包的OpenJDK 17)。这是一个服务,所以我可以看到几乎每半个小时发生一次线程转储!有趣的是,有其他JVM服务(和一些SOLR 8部署)* 没有 * 这个问题🤷🏽‍♂️.
无论如何,我和我们的devops团队一起工作,临时 * 排除 * 了正在运行部署的节点,你瞧,线程转储消失了!
宇宙的平衡已经恢复🧘🏻‍♂️。

相关问题