Wie stoppe ich alle Prozesse in einer Chroot?


16

Ich habe eine Reihe von LVM-Partitionen, die jeweils eine Ubuntu-Installation enthalten. Gelegentlich möchte ich eine apt-get dist-upgrade, um eine Installation auf die neuesten Pakete zu aktualisieren. Ich mache das mit chroot - der Prozess ist normalerweise ungefähr so:

$ 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

[Nicht gezeigt: Ich ein- und aushängen auch /mnt/chroot-0/{dev,sys,proc}als Bind-Mounts auf die reale /dev, /sysund /proc, wie die dist-upgrade scheint diese zu erwarten anwesend sein]

Nach dem Upgrade auf "exact" funktioniert dieser Vorgang jedoch nicht mehr. Die endgültige Umount-Datei schlägt fehl, da noch Dateien im /mnt/chroot-0Dateisystem geöffnet sind . lsofBestätigt, dass in der Chroot Prozesse mit geöffneten Dateien vorhanden sind. Diese Prozesse wurden während des dist-Upgrades gestartet. Dies liegt vermutlich daran, dass bestimmte Dienste in der Chroot neu gestartet werden müssen (z. B. durch service postgresql restart), nachdem das Paket aktualisiert wurde.

Ich denke also, ich muss upstart mitteilen, dass alle Dienste, die in dieser Chroot ausgeführt werden, gestoppt werden sollen. Gibt es eine Möglichkeit, dies zuverlässig zu tun?

Ich habe es versucht:

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

Wo initctl list scheint das Richtige zu tun und nur Prozesse aufzulisten, die in dieser bestimmten Wurzel gestartet wurden. Ich habe versucht, dies auch hinzuzufügen, wie von Tuminoid vorgeschlagen:

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

Diese scheinen jedoch nicht alles zu erfassen; Prozesse, die auf PID 1 zurückgesetzt wurden, werden nicht gestoppt. Ich habe auch versucht:

sudo chroot /mnt/chroot-0 telinit 0

In diesem Fall unterscheidet init jedoch nicht zwischen den einzelnen Roots und fährt die gesamte Maschine herunter.

Gibt es eine Möglichkeit, init anzuweisen, alle Prozesse in einer bestimmten Chroot zu stoppen, damit ich das Dateisystem sicher trennen kann? Verfügt upstart über eine Möglichkeit, alle untergeordneten Prozesse (wie beim regulären Herunterfahren) innerhalb einer Chroot SIGTERM / SIGKILL zuzuweisen?


Dies ist keine Antwort auf Ihre eigentliche Frage, könnte aber hilfreich sein: Ich empfehle Ihnen, sich das lxc-Paket anzusehen. lxc bietet einfache Tools zum Starten und sauberen Herunterfahren von Instanzen in Containern.
Ion

Antworten:


16

Ich traue nichts außer dem Kernel, um hier einen vernünftigen Zustand beizubehalten, also verwende ich init nicht (ab), um diesen Job zu erledigen, noch zähle ich darauf, dass ich wirklich weiß, was gemountet ist oder nicht (einige Pakete) kann zusätzliche Dateisysteme wie binfmt_misc mounten). Also verwende ich zum Prozessschlachten:

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

Und zum Ummounten von Chroots verwende ich:

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

Als Ergänzung möchte ich darauf hinweisen, dass es wahrscheinlich falsch ist, dies als Init-Problem zu betrachten, es sei denn, Sie haben tatsächlich ein Init in der Chroot und einen separaten Prozessraum (dh im Fall von LXC-Containern). . Mit einem einzelnen Init (außerhalb der Chroot) und einem gemeinsam genutzten Prozessbereich ist dies nicht mehr das Problem von "init", sondern es liegt ganz bei Ihnen, die Prozesse zu finden, die den fehlerhaften Pfad haben, daher der obige Prozesspfad.

Aus Ihrem ersten Beitrag geht nicht hervor, ob es sich um vollständig bootfähige Systeme handelt, die Sie nur extern aktualisieren (so lese ich es), oder ob es sich um Chroots handelt, die Sie für Dinge wie die Erstellung von Paketen verwenden. In letzterem Fall möchten Sie möglicherweise auch eine Policy-rc.d (wie die, die von mk-sbuild abgelegt wurde), die nur das Starten von Jobs von vornherein verbietet. Das ist natürlich keine vernünftige Lösung, wenn dies auch bootfähige Systeme sein sollen.


