Jenkins中无法启动子进程的解决办法

Jenkins中无法启动子进程的解决办法

 先介绍下场景:

在Jenkins中新建了一个Job,假设你在一些列Build Step之前/之后,启动了一个进程,打个比方说启动一个Jboss进程。等到Build完成,你去Console Output中查看显示启动成功,甚至PID也有了。但是当你去后台查看的时候,发现其实这个进程根本不存在,并没有启动成功。

不过如果你使用的是较早的Hudson版本(Ver 1.136),并且是直接在页面中的Build Session中(如Excute Shell)执行命令的话,你可能会得到如下的提示:

Process leaked file descriptors. See http://wiki.hudson-ci.org/display/HUDSON/Spawning+processes+from+build for more information

如果你看过这篇文章,那么你就会明白造成这种问题的原因是什么:Jenkins默认会在Build结束后Kill掉所有的衍生进程,用官方的话来说就是:

To reliably kill processes spawned by a job during a build, Jenkins contains a bit of native code to list up such processes and kill them.

 问题分析:

The reason this problem happens is because of file descriptor leak and how they are inherited from one process to another. Jenkins and the child process are connected by three pipes (stdin/stdout/stderr.) This allows Jenkins to capture the output from the child process. Since the child process may write a lot of data to the pipe and quit immediately after that, Jenkins needs to make sure that it drained the pipes before it considers the build to be over. Jenkins does this by waiting for EOF.

When a process terminates for whatever reasons, the operating system closes all the file descriptors it owned. So even if the process didn't close stdout/stderr, Jenkins will nevertheless get EOF.

The complication happens when those file descriptors are inherited to other processes. Let's say the child process forks another process to the background. The background process (AKA daemon) inherits all the file descriptors of the parent, including the writing side of the stdout/stderr pipes that connect the child process and Jenkins. If the daemon forgets to close them, Jenkins won't get EOF for pipes even when the child process exits, because daemon still have those descriptors open. That's how this problem happens.

从官方的说明中可以看出,造成这种问题的原因,是由于文件描述符的丢失以及子进程是如何继承的。

Jenkins和子进程之间的通信使用三根管道进行:stdin,stdout,stderr,由于Jenkins可以捕捉到子进程的输出,而子进程可能往stdout中写入大量数据然后立即退出,Jenkins为了完整的读取子进程的输出,会在用于子进程stdout的管道上等待EOF。这样,无论子进程由于什么原因退出,系统将关闭进程使用的文件描述符,因而Jenkins总是可以收到EOF。

但是当子进程在后台fork出另一个进程的时候(比如A fork出了B,启动一个daemon进程),情况就出现一些变化。由于进程B会继承进程A的文件描述 符,如果进程B没有关闭这些描述符,即使进程A退出,这些描述符依然是打开的,Jenkins将不会在相应管道上收到EOF。

通常一个实现良好的daemon会关闭所有文件描述符以避免这个问题,但是总有一些实现没有遵循这个规则。在更旧版本的Hudson上,这个问题甚至将导致Job无法结束,Jenkins一直在那等待EOF。

 解决办法:

现在我们知道,Jboss之所以没有启动成功,是因为它没有关闭继承的文件描述符,Jenkins在Job构建过程结束后认为Jboss进程未终止,因而将其kill掉了。

1. 使用daemonize工具

在这个页面中https://wiki.jenkins-ci.org/display/JENKINS/Spawning+processes+from+build介绍了一种方法:

On Unix, you can use a wrapper like this to make the daemon behave.

在Unix上,你可以使用daemonize工具将程序作为实现良好的daemon进程运行以避免这个问题:

安装daemonize工具包,使用daemonize命令wrap 相应进程的启动指令,类似于:

  1. daemonize ${your.process} start  

另外,页面中也给出了如何在Windows平台解决这个问题的方法,具体的看页面中的介绍就可以了。

2. 重设环境变量BUILD_ID

在研究这个问题的时候,找到了另外一篇文章:https://wiki.jenkins-ci.org/display/JENKINS/ProcessTreeKiller,这篇文章进一步描述了Hudson杀掉衍生进程的情况:

The ProcessTreeKiller takes advantage of the fact that by default a new process gets a copy of the environment variables of its spawning/creating process.

It sets a specific environment variable in the process executing the build job. Later, when the user requests to stop the build job's process it gets a list of all processes running on the computer and their environment variables, and looks for the environment variable that it initially set for the build job's process.

Every job with that environment variable in its environment is then terminated.

意思就是说,Jenkins会在执行Job时设置一系列的环境变量,这些环境变量将被Job衍生出的进程所继承。Jenkins在Kill掉衍生进程的时候会查看进程的环境变量,如果找到它之前设置的环境变量,就将其杀掉。

因此,Wiki中也给出了另外一个简单的方法来避免进程被杀掉:

A convenient way to achieve that is to change the environment variable BUILD_ID which Jenkins's ProcessTreeKiller is looking for. This will cause Jenkins to assume that your daemon is not spawned by the Jenkins build.

修改Hudson设置的环境变量BUILD_ID的值,从而让Jenkins认为此进程不是由Job的构建过程衍生的,如:

  1. BUILD_ID=dontKillMe /usr/apache/bin/httpd  

后面的"/usr/apache/bin/httpd"可以省略,即只需要在parameter build trigger中加入一个string parameter,变量名为BUILD_ID,值为dontKillMe即可。

3. 启动时添加禁用参数

除以上两种方式以外,上面的Wiki页面中还提到了第三种解决办法,个人觉得这种方案最靠谱最彻底,不需要再去配置每个Job了:

