如何停止chroot中的所有进程?


16

我有多个LVM分区,每个分区都包含Ubuntu安装。有时,我想执行apt-get dist-upgrade,将安装更新为最新的软件包。我使用chroot进行此操作-该过程通常是这样的:

$ sudo mount /dev/local/chroot-0 /mnt/chroot-0
$ sudo chroot /mnt/chroot-0 sh -c 'apt-get update && apt-get dist-upgrade'
$ sudo umount /mnt/chroot-0

[未显示:我还装载和卸载/mnt/chroot-0/{dev,sys,proc}的绑定坐骑到真正的/dev/sys并且/proc,作为距离-升级似乎希望这些在场]

但是,升级到精确版本后,该过程将不再起作用-最终的卸载将失败,因为文件/mnt/chroot-0系统上仍然有打开的文件。lsof确认chroot中有打开文件的进程。这些过程已在dist升级期间启动,我认为这是因为chroot中的某些服务在service postgresql restart软件包升级后需要重新启动(例如,通过)。

因此,我认为我需要告诉upstart停止在此chroot中运行的所有服务。有没有办法可靠地做到这一点?

我试过了:

cat <<EOF | sudo chroot /mnt/chroot-0 /bin/sh
# stop 'initctl' services 
initctl list | awk '/start\/running/ {print \$1}' | xargs -n1 -r initctl stop
EOF

initctl list 看来,做正确的事情,只有在这个特定的根已经启动列表的过程。正如Tuminoid的建议,我也尝试添加此内容:

cat <<EOF | sudo chroot /mnt/chroot-0 /bin/sh
# stop 'service' services
service --status-all 2>/dev/null |
    awk '/^ \[ \+ \]/ { print \$4}' |
    while read s; do service \$s stop; done
EOF

但是,这些似乎并不能解决所有问题。已守护进程并重新绑定到PID 1的进程不会停止。我也尝试过:

sudo chroot /mnt/chroot-0 telinit 0

但是在这种情况下,init 不会区分单独的根,而是关闭了整个计算机。

因此,是否有任何方法可以告诉init停止特定chroot中的所有进程,以便我可以安全地卸载文件系统?新贵公司是否有任何设施可以对chroot中的所有子进程执行SIGTERM / SIGKILL操作(如在常规关机过程中一样)?


这不是您实际问题的答案,但可能会有所帮助:我建议查看lxc软件包。lxc提供了简单的工具来启动和清除容器中的实例。
ion

Answers:


16

除了内核在这里保持正常状态外,我什么都不信任,所以我不(ab)使用init来完成此工作,也不要指望自己真正知道已安装或未安装的内容(某些软件包)可以挂载额外的文件系统,例如binfmt_misc)。因此,对于过程屠宰,我使用:

PREFIX=/mnt/chroot-0
FOUND=0

