¿Cómo detengo todos los procesos en un chroot?


16

Tengo varias particiones LVM, cada una con una instalación de Ubuntu. Ocasionalmente, quiero hacer un apt-get dist-upgrade, para actualizar una instalación a los paquetes más recientes. Hago esto con chroot: el proceso suele ser algo como:

$ 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

[No se muestra: Yo también montar y desmontar /mnt/chroot-0/{dev,sys,proc}como bind-montajes a lo real /dev, /sysy /proc, como el dist-upgrade parece esperar que éstos estén presentes]

Sin embargo, después de actualizar a preciso, este proceso ya no funciona: el montaje final fallará porque todavía hay archivos abiertos en el /mnt/chroot-0sistema de archivos. lsofconfirma que hay procesos con archivos abiertos en el chroot. Estos procesos se han iniciado durante la actualización dist, supongo que esto se debe a que ciertos servicios en el chroot deben reiniciarse (p. Ej., A través de service postgresql restart) después de actualizar el paquete.

Entonces, me imagino que necesito decirle a advenedizo que detenga todos los servicios que se ejecutan dentro de este chroot. ¿Hay alguna manera de hacer esto de manera confiable?

He intentado:

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

Where initctl list parece hacer lo correcto y solo enumera los procesos que se han iniciado en esta raíz en particular. Intenté agregar esto también, como lo sugiere 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

Sin embargo, estos no parecen atrapar todo; los procesos que han sido demonizados y han sido reparentados a PID 1 no se detienen. También he intentado:

sudo chroot /mnt/chroot-0 telinit 0

Pero en este caso, init no distingue entre las raíces separadas y apaga toda la máquina.

Entonces, ¿hay alguna forma de decirle a init que detenga todos los procesos en un chroot particular, para que pueda desmontar el sistema de archivos de manera segura? ¿Upstart tiene alguna facilidad para SIGTERM / SIGKILL todos los procesos secundarios (como se haría durante el apagado regular) dentro de un chroot?


Esta no es una respuesta a su pregunta real, pero podría ser útil: recomiendo mirar el paquete lxc. lxc proporciona herramientas sencillas para iniciar y cerrar instancias de forma limpia en contenedores.
ion

Respuestas:


16

No confío en nada más que en el núcleo para mantener un estado sano aquí, por lo que no (ab) uso init para hacer este trabajo, ni cuento conmigo mismo para saber qué está montado o no (algunos paquetes puede montar sistemas de archivos adicionales, como binfmt_misc). Entonces, para el proceso de matanza, uso:

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

Y para desmontar chroots, 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

Como apéndice, señalaría que abordar esto como un problema de inicio es probablemente la forma incorrecta de verlo, a menos que realmente tenga un init en el chroot y un espacio de proceso separado (es decir, en el caso de los contenedores LXC) . Con un solo init (fuera del chroot) y un espacio de proceso compartido, esto ya no es el "problema de init", sino que depende de usted encontrar los procesos que tienen el camino ofensivo, de ahí el camino de proceso anterior.

No está claro en su publicación inicial si estos son sistemas totalmente arrancables que solo está actualizando externamente (que es como lo leí), o si son chroots que usa para cosas como compilaciones de paquetes. Si es lo último, es posible que también desee una política-rc.d en su lugar (como la que dejó mk-sbuild) que solo prohíbe los trabajos de inicio que comiencen en primer lugar. Obviamente, esa no es una solución sensata si también están destinados a ser sistemas de arranque.


Son sistemas de arranque, pero policy-rc.dparece un enfoque interesante (simplemente podría eliminarlo después de interactuar con el chroot). ¿Afecta esto tanto a los trabajos /etc/rc*.dcomo a los /etc/init/*.confestilos?
Jeremy Kerr


Ni advenedizo ni sysvinit "consulta la política-rc.d", es invoke-rc.d que lo hace, que todos los scripts de postinst están destinados a interactuar con trabajos init. En la práctica, parece DTRT, excepto en el caso de paquetes rotos (que deberían repararse). Aún así, la secuencia de comandos de "purga con fuego" anterior hace el trabajo, ya sea que el problema sea algo que se deslice más allá de la política, que no exista una política en vigor o que se deje un largo proceso de algún otro tipo (el principal caso de uso para el buildds aquí son cosas backgrounded durante la construcción en sí o de otra manera unparented de sbuild).
infinito

1
Un problema al tratar de evitar el soporte chroot de utpstart. Estoy bastante seguro de que kill -9 no evitará que el advenedizo reaparezca el trabajo advenedizo si se ha especificado el reaparición. Por lo tanto, realmente aún necesita interrogar a los advenedizos desde el interior del chroot para averiguar si las cosas siguen funcionando. Creo que esto es bastante desafortunado, y deberíamos tener alguna forma, desde afuera, de los chroots para matar estos trabajos. Dicho esto, veo dónde debe estar completa la lista initctl / awk / grep + suyo.
SpamapS

1
@SpamapS: buen punto: matar los trabajos de inicio de forma manual realmente hace que se reinicien. Sería genial poder decirle a advenedizo que realice un apagado específico de chroot, deteniendo trabajos definidos y luego eliminando cualquier proceso reparado restante que tenga un directorio raíz dentro del chroot.
Jeremy Kerr

0

Ya identificó el problema usted mismo: algunas cosas se ejecutan service ...durante la actualización de dist y serviceno son parte de Upstart, sino parte de sysvinit. Agregue una magia awk similar service --status-allpara detener los servicios sysvinit que utilizó para los servicios de Upstart.


3
Ah gracias. Es casi mejor, pero eso tampoco cubre todos los servicios. He ejecutado sudo chroot /mnt/chroot-0 service --list-ally sudo chroot /mnt/chroot-0 initctl list, que informan que no hay servicios en ejecución. Sin embargo, /usr/bin/epmd(desde erlang-base) todavía se está ejecutando.
Jeremy Kerr

0

Sé que esta pregunta es bastante antigua, pero creo que es tan relevante hoy como lo fue en 2012, y espero que alguien encuentre útil este código. Escribí el código de algo que estaba haciendo, pero pensé en compartirlo.

Mi código es diferente, pero las ideas son muy similares a @infinity (de hecho, la única razón por la que ahora sé sobre / proc / * / root es por su respuesta: ¡gracias @infinity!). También agregué algunas funcionalidades adicionales geniales

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

Ahora harías 2 cosas para asegurarte de que Chroot se pueda desmontar:

Elimine todos los procesos que puedan estar ejecutándose en el 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

Elimine todos los procesos que puedan estar ejecutándose fuera del chroot, pero que interfieran con él (por ejemplo, si su chroot es / mnt / chroot y dd está escribiendo en / mnt / chroot / testfile, / mnt / chroot no se desmontará)

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: Ejecute todo el código como root

Además, para una versión menos compleja, reemplace KILL_PID con kill -SIGTERMokill -SIGKILL


0

jchroot : un chroot con más aislamiento.

Después de que se haya ejecutado su comando, se eliminará cualquier proceso iniciado por la ejecución de este comando, se liberará cualquier IPC, se desmontará cualquier punto de montaje. ¡Todo limpio!

Schroot aún no puede hacer esto, pero esto está planeado

Lo probé con éxito en OpenVZ VPS, que no puede usar docker o lxc.

Por favor lea el blog del autor para los detalles:

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


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.