you can disable this feature by setting a Java property named "hudson.util.ProcessTree.disable" to the value "true". This can be done as a parameter to the "java" binary when starting Jenkins:

在启动Jenkins的时候直接通过Java选项来关闭Jenkins杀掉所有衍生进程的这个功能:

  1. java -Dhudson.util.ProcessTree.disable=true -jar jenkins.war  
<think>好的,我现在需要解决用户的问题:Docker中的Jenkins在更新后无法启动。首先,我得仔细分析可能的原因,并参考用户提供的引用内容。 首先,用户提到的引用[5]中提到,安装插件后升级了Jenkins版本,导致容器无限重启。这可能是因为插件依赖的Jenkins版本与当前版本不兼容,或者升级过程中出现错误。我需要考虑如何回滚到之前的版本或者修复配置。 接下来,用户可能没有正确备份数据,导致升级失败后无法恢复。引用[4]提到使用数据卷挂载Jenkins_home,如果用户遵循了这一点,可以尝试恢复数据。需要检查数据卷的状态,确保配置文件没有损坏。 另外,权限问题也可能导致启动失败。引用[2]中讨论了权限被拒绝的问题,解决方案是将Jenkins用户加入docker组。如果更新后权限设置被重置,可能需要重新配置用户组或检查文件权限,尤其是/var/run/docker.sock的权限是否正确。 还有可能,Docker服务本身的问题,比如服务未运行(引用[1]),或者容器启动参数不正确。需要检查Docker服务状态,确认容器启动命令是否正确,例如挂载的卷、端口映射等是否在更新后发生变化。 查看容器日志是关键步骤,用户提到在引用[5]中使用docker logs命令但没有输出。这可能是因为容器无法启动到生成日志的阶段,或者日志路径有问题。需要尝试其他方法获取日志,比如直接进入容器内部查看,或者检查宿主机的日志系统如journalctl。 如果回滚版本可行,用户需要拉取旧版本的Jenkins镜像并重新启动容器,同时确保挂载的数据卷正确。引用[4]中的运行命令可以作为参考,调整镜像版本为之前的稳定版。 最后,预防措施也很重要,比如定期备份、使用特定版本标签避免自动升级,以及测试环境先行。这些步骤能帮助用户避免未来出现类似问题。 综合以上分析,解决方案应分步骤进行:检查日志、恢复备份、调整版本、修复权限和配置,最后采取预防措施。需要确保用户按照步骤操作,并在每个环节验证结果,以逐步排除问题原因。</think>### Docker中Jenkins更新后无法启动的解决方案 #### 1. **检查容器日志** 首先通过以下命令查看容器的实时日志,定位具体错误原因: ```bash docker logs -f <jenkins容器名或ID> ``` 若日志无输出(如引用[5]描述的无限重启场景),尝试强制进入容器查看内部状态: ```bash docker exec -it <容器ID> /bin/bash -c "tail -n 100 /var/jenkins_home/logs/*.log" ``` 若容器无法启动,可能需要通过数据卷直接查看日志文件(假设数据卷挂载在`$pwd`目录): ```bash cd <宿主机的Jenkins数据目录>/logs tail -n 100 *.log ``` #### 2. **回滚到旧版本** 若更新后因版本冲突导致崩溃(如引用[5]中插件依赖的版本不兼容问题),重新运行旧版本镜像: ```bash # 停止并删除当前容器 docker stop <容器ID> && docker rm <容器ID> # 拉取旧版本镜像(例如2.346.2) docker pull jenkins/jenkins:2.346.2 # 重新运行容器(挂载原有数据卷) docker run --name jenkins -v $pwd:/var/jenkins_home -p 8080:8080 -d jenkins/jenkins:2.346.2 ``` #### 3. **修复配置文件** 若日志显示配置文件损坏(如`config.xml`错误),进入数据卷目录手动修复: ```bash cd <宿主机的Jenkins数据目录> vim config.xml # 删除或修复异常配置段落 ``` #### 4. **调整权限问题** 若权限问题导致启动失败(如引用[2]中`/var/run/docker.sock`权限问题): ```bash # 将Jenkins用户加入docker组 docker exec -it <容器ID> /bin/sh -c "groupadd docker && usermod -aG docker jenkins" # 或调整宿主机文件权限(谨慎操作) chmod 777 /var/run/docker.sock ``` #### 5. **清理临时文件** 删除数据卷中的临时锁定文件(如`jenkins.plugins.git.GitSCM`相关错误): ```bash rm -rf <数据卷目录>/updates/*.json rm -rf <数据卷目录>/plugins/*.lock ``` #### 6. **重建容器并验证** 重新启动容器后,验证服务状态: ```bash curl http://localhost:8080 # 检查HTTP响应状态 ``` --- ### 关键操作注意事项 | 步骤 | 风险 | 备注 | |------|------|------| | 回滚版本 | 需确认旧镜像与数据兼容 | 优先选择`-lts`标签的稳定版[^4] | | 修改配置文件 | 错误修改可能导致二次崩溃 | 建议先备份`config.xml` | | 调整权限 | 过度开放权限引入安全隐患 | 推荐使用用户组而非`777`[^2] | --- ### 预防措施 1. **定期备份数据卷** 使用`tar`命令或第三方工具备份`/var/jenkins_home`目录。 2. **固定镜像版本** 避免使用`latest`标签,改用`jenkins/jenkins:2.401.2-lts`等明确版本号。 3. **测试环境先行** 在非生产环境中验证插件更新和版本升级的兼容性。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值