У меня есть много разделов 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
, поскольку dist-обновление, кажется, ожидает, что они будут присутствовать]
Однако после обновления до точного, этот процесс больше не работает - финал umount перестанет работать, потому что существуют все еще открытые файлы на /mnt/chroot-0
файловая система. lsof
подтверждает, что существуют процессы с открытыми файлами в chroot. Эти процессы были запущены во время dist-обновления, я предполагаю, что это вызвано тем, что определенные сервисы в chroot должны быть перезапущены (например, через service postgresql restart
) после того, как пакет обновлен.
Так, я полагаю, что должен сказать выскочке останавливать все сервисы, которые работают в этом 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
Однако они, кажется, не ловят все; процессы, которые имеют daemonised и повторно порожденный к PID 1, не становятся остановленными. Я также попробовал:
sudo chroot /mnt/chroot-0 telinit 0
Но в этом случае, init не различает отдельные корни и закрывает всю машину.
Так, есть ли какой-либо способ сказать init останавливать все процессы в конкретном chroot, так, чтобы я мог безопасно размонтировать файловую систему? У выскочки есть какое-либо средство к SIGTERM/SIGKILL всеми дочерними процессами (как был бы сделан во время регулярного завершения работы) в chroot?
Я не доверяю ничему, кроме ядра, чтобы поддерживать здесь нормальное состояние, поэтому я не (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 и отдельного пространства процессов (например, в случае контейнеров LXC). С одним init (вне chroot) и общим пространством процессов это уже не «проблема init», а скорее только ваша задача - найти процессы, которые имеют неправильный путь, отсюда и вышеприведенный обход процесса. [ 114]
Из вашего первоначального поста не ясно, являются ли они полностью загружаемыми системами, которые вы просто обновляете извне (как я это прочитал), или они являются chroot-файлами, которые вы используете для таких вещей, как сборки пакетов. Если это последнее, вам также может понадобиться policy-rc.d (например, тот, который был добавлен в mk-sbuild), который просто запрещает запускать задания init, начиная с самого начала. Очевидно, что это не вменяемое решение, если речь идет и о загрузочных системах.
Вы уже определили проблему самостоятельно: некоторые вещи запускаются service ...
во время dist-upgrade и service
не является частью Upstart, а частью sysvinit
. Добавьте аналогичную магию awk вокруг service --status-all
, чтобы остановить sysvinit сервисы, которые вы использовали для сервисов Upstart.
Я знаю, что этот вопрос довольно старый, но я думаю, что он так же актуален сегодня, как и в 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
}
Теперь вы должны сделать 2 вещи, чтобы убедиться, что 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
jchroot: chroot с большей изоляцией.
После того, как Ваша команда была выполнена, любой процесс, запущенный выполнением этой команды, будет уничтожен, любой IPC будет освобожден, любая точка монтирования будет размонтирована. Все убирают!
schroot еще не может сделать это, но это планируется
Я протестировал его успешно в OpenVZ VPS, который не может использовать докера или lxc.
Прочитайте блог автора для деталей:
https://vincent.bernat.im/en/blog/2011-jchroot-isolation.html
schroot: у него есть особенность управления сессиями. Когда вы останавливаете сеанс, все его процессы уничтожаются.
https://github.com/dnschneid/crouton/blob/master/host-bin/unmount-chroot : эти сценарии убивают весь процесс chroot и отключают все подключенные устройства.