Come posso interrompere tutti i processi in un chroot?


16

Ho un numero di partizioni LVM, ognuna contenente un'installazione Ubuntu. Occasionalmente, voglio fare un apt-get dist-upgrade, per aggiornare un'installazione ai pacchetti più recenti. Lo faccio con chroot - il processo è di solito qualcosa del tipo:

$ 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

[Non mostrato: ho anche montare e smontare /mnt/chroot-0/{dev,sys,proc}come bind-monti al vero /dev, /syse /proc, come il dist-upgrade sembra aspettarsi che questi siano presenti]

Tuttavia, dopo l'upgrade per la precisione, questo processo non funziona più: l'umount finale fallirà perché ci sono ancora file aperti sul /mnt/chroot-0filesystem. lsofconferma che ci sono processi con file aperti nel chroot. Questi processi sono stati avviati durante l'aggiornamento dist, suppongo che ciò sia dovuto al fatto che alcuni servizi nel chroot devono essere riavviati (ad es. Attraverso service postgresql restart) dopo l'aggiornamento del pacchetto.

Quindi, immagino di dover dire a upstart di interrompere tutti i servizi in esecuzione all'interno di questo chroot. C'è un modo per farlo in modo affidabile?

Ho provato:

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

Dove initctl list sembra fare la cosa giusta e elenca solo i processi che sono stati avviati in questa particolare radice. Ho provato ad aggiungere anche questo, come suggerito da 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

Tuttavia, questi non sembrano catturare tutto; i processi che sono stati demonizzati e reintegrati nel PID 1 non vengono arrestati. Ho anche provato:

sudo chroot /mnt/chroot-0 telinit 0

Ma in questo caso, init non fa distinzione tra le radici separate e spegne l'intera macchina.

Quindi, c'è un modo per dire a init di interrompere tutti i processi in un chroot particolare, in modo da poter smontare in sicurezza il filesystem? Upstart ha qualche possibilità di SIGTERM / SIGKILL tutti i processi figlio (come si farebbe durante l'arresto regolare) all'interno di un chroot?


Questa non è una risposta alla tua vera domanda, ma potrebbe essere utile: ti consiglio di guardare il pacchetto lxc. lxc fornisce strumenti semplici per avviare e chiudere in modo pulito le istanze in contenitori.
ion

Risposte:


16

Non mi fido di nient'altro che del kernel per mantenere uno stato sano qui, quindi non uso (ab) init per portare a termine questo lavoro, né faccio affidamento su di me stesso che so effettivamente cosa è o non è montato (alcuni pacchetti può montare filesystem extra, come binfmt_misc). Quindi, per la macellazione di processo, utilizzo:

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

E per smistare chroot, io uso:

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

Come addendum, vorrei sottolineare che affrontare questo come un problema di init è probabilmente il modo sbagliato di vederlo, a meno che tu non abbia effettivamente un init nel chroot e uno spazio di processo separato (cioè: nel caso dei contenitori LXC) . Con un singolo init (al di fuori del chroot) e uno spazio di processo condiviso, questo non è più un "problema di init", ma dipende solo da te per trovare i processi che hanno il percorso offensivo, da cui il proc walk sopra.