for ROOT in /proc/*/root; do
    LINK=$(readlink $ROOT)
    if [ "x$LINK" != "x" ]; then
        if [ "x${LINK:0:${#PREFIX}}" = "x$PREFIX" ]; then
            # this process is in the chroot...
            PID=$(basename $(dirname "$ROOT"))
            kill -9 "$PID"
            FOUND=1
        fi
    fi
done

if [ "x$FOUND" = "x1" ]; then
    # repeat the above, the script I'm cargo-culting this from just re-execs itself
fi

对于卸载chroot,我使用:

PREFIX=/mnt/chroot-0
COUNT=0

while grep -q "$PREFIX" /proc/mounts; do
    COUNT=$(($COUNT+1))
    if [ $COUNT -ge 20 ]; then
        echo "failed to umount $PREFIX"
        if [ -x /usr/bin/lsof ]; then
            /usr/bin/lsof "$PREFIX"
        fi
        exit 1
    fi
    grep "$PREFIX" /proc/mounts | \
        cut -d\  -f2 | LANG=C sort -r | xargs -r -n 1 umount || sleep 1
done

作为附录,我要指出的是,将它作为一个init问题来对待它可能是错误的方法,除非您实际上在chroot中有一个init和一个单独的进程空间(即:对于LXC容器) 。使用单个init(位于chroot之外)和共享的进程空间,这不再是“ init的问题”,而是要由您自己来查找碰巧具有违规路径的进程,因此可以进行上述过程。

从最初的帖子中还不清楚这些系统是否是完全可启动的系统,而您只是在外部进行升级(这就是我的阅读方式),或者它们是否是您用于软件包构建之类的chroot。如果是后者,那么您可能还需要一个policy-rc.d(例如mk-sbuild所插入的那个),该策略首先禁止init作业。显然,如果它们也想成为可引导系统,那不是一个理智的解决方案。


它们是可引导系统,但policy-rc.d看起来像是一种有趣的方法(我可以在与chroot交互后将其删除)。这是否会影响双方/etc/rc*.d-和/etc/init/*.conf式的工作?
杰里米·克尔


既不是新贵也不是sysvinit的“ consult policy-rc.d”,而是通过invoke-rc.d来执行的,所有postinst脚本均应将其用于与init作业进行交互。实际上,对于DTRT来说,除了破损的包(应该修复)之外。尽管如此,无论问题是由于策略遗漏,没有适当的政策,还是长期运行的其他某种形式的流程(上述主要用例),上述“用火清除”脚本仍能胜任这里的buildds是在构建过程中本身是背景的东西,或在sbuild中没有父对象的东西。
无限

1
尝试解决utpstart的chroot支持问题之一。我相当确定,kill -9不会阻止重新启动重新启动的Upstart作业(如果已指定重新启动)。因此,您实际上仍然确实需要从chroot内部查询新贵以了解事情是否还在运行。我认为这很不幸,我们应该有一些其他途径来杀死这些工作。就是说,我确实知道initctl list / awk / grep方法+您的方法应该在哪里完成。
SpamapS 2012年

1
@SpamapS:很好-手动杀死init作业确实会导致它们重新启动。能够告诉upstart执行特定于chroot的关闭,停止定义的作业,然后杀死在chroot中具有根目录的所有剩余的已重新关联的进程,将是很棒的。
杰里米·克尔

0

您已经自己确定了问题:有些事情service ...在dist-upgrade期间运行,service不是Upstart的一部分,而是的一部分sysvinit。添加类似的awk魔术service --status-all以停止用于Upstart服务的sysvinit服务。


3
嗯谢谢 几乎更好,但这也不涵盖所有服务。我已经运行sudo chroot /mnt/chroot-0 service --list-allsudo chroot /mnt/chroot-0 initctl list,都报告没有服务在运行。但是,/usr/bin/epmd(来自erlang-base)仍在运行。
Jeremy Kerr

0

我知道这个问题已经很老了,但是我认为今天的问题与2012年的问题一样重要,希望有人认为此代码有用。我为正在做的事情编写了代码,但以为我会分享。

我的代码不同,但是思想与@infinity非常相似(实际上-我现在对/ proc / * / root唯一了解的原因是他的回答-感谢@infinity!)。我还添加了一些很酷的附加功能

#Kills any PID passed to it
#At first it tries nicely with SIGTERM
#After a timeout, it uses SIGKILL
KILL_PID()
{
        PROC_TO_KILL=$1

        #Make sure we have an arg to work with
        if [[ "$PROC_TO_KILL" == "" ]]
        then
                echo "KILL_PID: \$1 cannot be empty"
                return 1
        fi

        #Try to kill it nicely
        kill -0 $PROC_TO_KILL &>/dev/null && kill -15 $PROC_TO_KILL

        #Check every second for 5 seconds to see if $PROC_TO_KILL is dead
        WAIT_TIME=5

        #Do a quick check to see if it's still running
        #It usually takes a second, so this often doesn't help
        kill -0 $PROC_TO_KILL &>/dev/null &&
        for SEC in $(seq 1 $WAIT_TIME)
        do
                sleep 1

                if [[ "$SEC" != $WAIT_TIME ]]
                then
                        #If it's dead, exit
                        kill -0 $PROC_TO_KILL &>/dev/null || break
                else
                        #If time's up, kill it
                        kill -0 $PROC_TO_KILL &>/dev/null && kill -9 $PROC_TO_KILL
                fi
        done
}

现在,您将要做两件事以确保可以卸载chroot:

杀死可能在chroot中运行的所有进程:

CHROOT=/mnt/chroot/

#Find processes who's root folder is actually the chroot
for ROOT in $(find /proc/*/root)
do
        #Check where the symlink is pointing to
        LINK=$(readlink -f $ROOT)

        #If it's pointing to the $CHROOT you set above, kill the process
        if echo $LINK | grep -q ${CHROOT%/}
        then
                PID=$(basename $(dirname "$ROOT"))
                KILL_PID $PID
        fi
done

杀死所有可能在chroot外运行的进程,但是会干扰它(例如:如果chroot是/ mnt / chroot而dd正在写入/ mnt / chroot / testfile,则/ mnt / chroot将无法卸载)

CHROOT=/mnt/chroot/

#Get a list of PIDs that are using $CHROOT for anything
PID_LIST=$(sudo lsof +D $CHROOT 2>/dev/null | tail -n+2 | tr -s ' ' | cut -d ' ' -f 2 | sort -nu)

#Kill all PIDs holding up unmounting $CHROOT
for PID in $PID_LIST
do
        KILL_PID $PID
done

注意:以超级用户身份运行所有代码

另外,对于不太复杂的版本,请用kill -SIGTERM或替换KILL_PIDkill -SIGKILL


0

jchroot:具有更多隔离性的chroot。

执行完命令后,由该命令执行启动的所有进程将被杀死,所有IPC将被释放,所有挂载点将被挂载。全部干净!

schroot尚无法执行此操作,但已计划

我已经在不能使用docker或lxc的OpenVZ VPS中成功测试了它。

请阅读作者的博客以获取详细信息:

https://vincent.bernat.im/zh/blog/2011-jchroot-isolation.html


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.