Como faço para parar todos os processos em um chroot?

16

Eu tenho várias partições LVM, cada uma contendo uma instalação do Ubuntu. Ocasionalmente, quero fazer um apt-get dist-upgrade, para atualizar uma instalação para os pacotes mais recentes. Eu faço isso com chroot - o processo geralmente é 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

[Não apresentados: Eu também montar e desmontar /mnt/chroot-0/{dev,sys,proc}como Bind-montagens para o real /dev, /syse /proc, como o dist-upgrade parece esperar que eles estejam presentes]

No entanto, após a atualização precisa, esse processo não funciona mais - a quantia final falhará porque ainda existem arquivos abertos no /mnt/chroot-0sistema de arquivos. lsofconfirma que existem processos com arquivos abertos no chroot. Esses processos foram iniciados durante a dist-upgrade, presumo que isso ocorra porque certos serviços no chroot precisam ser reiniciados (por exemplo, através service postgresql restart) após a atualização do pacote.

Então, acho que preciso dizer ao iniciante para interromper todos os serviços que estão sendo executados neste chroot. Existe uma maneira de fazer isso de maneira confiável?

Eu tentei:

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

Onde initctl list parece fazer a coisa certa e listar apenas os processos que foram iniciados nessa raiz específica. Eu tentei adicionar isso também, como sugerido por 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

No entanto, isso não parece pegar tudo; processos que foram daemonizados e reparados no PID 1 não são interrompidos. Eu também tentei:

sudo chroot /mnt/chroot-0 telinit 0

Mas, neste caso, o init não faz distinção entre as raízes separadas e desliga a máquina inteira.

Então, existe alguma maneira de dizer ao init para parar todos os processos em um chroot específico, para que eu possa desmontar com segurança o sistema de arquivos? O iniciante tem algum recurso para SIGTERM / SIGKILL todos os processos filhos (como seria feito durante o desligamento regular) dentro de um chroot?

Jeremy Kerr
fonte
Esta não é uma resposta para sua pergunta real, mas pode ser útil: recomendo examinar o pacote lxc. O lxc fornece ferramentas fáceis para iniciar e desligar instâncias de maneira limpa em contêineres.
iões

Respostas:

16

Eu não confio em nada além do kernel para manter um estado sadio aqui, então não ab) uso o init para concluir este trabalho, nem conto comigo mesmo sabendo o que está ou não está montado (alguns pacotes pode montar sistemas de arquivos extras, como binfmt_misc). Então, para o abate de processos, eu 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

E para desmontar chroots, eu 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 um adendo, eu apontaria que abordar isso como um problema de init provavelmente é a maneira errada de encará-lo, a menos que você tenha um init no chroot e um espaço de processo separado (por exemplo, no caso de contêineres LXC) . Com um único init (fora do chroot) e um espaço de processo compartilhado, isso não é mais um "problema do init", mas apenas com você para encontrar os processos que possuem o caminho incorreto, portanto, o procedimento acima.

Não está claro em sua postagem inicial se estes são sistemas totalmente inicializáveis ​​que você está apenas atualizando externamente (que é como eu o leio), ou se são chroots que você usa para coisas como compilação de pacotes. Se for o último, você também pode querer um policy-rc.d (como o do mk-sbuild) que proíbe apenas os trabalhos de inicialização iniciados em primeiro lugar. Obviamente, essa não é uma solução sensata se esses também forem sistemas inicializáveis.

