Как мне остановить все процессы в 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}как bind-mounts для реального /dev, /sysи /proc, как представляется, dist-upgrade ожидает, что они будут присутствовать]

Однако после точного обновления этот процесс больше не работает - окончательное размонтирование завершится неудачно, поскольку в /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

Где, initctl list кажется, делает правильные вещи и только список процессов, которые были запущены в этом конкретном корне. Я тоже попытался добавить это, как подсказал Туминоид:

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 какое-либо средство для SIGTERM / SIGKILL всех дочерних процессов (как это было бы сделано при обычном завершении работы) внутри chroot?

Джереми Керр
источник
Это не ответ на ваш реальный вопрос, но может быть полезным: я рекомендую взглянуть на пакет lxc. lxc предоставляет простые инструменты для запуска и чистого выключения экземпляров в контейнерах.
Ион

Ответы:

16

Я не доверяю ничему, кроме того, чтобы ядро ​​сохраняло нормальное состояние здесь, поэтому я не использую 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

В качестве дополнения я хотел бы указать, что подход к этому вопросу как к проблеме инициализации, вероятно, является неправильным подходом, если только у вас фактически нет инициализации в chroot и отдельного пространства процессов (т. Е. В случае контейнеров LXC). , С одним init (за исключением chroot) и общим пространством процессов это больше не «проблема init», а, скорее, зависит только от вас, чтобы найти процессы, которые имеют неправильный путь, отсюда и вышеприведенный обход процесса.

Из вашего первоначального поста не ясно, являются ли они полностью загружаемыми системами, которые вы просто обновляете извне (как я это прочитал), или они являются chroot-файлами, которые вы используете для таких вещей, как сборки пакетов. Если это последнее, вам также может понадобиться policy-rc.d (например, тот, который был добавлен в mk-sbuild), который просто запрещает запускать задания init, начиная с самого начала. Очевидно, что это не вменяемое решение, если речь идет и о загрузочных системах.

бесконечность
источник
Это загрузочные системы, но policy-rc.dвыглядит как интересный подход (я мог бы просто удалить его после взаимодействия с chroot). Это влияет как на работу, так /etc/rc*.dи на /etc/init/*.confработу?
Джереми Керр
Хм, очевидно нет: bugs.launchpad.net/ubuntu/+source/upstart/+bug/939105
Джереми Керр,
Ни выскочка, ни sysvinit "consult policy-rc.d", это делает invoke-rc.d, который все сценарии postinst предназначены для взаимодействия с заданиями инициализации. На практике это похоже на DTRT, за исключением случая с разбитыми пакетами (которые должны быть исправлены). Тем не менее, вышеприведенный сценарий «продувки огнем» делает свою работу, независимо от того, является ли проблема чем-то проскальзывающим мимо политики, никакой политики не существует или долго продолжающийся процесс какого-то другого рода остается (основной вариант использования для buildds - это объекты, которые были заданы во время самой сборки или иным образом не связаны с sbuild).
бесконечность
1
Одна проблема при попытке обойти поддержку chroot utpstart. Я вполне уверен, что kill -9 не будет препятствовать возрождению upstart, если для него задан респаун. Так что вам действительно все равно нужно опросить выскочку изнутри chroot, чтобы узнать, все ли еще работает. Я думаю, что это довольно прискорбно, и у нас должен быть какой-то путь извне chroot, чтобы убить эти задания. Тем не менее, я вижу, где должен быть завершен подход initctl list / awk / grep + ваш.
SpamapS
1
@SpamapS: хороший момент - уничтожение заданий инициализации вручную действительно приводит к их перезапуску. Было бы здорово, если бы вы могли сказать upstart выполнить специфичное для chroot завершение работы, остановить определенные задания, а затем убить любой оставшийся перекомпонованный процесс, имеющий корневой каталог внутри chroot.
Джереми Керр
0

Вы уже определили проблему самостоятельно: некоторые вещи запускаются service ...во время dist-upgrade и serviceне являются частью Upstart, а частью sysvinit. Добавьте подобную магию awk, service --status-allчтобы остановить службы sysvinit, которые вы использовали для служб Upstart.

Tuminoid
источник
3
Ах, спасибо. Это почти лучше, но это также не распространяется на все услуги. Я запустил sudo chroot /mnt/chroot-0 service --list-allи sudo chroot /mnt/chroot-0 initctl list, которые оба сообщают об отсутствии запущенных сервисов. Тем не менее, /usr/bin/epmd(от erlang-base) все еще работает.
Джереми Керр
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
}

Теперь вы должны сделать 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

Tal
источник
0

jchroot : chroot с большей изоляцией.

После выполнения вашей команды любой процесс, запущенный выполнением этой команды, будет уничтожен, любой IPC будет освобожден, любая точка монтирования будет отключена. Все чисто!

Schroot пока не может этого сделать, но это планируется

Я успешно проверил его в OpenVZ VPS, который не может использовать Docker или LXC.

Пожалуйста, прочитайте блог автора для деталей:

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

подобно
источник
-1

schroot: у него есть особенность управления сессиями. Когда вы останавливаете сеанс, все его процессы уничтожаются.

https://github.com/dnschneid/crouton/blob/master/host-bin/unmount-chroot : этот сценарий завершает весь процесс chroot и отключает все подключенные устройства.

sdkie
источник
Может быть полезно объяснить, как использование schroot вместо chroot может помочь решить проблему OP.
пожалуйста, удалите меня