คิดอย่างรอบคอบก่อนที่จะใช้ Docker-in-Docker สำหรับ CI หรือสภาพแวดล้อมการทดสอบ

คิดอย่างรอบคอบก่อนที่จะใช้ Docker-in-Docker สำหรับ CI หรือสภาพแวดล้อมการทดสอบ

Docker-in-Docker คือสภาพแวดล้อม Docker daemon เสมือนจริงที่ทำงานภายในคอนเทนเนอร์เพื่อสร้างอิมเมจคอนเทนเนอร์ วัตถุประสงค์หลักของการสร้าง Docker-in-Docker คือเพื่อช่วยพัฒนา Docker เอง หลายคนใช้มันเพื่อรัน Jenkins CI สิ่งนี้ดูเหมือนเป็นเรื่องปกติในตอนแรก แต่จากนั้นก็เกิดปัญหาซึ่งสามารถหลีกเลี่ยงได้โดยการติดตั้ง Docker ในคอนเทนเนอร์ Jenkins CI บทความนี้จะบอกวิธีการทำเช่นนี้ หากคุณสนใจวิธีแก้ปัญหาขั้นสุดท้ายโดยไม่มีรายละเอียด เพียงอ่านหัวข้อสุดท้ายของบทความ “การแก้ปัญหา”

คิดอย่างรอบคอบก่อนที่จะใช้ Docker-in-Docker สำหรับ CI หรือสภาพแวดล้อมการทดสอบ

นักเทียบท่าในนักเทียบท่า: "ดี"

กว่าสองปีที่แล้วฉันใส่ Docker ธง –ได้รับสิทธิพิเศษและเขียน dind เวอร์ชันแรก. เป้าหมายคือการช่วยให้ทีมหลักพัฒนา Docker ได้เร็วขึ้น ก่อน Docker-in-Docker วงจรการพัฒนาโดยทั่วไปมีลักษณะดังนี้:

  • แฮ็คแฮ็ค;
  • สร้าง;
  • การหยุด Docker daemon ที่กำลังรันอยู่
  • เปิดตัว Docker daemon ใหม่
  • การทดสอบ
  • ทำซ้ำวงจร

หากคุณต้องการสร้างชุดประกอบที่สวยงามและทำซ้ำได้ (นั่นคือในภาชนะ) มันก็ซับซ้อนมากขึ้น:

  • แฮ็คแฮ็ค;
  • ตรวจสอบให้แน่ใจว่า Docker เวอร์ชันใช้งานได้กำลังทำงานอยู่
  • สร้าง Docker ใหม่ด้วย Docker เก่า
  • หยุดนักเทียบท่าดีมอน;
  • เริ่ม Docker daemon ใหม่
  • ทดสอบ;
  • หยุด Docker daemon ใหม่
  • ทำซ้ำ.

ด้วยการถือกำเนิดของ Docker-in-Docker กระบวนการจึงง่ายขึ้น:

  • แฮ็คแฮ็ค;
  • การประกอบ + การเปิดตัวในขั้นตอนเดียว
  • ทำซ้ำวงจร

วิธีนี้จะดีกว่าไม่ใช่เหรอ?

คิดอย่างรอบคอบก่อนที่จะใช้ Docker-in-Docker สำหรับ CI หรือสภาพแวดล้อมการทดสอบ

นักเทียบท่าในนักเทียบท่า: "แย่"

อย่างไรก็ตาม ตรงกันข้ามกับความเชื่อที่นิยมนัก Docker-in-Docker ไม่ใช่ดาว ม้า และยูนิคอร์น 100% สิ่งที่ฉันหมายถึงคือมีปัญหาหลายประการที่นักพัฒนาจำเป็นต้องทราบ

