我有多个 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
,因为 dist-upgrade 似乎期望这些存在 ]/sys
/proc
但是,升级到 precise 后,此过程不再有效 - 最终的 umount 将失败,因为/mnt/chroot-0
文件系统上仍有打开的文件。lsof
确认 chroot 中有打开文件的进程。这些进程已在 dist-upgrade 期间启动,我假设这是因为 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
Whereinitctl 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 中的所有进程,以便我可以安全地卸载文件系统? upstart 是否有任何工具可以在 chroot 中 SIGTERM/SIGKILL 所有子进程(就像在常规关闭期间所做的那样)?
答案1
除了内核,我不相信任何东西能在这里保持正常状态,所以我不会(滥用)使用 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 的问题”,而只是由您自己来找到恰好具有有问题的路径的进程,因此才有上述 proc 遍历。
从您的初始帖子中无法确定这些是完全可启动的系统,您只是从外部进行升级(我是这样理解的),还是您用于软件包构建等操作的 chroot。如果是后者,您可能还需要一个 policy-rc.d(如 mk-sbuild 插入的那个),它首先禁止 init 作业启动。显然,如果这些系统也是可启动的系统,那么这不是一个明智的解决方案。
答案2
您自己已经发现了问题:有些东西service ...
在 dist-upgrade 期间运行,它们service
不是 Upstart 的一部分,而是 的一部分sysvinit
。添加类似的 awk magic 来service --status-all
停止 sysvinit 服务,就像您用于 Upstart 服务一样。
答案3
我知道这个问题已经很老了,但我认为它今天和 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
注意:以 root 身份运行所有代码
另外,对于不太复杂的版本,可以使用以下任一方法替换 KILL_PID kill -SIGTERM
:kill -SIGKILL
答案4
只是所选答案的缩写版本
可以使用正则表达式来同时处理多个 chroot。
终止单个或多个 $CHROOTS 中的所有进程。
狂欢
for p in /proc/*/root; do [[ `readlink $p` =~ ^$CHROOTS ]] && kill ${p//[^0-9]/}; done
perl (更快)
perl -e "kill 15, map {/\d+/;\$&} grep {readlink =~ m:^$CHROOTS:} @ARGV" /proc/*/root
递归地卸载单个 $CHROOT。
狂欢
i=20; while ! umount -R "$CHROOT" && ((--i)); do sleep 1; done
递归地卸载多个 $CHROOTS。
狂欢
i=20; while ((i--))&&sleep 1&&a=`sed -nr "s:^\S+ ($CHROOTS) .*:\1:p" /etc/mtab`&&[[ $a ]]; do xargs umount -qR<<<"$a";done