web-dev-qa-db-fra.com

Comment puis-je arrêter tous les processus dans un chroot?

J'ai un certain nombre de partitions LVM, chacune contenant une installation Ubuntu. De temps en temps, je veux faire un apt-get dist-upgrade, pour mettre à jour une installation avec les paquets les plus récents. Je fais cela avec chroot - le processus est généralement quelque chose comme:

$ 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 affiché: je monte et démonte également /mnt/chroot-0/{dev,sys,proc} en tant que bind-monte sur les vrais /dev, /sys et /proc, la dist-upgrade semblant s'attendre à ce qu'ils soient présents]

Cependant, après une mise à niveau précise, ce processus ne fonctionne plus - l'umount final échouera car des fichiers sont toujours ouverts sur le système de fichiers /mnt/chroot-0. lsof confirme qu'il existe des processus avec des fichiers ouverts dans le chroot. Ces processus ont été lancés pendant la mise à niveau de dist, je suppose que cela est dû au fait que certains services du chroot doivent être redémarrés (par exemple, via service postgresql restart) après la mise à niveau du paquet.

Donc, je suppose que je dois dire à Upstart d’arrêter tous les services qui fonctionnent dans ce chroot. Y a-t-il un moyen fiable de le faire?

J'ai essayé:

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 semble agir correctement et ne répertorie que les processus démarrés dans cette racine particulière. J'ai aussi essayé d'ajouter ceci, comme suggéré par 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

Cependant, cela ne semble pas tout comprendre; Les processus qui ont été démonisés et qui ont été réparés sur le PID 1 ne sont pas arrêtés. J'ai aussi essayé:

Sudo chroot /mnt/chroot-0 telinit 0

Mais dans ce cas, init non distingue les racines séparées et arrête la machine entière.

Alors, y a-t-il un moyen d'indiquer à init d'arrêter tous les processus d'un chroot particulier, afin que je puisse démonter le système de fichiers en toute sécurité? Est-ce que upstart a une installation pour SIGTERM/SIGKILL tous les processus enfants (comme ce serait le cas lors d'un arrêt normal) au sein d'un chroot?

16
Jeremy Kerr

Je n’ai confiance en rien, si ce n’est le noyau qui maintient l’état sain d’esprit ici, donc je n’utilise (ab) pas init pour faire ce travail, et je ne compte pas sur moi-même pour savoir ce qui est monté ou non (certains paquets peut monter des systèmes de fichiers supplémentaires, comme binfmt_misc). Donc, pour l'abattage de processus, j'utilise:

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

Et pour démonter les chroots, j'utilise:

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

En tant qu'additif, je ferais remarquer que le fait de considérer cela comme un problème init est probablement une mauvaise façon de voir les choses, à moins que vous n'ayez réellement un init dans le chroot et un espace de processus séparé (c'est-à-dire: dans le cas de conteneurs LXC) . Avec un seul init (en dehors du chroot) et un espace de processus partagé, ce n'est plus un "problème d'init", mais plutôt à vous de trouver les processus qui se trouvent avoir le chemin offensant, d'où le processus ci-dessus.

Votre message initial ne dit pas clairement s'il s'agit de systèmes entièrement amorçables que vous ne faites que mettre à niveau en externe (c'est ce que je lis), ou s'il s'agit de chroots que vous utilisez pour des tâches telles que la construction de paquetages. Si c'est le dernier cas, vous voudrez peut-être aussi un policy-rc.d (comme celui déposé par mk-sbuild) qui interdit simplement aux travaux d'initialisation de démarrer à la base. De toute évidence, ce n'est pas une solution sensée si ces systèmes sont également censés être des systèmes amorçables.

16
infinity

Vous avez déjà identifié le problème vous-même: certaines choses fonctionnent avec service ... lors de la mise à niveau de dist et upgrade et service ne fait pas partie de Upstart, mais fait partie de sysvinit. Ajoutez une magie awk similaire autour de service --status-all pour arrêter les services sysvinit tels que vous les avez utilisés pour les services Upstart.

0
Tuminoid

Je sais que cette question est assez ancienne, mais je pense qu'elle est aussi pertinente aujourd'hui qu'en 2012, et j'espère que quelqu'un trouvera ce code utile. J'ai écrit le code pour quelque chose que je faisais, mais je pensais le partager.

Mon code est différent, mais les idées sont très similaires à @infinity (en fait - la seule raison pour laquelle je connais maintenant le processus/proc/*/root est à cause de sa réponse - merci @infinity!). J'ai également ajouté des fonctionnalités supplémentaires intéressantes

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

Maintenant, vous feriez deux choses pour vous assurer que chroot peut être démonté:

Éliminez tous les processus en cours d'exécution dans le 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

Supprimez tous les processus qui peuvent être exécutés en dehors du chroot, mais en interférant avec celui-ci (par exemple, si votre chroot est/mnt/chroot et que dd écrit dans/mnt/chroot/testfile,/mnt/chroot ne pourra pas être démonté)

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

Remarque: Exécuter tout le code en tant que root

De même, pour une version moins complexe, remplacez KILL_PID par kill -SIGTERM ou kill -SIGKILL

0
Tal

jchroot : un chroot avec plus d'isolement.

Une fois que votre commande a été exécutée, tout processus démarré par l'exécution de cette commande sera tué, tout IPC sera libéré, tout point de montage sera démonté. Tout propre!

schroot n'est pas encore capable de le faire, mais c'est prévu

Je l'ai testé avec succès dans OpenVZ VPS, qui ne peut pas utiliser Docker ou LXC.

S'il vous plaît lire le blog de l'auteur pour les détails:

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

0
Like