หนึ่งในนั้นเกี่ยวข้องกับ LSM (โมดูลความปลอดภัยของ Linux) เช่น AppArmor และ SELinux: เมื่อเรียกใช้คอนเทนเนอร์ "Internal Docker" อาจพยายามใช้โปรไฟล์ความปลอดภัยที่จะขัดแย้งหรือสร้างความสับสนให้กับ "External Docker" นี่เป็นปัญหาที่ยากที่สุดในการแก้ไขเมื่อพยายามผสานการใช้งานดั้งเดิมของแฟล็ก –privileged การเปลี่ยนแปลงของฉันได้ผลและการทดสอบทั้งหมดจะส่งผ่านบนเครื่อง Debian ของฉันและ Ubuntu ทดสอบ VMs แต่พวกมันจะพังและเบิร์นบนเครื่องของ Michael Crosby (เขามี Fedora อย่างที่ฉันจำได้) ฉันจำสาเหตุที่แท้จริงของปัญหาไม่ได้ แต่อาจเป็นเพราะ Mike เป็นคนฉลาดที่ทำงานร่วมกับ SELINUX=enforce (ฉันใช้ AppArmor) และการเปลี่ยนแปลงของฉันไม่ได้คำนึงถึงโปรไฟล์ SELinux

นักเทียบท่าในนักเทียบท่า: "ความชั่วร้าย"

ปัญหาที่สองคือกับไดรเวอร์ที่เก็บข้อมูล Docker เมื่อคุณเรียกใช้ Docker-in-Docker Docker ภายนอกจะทำงานบนระบบไฟล์ปกติ (EXT4, BTRFS หรืออะไรก็ตามที่คุณมี) และ Docker ภายในจะทำงานบนระบบคัดลอกเมื่อเขียน (AUFS, BTRFS, Device Mapper ฯลฯ) ขึ้นอยู่กับการกำหนดค่าให้ใช้ Docker ภายนอก) สิ่งนี้จะสร้างชุดค่าผสมหลายอย่างที่ใช้งานไม่ได้ ตัวอย่างเช่น คุณจะไม่สามารถเรียกใช้ AUFS ทับ AUFS ได้

หากคุณเรียกใช้ BTRFS บน BTRFS ควรใช้งานได้ในตอนแรก แต่เมื่อมีวอลลุ่มย่อยที่ซ้อนกัน การลบวอลลุ่มย่อยพาเรนต์จะล้มเหลว โมดูล Device Mapper ไม่มีเนมสเปซ ดังนั้นหากอินสแตนซ์ Docker หลายอินสแตนซ์ใช้งานบนเครื่องเดียวกัน พวกเขาทั้งหมดจะสามารถเห็น (และมีอิทธิพลต่อ) อิมเมจซึ่งกันและกันและบนอุปกรณ์สำรองข้อมูลคอนเทนเนอร์ นี้ไม่ดี.

มีวิธีแก้ไขปัญหาเหล่านี้มากมาย ตัวอย่างเช่น หากคุณต้องการใช้ AUFS ใน Docker ภายใน เพียงเปลี่ยนโฟลเดอร์ /var/lib/docker ให้เป็นโวลุ่ม แล้วคุณจะสบายดี นักเทียบท่าได้เพิ่มเนมสเปซฐานบางส่วนให้กับชื่อเป้าหมาย Device Mapper ดังนั้นหากมีการเรียกใช้ Docker หลายครั้งบนเครื่องเดียวกัน การเรียกเหล่านั้นจะไม่ทับซ้อนกัน

อย่างไรก็ตาม การตั้งค่าดังกล่าวไม่ได้ง่ายเลย ดังที่เห็นได้จากสิ่งเหล่านี้ บทความ ในพื้นที่เก็บข้อมูล Dind บน GitHub

Docker-in-Docker: มันแย่ลงเรื่อยๆ

แล้วบิลด์แคชล่ะ? นี่อาจเป็นเรื่องยากทีเดียว ผู้คนมักถามฉันว่า “ถ้าฉันใช้ Docker-in-Docker ฉันจะใช้รูปภาพที่โฮสต์บนโฮสต์ของฉัน แทนที่จะดึงทุกอย่างกลับเข้าไปใน Docker ภายในของฉันได้อย่างไร”

ผู้กล้าได้กล้าเสียบางคนพยายามผูก /var/lib/docker จากโฮสต์ไปยังคอนเทนเนอร์ Docker-in-Docker บางครั้งพวกเขาแชร์ /var/lib/docker กับหลายคอนเทนเนอร์

