ฉันจะหยุดกระบวนการทั้งหมดใน chroot ได้อย่างไร?


16

ฉันมีพาร์ติชั่น LVM เป็นจำนวนมากแต่ละอันมีการติดตั้ง Ubuntu ในบางครั้งฉันต้องการทำ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เป็นออัพเกรดดูเหมือนว่าจะคาดหวังเหล่านี้จะนำเสนอ]

อย่างไรก็ตามหลังจากอัปเกรดเป็นแม่นยำกระบวนการนี้จะไม่ทำงานอีกต่อไป - umount สุดท้ายจะล้มเหลวเนื่องจากยังคงมีไฟล์ที่เปิดอยู่ใน/mnt/chroot-0ระบบไฟล์ lsofยืนยันว่ามีกระบวนการที่เปิดไฟล์ใน chroot กระบวนการเหล่านี้ได้รับการเริ่มต้นในระหว่างการอัพเกรด dist ฉันคิดว่านี่เป็นเพราะบริการบางอย่างใน chroot จำเป็นต้องเริ่มต้นใหม่ (เช่นผ่านservice postgresql restart) หลังจากแพ็คเกจถูกอัพเกรด

ดังนั้นฉันคิดว่าฉันต้องบอกคนธรรมดาเพื่อหยุดบริการทั้งหมดที่กำลังทำงานอยู่ภายใน chroot นี้ มีวิธีที่จะทำสิ่งนี้ได้อย่างน่าเชื่อถือหรือไม่?

ฉันได้พยายาม:

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

อย่างไรก็ตามสิ่งเหล่านี้ดูเหมือนจะไม่ได้จับทุกอย่าง กระบวนการที่มีการ daemonised และรับการ reputed PID 1 จะไม่หยุด ฉันยังลอง:

sudo chroot /mnt/chroot-0 telinit 0

แต่ในกรณีนี้ init ไม่ได้แยกความแตกต่างระหว่างรูทที่แยกจากกันและปิดเครื่องทั้งหมด

ดังนั้นมีวิธีใดที่จะบอกให้ init หยุดกระบวนการทั้งหมดใน chroot นั้น ๆ เพื่อที่ฉันจะสามารถ unmount ระบบไฟล์ได้อย่างปลอดภัย? การพุ่งพรวดมีสิ่งอำนวยความสะดวกใด ๆ ในการ SIGTERM / SIGKILL กระบวนการลูกทั้งหมด (เช่นจะทำในระหว่างการปิดระบบปกติ) ภายใน chroot หรือไม่?


นี่ไม่ใช่คำตอบสำหรับคำถามจริงของคุณ แต่อาจมีประโยชน์: ฉันแนะนำให้ดูแพ็คเกจ lxc lxc มีเครื่องมือง่าย ๆ ในการเริ่มต้นและปิดอินสแตนซ์ในภาชนะบรรจุให้สะอาดหมดจด
ไอออน

คำตอบ:


16

ฉันไม่เชื่อถืออะไรเลยนอกจากเคอร์เนลที่จะรักษาสถานะที่มีสติอยู่ที่นี่ดังนั้นฉันจึงไม่ (ab) ใช้ 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

และสำหรับการติดตั้ง chroots ฉันใช้:

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

ในฐานะที่เป็นภาคผนวกฉันจะชี้ให้เห็นว่าการเข้าใกล้สิ่งนี้เป็นปัญหาเริ่มต้นอาจเป็นวิธีที่ผิดที่จะดูเว้นแต่คุณจะมี init ใน chroot และพื้นที่กระบวนการแยกต่างหาก (เช่น: ในกรณีของตู้คอนเทนเนอร์ LXC) . ด้วย init เดียว (นอก chroot) และพื้นที่กระบวนการที่ใช้ร่วมกันนี่ไม่ใช่ "ปัญหาของ init" อีกต่อไป แต่ขึ้นอยู่กับคุณที่จะหากระบวนการที่เกิดขึ้นเพื่อให้มีเส้นทางที่ไม่เหมาะสมดังนั้น proc เดินข้างบน

ยังไม่ชัดเจนจากการโพสต์เริ่มต้นของคุณหากระบบเหล่านี้เป็นระบบที่สามารถบูตได้อย่างเต็มที่ซึ่งคุณเพิ่งอัพเกรดจากภายนอก (ซึ่งเป็นวิธีที่ฉันอ่าน) หรือว่าเป็นสิ่งที่คุณใช้สำหรับสิ่งต่างๆเช่นแพ็คเกจการสร้าง หากเป็นตัวหลังคุณอาจต้องการ policy-rc.d แทน (เช่นที่ mk-sbuild ดรอป) ซึ่งห้ามการเริ่มงาน init ที่เริ่มต้นตั้งแต่แรก เห็นได้ชัดว่านั่นไม่ใช่วิธีแก้ปัญหาที่มีเหตุผลหากสิ่งเหล่านี้หมายถึงระบบที่ใช้บู๊ตได้


