Dumb-init เป็นหัวหน้างานกระบวนการง่าย ๆ และระบบ init ที่ออกแบบมาเพื่อทำงานเป็น PID 1 ภายในสภาพแวดล้อมคอนเทนเนอร์น้อยที่สุด (เช่น Docker) มันถูกปรับใช้เป็นไบนารีขนาดเล็กที่เชื่อมโยงแบบคงที่ใน C.
คอนเทนเนอร์ที่มีน้ำหนักเบาได้เป็นที่นิยมในการใช้งานกระบวนการหรือบริการเดียวโดยไม่มีระบบเริ่มต้นปกติเช่น SystemD หรือ Sysvinit อย่างไรก็ตามการละเว้นระบบ init มักจะนำไปสู่การจัดการกระบวนการและสัญญาณที่ไม่ถูกต้องและอาจส่งผลให้เกิดปัญหาเช่นภาชนะที่ไม่สามารถหยุดได้อย่างสง่างามหรือรั่วไหลของภาชนะที่ควรถูกทำลาย
dumb-init ช่วยให้คุณสามารถนำหน้าคำสั่งของคุณด้วย dumb-init มันทำหน้าที่เป็น PID 1 และวางไข่คำสั่งของคุณทันทีเป็นกระบวนการเด็กดูแลการจัดการและส่งสัญญาณอย่างเหมาะสมตามที่พวกเขาได้รับ
โดยปกติเมื่อคุณเปิดตัวคอนเทนเนอร์ Docker กระบวนการที่คุณดำเนินการจะกลายเป็น PID 1 ให้มันเป็นนิสัยใจคอและความรับผิดชอบที่มาพร้อมกับการเป็นระบบ init สำหรับคอนเทนเนอร์
มีสองประเด็นทั่วไปที่นำเสนอ:
ในกรณีส่วนใหญ่สัญญาณจะไม่ถูกจัดการอย่างถูกต้อง
เคอร์เนล Linux ใช้การจัดการสัญญาณพิเศษกับกระบวนการที่ทำงานเป็น PID 1
เมื่อกระบวนการถูกส่งสัญญาณในระบบ Linux ปกติเคอร์เนลจะตรวจสอบตัวจัดการที่กำหนดเองใด ๆ ก่อนที่กระบวนการได้ลงทะเบียนสำหรับสัญญาณนั้นและมิฉะนั้นจะกลับไปสู่พฤติกรรมเริ่มต้น (ตัวอย่างเช่นฆ่ากระบวนการใน SIGTERM )
อย่างไรก็ตามหากกระบวนการที่ได้รับสัญญาณคือ PID 1 จะได้รับการรักษาพิเศษโดยเคอร์เนล หากยังไม่ได้ลงทะเบียนตัวจัดการสัญญาณเคอร์เนลจะไม่กลับไปสู่พฤติกรรมเริ่มต้นและไม่มีอะไรเกิดขึ้น กล่าวอีกนัยหนึ่งหากกระบวนการของคุณไม่ได้จัดการกับสัญญาณเหล่านี้อย่างชัดเจนการส่ง SIGTERM จะไม่มีผลเลย
ตัวอย่างทั่วไปคืองาน CI ที่ docker run my-container script : การส่ง SIGTERM ไปยังกระบวนการ docker run โดยทั่วไปจะฆ่าคำสั่ง docker run แต่ปล่อยให้คอนเทนเนอร์ทำงานอยู่ในพื้นหลัง
กระบวนการซอมบี้กำพร้าไม่ได้รับการเก็บเกี่ยวอย่างถูกต้อง
กระบวนการกลายเป็นซอมบี้เมื่อออกและยังคงเป็นซอมบี้จนกว่าผู้ปกครองจะเรียกการเรียกใช้ระบบ wait() บางอย่าง มันยังคงอยู่ในตารางกระบวนการเป็นกระบวนการ "หมดอายุ" โดยทั่วไปกระบวนการหลักจะเรียก wait() ทันทีและหลีกเลี่ยงซอมบี้ที่มีชีวิตยืนยาว
หากผู้ปกครองออกจากก่อนลูกเด็กจะเป็น "เด็กกำพร้า" และได้รับการดูแลใหม่ภายใต้ PID 1 ระบบ init จึงรับผิดชอบใน wait() -การใช้กระบวนการซอมบี้เด็กกำพร้า
แน่นอนว่ากระบวนการส่วนใหญ่ จะไม่ wait() ในกระบวนการสุ่มที่เกิดขึ้นกับพวกเขาดังนั้นภาชนะบรรจุมักจะจบลงด้วยซอมบี้หลายสิบตัวที่รูทที่ PID 1
dumb-init ทำ dumb-init ทำงานเป็น PID 1 ทำหน้าที่เหมือนระบบ init ง่าย ๆ มันเปิดตัวกระบวนการเดียวจากนั้นพร็อกซีทั้งหมดได้รับสัญญาณไปยังเซสชันที่รูทในกระบวนการเด็กนั้น
เนื่องจากกระบวนการจริงของคุณไม่ได้เป็น PID 1 อีกต่อไปเมื่อได้รับสัญญาณจาก dumb-init ตัวจัดการสัญญาณเริ่มต้นจะถูกนำไปใช้และกระบวนการของคุณจะทำงานตามที่คุณคาดหวัง หากกระบวนการของคุณตายไปแล้ว dumb-init จะตายด้วยการดูแลเพื่อทำความสะอาดกระบวนการอื่น ๆ ที่อาจยังคงอยู่
ในโหมดเริ่มต้น dumb-init จะสร้างเซสชันที่รูทที่เด็กและส่งสัญญาณไปยังกลุ่มกระบวนการทั้งหมด สิ่งนี้มีประโยชน์หากคุณมีลูกที่ประพฤติตัวไม่ดี (เช่นเชลล์สคริปต์) ซึ่งปกติจะไม่ส่งสัญญาณลูก ๆ ก่อนที่จะตาย
สิ่งนี้อาจเป็นประโยชน์นอกคอนเทนเนอร์ Docker ในหัวหน้างานทั่วไปเช่น Daemontools หรือ Supervisord สำหรับการควบคุมสคริปต์เชลล์ โดยปกติสัญญาณเช่น SIGTERM ที่ได้รับจากเชลล์จะไม่ถูกส่งต่อไปยังกระบวนการย่อย แต่กระบวนการเชลล์เท่านั้นที่ตาย ด้วย dumb-init คุณสามารถเขียนสคริปต์เชลล์ที่มีความโง่เขลาใน Shebang:
#!/usr/bin/dumb-init /bin/sh
my-web-server & # launch a process in the background
my-other-server # launch another process in the foreground
ตามปกติแล้ว SIGTERM ที่ส่งไปยังเปลือกจะฆ่าเปลือก แต่ปล่อยให้กระบวนการเหล่านั้นทำงาน (ทั้งพื้นหลังและเบื้องหน้า!) ด้วย dumb-init กระบวนการย่อยของคุณจะได้รับสัญญาณเดียวกันกับที่เชลล์ของคุณทำ
หากคุณต้องการส่งสัญญาณไปยังเด็กโดยตรงเท่านั้นคุณสามารถเรียกใช้กับอาร์กิวเมนต์ --single-child หรือตั้งค่าตัวแปรสภาพแวดล้อม DUMB_INIT_SETSID=0 เมื่อทำงาน dumb-init ในโหมดนี้ Dumb-init มีความโปร่งใสอย่างสมบูรณ์ คุณสามารถใช้หลายอย่างเข้าด้วยกัน (เช่น dumb-init dumb-init echo 'oh, hi' )
dumb-init ช่วยให้การเขียนสัญญาณเข้ามาใหม่ก่อนที่จะพร็อกซ์ สิ่งนี้มีประโยชน์ในกรณีที่คุณมีหัวหน้างาน Docker (เช่น Mesos หรือ Kubernetes) ซึ่งมักจะส่งสัญญาณมาตรฐาน (เช่น Sigterm) แอพบางตัวต้องการสัญญาณหยุดที่แตกต่างกันเพื่อทำความสะอาดอย่างสง่างาม
ตัวอย่างเช่นในการเขียนสัญญาณ sigterm (หมายเลข 15) ถึง sigquit (หมายเลข 3) เพียงเพิ่ม --rewrite 15:3 บนบรรทัดคำสั่ง
ในการวางสัญญาณทั้งหมดคุณสามารถเขียนใหม่ไปยังหมายเลขพิเศษ 0
เมื่อทำงานในโหมด setsid มันไม่เพียงพอที่จะส่งต่อ SIGTSTP / SIGTTIN / SIGTTOU ในกรณีส่วนใหญ่เนื่องจากหากกระบวนการไม่ได้เพิ่มตัวจัดการสัญญาณที่กำหนดเองสำหรับสัญญาณเหล่านี้เคอร์เนลจะไม่ใช้พฤติกรรมการจัดการสัญญาณเริ่มต้น (ซึ่งจะระงับกระบวนการ) เนื่องจากเป็นสมาชิกของกลุ่ม ด้วยเหตุนี้เราจึงตั้งค่าการเขียนใหม่เป็น SIGSTOP จากสัญญาณทั้งสาม คุณสามารถยกเลิกพฤติกรรมนี้ได้โดยการเขียนสัญญาณกลับไปเป็นค่าดั้งเดิมหากต้องการ
หนึ่งข้อแม้ที่มีคุณสมบัตินี้: สำหรับสัญญาณควบคุมงาน ( SIGTSTP , SIGTTIN , SIGTTOU ), Dumb-init จะระงับตัวเองเสมอหลังจากได้รับสัญญาณแม้ว่าคุณจะเขียนมันเป็นอย่างอื่นก็ตาม
คุณมีตัวเลือกเล็กน้อยสำหรับการใช้ dumb-init :
การแจกแจง Linux ยอดนิยมจำนวนมาก (รวมถึง Debian (ตั้งแต่ stretch ) และอนุพันธ์ของ Debian เช่น Ubuntu (ตั้งแต่ bionic )) ตอนนี้มีแพ็คเกจที่โง่เขลาในที่เก็บอย่างเป็นทางการของพวกเขา
ในการแจกแจงที่ใช้ Debian คุณสามารถเรียกใช้ apt install dumb-init เพื่อติดตั้ง dumb-init เช่นเดียวกับที่คุณติดตั้งแพ็คเกจอื่น ๆ
หมายเหตุ: Dibl-Init รุ่นที่จัดสรรแบบ distro ส่วนใหญ่ไม่ได้เชื่อมโยงแบบคงที่ซึ่งแตกต่างจากรุ่นที่เรามีให้ (ดูตัวเลือกอื่น ๆ ด้านล่าง) โดยปกติแล้วจะดีอย่างสมบูรณ์ แต่หมายความว่าโดยทั่วไปแล้วรุ่นโง่เหล่านี้จะไม่ทำงานเมื่อคัดลอกไปยัง Linux distros อื่น ๆ ซึ่งแตกต่างจากรุ่นที่เชื่อมโยงแบบคงที่ที่เรามีให้
หากคุณมีเซิร์ฟเวอร์ APT ภายในการอัปโหลด. .deb ไปยังเซิร์ฟเวอร์ของคุณเป็นวิธีที่แนะนำในการใช้ dumb-init ใน DockerFiles ของคุณคุณสามารถ apt install dumb-init และจะพร้อมใช้งาน
แพ็คเกจ Debian มีให้บริการจากแท็บ GitHub Reasees หรือคุณสามารถเรียกใช้ make builddeb ด้วยตัวเอง
.deb ด้วยตนเอง (Debian/Ubuntu) หากคุณไม่มีเซิร์ฟเวอร์ APT ภายในคุณสามารถใช้ dpkg -i เพื่อติดตั้งแพ็คเกจ. .deb คุณสามารถเลือกวิธีที่คุณได้รับ .deb ลงในคอนเทนเนอร์ของคุณ (ติดตั้งไดเรกทอรีหรือ wget -เป็นตัวเลือกบางอย่าง)
ความเป็นไปได้อย่างหนึ่งคือคำสั่งต่อไปนี้ใน DockerFile ของคุณ:
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_amd64.deb
RUN dpkg -i dumb-init_*.debเนื่องจาก Dumb-init ถูกปล่อยออกมาเป็นไบนารีที่เชื่อมโยงแบบคงที่คุณจึงสามารถ plop ลงในภาพของคุณได้ นี่คือตัวอย่างของการทำสิ่งนั้นใน Dockerfile:
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64
RUN chmod +x /usr/local/bin/dumb-init แม้ว่า dumb-init จะถูกเขียนขึ้นใน C ทั้งหมด แต่เรายังมีแพ็คเกจ Python ที่รวบรวมและติดตั้งไบนารี สามารถติดตั้งได้จาก PYPI โดยใช้ pip คุณจะต้องติดตั้งคอมไพเลอร์ C ก่อน (บน Debian/Ubuntu, apt-get install gcc นั้นเพียงพอ) จากนั้นเพียงแค่ pip install dumb-init
ตั้งแต่ 1.2.0 แพ็คเกจที่ PYPI มีให้บริการเป็นคลังเก็บล้อที่สร้างไว้ล่วงหน้าและไม่จำเป็นต้องรวบรวมในการแจกแจง Linux ทั่วไป
เมื่อติดตั้งภายในคอนเทนเนอร์ Docker ของคุณเพียงแค่นำคำสั่งของคุณด้วย dumb-init (และตรวจสอบให้แน่ใจว่าคุณใช้ไวยากรณ์ JSON ที่แนะนำ)
ภายใน Dockerfile มันเป็นวิธีปฏิบัติที่ดีในการใช้ Dumb-init เป็นจุดเข้างานคอนเทนเนอร์ของคุณ "entrypoint" เป็นคำสั่งบางส่วนที่ได้รับการเตรียมการตามคำสั่ง CMD ของคุณทำให้เหมาะสำหรับคนโง่:
# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT [ "/usr/bin/dumb-init" , "--" ]
# or if you use --rewrite or other cli flags
# ENTRYPOINT ["dumb-init", "--rewrite", "2:3", "--"]
CMD [ "/my/script" , "--with" , "--args" ] หากคุณประกาศจุดเข้าใช้งานในภาพพื้นฐานภาพใด ๆ ที่ลงมาจากมันไม่จำเป็นต้องประกาศโง่ ๆ พวกเขาสามารถตั้งค่า CMD ได้ตามปกติ
สำหรับการใช้งานแบบครั้งเดียวแบบโต้ตอบคุณสามารถเติมเต็มด้วยตนเองได้:
$ docker run my_container dumb-init python -c 'while True: pass'
การเรียกใช้คำสั่งเดียวกันนี้โดยไม่ dumb-init จะส่งผลให้ไม่สามารถหยุดคอนเทนเนอร์ได้โดยไม่ต้อง SIGKILL แต่ด้วย dumb-init คุณสามารถส่งสัญญาณที่มีมนุษยธรรมได้มากขึ้นเช่น SIGTERM
เป็นสิ่งสำคัญที่คุณจะต้องใช้ไวยากรณ์ JSON สำหรับ CMD และ ENTRYPOINT มิฉะนั้น Docker เรียกใช้เชลล์เพื่อเรียกใช้คำสั่งของคุณส่งผลให้เชลล์เป็น PID 1 แทนที่จะเป็นคนโง่
บ่อยครั้งที่คอนเทนเนอร์ต้องการทำงานก่อนเริ่มต้นซึ่งไม่สามารถทำได้ในช่วงเวลาที่สร้าง ตัวอย่างเช่นคุณอาจต้องการเทมเพลตไฟล์กำหนดค่าบางไฟล์ตามตัวแปรสภาพแวดล้อม
วิธีที่ดีที่สุดในการรวมเข้ากับ Dumb-init นั้นเป็นเช่นนี้:
ENTRYPOINT [ "/usr/bin/dumb-init" , "--" ]
CMD [ "bash" , "-c" , "do-some-pre-start-thing && exec my-server" ]ด้วยการใช้ dumb-init เป็นจุดเข้าใช้งานคุณจะมีระบบเริ่มต้นที่เหมาะสมอยู่เสมอ
ส่วน exec ของคำสั่ง bash มีความสำคัญเนื่องจากแทนที่กระบวนการ bash ด้วยเซิร์ฟเวอร์ของคุณเพื่อให้เชลล์มีอยู่ในทันทีในช่วงเริ่มต้น
การสร้างไบนารีแบบโง่ต้องใช้คอมไพเลอร์ที่ใช้งานได้และส่วนหัว LIBC และค่าเริ่มต้นเป็น Glibc
$ make
การรวบรวมแบบสัดส่วนแบบ dumb-init มีมากกว่า 700kb เนื่องจาก GLIBC แต่ MUSL เป็นตัวเลือกในขณะนี้ บน debian/ubuntu apt-get install musl-tools เพื่อติดตั้งแหล่งที่มาและ wrappers จากนั้นเพียงแค่:
$ CC=musl-gcc make
เมื่อคอมไพล์แบบคงที่ด้วย MUSL ขนาดไบนารีอยู่ที่ประมาณ 20KB
เราใช้อนุสัญญา Debian มาตรฐานเพื่อระบุการพึ่งพาการสร้าง (ดูใน debian/control ) วิธีที่ง่ายในการเริ่มต้นคือ apt-get install build-essential devscripts equivs และจากนั้น sudo mk-build-deps -i --remove เพื่อติดตั้งการพึ่งพาการสร้างที่หายไปทั้งหมดโดยอัตโนมัติ จากนั้นคุณสามารถใช้ make builddeb เพื่อสร้างแพ็คเกจ Debian ที่เป็นใบ้
หากคุณต้องการบิลด์ Debian อัตโนมัติโดยใช้ Docker เพียงเรียกใช้ make builddeb-docker สิ่งนี้ง่ายกว่า แต่ต้องการให้คุณมี Docker ทำงานบนเครื่องของคุณ