Jak zatrzymać wszystkie procesy w chroot?

16

Mam wiele partycji LVM, z których każda zawiera instalację Ubuntu. Czasami chcę to zrobić apt-get dist-upgrade, aby zaktualizować instalację do najnowszych pakietów. Robię to za pomocą chroot - proces ten zwykle przypomina:

$ 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

[nie pokazano: montuję i odmontowuję także /mnt/chroot-0/{dev,sys,proc}jako bind-mounty do rzeczywistych /dev, /sysa /procponieważ dist-upgrade wydaje się oczekiwać, że będą obecne]

Jednak po aktualizacji do dokładności ten proces już nie działa - końcowy umount zakończy się niepowodzeniem, ponieważ w /mnt/chroot-0systemie plików nadal są otwarte pliki . lsofpotwierdza, że ​​w chroot są procesy z otwartymi plikami. Procesy te zostały uruchomione podczas aktualizacji dist, zakładam, że dzieje się tak, ponieważ niektóre usługi w chroot muszą zostać zrestartowane (np. Poprzez service postgresql restart) po aktualizacji pakietu.

Tak więc, myślę, że muszę powiedzieć upstart, aby zatrzymać wszystkie usługi uruchomione w tym chroot. Czy istnieje sposób, aby to zrobić niezawodnie?

Próbowałem:

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

Gdzie initctl list wydaje się, że robi to dobrze i wyświetla listę procesów, które zostały uruchomione w tym konkretnym katalogu głównym. Próbowałem też dodać to, jak sugeruje 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

Nie wydają się one jednak łapać wszystkiego; procesy, które zostały zdemonizowane i ponownie powiązane z PID 1, nie zostają zatrzymane. Próbowałem też:

sudo chroot /mnt/chroot-0 telinit 0

Ale w tym przypadku init nie rozróżnia oddzielnych korzeni i zamyka całą maszynę.

Czy jest więc jakiś sposób, aby powiedzieć initowi, aby zatrzymał wszystkie procesy w konkretnym chroocie, abym mógł bezpiecznie odmontować system plików? Czy upstart ma jakieś możliwości SIGTERM / SIGKILL wszystkich procesów potomnych (tak jak w przypadku regularnego wyłączania) w chroot?

Jeremy Kerr
źródło
To nie jest odpowiedź na twoje aktualne pytanie, ale może być pomocne: Polecam przejrzeć pakiet lxc. lxc zapewnia łatwe narzędzia do uruchamiania i czystego zamykania instancji w kontenerach.
ion

Odpowiedzi:

16

Nie ufam nic poza jądrem, aby utrzymać tutaj rozsądny stan, więc nie używam init do wykonania tej pracy, ani nie liczę na to, że wiem, co jest zamontowane, a co nie (niektóre pakiety może montować dodatkowe systemy plików, takie jak binfmt_misc). Do uboju procesowego używam:

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

A do montowania chrootów używam:

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

Jako uzupełnienie wskazałbym, że podejście do tego jako problemu z inicjacją jest prawdopodobnie niewłaściwym sposobem patrzenia na ten problem, chyba że faktycznie masz inicjację w chroot i oddzielną przestrzeń procesu (tj. W przypadku kontenerów LXC) . Z jednym inicjatorem (poza chroot) i wspólną przestrzenią procesową, nie jest to już „problem inicjacyjny”, ale raczej od ciebie zależy znalezienie procesów, które mają ścieżkę obrażającą, stąd powyższy proces.

Z pierwszego postu nie jest jasne, czy są to systemy w pełni rozruchowe, które aktualizujesz tylko zewnętrznie (tak to czytam), czy też chrooty, których używasz do takich rzeczy jak kompilacje pakietów. Jeśli jest to drugie, możesz także chcieć mieć policy-rc.d (takie jak ten, który został dodany przez mk-sbuild), który po prostu zabrania inicjowania zadań rozpoczynających się w pierwszej kolejności. Oczywiście nie jest to rozsądne rozwiązanie, jeśli mają one być również systemami rozruchowymi.