คิดอย่างรอบคอบก่อนที่จะใช้ Docker-in-Docker สำหรับ CI หรือสภาพแวดล้อมการทดสอบ
คุณต้องการที่จะเสียหายข้อมูลของคุณ? เพราะนี่คือสิ่งที่จะทำให้ข้อมูลของคุณเสียหาย!

Docker daemon ได้รับการออกแบบมาอย่างชัดเจนเพื่อให้สามารถเข้าถึง /var/lib/docker ได้แต่เพียงผู้เดียว ไม่ควร "แตะ กระตุ้น หรือแยง" ไฟล์ Docker ใดๆ ที่อยู่ในโฟลเดอร์นี้อีก

ทำไมจึงเป็นเช่นนี้? เพราะนี่คือผลลัพธ์ของหนึ่งในบทเรียนที่ยากที่สุดที่ได้เรียนรู้ขณะพัฒนา dotCloud เอ็นจิ้นคอนเทนเนอร์ dotCloud ทำงานโดยมีหลายกระบวนการที่เข้าถึง /var/lib/dotcloud พร้อมกัน เคล็ดลับอันชาญฉลาด เช่น การแทนที่ไฟล์อะตอมมิก (แทนการแก้ไขแบบแทนที่) การพริกไทยโค้ดด้วยการล็อคคำแนะนำและการบังคับ และการทดลองอื่นๆ กับระบบที่ปลอดภัย เช่น SQLite และ BDB ไม่ได้ผลเสมอไป ตอนที่เราออกแบบ Container Engine ใหม่ ซึ่งในที่สุดก็กลายมาเป็น Docker หนึ่งในการตัดสินใจในการออกแบบที่สำคัญคือการรวมการทำงานของคอนเทนเนอร์ทั้งหมดไว้ภายใต้ Daemon เดียว เพื่อกำจัดเรื่องไร้สาระที่เกิดขึ้นพร้อมกันทั้งหมด

อย่าเข้าใจฉันผิด: เป็นไปได้ที่จะสร้างสิ่งที่ดี เชื่อถือได้ และรวดเร็ว ซึ่งเกี่ยวข้องกับกระบวนการที่หลากหลายและการควบคุมแบบขนานสมัยใหม่ แต่เราคิดว่ามันง่ายกว่าและง่ายกว่าในการเขียนและบำรุงรักษาโค้ดโดยใช้ Docker เป็นผู้เล่นเพียงคนเดียว

ซึ่งหมายความว่าหากคุณแชร์ไดเร็กทอรี /var/lib/docker ระหว่าง Docker หลายอินสแตนซ์ คุณจะประสบปัญหา แน่นอนว่าวิธีนี้ใช้ได้ผล โดยเฉพาะในช่วงแรกของการทดสอบ “ฟังนะแม่ ฉันสามารถเรียกใช้ Ubuntu ในฐานะนักเทียบท่าได้!” แต่ลองทำอะไรที่ซับซ้อนกว่านี้ เช่น ดึงภาพเดียวกันจากสองกรณีที่แตกต่างกัน แล้วคุณจะเห็นโลกลุกเป็นไฟ

ซึ่งหมายความว่าหากระบบ CI ของคุณสร้างและสร้างใหม่ ทุกครั้งที่คุณรีสตาร์ทคอนเทนเนอร์ Docker-in-Docker คุณจะเสี่ยงที่จะปล่อย Nuke ลงในแคชของมัน นี่ไม่เจ๋งเลย!

วิธีการแก้ปัญหา

ลองย้อนกลับไปดู คุณต้องการ Docker-in-Docker จริงๆ หรือคุณเพียงต้องการที่จะสามารถรัน Docker และสร้างและรันคอนเทนเนอร์และอิมเมจจากระบบ CI ของคุณในขณะที่ระบบ CI นั้นอยู่ในคอนเทนเนอร์

