Comment arrêter tous les processus dans un chroot?


16

J'ai un certain nombre de partitions LVM, chacune contenant une installation Ubuntu. Parfois, je souhaite faire une apt-get dist-upgrademise à jour d'une installation vers les packages les plus récents. Je le fais 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 illustré: je monte et démonte également en tant que fixations /mnt/chroot-0/{dev,sys,proc}de liaison au réel /dev, /syset /proc, comme la mise à niveau dist semble s'attendre à ce qu'elles soient présentes]

Cependant, après une mise à niveau précise, ce processus ne fonctionne plus - le umount final échouera car il y a encore des fichiers ouverts sur le /mnt/chroot-0système de fichiers. lsofconfirme qu'il existe des processus avec des fichiers ouverts dans le chroot. Ces processus ont été lancés pendant la mise à niveau dist, je suppose que c'est parce que certains services dans le chroot doivent être redémarrés (par exemple, via service postgresql restart) après la mise à niveau du package.

Donc, je pense que je dois dire à upstart d'arrêter tous les services qui s'exécutent dans ce chroot. Existe-t-il un moyen de le faire de manière fiable?

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 faire la bonne chose et ne lister que les processus qui ont été démarrés dans cette racine particulière. J'ai essayé d'ajouter cela aussi, 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, ceux-ci ne semblent pas tout attraper; les processus démonifiés et reparents au PID 1 ne sont pas arrêtés. J'ai aussi essayé:

sudo chroot /mnt/chroot-0 telinit 0

Mais dans ce cas, init ne fait pas de distinction entre les racines séparées et arrête la machine entière.

Alors, existe-t-il un moyen de dire à init d'arrêter tous les processus dans un chroot particulier, afin que je puisse démonter le système de fichiers en toute sécurité? L'arrivée a-t-elle la possibilité de SIGTERM / SIGKILL tous les processus enfants (comme ce serait le cas lors d'un arrêt normal) dans un chroot?


Ce n'est pas une réponse à votre question, mais cela pourrait être utile: je vous recommande de consulter le package lxc. lxc fournit des outils simples pour démarrer et arrêter proprement des instances dans des conteneurs.
ion

Réponses:


16

Je ne fais confiance à rien d'autre qu'au noyau pour garder un état sain ici, donc je n'utilise pas (ab) init pour faire ce travail, et je ne compte pas non plus sur moi-même sachant réellement ce qui est ou n'est pas monté (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 guise d'addendum, je voudrais souligner que considérer cela comme un problème d'initialisation est probablement la mauvaise façon de le voir, sauf si vous avez réellement un init dans le chroot et un espace de processus séparé (c'est-à-dire: dans le cas des conteneurs LXC) . Avec un seul init (en dehors du chroot) et un espace de processus partagé, ce n'est plus le "problème d'init", mais juste à vous de trouver les processus qui se trouvent avoir le chemin incriminé, d'où le processus ci-dessus.

Il ne ressort pas clairement de votre message initial s'il s'agit de systèmes entièrement amorçables que vous mettez à niveau uniquement en externe (c'est ainsi que je les lis), ou s'ils sont des chroots que vous utilisez pour des choses comme les builds de packages. Si c'est le dernier, vous voudrez peut-être aussi une politique-rc.d en place (comme celle déposée par mk-sbuild) qui interdit simplement le démarrage des travaux d'initialisation en premier lieu. Évidemment, ce n'est pas une solution sensée s'il s'agit également de systèmes amorçables.


Ce sont des systèmes amorçables, mais cela policy-rc.dressemble à une approche intéressante (je pourrais simplement le supprimer après avoir interagi avec le chroot). Cela affecte-t-il les emplois à la fois /etc/rc*.d- et de /etc/init/*.confstyle?
Jeremy Kerr


Ni upstart ni sysvinit "consult policy-rc.d", c'est invoke-rc.d qui le fait, que tous les scripts postinst sont censés utiliser pour interagir avec les travaux init. En pratique, cela semble DTRT, sauf dans le cas de paquets cassés (qui devraient être corrigés). Pourtant, le script de «purge avec le feu» ci-dessus fait l'affaire, que le problème soit quelque chose qui glisse au-delà de la politique, qu'aucune politique ne soit en place ou qu'un processus de longue durée d'une autre sorte ne soit laissé de côté (le principal cas d'utilisation du les buildds ici sont des éléments mis en arrière-plan pendant la construction elle-même ou non associés à sbuild).
infini le

1
Un problème avec la tentative de contourner le support chroot d'utpstart. Je suis assez certain que kill -9 n'empêchera pas upstart de réapparaître le job upstart s'il a une réapparition spécifiée. Vous avez donc vraiment besoin d'interroger par le haut depuis l'intérieur du chroot pour savoir si les choses fonctionnent toujours. Je pense que c'est assez malheureux, et nous devrions avoir un moyen de l'extérieur de chroots pour tuer ces emplois. Cela dit, je vois où l'approche initctl list / awk / grep + la vôtre devrait être complète.
SpamapS

1
@SpamapS: bon point - la suppression manuelle des travaux d'initialisation entraîne en effet leur redémarrage. Ce serait formidable de pouvoir dire à upstart d'effectuer un arrêt spécifique au chroot, d'arrêter les travaux définis, puis de tuer tout processus réparé restant ayant un répertoire racine dans le chroot.
Jeremy Kerr

0

Vous avez déjà identifié le problème vous-même: certaines choses fonctionnent service ...pendant la mise à niveau dist et servicene font pas partie d'Upstart, mais en font partie sysvinit. Ajoutez une magie awk similaire service --status-allpour arrêter les services sysvinit comme vous l'avez utilisé pour les services Upstart.


3
Ah merci. C'est presque mieux, mais cela ne couvre pas non plus tous les services. J'ai exécuté sudo chroot /mnt/chroot-0 service --list-allet sudo chroot /mnt/chroot-0 initctl list, qui ne signalent aucun service en cours d'exécution. Cependant, /usr/bin/epmd(depuis erlang-base) est toujours en cours d'exécution.
Jeremy Kerr

0

Je sais que cette question est assez ancienne, mais je pense qu'elle est aussi pertinente aujourd'hui qu'elle l'était en 2012, et j'espère que quelqu'un trouvera ce code utile. J'ai écrit le code de quelque chose que je faisais, mais j'ai pensé 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 / proc / * / root est à cause de sa réponse - merci @infinity!). J'ai également ajouté quelques 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 2 choses pour vous assurer que le chroot peut être démonté:

Tuez tous les processus qui peuvent s'exécuter 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

Tuez tous les processus qui peuvent s'exécuter en dehors du chroot, mais en interférant avec lui (ex: 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écutez tout le code en tant que root

De plus, pour une version moins complexe, remplacez KILL_PID par kill -SIGTERMoukill -SIGKILL


0

jchroot : un chroot avec plus d'isolement.

Une fois votre commande 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 en mesure de le faire, mais cela est prévu

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

Veuillez lire le blog de l'auteur pour les détails:

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


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.