web-dev-qa-db-ja.com

chroot内のすべてのプロセスを停止するにはどうすればよいですか?

UbuntuがインストールされたLVMパーティションがいくつかあります。時折、apt-get dist-upgradeを実行して、インストールを最新のパッケージに更新したいことがあります。私はこれをchrootで行います-プロセスは通常次のようなものです:

$ 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

[表示なし:/mnt/chroot-0/{dev,sys,proc}をバインドマウントとして実際の/dev/sys、および/procにマウントおよびアンマウントします。プレゼント]

ただし、正確にアップグレードした後、このプロセスは機能しなくなります。/mnt/chroot-0ファイルシステム上に開いているファイルが残っているため、最終的なumountは失敗します。 lsofは、chrootにファイルが開いているプロセスがあることを確認します。これらのプロセスはdist-upgrade中に開始されました。これは、パッケージのアップグレード後にchroot内の特定のサービスを(たとえば[service postgresql restart]経由で)再起動する必要があるためと思われます。

したがって、このchroot内で実行されているすべてのサービスを停止するようにupstartに指示する必要があると考えています。これを確実に行う方法はありますか?

私はもう試した:

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は正しいことをするようで、この特定のルートで開始されたプロセスのみをリストします。 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

ただし、これらはすべてをキャッチするわけではありません。デーモン化され、PID 1にリペアレント化されたプロセスは停止しません。私も試しました:

Sudo chroot /mnt/chroot-0 telinit 0

ただし、この場合、init does n'tは別々のルートを区別し、マシン全体をシャットダウンします。

だから、ファイルシステムを安全にアンマウントできるように、特定のchrootのすべてのプロセスを停止するようにinitに指示する方法はありますか? upstartには、chroot内のすべての子プロセスをSIGTERM/SIGKILLする機能がありますか?

16
Jeremy Kerr

ここでは健全な状態を維持するためにカーネル以外は何も信用していないため、initを使用してこのジョブを実行したり、マウントされているかどうかを実際に知っている(一部のパッケージ) binfmt_miscなどの追加のファイルシステムをマウントできます。だから、プロセス虐殺のために、私は使用します:

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

そして、chrootをアンマウントするために、私は使用します:

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

補遺として、実際にchrootと別のプロセス空間にinitがなければ(つまり:LXCコンテナの場合)、これをinit問題として扱うことはおそらく間違った見方であると指摘したい。単一のinit(chrootの外側)と共有プロセススペースにより、これはもはや「initの問題」ではなく、問題のパスを持つプロセスを見つけるのはあなた次第です。したがって、上記のproc walkです。

最初の投稿から、これらが完全にブート可能なシステムであり、外部からアップグレードするだけの場合(これは私が読んでいる方法です)、またはパッケージビルドなどに使用するchrootであるかどうかは明らかではありません。後者の場合、最初の場所から開始するinitジョブを禁止するpolicy-rc.d(mk-sbuildによってドロップされたものなど)を配置することもできます。明らかに、これらがブート可能なシステムであることを意図している場合、それは正解ではありません。

16
infinity

あなたはすでに自分で問題を特定しています:dist-upgradeの間にservice ...を実行し、serviceはUpstartの一部ではなく、sysvinitの一部です。 service --status-allの周りに同様のawkマジックを追加して、Upstartサービスに使用したsysvinitサービスを停止します。

0
Tuminoid

この質問はかなり古いことはわかっていますが、今日は2012年と同じくらい関連性があると思います。うまくいけば、誰かがこのコードを見つけてくれることを願っています。私は自分がやっていることのコードを書きましたが、共有したいと思いました。

私のコードは異なりますが、アイデアは@infinityに非常に似ています(実際-/ proc/*/rootについて知っている唯一の理由は、彼の答えのためです-@infinityに感謝します!)。クールな追加機能も追加しました

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

ここで、chrootをアンマウントできるようにするために2つのことを行います。

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

Chrootの外部で実行されている可能性があるすべてのプロセスを強制終了しますが、干渉します(例:chrootが/ mnt/chrootで、ddが/ mnt/chroot/testfileに書き込みを行う場合、/ mnt/chrootはアンマウントに失敗します)

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

注:すべてのコードをルートとして実行する

また、それほど複雑ではないバージョンの場合、KILL_PIDをkill -SIGTERMまたはkill -SIGKILLに置き換えます

0
Tal

jchroot :より分離されたchroot。

コマンドが実行されると、このコマンドの実行によって開始されたプロセスはすべて強制終了され、IPCはすべて解放され、マウントポイントはすべてマウント解除されます。すべてきれい!

schrootはまだこれを行うことができませんが、これは計画されています

Dockerまたはlxcを使用できないOpenVZ VPSで正常にテストしました。

詳細については、著者のブログをご覧ください。

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

0
Like