ฉันพนันได้เลยว่าคนส่วนใหญ่ต้องการตัวเลือกหลัง ซึ่งหมายความว่าพวกเขาต้องการระบบ CI เช่น Jenkins เพื่อให้สามารถรันคอนเทนเนอร์ได้ และวิธีที่ง่ายที่สุดในการทำเช่นนี้คือเพียงเสียบซ็อกเก็ต Docker ลงในคอนเทนเนอร์ CI ของคุณแล้วเชื่อมโยงกับแฟล็ก -v

พูดง่ายๆ ก็คือ เมื่อคุณเรียกใช้คอนเทนเนอร์ CI ของคุณ (เจนกินส์หรืออื่นๆ) แทนที่จะแฮ็กบางอย่างพร้อมกับ Docker-in-Docker ให้เริ่มต้นด้วยบรรทัด:

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

คอนเทนเนอร์นี้จะสามารถเข้าถึงซ็อกเก็ต Docker ได้แล้ว ดังนั้นจึงสามารถเรียกใช้คอนเทนเนอร์ได้ ยกเว้นว่าแทนที่จะเรียกใช้คอนเทนเนอร์ "ลูก" ก็จะเปิดตัวคอนเทนเนอร์ "พี่น้อง"

ลองใช้อิมเมจนักเทียบท่าอย่างเป็นทางการ (ซึ่งมีไบนารีนักเทียบท่า):

docker run -v /var/run/docker.sock:/var/run/docker.sock 
           -ti docker

มีลักษณะและทำงานเหมือนกับ Docker-in-Docker แต่ไม่ใช่ Docker-in-Docker: เมื่อคอนเทนเนอร์นี้สร้างคอนเทนเนอร์เพิ่มเติม คอนเทนเนอร์เหล่านี้จะถูกสร้างขึ้นใน Docker ระดับบนสุด คุณจะไม่พบผลข้างเคียงของการซ้อนและแคชแอสเซมบลีจะถูกแชร์ผ่านการโทรหลายครั้ง

หมายเหตุ: เวอร์ชันก่อนหน้าของบทความนี้แนะนำให้เชื่อมโยงไบนารี Docker จากโฮสต์ไปยังคอนเทนเนอร์ สิ่งนี้เริ่มไม่น่าเชื่อถือแล้ว เนื่องจากกลไก Docker ไม่ครอบคลุมไลบรารีแบบคงที่หรือใกล้เคียงคงที่อีกต่อไป

ดังนั้น หากคุณต้องการใช้ Docker จาก Jenkins CI คุณมี 2 ตัวเลือก:
การติดตั้ง Docker CLI โดยใช้ระบบแพ็กเกจอิมเมจพื้นฐาน (เช่น หากอิมเมจของคุณใช้ Debian ให้ใช้แพ็คเกจ .deb) โดยใช้ Docker API

โฆษณาบางส่วน🙂

ขอบคุณที่อยู่กับเรา คุณชอบบทความของเราหรือไม่? ต้องการดูเนื้อหาที่น่าสนใจเพิ่มเติมหรือไม่ สนับสนุนเราโดยการสั่งซื้อหรือแนะนำให้เพื่อน Cloud VPS สำหรับนักพัฒนา เริ่มต้นที่ $4.99, อะนาล็อกที่ไม่เหมือนใครของเซิร์ฟเวอร์ระดับเริ่มต้นซึ่งเราคิดค้นขึ้นเพื่อคุณ: ความจริงทั้งหมดเกี่ยวกับ VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps จาก $19 หรือจะแชร์เซิร์ฟเวอร์ได้อย่างไร (ใช้ได้กับ RAID1 และ RAID10 สูงสุด 24 คอร์ และสูงสุด 40GB DDR4)

Dell R730xd ถูกกว่า 2 เท่าในศูนย์ข้อมูล Equinix Tier IV ในอัมสเตอร์ดัม? ที่นี่ที่เดียวเท่านั้น 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 ทีวีจาก $199 ในเนเธอร์แลนด์! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - จาก $99! อ่านเกี่ยวกับ วิธีสร้างบริษัทโครงสร้างพื้นฐาน ระดับด้วยการใช้เซิร์ฟเวอร์ Dell R730xd E5-2650 v4 มูลค่า 9000 ยูโรต่อเพนนี?

ที่มา: will.com

เพิ่มความคิดเห็น