nieskończoność
źródło
Są to systemy rozruchowe, ale policy-rc.dwyglądają na ciekawe podejście (mógłbym je po prostu usunąć po interakcji z chrootem). Czy wpływa to zarówno na pracę, jak /etc/rc*.di na /etc/init/*.confstyl?
Jeremy Kerr,
Hm, najwyraźniej nie: bugs.launchpad.net/ubuntu/+source/upstart/+bug/939105
Jeremy Kerr
Ani upstart, ani sysvinit „skonsultować policy-rc.d”, robi to invoke-rc.d, którego wszystkie skrypty postinst mają wykorzystywać do interakcji z zadaniami init. W praktyce wydaje się to DTRT, z wyjątkiem przypadku uszkodzonych pakietów (które powinny zostać naprawione). Mimo to powyższe skrypty typu „oczyść z ogniem” wykonują zadanie, niezależnie od tego, czy chodzi o to, że problem wymyka się z przeszłości, nie ma żadnej polityki, czy też pozostaje długotrwały proces innego rodzaju (główny przypadek użycia buildds tutaj to rzeczy w tle podczas kompilacji lub w inny sposób niepowiązane z sbuildem).
nieskończoność
1
Jeden problem z próbą obejścia obsługi chroot utpstart. Jestem całkiem pewien, że zabicie -9 nie zapobiegnie odrodzeniu zadania upstartu, jeśli zostało ono określone. Tak naprawdę naprawdę musisz przesłuchać upstart z wnętrza chroota, aby dowiedzieć się, czy coś nadal działa. Myślę, że jest to dość niefortunne i powinniśmy mieć jakiś sposób z zewnątrz chroots, aby zabić te prace. To powiedziawszy, widzę, gdzie podejście initctl list / awk / grep + twoje powinno być kompletne.
SpamapS,
1
@SpamapS: dobra uwaga - ręczne zabijanie zadań inicjujących faktycznie powoduje ich ponowne uruchomienie. Byłoby wspaniale móc powiedzieć upstartowi, aby wykonał zamknięcie specyficzne dla chroot, zatrzymując zdefiniowane zadania, a następnie zabijając wszelkie pozostałe procesy, które mają katalog główny w chroot.
Jeremy Kerr
0

Już sam zidentyfikowałeś problem: niektóre rzeczy działają service ...podczas aktualizacji systemu i servicenie są częścią Upstart, ale częścią sysvinit. Dodaj podobną magię awk, service --status-allaby zatrzymać usługi sysvinit, tak jak w przypadku usług Upstart.

Tuminoid
źródło
3
Ach, dzięki. Jest prawie lepiej, ale nie obejmuje to również wszystkich usług. Uruchomiłem sudo chroot /mnt/chroot-0 service --list-alli sudo chroot /mnt/chroot-0 initctl list, które nie zgłaszają żadnych uruchomionych usług. Jednak /usr/bin/epmd(z erlang-base) nadal działa.
Jeremy Kerr,
0

Wiem, że to pytanie jest dość stare, ale myślę, że jest tak samo aktualne jak w 2012 roku i mam nadzieję, że ktoś uzna ten kod za przydatny. Napisałem kod do czegoś, co robiłem, ale pomyślałem, że się nim podzielę.

Mój kod jest inny, ale pomysły są bardzo podobne do @infinity (w rzeczywistości - jedynym powodem, dla którego teraz wiem o / proc / * / root, jest jego odpowiedź - dzięki @infinity!). Dodałem też kilka fajnych dodatkowych funkcji

#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
}

Teraz zrobiłbyś 2 rzeczy, aby upewnić się, że chroot można odmontować:

Zabij wszystkie procesy, które mogą być uruchomione w 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

Zabij wszystkie procesy, które mogą być uruchomione poza chrootem, ale przeszkadzają w nim (np. Jeśli twój chroot to / mnt / chroot, a dd pisze do / mnt / chroot / testfile, / mnt / chroot nie odmontuje się)

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

Uwaga: Uruchom cały kod jako root

Ponadto w przypadku mniej złożonej wersji zamień KILL_PID na jeden kill -SIGTERMlubkill -SIGKILL

Tal
źródło
0

jchroot : chroot z większą izolacją.

Po wykonaniu polecenia, każdy proces rozpoczęty przez wykonanie tego polecenia zostanie zabity, każdy IPC zostanie zwolniony, każdy punkt podłączenia zostanie odmontowany. Wszystko czyste!

schroot nie jest jeszcze w stanie tego zrobić, ale jest to planowane

Przetestowałem to z powodzeniem w OpenVZ VPS, które nie mogą używać dokera ani lxc.

Szczegółowe informacje można znaleźć na blogu autora:

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

Lubić
źródło
-1

schroot: Ma funkcję zarządzania sesjami. Po zatrzymaniu sesji wszystkie procesy zostają zabite.

https://github.com/dnschneid/crouton/blob/master/host-bin/unmount-chroot : Skrypty te zabijają cały proces chroot i odmontowują wszystkie zamontowane urządzenia.

sdkie
źródło
Pomocne może być wyjaśnienie, w jaki sposób używanie schroot zamiast chroot może pomóc rozwiązać problem PO.
usuń mnie