Docker คอนเทนเนอร์ 10 ปี
(cacm.acm.org)- นี่คือบทความวิชาการของ ACM ที่ส่องให้เห็น กระบวนการวิวัฒนาการทางเทคนิค ของ Docker ซึ่งนับตั้งแต่เปิดตัวครั้งแรกในปี 2013 ก็ได้เปลี่ยนวิธีที่นักพัฒนาสร้าง ดีพลอย และรันแอปพลิเคชันอย่างสิ้นเชิง โดยสรุปงานวิจัยด้านระบบที่สั่งสมมาหลายทศวรรษซึ่งซ่อนอยู่เบื้องหลัง CLI ที่ดูเรียบง่าย
- รากฐานทางเทคนิคสำคัญของ Docker คือการใช้ namespace ของ Linux เพื่อให้เกิดการแยกโปรเซสได้โดยไม่ต้องใช้ virtual machine และทำคอนเทนเนอร์แบบน้ำหนักเบาได้ด้วยการผสาน namespace ทั้ง 7 ประเภทที่ค่อย ๆ ถูกเพิ่มเข้ามาตั้งแต่ปี 2001
- เพื่อรองรับ macOS และ Windows มีการเลือกสถาปัตยกรรมแบบสวนทางความคิดเดิม โดยฝัง library virtual machine monitor (HyperKit) ไว้ภายในแอปเดสก์ท็อป และรัน Linux อยู่ภายในโปรเซสของผู้ใช้แทนแนวทาง hypervisor แบบเดิม
- ปัจจุบัน Docker รองรับฮาร์ดแวร์ต่างสถาปัตยกรรมอย่าง ARM·RISC-V และเวิร์กโหลด AI แล้ว และได้กลายเป็น โครงสร้างพื้นฐานมาตรฐานสำหรับการพัฒนา ครอบคลุมทั้งคลาวด์ เดสก์ท็อป และเอดจ์
- เมื่อเวิร์กโหลด AI เติบโตขึ้น การจัดการ การพึ่งพา GPU ได้กลายเป็นความท้าทายใหม่ และ Docker ก็ยังคงพัฒนาต่อเนื่อง ทั้งการรองรับ GPU ผ่าน CDI(Container Device Interface) และการผสาน TEE(สภาพแวดล้อมการทำงานที่เชื่อถือได้)
จุดกำเนิดทางเทคนิค
- ในช่วงต้นทศวรรษ 2000 การติดตั้ง dependency จำนวนมากด้วยตนเองบนลินุกซ์ดิสโทร รวมถึงการคอมไพล์และตั้งค่าซอฟต์แวร์เองเป็นเรื่องปกติ และเมื่อคลาวด์คอมพิวติ้งเริ่มเติบโตในปี 2010 กระบวนการนี้ก็ยิ่งซับซ้อนขึ้น
- Docker ทำให้ทุกอย่างง่ายขึ้น โดยให้นักพัฒนาแพ็กแอปพลิเคชันพร้อม dependency ทั้งหมดเป็น อิมเมจระบบไฟล์("คอนเทนเนอร์") เพื่อให้รันได้บนเครื่องใดก็ตามที่ติดตั้ง Docker
- ต่างจาก virtual machine ที่ไม่ต้องติดตั้งระบบปฏิบัติการทั้งชุด และสามารถรันได้ด้วยคำสั่งเพียงไม่กี่คำสั่ง
เวิร์กโฟลว์ทั่วไป
- เมื่อนักพัฒนาเขียน Dockerfile ก็จะเป็นการกำหนดขั้นตอนการบิลด์แบบเป็นลำดับโดยอิงไวยากรณ์ของเชลล์
- ตัวอย่างเว็บไซต์ Python: เริ่มจาก
FROM python:3แล้วอธิบายการติดตั้ง dependency การคัดลอกโค้ด การเปิดพอร์ต และคำสั่งรันไว้ในไฟล์เดียว
- ตัวอย่างเว็บไซต์ Python: เริ่มจาก
- ใช้
docker buildเพื่อสร้างอิมเมจคอนเทนเนอร์ และใช้docker pushเพื่อพุชไปยัง Docker Hub - รันด้วยการระบุการเมานต์ data volume และการเปิดเผย network port เช่น
docker run -v data:/app/data -p 80:80 - นับตั้งแต่ปี 2013 เป็นต้นมา CLI ได้ขยายความสามารถอย่างมากและแบ็กเอนด์ก็ถูกออกแบบใหม่ทั้งหมด แต่ เวิร์กโฟลว์พื้นฐาน คือการเขียน Dockerfile →
docker build→docker runยังคงเหมือนเดิม - พบ Dockerfile มากกว่า 3.4 ล้านไฟล์ อยู่ที่รากของ public repository บน GitHub
หลักการทำงานภายใน: Linux namespace
- เคอร์เนลของระบบปฏิบัติการแยกหน่วยความจำของโปรเซสออกจากกัน แต่ทรัพยากรของระบบหลายอย่าง เช่น ระบบไฟล์ ไฟล์ตั้งค่า และไลบรารีแบบไดนามิก ยังคงถูกแชร์ร่วมกัน
- ทำให้การติดตั้งหลายแอปบนเครื่องเดียวกันที่มี ข้อกำหนดไลบรารีแบบไดนามิกที่ขัดแย้งกัน เป็นเรื่องยากมาก
- และอาจเกิด การรบกวนที่ไม่ต้องการ ระหว่างโปรเซส เช่น การชนกันของ network port
- ปัญหานี้แก้ได้ด้วยการรันแต่ละแอปใน virtual machine (VM) แยกกัน แต่จะมีน้ำหนักมากมากจากเคอร์เนล ระบบไฟล์ แคช และ bridged network ที่ซ้ำซ้อน
- เนื่องจาก guest OS แต่ละตัวทำงานอย่างเป็นอิสระ จึง กำจัดความซ้ำซ้อน ของสตอเรจและหน่วยความจำได้ยาก
chroot()ของ Unix v7 ในปี 1978 อนุญาตให้มี root filesystem แยกต่างหากได้ แต่ไม่รองรับการประกอบระบบไฟล์จากหลายแอป- Nix และ Guix แก้ปัญหาด้วยการรีแพ็กแอปไปไว้ในไดเรกทอรีรายแอปพร้อม dynamic linking แต่ใช้งานกับซอฟต์แวร์แบบ proprietary ได้ยาก และไม่สามารถแก้ปัญหา network port ชนกันได้
- Docker เลือกใช้ namespace ของ Linux: ทำให้แต่ละโปรเซสควบคุมวิธีเข้าถึงทรัพยากรที่แชร์กัน เช่น ไฟล์และไดเรกทอรี ได้อย่างอิสระ
- ตัวอย่าง: โปรเซสสองตัวใน namespace คนละชุดสามารถตีความ
/etc/passwdเป็น/alice/etc/passwdและ/bob/etc/passwdได้ต่างกัน - namespace จะถูกนำมาใช้เฉพาะ ตอนเปิดทรัพยากร เท่านั้น หลังจากนั้น file descriptor จะทำงานเป็นทรัพยากรเคอร์เนลปกติโดยไม่มีโอเวอร์เฮดเพิ่ม
- ตัวอย่าง: โปรเซสสองตัวใน namespace คนละชุดสามารถตีความ
- ประวัติการนำ namespace เข้ามาใช้
- ปี 2001 Linux 2.5.2: mount namespace
- ปี 2006 Linux 2.6.19: IPC namespace
- ปี 2007 Linux 2.6.24: network stack namespace
- รองรับ namespace ทั้งหมด 7 ประเภท
- ต่างจาก Plan 9 ที่มีการออกแบบ namespace มาตั้งแต่ต้น ใน Linux นั้น namespace ถูก ค่อย ๆ เพิ่มเข้ามาแบบทีละขั้น จึงเป็นฟีเจอร์ระดับต่ำและใช้งานยาก
- ฟังก์ชันคล้ายกันใน FreeBSD และ Solaris ก็ไม่เคยไปถึงระดับการใช้งานอย่างแพร่หลาย
- สิ่งที่ Docker มีส่วนสำคัญในปี 2013 คือการหาจุดสมดุลเชิงปฏิบัติระหว่าง การแยกที่หนักแบบ VM กับ ความสะดวกในการใช้ส่วนประกอบพื้นฐานของ OS
โครงสร้างการรัน Linux container ของ Docker
- Docker ใช้โครงสร้างแบบ client-server โดยประกอบด้วยเซิร์ฟเวอร์ดีมอนที่รันบนโฮสต์ (
dockerd) และ CLI client ที่ส่งคำขอผ่าน RESTful API - ราวปี 2015 ได้แยก monolithic daemon ออกและจัดโครงสร้างใหม่เป็นคอมโพเนนต์เฉพาะทาง
- BuildKit: ประกอบอิมเมจระบบไฟล์
- containerd: สร้างอินสแตนซ์จากอิมเมจให้เป็นคอนเทนเนอร์ที่กำลังรัน และจัดการทรัพยากรเครือข่ายกับสตอเรจ
อิมเมจคอนเทนเนอร์
- เมื่อเรียก
docker buildจะสร้างอิมเมจแบบ layered filesystem ที่แสดงไฟล์ปฏิบัติการและข้อมูลตาม Dockerfile- เลเยอร์ล่างสุด: ดิสโทรระบบปฏิบัติการอย่าง Debian หรือ Alpine Linux (หรือประกอบเองด้วย tar archive)
- เลเยอร์ถัดมา: ความแตกต่างของระบบไฟล์ที่เกิดจากการรันคำสั่งแต่ละรายการใน Dockerfile
- จัดเก็บในระบบสตอเรจแบบ content-addressed: ใช้แฮชของอิมเมจระบบไฟล์เป็นคีย์
- ช่วย deduplicate ได้อย่างมีประสิทธิภาพ รับประกันความไม่เปลี่ยนแปลง และตรวจสอบการดัดแปลงผ่านแฮชได้
- ในปี 2016 Open Container Initiative(OCI) ได้ทำให้อิมเมจฟอร์แมตเป็นมาตรฐาน และมีอิมพลีเมนเทชันอิสระจำนวนมาก
- ใช้ระบบไฟล์ของ Linux อย่าง overlayfs, btrfs, ZFS เพื่อ snapshot และ clone เลเยอร์แบบ copy-on-write ได้อย่างมีประสิทธิภาพ
- รองรับ lazy-pulling ของอิมเมจผ่าน
stargzstorage snapshotter
อินสแตนซ์คอนเทนเนอร์
- เมื่อเรียก
docker runระบบจะจัดสรรทรัพยากรของระบบเพื่อสร้างโปรเซสที่ถูกแยกด้วย namespace จากอิมเมจ OCI ("คอนเทนเนอร์") - งานที่
containerdทำโดยการตั้งค่า namespace ที่จำเป็นให้แต่ละคอนเทนเนอร์แบบไดนามิก ได้แก่- กำหนด cgroups (control groups) ของโปรเซสเพื่อแยกทรัพยากรและจำกัดความเร็ว I/O
- remap network port ภายในคอนเทนเนอร์ไปยังพอร์ตที่เปิดออกภายนอกบนอินเทอร์เฟซของโฮสต์
- เชื่อมต่อ mutable storage volume ของระบบไฟล์โฮสต์เพื่อเก็บสถานะแอปแบบถาวร
- แยก process tree ของคอนเทนเนอร์ด้วย PID namespace
- ใช้ user namespace เพื่อแมป UID ภายในคอนเทนเนอร์ไปยัง UID อื่นบนโฮสต์ (เช่น UID 1000 ในคอนเทนเนอร์ → UID 12345 หรือ 23456 บนโฮสต์)
- แม้การตั้งค่า namespace จะมีโอเวอร์เฮดอยู่บ้าง แต่ ต่ำกว่าการ spawn Linux VM เต็มรูปแบบมาก และส่วนใหญ่ใช้เวลาไม่ถึง 1 วินาที
- เคอร์เนล Linux จะ garbage collect คอนเทนเนอร์ที่สิ้นสุดการทำงานแล้วเหมือนโปรเซสทั่วไป
ก้าวข้าม Linux: รองรับ macOS และ Windows
- ด้วยสถาปัตยกรรมแบบ client-server ทำให้ CLI สามารถส่งคำสั่งไปยัง Docker instance ระยะไกลผ่านการเชื่อมต่อเครือข่ายที่ปลอดภัยได้
- ในปี 2015 แม้ Docker จะถูกยอมรับอย่างกว้างขวางในการพัฒนาบน Linux แต่ก็เจอ อุปสรรคด้านการใช้งานที่ทำให้นักพัฒนา macOS/Windows ไม่สามารถรัน Linux container ได้
- นักพัฒนาส่วนใหญ่ใช้ macOS/Windows เป็นสภาพแวดล้อมการพัฒนาหลัก แต่อิมเมจระบบไฟล์ของ Docker รันได้เฉพาะบนเคอร์เนล Linux
- พร้อมกับการเติบโตของ public cloud ทำให้ Linux กลายเป็นสภาพแวดล้อมที่นิยมสำหรับการดีพลอย
การสร้างแอปพลิเคชัน Docker for Mac
- ข้อจำกัดหลัก: ต้อง ทำงานได้โดยไม่ต้องตั้งค่าเพิ่มเติม สำหรับนักพัฒนาที่คุ้นเคยกับ Docker เวอร์ชัน Linux และต้องสามารถรัน Docker image เดิมได้
- พลิกแนวทางเดิม (การรัน Linux แยกต่างหากข้างเดสก์ท็อป OS) โดย ฝังไฮเปอร์ไวเซอร์ ไว้ ภายใน แอปใน user space ของ macOS/Windows แล้วรัน Linux ในนั้น
- ได้แรงบันดาลใจจากงานวิจัย unikernel: พิสูจน์ให้เห็นว่าสามารถฝังคอมโพเนนต์ของ OS ลงในแอปพลิเคชันที่ใหญ่กว่าได้อย่างยืดหยุ่น
- HyperKit: library VMM ที่ใช้ส่วนขยาย hardware virtualization ของ Intel CPU เพื่อรัน Linux kernel ในโปรเซสผู้ใช้ทั่วไป
- Linux kernel ที่ฝังอยู่จะรัน Docker daemon ซึ่งจัดการคอนเทนเนอร์และทำหน้าที่เป็น Docker server endpoint ตามปกติ
- รายละเอียดการจัดการ Linux ทั้งหมดถูกซ่อนไว้ภายในแอปเดสก์ท็อป →
docker buildและdocker runบนเดสก์ท็อปถูกส่งต่อไปยังอินสแตนซ์ Linux ที่ฝังอยู่และ "ใช้งานได้เลย"
- แนวทางนี้ยังถูกนำไปใช้กับระบบคอนเทนเนอร์อื่นอย่าง Podman และกลายเป็นวิธีมาตรฐานในการรันคอนเทนเนอร์บน macOS/Windows
LinuxKit: ดิสทริบิวชัน Linux แบบคัสตอมสำหรับการฝังตัว
- ดิสทริบิวชัน Linux แบบคัสตอมที่ออกแบบมาให้ ฝังเป็นคอมโพเนนต์ของแอปอื่น ไม่ใช่ให้รันแบบสแตนด์อโลน
- สร้าง user space แบบคัสตอม ที่มีเฉพาะคอมโพเนนต์ขั้นต่ำที่จำเป็นต่อการรัน Docker container เพื่อให้เวลาเริ่มแอปต่ำที่สุด
- รันทุกคอมโพเนนต์เดี่ยวภายในคอนเทนเนอร์ ทำให้ไม่มี อะไรเลยที่รันอยู่ ใน root namespace ที่ใช้ตอนบูต
- ใช้ copy-on-write filesystem และ network namespace แบบเดียวกับที่ Docker container ใช้งาน
- การผสาน LinuxKit + HyperKit ทำให้บูตโปรเซส Linux ได้ด้วยความเร็ว แทบไม่ต่างจากโปรเซส native บน macOS
- เปิดตัวในปี 2016 ในแอป Docker for Mac และ Windows
ปัญหาเครือข่ายและทางแก้ด้วย SLIRP
- การเชื่อมต่อเครือข่ายจากคอนเทนเนอร์ Linux ที่ฝังอยู่ไปยัง macOS/Windows เป็นปัญหาที่ยากกว่าที่คาด
- วิธี Ethernet bridging แบบเดิมต้องการการจัดการเครือข่ายที่ซับซ้อน และ ไฟร์วอลล์กับตัวตรวจจับไวรัส บนเดสก์ท็อปองค์กรตรวจพบว่าเป็นทราฟฟิกที่อาจเป็นอันตราย จนเกิดบั๊กรายงานจากผู้ใช้เบต้านับพันรายการ
- ทางออกคือ SLIRP: เครื่องมือที่ถูกใช้ครั้งแรกในช่วงกลางทศวรรษ 1990 เพื่อเชื่อมต่อ Palm Pilot PDA เข้ากับอินเทอร์เน็ต
- ระหว่าง TCP handshake ของคอนเทนเนอร์ เฟรม Ethernet จะถูกส่งไปยังโฮสต์ผ่าน shared memory ด้วย virtio protocol
- ใช้ไลบรารี unikernel ของ MirageOS เพื่อแปลงคำขอ networking ของ Linux ให้เป็น native socket call ของ macOS/Windows
- สแตก TCP/IP ใน user space ชื่อ vpnkit ที่เขียนด้วย OCaml จะรับข้อมูลบน host OS และเรียก macOS
connect()syscall - ในมุมมองของนโยบาย VPN ทราฟฟิกขาออกจะถูกรับรู้ว่าเกิดจาก แอป Docker ไม่ใช่จากเครื่องอีกเครื่องหนึ่ง
- หลังปล่อย vpnkit ในการทดสอบเบต้าเมื่อปี 2016 บั๊กรายงานจากผู้ใช้องค์กรลดลง มากกว่า 99%
- ต่อมาแนวทาง SLIRP ยังถูกนำไปใช้ในวงการ serverless cloud ด้วย โดยเทคนิคเครือข่ายยุค dial-up เก่าถูกนำมาแก้ปัญหาการจัดการคอนเทนเนอร์แบบใหม่
การจัดการทราฟฟิกเครือข่ายขาเข้า
- เมื่อคอนเทนเนอร์ Linux เปิดฟังพอร์ต มันจะไม่ถูกเปิดเผยสู่อินเทอร์เน็ตโดยอัตโนมัติ เว้นแต่จะร้องขออย่างชัดเจนจาก CLI (เช่น
docker run -p 80:80 nginx) - ประสบการณ์ใช้งานในอุดมคติ: พอร์ตของคอนเทนเนอร์ควรไปโผล่ที่ IP ของเดสก์ท็อปโดยตรง เพื่อให้เข้าผ่านเบราว์เซอร์ได้ที่
http://localhost:8080- ระบบ virtualisation บนเดสก์ท็อปแบบเดิมอย่าง VMware Fusion จะเปิดเผย IP กลางชั่วคราว แทน
localhost
- ระบบ virtualisation บนเดสก์ท็อปแบบเดิมอย่าง VMware Fusion จะเปิดเผย IP กลางชั่วคราว แทน
- ติดตั้ง โปรแกรม eBPF แบบคัสตอมในเคอร์เนล LinuxKit → กระตุ้นให้สร้าง listening socket ที่สอดคล้องกันบนเดสก์ท็อปโฮสต์ → เปิดใช้งานตัวทำ port forward
- ผลลัพธ์: เมื่อรันคอนเทนเนอร์ Linux บน Mac จะเข้าถึงผ่าน
localhostได้ทันที ให้ประสบการณ์นักพัฒนาแบบเดียวกับเครื่อง Linux แบบ native
สตอเรจ
- นักพัฒนาต้องสามารถแก้ไขโค้ดบนเครื่องโลคัล พร้อมกับรันโค้ดและการทดสอบภายในคอนเทนเนอร์ได้
- บน Linux สามารถเข้าถึงไฟล์แบบ live ได้ผ่าน bind mount ด้วย
docker run -v /host:/container - macOS และ Windows ใช้เคอร์เนลคนละแบบ จึงไม่สามารถใช้ bind mount ได้
- Docker ใช้โปรโตคอล shared memory virtio-fs ที่มีต้นกำเนิดจาก KVM hypervisor เพื่อส่งการดำเนินการ filesystem ไปยังโฮสต์ (ในรูปแบบคำขอ FUSE)
- โฮสต์จะรับคำขอเหล่านี้แล้วเรียก syscall
open,read,writeที่สอดคล้องกัน
- โค้ดและข้อมูลของนักพัฒนายังคงอยู่บน host filesystem จึงเข้าถึงได้จากเครื่องมือสำรองข้อมูลและค้นหาอย่าง Apple Time Capsule หรือ Spotlight
Windows Services for Linux (WSL)
- ในปี 2017 Microsoft เปิดตัว WSL: รันแอป Linux ได้โดยตรงบน Windows
- เวอร์ชันแรกใช้แนวทาง library OS ที่ แปลง system call ของไบนารี Linux ให้เป็น system call ของ Windows แบบไดนามิก แทนการใช้ virtualisation
- แม้จะสำเร็จกับหลายแอป แต่มี system call ที่รองรับไม่เพียงพอ สำหรับการรัน Docker container
- ปี 2018 เปิดตัว WSL2: ออกแบบใหม่ให้รัน Linux VM เต็มรูปแบบ อยู่เบื้องหลัง คล้าย Docker for Mac
- Docker บน WSL2 รัน daemon และ user container ภายในดิสทริบิวชัน LinuxKit WSL
- จัดการ Docker API และ network port forwarding ให้กับทั้งตัว Windows เองและดิสทริบิวชัน Linux อื่น
- สถาปัตยกรรมสำคัญที่ทำให้ Docker container พัฒนาข้ามแพลตฟอร์มได้คือแนวทาง library OS ที่นำสิ่งซึ่งเดิมเป็น “โค้ดสำหรับเคอร์เนลเท่านั้น” มาใช้ซ้ำเป็นไลบรารีใน user space เพื่อฝังลงในแอปอื่น
- ความสำเร็จของสถาปัตยกรรมนี้พิสูจน์ได้จากการที่มัน มองไม่เห็นแต่มีอยู่ทุกหนแห่ง: นักพัฒนาหลายล้านคนใช้ Docker ทุกวันโดยไม่ต้องกังวลว่ากำลังรันอยู่บน OS อะไร
เวิร์กโฟลว์นักพัฒนาแบบใหม่: หลายสถาปัตยกรรม CPU
- ในยุคแรกของ Docker เวิร์กโหลดบนคลาวด์ส่วนใหญ่ยังอยู่บนสถาปัตยกรรม Intel
- สถานการณ์เปลี่ยนไปเมื่อมีการเปิดตัว Amazon Graviton ARM ในปี 2018 และ Apple M1 ARM ในปี 2020
- การรันเวิร์กโหลดบน ARM สามารถ ลดต้นทุนและเพิ่มประสิทธิภาพ ได้
- จำเป็นต้องรองรับหลายสถาปัตยกรรม CPU ภายใน Docker image เดียวกัน เช่น Intel, ARM, POWER และ RISC-V
- ฝั่งเซิร์ฟเวอร์ได้ขยายฟอร์แมต OCI image ให้รองรับ multiarch manifests
- สำหรับการ build image หลายสถาปัตยกรรม CPU บนโฮสต์เครื่องเดียว ใช้ความสามารถ
binfmt_miscของ Linux- แปลงระหว่างไบนารี ARM และ Intel ได้อย่างโปร่งใสผ่าน QEMU
- โดยหลักแล้วจะมี overhead เฉพาะในขั้นตอน build และ multiarch image ที่ได้จะ รันแบบ native ได้บนทุกโฮสต์
- Apple นำการรองรับทั้งฮาร์ดแวร์และซอฟต์แวร์สำหรับการแปลงชุดคำสั่ง CPU ผ่าน Rosetta มาใช้ ทำให้ผสานเข้ากับสถาปัตยกรรม Docker ได้ง่าย
- ปัจจุบันการ รันคอนเทนเนอร์ Intel และ ARM ควบคู่กัน กลายเป็นเวิร์กโฟลว์ปกติของนักพัฒนา
การจัดการความลับผ่าน Trusted Execution Environment (TEE)
- ในสภาพแวดล้อมคอนเทนเนอร์ การจัดการ ความลับ (secrets) เช่น รหัสผ่านหรือ API key เป็นความท้าทายมาโดยตลอด
- ต้อง ฉีดเข้าไปแบบไดนามิก โดยไม่ฝังไว้ในอิมเมจไฟล์ซิสเต็ม
- Docker รองรับ socket forwarding ทำให้สามารถเมานต์ local domain socket เข้าไปในคอนเทนเนอร์ได้
- ในกรณีของ Docker for Mac/Windows ยังสามารถ forward socket ไปถึง Linux VM ได้
- ทำให้ใช้งานระบบจัดการคีย์อย่าง
ssh-agentภายในคอนเทนเนอร์ได้โดยไม่ต้องเปิดเผยคีย์โดยตรง
- socket forwarding ให้การป้องกันในระดับพื้นฐาน แต่ต่อมัลแวร์ในซัพพลายเชนซอฟต์แวร์ที่ซับซ้อนขึ้นนั้น จำเป็นต้องมี ชั้นการป้องกันเพิ่มเติม
- นำ การป้องกันระดับไฮเปอร์ไวเซอร์ มาใช้โดยตรงภายใน container runtime เพื่อยกระดับการป้องกันข้ามคอนเทนเนอร์
- Trusted Execution Environment (TEE): ความสามารถด้านฮาร์ดแวร์ของ CPU สมัยใหม่ที่สามารถสร้าง confidential VM ซึ่ง ปกป้องข้อมูลลับได้แม้จาก host OS
- สามารถบังคับใช้ข้อจำกัดการเข้าถึงข้อมูลข้ามขอบเขตของแอป เคอร์เนล และไฮเปอร์ไวเซอร์ได้
- อย่างไรก็ตาม การตั้งค่าและใช้งาน TEE ก็มี ความซับซ้อนในการดูแลจัดการ คล้ายกับการทำ OS virtualization
- คณะทำงาน Confidential Containers กำลังพัฒนาแอปที่ทำงานภายใน TEE และจัดการด้วย Docker
- Docker CLI จะ forward ข้อความที่เข้ารหัส จาก TEE ภายในเครื่องเดสก์ท็อป ผ่านโฮสต์ ไปยังสภาพแวดล้อม TEE บนคลาวด์ระยะไกล
- นักพัฒนาสามารถยืนยันตัวตนกับสภาพแวดล้อมคลาวด์ที่มีความอ่อนไหวได้โดยไม่ต้องไปถึงสถานที่จริง และข้อมูลรับรองจะ ถูกเก็บอย่างปลอดภัยใน desktop enclave
รองรับ GPGPU สำหรับเวิร์กโหลด AI
- การมาของเวิร์กโหลด AI ทำให้เกิดความท้าทายรูปแบบใหม่ทั้งหมด: เวิร์กโหลดแมชชีนเลิร์นนิงส่วนใหญ่ทำงานบน GPU
- ปัญหาหลักคือ เวิร์กโหลด GPU ต้องใช้ kernel GPU driver และ user-space library ที่ตรงกันอย่างแม่นยำ ขณะที่หลายคอนเทนเนอร์ทำงานอยู่บนเคอร์เนลที่ใช้ร่วมกันเพียงตัวเดียว
- หากสองแอปต้องการ คนละเวอร์ชัน ของ kernel GPU driver ตัวเดียวกัน ก็จะเกิดความขัดแย้งพื้นฐานแบบเดียวกับที่ Docker ตั้งใจแก้ตั้งแต่แรก
- ตั้งแต่เดือนมีนาคม 2023 Docker รองรับ CDI (Container Device Interface)
- ปรับแต่งอิมเมจไฟล์ซิสเต็มในจังหวะที่เริ่มต้นคอนเทนเนอร์
- ทำ bind mount ไฟล์อุปกรณ์ GPU และ dynamic library เฉพาะของ GPU พร้อมสร้างแคช
ld.soใหม่
- CDI ช่วยรับประกันความพกพาของอิมเมจระหว่าง GPU บางคลาสและบางผู้ผลิต แต่ระหว่าง OS ที่ต่างกันหรือแบรนด์ฮาร์ดแวร์ที่ต่างกันยัง ไม่ลื่นไหลอย่างสมบูรณ์
- เนื่องจาก dynamic library ที่ CDI เพิ่มเข้ามานิยาม API ที่ต่างกัน จึงไม่มีสิ่งที่เทียบได้กับ Linux system call ABI ที่มีเสถียรภาพซึ่งเป็นอินเทอร์เฟซดั้งเดิมของคอนเทนเนอร์
- เหตุผลที่แอปสำหรับ Nvidia GPU รันบน Apple M-series CPU ได้ยาก คือการรองรับ GPU virtualization ยัง ไม่พัฒนาไปถึงระดับที่แปลง vector instruction ระหว่างฮาร์ดแวร์ที่หลากหลายได้
- กำลังพัฒนา วิธีที่ยืดหยุ่นและปลอดภัยยิ่งขึ้น ในการจัดการ dependency ที่เกี่ยวข้องกับ GPU ร่วมกับชุมชนคอนเทนเนอร์และผู้ผลิต GPU
- คาดหวังว่าโครงการริเริ่มด้าน portable interface จะนำไปสู่ฉันทามติ
บทสรุป
- Docker เริ่มต้นในปี 2013 ด้วยเป้าหมายที่จะช่วยให้นักพัฒนาสร้าง แชร์ และรันแอปได้ง่ายขึ้น
- ปัจจุบันได้ถูกรวมเข้ากับเวิร์กโฟลว์การพัฒนาบนคลาวด์และเดสก์ท็อปมาตรฐานอย่างลึกซึ้ง มี นักพัฒนาหลายล้านคนทั่วโลกใช้งานทุกวัน และรองรับ คำขอหลายพันล้านครั้งต่อเดือน
- เป้าหมายที่สม่ำเสมอมาคือการรักษาชุมชนโอเพนซอร์สที่ มีชีวิตชีวาและหลากหลาย ซึ่งสร้างมาตรฐานเพื่อการทำงานร่วมกัน
- CNCF (Cloud Native Computing Foundation) ทำหน้าที่เป็นผู้ดูแลคอมโพเนนต์หลักหลายตัว
- Open Container Initiative (สังกัด Linux Foundation) เป็นผู้ดูแลฟอร์แมตอิมเมจ
- ปัจจุบันมีอิมพลีเมนเทชันขององค์ประกอบเหล่านี้จำนวนมากที่ยังพัฒนาอย่างต่อเนื่อง และมีการนำไปใช้งานเพิ่มขึ้นในสภาพแวดล้อม edge เช่น คลาวด์ เดสก์ท็อป ยานยนต์ มือถือ และยานอวกาศ
- ณ ปี 2025 เวิร์กโฟลว์ของนักพัฒนาทั่วไปได้รวมการทดสอบและดีพลอยอย่างต่อเนื่อง, IDE language server และ การช่วยเขียนโค้ดด้วย AI ผ่าน agentic coding
- จากมุมมองของ Docker เวิร์กโฟลว์หลักแบบ “build and run” ยังคล้ายกับเมื่อ 10 ปีก่อนมาก แต่ การสนับสนุนจากระบบเพื่อลดแรงเสียดทานในสภาพแวดล้อมที่หลากหลาย ได้รับการเสริมอย่างมาก
- เป้าหมายคือทำให้ Docker เป็น เพื่อนร่วมทางที่มองไม่เห็น ที่ช่วยให้นักพัฒนาส่งโค้ดขึ้นใช้งานได้เร็วขึ้น และ ออกแบบให้ขยายได้ เพื่อรองรับเวิร์กโฟลว์การเขียนโค้ดด้วย AI สมัยใหม่
1 ความคิดเห็น
ความเห็นจาก Hacker News
ตอนที่ Docker ออกมาใหม่ ๆ ตอนนั้นก็เริ่มเอียนกับคำว่า ‘นวัตกรรม’ อีกชิ้นแล้ว
ไม่ชอบทั้ง NoSQL ที่ยังทำไม่เสร็จดี, ไมโครเซอร์วิสแบบยัดเยียด, และกระแสที่พยายามเปลี่ยนทุกการเรียกฟังก์ชันให้เป็น RPC
แต่ตอนนี้กลับใช้มันบ่อยเพราะความเรียบง่าย
อย่างไรก็ตาม คอนเทนเนอร์ของคนอื่น ก็ยังเหมือนนรกอยู่ดี อย่างน้อยฉันพยายามทำให้มันเรียบง่ายไว้ แต่บางคนยัดทุกอย่างลงไปจนให้ความรู้สึกว่าเจ้าตัวเองก็ไม่รู้กระบวนการดีพลอยด้วยซ้ำ
ตอนนี้เหมือนเราอยู่ในยุคที่มีโค้ดทะลักออกมา ทั้งที่นักพัฒนาเองก็ยังไม่เคยเปิดดู เพราะอาศัย LLM
มีความพยายามมากมาย แต่สุดท้ายเหตุผลที่ Dockerfile ยังอยู่รอดคือความยืดหยุ่น
เพราะมันคุ้นเคยกับโครงสร้างแบบเดิม ๆ คือคัดลอกไฟล์แล้วรันคำสั่ง
ถึงจะดูหยาบ ๆ แต่ความยืดหยุ่นแบบเรียบง่ายนี้ก็น่าจะยังเป็นกระแสหลักต่อไป
ถ้าทุกคนใช้ Nix หรือ Bazel กันหมด
docker buildอาจกลายเป็นเรื่องชวนขำก็ได้ถ้าแฮชต่างกันในแต่ละบิลด์ก็เชื่อถือไม่ได้ ดังนั้นตราบใดที่ปัจจัยอย่างเวลาแก้ไขไฟล์ยังถูกรวมในแฮช ความสอดคล้องแบบสมบูรณ์ก็ยังทำได้ยาก
ส่วนตัวชอบ mkosi แต่ไม่ใช่ทุกคนที่อยากเริ่มจาก OS template เปล่า ๆ
ในกรณีส่วนใหญ่ก็แค่ pull จาก public registry แล้ว push ไป private registry ทำให้แทบไม่ได้ใช้ local image เลย
มันอาจไม่มีประสิทธิภาพนัก แต่ก็ดูยังไม่แย่พอจะต้องเปลี่ยน
จำได้ว่า Docker ถูกเปิดตัวครั้งแรกที่ PyCon US Santa Clara ปี 2013
ถ้าดู วิดีโองานนำเสนอบน YouTube ก็ถือเป็นช่วงเวลาประวัติศาสตร์เลย
ดูเหมือนจะมีความสับสนเพราะช่วงเวลาที่ตีพิมพ์บทความกับช่วงที่นำเสนอจริงไม่ตรงกัน แต่คร่าว ๆ ก็ราว 13 ปีก่อน
“Twelve years of Docker containers” ฟังดูไม่ลื่นเท่า “A decade of Docker”
จำ โพสต์เปิดตัวช่วงแรกบน HN ได้
ตอนนั้นเหนื่อยกับทางเลือกอย่าง LXC หรือ Vagrant แล้ว แต่ Docker เหมือน ผู้กอบกู้ จริง ๆ
น่าสนใจที่ Docker เอา SLIRP ซึ่งเป็นเครื่องมือ dial-up จากยุค 1990 กลับมาใช้ใหม่เพื่อทะลุไฟร์วอลล์
มันรับการเชื่อมต่อขาเข้าไม่ได้ แต่ก็เป็นแนวคิดคล้าย ๆ ต้นแบบของ NAT
อยากใช้ Docker มานานแล้ว แต่ทุกครั้งที่ลองก็มักขาดฟีเจอร์ที่ต้องการไปเสมอ
น่าจะเป็นเพราะใช้ เทคสแต็ก ที่ไม่ค่อยมีคนใช้
ความยิ่งใหญ่ของ Docker คือทำให้คำว่า “ใช้ได้บนเครื่องฉันนะ” กลายเป็นมาตรฐานอุตสาหกรรม
ตอนนี้เราอยู่ในยุคที่สามารถ ‘ส่งมอบ’ ตัวเครื่องนั้นไปขึ้นโปรดักชันได้เลย
เมื่อก่อนการดีพลอยเป็นกระบวนการใหญ่โตมาก แต่ตอนนี้กลายเป็นว่าเราดีพลอยไฟล์ซิสเต็มได้ตรง ๆ แล้ว
นวัตกรรมของ coding agent ในตอนนี้ก็ให้ความรู้สึกเป็นการเปลี่ยนผ่านทางวัฒนธรรมคล้ายกัน
ถ้าสามารถสร้างสภาพแวดล้อมเดิมได้ด้วยสคริปต์ 10 บรรทัด การ “ดีพลอยเครื่อง” ก็ไม่ได้แย่อะไร
เราควรถามตัวเองว่าทำไมแนวทางแบบนี้ถึงดึงดูดใจนักขนาดนั้น
เหตุผลที่ abstraction ชนะเสมอ ก็เพราะการแก้ปัญหาที่ต้นตอมันยากเกินไป
ไม่ค่อยรู้เรื่อง networking เท่าไร แต่บน Mac อยากให้คอนเทนเนอร์มี IP address แยกต่างหาก
อยากเข้าผ่าน
container_ip:80ได้โดยไม่ต้องทำ port mappingบน Linux ทำได้ แต่บน Mac ต้องผ่าน VM เลยซับซ้อน
เคยลอง วิธีที่ใช้ WireGuard แต่พอ Docker อัปเดตก็พังทุกที
ถ้ามีวิธีที่รองรับอย่างเป็นทางการก็คงดี
Tailscale Docker Extension จะจัดการการตั้งค่าให้อัตโนมัติ
ถ้าเหตุผลที่อยากเลี่ยง port mapping เป็นเพราะพอร์ตเปลี่ยนแบบไดนามิก ก็แนะนำให้ลองตัวเลือก
--net=hostและเปิดการตั้งค่า Host Networkingปี 2013 เป็นปีที่เรียกได้ว่า แพ็กเกจจิ้งออกดอกออกผล เพราะทั้ง Docker, Guix และ NixOS ต่างก็ออกมาในปีเดียวกัน
ไปค้นพบข้อเท็จจริงนี้ตอนเขียนบทความที่เกี่ยวข้อง
เลยสงสัยว่าหลังจากนั้นเคยมีปีไหนอีกไหมที่มีหลายโปรเจกต์เจ๋ง ๆ โผล่มาพร้อมกันแบบนี้
Guix กับ Nix ยังอยู่ในกลุ่ม ผู้ใช้เฉพาะทาง เป็นหลัก
พอ ML กับ AI เข้าไปอยู่ทุกที่ ขนาดอิมเมจก็ โตแบบทวีคูณ
แค่ torch ตัวเดียวก็กินพื้นที่หลาย GB แล้ว
คิดถึงยุคอิมเมจ 30MB สมัยก่อน
ไฟล์ไม่สามารถแชร์ข้ามเลเยอร์กันได้ ทำให้สิ้นเปลืองมาก
เพราะอย่างนั้นตอนนี้เลยกำลังทำ registry ทางเลือก ที่รองรับ dedupe ระดับไฟล์ด้วยตัวเอง
เรื่องที่ Docker ปลอมตัวเป็น VPN เพื่อหลอกซอฟต์แวร์ความปลอดภัยสำหรับองค์กรก็น่าสนใจมากจริง ๆ
มองในฐานะประวัติศาสตร์ทางเทคนิคก็นับว่าเป็นกรณีศึกษาที่สนุกมาก