พวกมันเป็นระบบที่สามารถบู๊ตได้ แต่policy-rc.dดูเหมือนว่าจะเป็นวิธีที่น่าสนใจ (ฉันสามารถลบมันออกไปหลังจากโต้ตอบกับ chroot) สิ่งนี้มีผลต่องานทั้งแบบ/etc/rc*.dและ/etc/init/*.confสไตล์หรือไม่?
Jeremy Kerr


ไม่พุ่งพรวดหรือ sysvinit "ปรึกษานโยบาย -rc.d" มันเรียกใช้ -rc.d ที่ทำเช่นนั้นซึ่งสคริปต์ postinst ทั้งหมดจะหมายถึงการใช้ในการโต้ตอบกับงาน init ในทางปฏิบัติดูเหมือนว่า DTRT ยกเว้นในกรณีของแพ็คเกจที่เสียหาย (ซึ่งควรได้รับการแก้ไข) ยังคงการเขียนสคริปต์ "กวาดล้างด้วยไฟ" ข้างต้นทำหน้าที่ไม่ว่าจะเป็นปัญหาที่เกิดขึ้นในนโยบายที่ผ่านมาไม่มีนโยบายอยู่หรือกระบวนการที่ใช้เวลานานของการเรียงลำดับอื่น ๆ ที่เหลืออยู่ (กรณีใช้งานหลักสำหรับ บิลด์ที่นี่เป็นสิ่งที่พื้นหลังในระหว่างการสร้างตัวเองหรือไม่สนใจจาก sbuild)
ไม่มีที่สิ้นสุด

1
ปัญหาหนึ่งในการพยายามหลีกเลี่ยงการสนับสนุน chroot ของ utpstart ฉันค่อนข้างแน่ใจว่าการฆ่า -9 จะไม่ป้องกันไม่ให้เกิดการพุ่งพรวดจากงานที่พุ่งพรวดหากมีการระบุเกิดใหม่ ดังนั้นคุณยังคงต้องสอบปากคำพุ่งพรวดจากภายใน chrootเพื่อดูว่าสิ่งต่าง ๆ ยังคงทำงานอยู่หรือไม่ ฉันคิดว่านี่เป็นเรื่องที่โชคร้ายและเราควรมีวิธีจากคนนอกเพื่อฆ่างานเหล่านี้ ที่กล่าวว่าฉันจะดูที่รายการ initctl / awk / grep + คุณควรจะเสร็จสมบูรณ์
SpamapS

1
@SpamapS: จุดดี - การฆ่างาน init ด้วยตนเองย่อมส่งผลให้พวกเขาเริ่มต้นใหม่ คงจะดีถ้าบอกให้พุ่งพรวดเพื่อทำการปิดเฉพาะ chroot หยุดงานที่กำหนดแล้วฆ่ากระบวนการ reparented ใด ๆ ที่เหลือที่มีไดเรกทอรีรากภายใน chroot
Jeremy Kerr

0

คุณระบุปัญหาด้วยตัวเองแล้ว: บางสิ่งทำงาน service ...ระหว่างอ. อัปเกรดและserviceไม่ได้เป็นส่วนหนึ่งของการพุ่งพรวด sysvinitแต่เป็นส่วนหนึ่งของ เพิ่มเวทมนตร์ awk ที่คล้ายกันservice --status-allเพื่อหยุดบริการ sysvinit ตามที่คุณใช้สำหรับบริการพุ่งพรวด


3
อ่าขอบคุณ มันเกือบจะดีกว่า แต่ก็ไม่ได้ครอบคลุมบริการทั้งหมดเช่นกัน ผมเคยทำงานsudo chroot /mnt/chroot-0 service --list-allและsudo chroot /mnt/chroot-0 initctl listซึ่งรายงานทั้งบริการไม่ทำงาน อย่างไรก็ตาม/usr/bin/epmd(จาก erlang-base) ยังคงทำงานอยู่
Jeremy Kerr

0

ฉันรู้ว่าคำถามนี้ค่อนข้างเก่า แต่ฉันคิดว่ามันมีความเกี่ยวข้องในวันนี้เหมือนในปี 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
}

ตอนนี้คุณจะทำ 2 สิ่งเพื่อให้แน่ใจว่า chroot สามารถถูกถอดออกได้:

ฆ่ากระบวนการทั้งหมดที่อาจทำงานอยู่ใน 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

jchroot : chroot ที่มีความเหงามากกว่า

หลังจากที่คำสั่งของคุณได้รับการดำเนินการกระบวนการใด ๆ ที่เริ่มต้นโดยการดำเนินการตามคำสั่งนี้จะถูกฆ่า IPC ใด ๆ จะถูกปล่อยให้เป็นอิสระจุดเชื่อมต่อใด ๆ จะถูกยกเลิกการต่อเชื่อม สะอาดหมดจด!

Schroot ยังไม่สามารถทำเช่นนี้ แต่มีการวางแผน

ฉันได้ทดสอบมันสำเร็จใน OpenVZ VPS ซึ่งไม่สามารถใช้ตัวเทียบท่าหรือ lxc

โปรดอ่านบล็อกของผู้เขียนเพื่อดูรายละเอียด:

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


-1

schroot: มันมีคุณสมบัติของการจัดการเซสชั่น เมื่อคุณหยุดเซสชันกระบวนการทั้งหมดจะถูกฆ่า

https://github.com/dnschneid/crouton/blob/master/host-bin/unmount-chroot : สคริปต์นี้จะฆ่ากระบวนการ chroot ทั้งหมดและยกเลิกการต่อเชื่อมอุปกรณ์ที่ติดตั้งทั้งหมด


การอธิบายวิธีใช้ schroot บน chroot สามารถช่วยแก้ปัญหา OP ได้
โปรดลบฉัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.