Es handelt sich um bootfähige Systeme, aber es policy-rc.dscheint ein interessanter Ansatz zu sein (ich könnte ihn einfach entfernen, nachdem ich mit der Chroot interagiert habe). Betrifft dies sowohl Jobs im /etc/rc*.d- als auch im - /etc/init/*.confStil?
Jeremy Kerr


Weder upstart noch sysvinit "consult policy-rc.d", sondern invoke-rc.d, mit dem alle Postinst-Skripte mit init-Jobs interagieren sollen. In der Praxis scheint es DTRT, außer im Fall von defekten Paketen (die behoben werden sollten). Trotzdem erledigt das obige "Purge with Fire" -Skript die Aufgabe, unabhängig davon, ob die Richtlinie abgelaufen ist, keine Richtlinie vorhanden ist oder ein lang laufender Prozess einer anderen Art übrig bleibt (der Hauptanwendungsfall für das Skript) buildds sind hier Dinge, die während des Builds selbst oder auf andere Weise von sbuild unabhängig gemacht wurden.
Unendlichkeit

1
Ein Problem beim Versuch, die chroot-Unterstützung von utpstart zu umgehen. Ich bin mir ziemlich sicher, dass kill -9 den Upstart-Job nicht daran hindert, ihn erneut zu starten, wenn es einen festgelegten Respawn gibt. Sie müssen also immer noch den Emporkömmling aus der Chroot heraus abfragen , um herauszufinden, ob noch etwas läuft. Ich denke, das ist ziemlich unglücklich, und wir sollten einen Weg von außen haben, um diese Jobs zu töten. Das heißt, ich sehe, wo die Initialenliste / awk / grep Ansatz + Ihre sollte vollständig sein.
SpamapS

1
@SpamapS: ein guter Punkt - das manuelle Beenden von Init-Jobs führt in der Tat dazu, dass sie neu gestartet werden. Wäre großartig, wenn Sie upstart anweisen könnten, ein chroot-spezifisches Herunterfahren durchzuführen, definierte Jobs zu stoppen und dann alle verbleibenden reparierten Prozesse zu beenden, die ein Stammverzeichnis in der chroot haben.
Jeremy Kerr

0

Sie haben das Problem bereits selbst identifiziert: Einige Dinge werden service ...während des dist-Upgrades ausgeführt und servicegehören nicht zu Upstart, sondern zu sysvinit. Fügen Sie ähnliche awk-Magie hinzu, um service --status-allsysvinit-Dienste zu stoppen, wie Sie sie für Upstart-Dienste verwendet haben.


3
Ah danke. Es ist fast besser, aber das deckt auch nicht alle Dienste ab. Ich habe sudo chroot /mnt/chroot-0 service --list-allund ausgeführt sudo chroot /mnt/chroot-0 initctl list, die beide keine laufenden Dienste melden. Allerdings /usr/bin/epmdläuft (von erlang-base) noch.
Jeremy Kerr

0

Ich weiß, dass diese Frage ziemlich alt ist, aber ich denke, sie ist heute genauso aktuell wie 2012, und hoffentlich findet jemand diesen Code nützlich. Ich schrieb den Code für etwas, was ich tat, dachte aber, ich würde ihn teilen.

Mein Code ist anders, aber die Ideen sind @infinity sehr ähnlich (der einzige Grund, warum ich jetzt über / proc / * / root Bescheid weiß, ist seine Antwort - danke @infinity!). Ich habe auch einige coole zusätzliche Funktionen hinzugefügt

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

Jetzt würden Sie zwei Dinge tun, um sicherzustellen, dass die Chroot entfernt werden kann:

Beenden Sie alle Prozesse, die möglicherweise in der Chroot ausgeführt werden:

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

Beenden Sie alle Prozesse, die möglicherweise außerhalb der Chroot ausgeführt werden, diese jedoch beeinträchtigen (Beispiel: Wenn Ihre Chroot / mnt / chroot ist und dd in / mnt / chroot / testfile schreibt, schlägt das Aufheben der Bereitstellung von / mnt / chroot fehl).

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

Hinweis: Führen Sie den gesamten Code als root aus

Ersetzen Sie für eine weniger komplexe Version KILL_PID durch kill -SIGTERModerkill -SIGKILL


0

jchroot : eine Chroot mit mehr Isolation.

Nachdem Ihr Befehl ausgeführt wurde, wird jeder Prozess, der durch die Ausführung dieses Befehls gestartet wurde, abgebrochen, alle IPCs werden freigegeben, alle Bereitstellungspunkte werden deaktiviert. Alles sauber!

schroot kann das noch nicht, aber das ist geplant

Ich habe es erfolgreich in OpenVZ VPS getestet, das Docker oder LXC nicht verwenden kann.

Bitte lesen Sie den Blog des Autors für die Details:

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


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.