infinidade
fonte
São sistemas inicializáveis, mas policy-rc.dparece uma abordagem interessante (eu poderia simplesmente removê-lo depois de interagir com o chroot). Isso afeta os trabalhos de estilo /etc/rc*.de /etc/init/*.confestilo?
Jeremy Kerr
Hm, evidentemente, não: bugs.launchpad.net/ubuntu/+source/upstart/+bug/939105
Jeremy Kerr
Nem o iniciante nem o sysvinit "consult policy-rc.d", é invoke-rc.d que faz isso, que todos os scripts postinst devem usar para interagir com tarefas init. Na prática, parece o DTRT, exceto no caso de pacotes quebrados (que devem ser corrigidos). Ainda assim, o script "purgar com fogo" acima faz o trabalho, se o problema é algo que está escapando à política, nenhuma política está em vigor ou um processo de longa duração de algum outro tipo está sendo deixado de lado (o principal caso de uso do buildds aqui são itens de plano de fundo durante a construção em si ou não aparentados do sbuild).
Infinito
11
Um problema ao tentar solucionar o suporte chroot do utpstart. Estou bastante certo de que kill -9 não impedirá o iniciante de reaparecer o trabalho inicial se ele tiver reaparecido especificado. Então você ainda precisa interrogar o iniciante de dentro do chroot para descobrir se as coisas ainda estão funcionando. Eu acho que isso é bastante lamentável, e devemos ter alguma maneira de chroots de fora para matar esses empregos. Dito isto, vejo onde a abordagem initctl list / awk / grep + sua deve estar completa.
SpamapS
11
@SpamapS: bom ponto - matar manualmente os trabalhos init realmente resulta no reinício. Seria ótimo poder dizer ao iniciante para executar um desligamento específico do chroot, parando trabalhos definidos e matando qualquer processo reparado restante que tenha um diretório raiz dentro do chroot.
30812 Jeremy Kerr
0

Você já identificou o problema: algumas coisas são executadas service ...durante o dist-upgrade e servicenão fazem parte do Upstart, mas fazem parte sysvinit. Adicione mágica awk semelhante service --status-allpara interromper os serviços sysvinit usados ​​para os serviços Upstart.

Tuminoide
fonte
3
Ah obrigada. É quase melhor, mas isso também não abrange todos os serviços. Eu executei sudo chroot /mnt/chroot-0 service --list-alle sudo chroot /mnt/chroot-0 initctl list, que ambos relatam nenhum serviço em execução. No entanto, /usr/bin/epmd(da erlang-base) ainda está sendo executado.
11302 Jeremy Kerr
0

Sei que essa pergunta é bastante antiga, mas acho que é tão relevante hoje quanto em 2012 e espero que alguém ache esse código útil. Eu escrevi o código para algo que estava fazendo, mas pensei em compartilhá-lo.

Meu código é diferente, mas as idéias são muito parecidas com @infinity (na verdade - a única razão pela qual eu sei agora sobre / proc / * / root é por causa de sua resposta - obrigado @infinity!). Eu também adicionei algumas funcionalidades adicionais interessantes

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

Agora você faria duas coisas para garantir que o chroot possa ser desmontado:

Mate todos os processos que podem estar em execução no 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

Mate todos os processos que podem estar em execução fora do chroot, mas interferindo nele (por exemplo: se o chroot for / mnt / chroot e o dd estiver gravando em / mnt / chroot / testfile, o / mnt / chroot falhará na desmontagem)

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: Execute todo o código como root

Além disso, para uma versão menos complexa, substitua KILL_PID por um kill -SIGTERMoukill -SIGKILL

Tal
fonte
0

jchroot : um chroot com mais isolamento.

Após a execução do seu comando, qualquer processo iniciado pela execução desse comando será eliminado, qualquer IPC será liberado, qualquer ponto de montagem será desmontado. Tudo limpo!

O schroot ainda não é capaz de fazer isso, mas isso está planejado

Eu testei com sucesso no OpenVZ VPS, que não pode usar o docker ou o lxc.

Por favor, leia o blog do autor para obter detalhes:

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

Gostar
fonte
-1

schroot: Possui o recurso de gerenciamento de sessões. Quando você para a sessão, todos os seus processos são interrompidos.

https://github.com/dnschneid/crouton/blob/master/host-bin/unmount-chroot : esses scripts eliminam todo o processo chroot e desmontam todos os dispositivos montados.

sdkie
fonte
Pode ser útil explicar como o uso de schroot sobre chroot pode ajudar a resolver o problema do OP.
delete delete me