java调试命令不成功_LXC Java调试命令无效与共享PID Namespace

我们在项目中使用LXC(Linux Container)对系统进行资源控制,上线期间发现一个问题,使用LXC启动Java进程后,java调试命令(如jps/jstat)无效。其实,java调试命令无效只是问题的表面现象,真正原因在于Container与宿主机没有共享PID Namespace。本文将分析其中原因,并给出解决方案。

一、问题现象

我们发现,使用lxc启动Java进程后,jvm的调试命令(如jps/jstat)无效。正常情况下,jps命令可以获得java进程的pid,是其他许多java/jvm调试命令的基础,因此我们以jps命令为例分析此问题。我们知道,Jps命令查看进程信息实际依赖目录/tmp/hsperfdata_xxx中的临时文件,且该目录下所有文件都以进程的pid为文件名。举例来说,我们在物理节点启动一个Java进程(不使用lxc),可以通过jps命令查看pid,且发现在目录/tmp/hsperfdata_xxx下存在一个同名的文件。具体情况如图1所示。

04cc106e7b7fc1718dfcc3b698106915.png

图1物理节点上启动Java进程

测试与调研发现,当我们使用LXC启动java进程时,jps返回的是lxc内部pid,而不是在宿主机上的pid,目录/tmp/hsperfdata_xxx下的文件也是以lxc内部pid命名。当我们使用lxc-ps或者ps命令时,我可以获得该java进程在宿主机上真正的pid。由于pid号无法正确对应,导致jps和其他java调试命令无效。具体情况如图2所示。

bf515ff4c9e66e2ae6bc5eaf24c1244e.png

图2 Container内启动Java进程

一位博友似乎遇到过类似问题,在其博客()上给出了巧妙的解决方法,给予我很多启示。不过,我认为该方法稍显繁杂,也没有从根本上解决问题,因此我提出了自己的分析思路与解决方法。

二、问题分析

上述现象表明,lxc内部与宿主机使用两套独立的PID Namespace,二者没有真正共享PIDNamespace。该问题与lxc源码中的“lxc clone机制”有关。

阅读lxc-0.7.5源码,当使用lxc-start或lxc-execute命令创建一个新的Container时,lxc会调用函数lxc_spawn(存在与lxc源文件start.c中),而lxc_spwan又会调用函数lxc_clone(存在与lxc源文件namespace.c),去clone一个lxc。在调用lxc_clone之前,会先设置clone_flags,相应源码如表1代码所示。

表1 lxc源码设置clone_flags

clone_flags  = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS;

// lxc 源代码设置的clone_flags

lxc源码设置的clone_flags包括CLONE_NEWPID标志位,该标志位表明新lxc使用一套新的独立的PIDNamespace。

三、解决方案

明白了问题所在和代码实现,我们开始解决此问题。这里特别强调一点,本项目需求是资源控制,对隔离性没有要求,不要求Container之间、Contianer与宿主机之间使用独立的Namespace。只有符合上述前提,才可以使用本解决方案。

具体来说,我修改lxc源码。在源码lxc-0.7.5-test/src/lxc/start.c中,找到函数lxc_spawn,修改clone_flags,去除CLONE_NEWPID标志位,使得container与宿主机共享同一个PIDNamespace。修改后代码如表2代码所示。然后编译安装(./configure, make, sudo make install)。

表2修改后的lxc源码设置clone_flags

clone_flags  = CLONE_NEWUTS| CLONE_NEWIPC|CLONE_NEWNS;

//  设置的clone_flags,去除CLONE_NEWPID

我们实验一下,这样修改是否有效。当我们使用修改后的LXC启动java进程时,jps直接返回宿主机上的pid,目录/tmp/hsperfdata_xxx下的文件也是如此。jps的返回结果与lxc-ps(或者ps)命令的返回结果一致,具体情况如图3所示。由于jps可以返回正确的pid,其他以jps为基础的调试命令也可以正常使用。经实际测试,Java调试工具全部有效。

802d4d16e809127fb152feefbad4eb32.png

图3修改LXC代码后Container内启动Java进程

四、新问题

在解决上述问题(共享PIDnamespace)之后,产生了一个原来不存在的新问题。对于启动多个进程的脚本,lxc-stop\lxc-kill命令只能杀掉父进程,而不能杀掉子进程。以图4为例,Container内包括三个进程sh myloop.sh(7708)、java MyLoop(7709)、java Myoop(7710),前一个是后两个的父进程。当时用lxc-stop命令时,OS只杀死了父进程,而两个子进程的父进程变为了init(pid:1),即子进程变为了孤儿进程,被init“收养”。

a01548f9cb6afb26e2409d1ffa842c8c.png

图4修改LXC代码后无法一次性删除所有进程

我们发现,新问题与共享PID namespace有关。在修改lxc源码前,lxc内部使用单独一套PIDNamespace,内部存在一个与init类似的初始化进程,当使用lxc-stop命令时,会杀掉初始化进程,进而杀掉lxc内所有进程。在修改lxc源码后,lxc内部与宿主机共享一套PID Namespace。此时,除了资源控制因素外,lxc内的进程与直接运行在宿主机上的进程没有本质区别。对于启动多个进程的脚本,lxc-stop命令只能杀掉其中的父进程,而不能杀掉其子进程。当然,使用kill命令仍然可以杀死对应进程。

五、新问题解决方案

本文讨论的两个问题是“共享PID Namespace”这枚硬币的两面,所以我认为这两个问题很难同时得到根本解决,需要进行权衡。在本项目中,我们认为共享PID Namespace更为重要。因此,我提出一个具有可行性的方案,当应用方需要重新部署时,可以按照以下步骤:

1)应用方删除所有应用进程;

2)使用lxc-stop/lxc-kill命令删除lxc;(正常情况下,当lxc内部所有应用进程被杀死后,lxc会自行退出。但为了保险起见,在删除所有应用进程后,仍需要显式地删除lxc);

3)使用lxc重新启动应用。

根据以上步骤,我们可以完全删除Container,具体情况如图5所示。

d855dea826d3baca3f7efa3395a4f9ca.png

图5修改LXC代码后先删除所有进程再删除Container

参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值