Dal tuo post iniziale non è chiaro se questi sono sistemi completamente avviabili che stai solo aggiornando esternamente (che è come l'ho letto) o se sono chroot che usi per cose come build di pacchetti. Se è quest'ultimo, potresti anche voler mettere in atto una policy-rc.d (come quella rilasciata da mk-sbuild) che vieti solo i lavori di init che iniziano in primo luogo. Ovviamente, questa non è una soluzione sana se anche questi devono essere sistemi di avvio.


Sono sistemi avviabili, ma policy-rc.dsembra un approccio interessante (potrei semplicemente rimuoverlo dopo aver interagito con il chroot). Questo influenza sia i lavori in stile /etc/rc*.dche quelli in /etc/init/*.confstile?
Jeremy Kerr,


Né upstart né sysvinit "consult policy-rc.d", è invoke-rc.d che lo fa, che tutti gli script postinst devono usare per interagire con i lavori di init. In pratica, sembra DTRT, tranne nel caso di pacchetti rotti (che dovrebbero essere corretti). Tuttavia, lo script "purga con il fuoco" di cui sopra fa il possibile, sia che il problema stia scivolando oltre la politica, che non sia in atto una politica o che venga lasciato un processo di lunga durata di qualche altro tipo (il principale caso d'uso per il i buildd qui sono cose con background durante la compilazione stessa o altrimenti non parenti da sbuild).
infinito

1
Un problema con il tentativo di aggirare il supporto chroot di utpstart. Sono abbastanza sicuro che uccidere -9 non impedirà a upstart di rigenerare il lavoro upstart se è stato respawn specificato. Quindi hai davvero bisogno di interrogare upstart dall'interno del chroot per scoprire se le cose sono ancora in esecuzione. Penso che questo sia piuttosto sfortunato, e dovremmo avere un modo dall'esterno chroot per uccidere questi lavori. Detto questo, vedo dove l'elenco initctl / awk / approccio grep + il tuo dovrebbe essere completo.
SpamapS

1
@SpamapS: un buon punto: l'uccisione manuale dei lavori di init fa sì che vengano riavviati. Sarebbe bello essere in grado di dire a upstart di eseguire un arresto specifico di chroot, arrestando lavori definiti e quindi uccidendo qualsiasi processo residuo rimanente che abbia una directory radice all'interno del chroot.
Jeremy Kerr,

0

Hai già identificato il problema da solo: alcune cose funzionano service ...durante dist-upgrade e servicenon fanno parte di Upstart, ma fanno parte di sysvinit. Aggiungi magia awk simile in giro service --status-allper interrompere i servizi sysvinit come hai usato per i servizi Upstart.


3
Ah grazie. È quasi meglio, ma non copre neanche tutti i servizi. Ho eseguito sudo chroot /mnt/chroot-0 service --list-alle sudo chroot /mnt/chroot-0 initctl list, che entrambi non segnalano alcun servizio in esecuzione. Tuttavia, /usr/bin/epmd(da erlang-base) è ancora in esecuzione.
Jeremy Kerr l'

0

So che questa domanda è piuttosto vecchia, ma penso che sia rilevante oggi come lo era nel 2012, e spero che qualcuno trovi utile questo codice. Ho scritto il codice per qualcosa che stavo facendo, ma ho pensato di condividerlo.

Il mio codice è diverso, ma le idee sono molto simili a @infinity (in effetti - l'unica ragione per cui ora so di / proc / * / root è grazie alla sua risposta - grazie a @infinity!). Ho anche aggiunto alcune interessanti funzionalità aggiuntive

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

Ora faresti 2 cose per assicurarti che chroot possa essere smontato:

Uccidi tutti i processi che potrebbero essere in esecuzione nel 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

Uccidi tutti i processi che potrebbero essere in esecuzione al di fuori del chroot, ma interferendo con esso (es: se il tuo chroot è / mnt / chroot e dd sta scrivendo in / mnt / chroot / testfile, / mnt / chroot non riuscirà a smontare)

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

Nota: eseguire tutto il codice come root

Inoltre, per una versione meno complessa, sostituire KILL_PID con kill -SIGTERMokill -SIGKILL


0

jchroot : un chroot con più isolamento.

Dopo l'esecuzione del comando, qualsiasi processo avviato dall'esecuzione di questo comando verrà interrotto, qualsiasi IPC verrà liberato, qualsiasi punto di montaggio verrà smontato. Tutto pulito!

schroot non è ancora in grado di farlo, ma questo è previsto

L'ho testato con successo in OpenVZ VPS, che non può usare docker o lxc.

Si prega di leggere il blog dell'autore per i dettagli:

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


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.