สำรวจหลักการ UNIX ของ Ken Thompson—เครื่องมือเล็ก ๆ, ท่อ, แนวคิดไฟล์เป็นอินเทอร์เฟซ และขอบเขตที่ชัดเจน—และวิธีที่แนวคิดเหล่านี้หล่อหลอมคอนเทนเนอร์, Linux และโครงสร้างพื้นฐานคลาวด์

Ken Thompson ไม่ได้ตั้งใจสร้าง “ระบบปฏิบัติการที่คงทนตลอดไป” แต่ร่วมกับ Dennis Ritchie และทีมที่ Bell Labs เขาต้องการระบบเล็ก ๆ ที่ใช้งานได้ง่าย ที่นักพัฒนาจะเข้าใจ ปรับปรุง และย้ายข้ามเครื่องได้ UNIX ถูกหล่อหลอมด้วยเป้าหมายเชิงปฏิบัติ: ทำให้แกนหลักเรียบง่าย ให้เครื่องมือทำงานร่วมกันได้ดี และหลีกเลี่ยงการล็อกผู้ใช้ไว้กับรูปแบบคอมพิวเตอร์หนึ่งเครื่อง
สิ่งที่น่าประหลาดใจคือการตัดสินใจในช่วงต้นเหล่านั้นสอดรับกับการคำนวณสมัยใหม่ได้ดีเพียงใด เราแลกเทอร์มินัลกับแดชบอร์ดเว็บ และเซิร์ฟเวอร์เดี่ยวกับฝูงของเครื่องเสมือน แต่คำถามเดิม ๆ ยังคงโผล่มา:
ฟีเจอร์เฉพาะของ UNIX อาจวิวัฒน์ (หรือตกยุค) แต่หลักการออกแบบยังคงมีประโยชน์เพราะมันอธิบาย วิธีการสร้างระบบ:
แนวคิดเหล่านี้ปรากฏอยู่ทั่วไป—ตั้งแต่ความเข้ากันได้กับ Linux และ POSIX ไปจนถึงรันไทม์คอนเทนเนอร์ที่พึ่งพาการแยกกระบวนการ, namespaces และเทคนิคไฟล์ซิสเต็ม
เราจะเชื่อมแนวคิดยุค Thompson ของ UNIX กับสิ่งที่คุณเจอวันนี้:
นี่คือคู่มือเชิงปฏิบัติ: ใช้คำศัพท์น้อยที่สุด มีตัวอย่างที่ชัดเจน และเน้นที่ “ทำไมมันถึงทำงานได้” มากกว่าข้อมูลฟุ้งเฟ้อ หากคุณต้องการแบบจำลองทางความคิดอย่างรวดเร็วสำหรับพฤติกรรมของคอนเทนเนอร์และระบบปฏิบัติการคลาวด์ คุณมาถูกที่แล้ว
คุณยังสามารถเลื่อนไปที่ /blog/how-unix-ideas-show-up-in-containers เมื่อคุณพร้อมได้
UNIX ไม่ได้เริ่มด้วยกลยุทธ์แพลตฟอร์มใหญ่โต แต่มันเกิดจากระบบเล็ก ๆ ที่ใช้งานได้จริง สร้างโดย Ken Thompson (กับผลงานสำคัญของ Dennis Ritchie และคนอื่น ๆ ที่ Bell Labs) ที่ให้ความสำคัญกับความชัดเจน ความเรียบง่าย และการทำงานที่เป็นประโยชน์
ในวันแรก ๆ ระบบปฏิบัติการมักผูกติดกับรุ่นของเครื่อง ถ้าคุณเปลี่ยนฮาร์ดแวร์ คุณก็ต้องเปลี่ยน OS (และบ่อยครั้งซอฟต์แวร์) ด้วย
ระบบปฏิบัติการพกพา หมายถึงสิ่งที่เป็นประโยชน์: แนวคิดและโค้ดส่วนใหญ่สามารถรันบนเครื่องต่าง ๆ ได้โดยไม่ต้องเขียนใหม่มาก การเขียน UNIX ด้วย C ลดการพึ่งพา CPU ใด CPU หนึ่ง และทำให้เป็นไปได้ที่ผู้อื่นจะนำ UNIX ไปใช้และปรับแต่งได้จริง
เมื่อคนพูดว่า “UNIX” พวกเขาอาจหมายถึงเวอร์ชันดั้งเดิมของ Bell Labs, เวอร์ชันเชิงพาณิชย์, หรือระบบสมัยใหม่ที่คล้าย UNIX (เช่น Linux หรือ BSD) เส้นใยร่วมกันไม่ใช่แบรนด์เดียว แต่เป็นชุดการตัดสินใจด้านการออกแบบและอินเทอร์เฟซที่ใช้ร่วมกัน
นี่แหละที่ทำให้ POSIX สำคัญ: มาตรฐานนี้รวบรวมพฤติกรรม UNIX หลายอย่าง (คำสั่ง, การเรียกระบบ, ข้อตกลง) ช่วยให้ซอฟต์แวร์ยังคงเข้ากันได้ข้ามระบบต่าง ๆ แม้ว่าการใช้งานพื้นฐานจะแตกต่างกัน
UNIX ทำให้เป็นที่นิยมกฎที่ดูเรียบง่าย: สร้างโปรแกรมที่ทำงานเดียวให้ดี และทำให้มันง่ายต่อการประกอบเข้าด้วยกัน ทีม UNIX ในยุคแรกไม่ได้ตั้งใจสร้างแอปแบบรวมทุกอย่าง แต่ต้องการยูทิลิตี้ขนาดเล็กที่มีพฤติกรรมชัดเจน เพื่อให้คุณนำมาต่อกันแล้วแก้ปัญหาได้จริง
เครื่องมือที่ทำงานเดียวดีจะเข้าใจง่ายกว่าเพราะมีชิ้นเคลื่อนไหวน้อยลง ทดสอบง่ายกว่า: คุณให้อินพุตที่รู้จักและตรวจดูเอาต์พุตโดยไม่ต้องตั้งสภาพแวดล้อมทั้งหมด เมื่อความต้องการเปลี่ยน คุณสามารถแทนที่ชิ้นเดียวโดยไม่ต้องเขียนใหม่ทั้งหมด
แนวทางนี้ยังส่งเสริมการ “แทนที่ได้” หากยูทิลิตี้ชิ้นหนึ่งช้า จำกัด หรือขาดฟีเจอร์ คุณสามารถเปลี่ยนเป็นอันที่ดีกว่าหรือเขียนใหม่ได้ ตราบใดที่ยังคงคาดหวังอินพุต/เอาต์พุตพื้นฐานเดิม
คิดว่าเครื่องมือ UNIX เหมือนบล็อก LEGO แต่ละบล็อกเรียบง่าย พลังอยู่ที่วิธีการเชื่อมต่อกัน
ตัวอย่างคลาสสิกคือการประมวลผลข้อความ ที่คุณแปลงข้อมูลทีละขั้นตอน:
cat access.log | grep " 500 " | sort | uniq -c | sort -nr | head
แม้คุณจะไม่จำคำสั่งทั้งหมด แต่แนวคิดชัดเจน: เริ่มจากข้อมูล กรอง สรุป แล้วแสดงผลสูงสุด
ไมโครเซอร์วิสไม่ใช่ “เครื่องมือ UNIX บนเครือข่าย” และการบังคับให้เทียบแบบนั้นอาจทำให้เข้าใจผิด แต่สัญชาตญาณพื้นฐานคุ้นเคย: รักษาส่วนประกอบให้มุ่งหน้าที่ชัดเจน กำหนดขอบเขตชัด และประกอบระบบใหญ่จากชิ้นเล็กที่พัฒนาได้อย่างอิสระ
UNIX ได้พลังจากข้อสอดคล้องง่าย ๆ: โปรแกรมควรอ่านอินพุตจากที่หนึ่งและเขียนเอาต์พุตไปยังอีกที่ในวิธีที่คาดเดาได้ ข้อสอดคล้องนี้ทำให้รวมเครื่องมือเล็ก ๆ เป็น “ระบบ” ที่ใหญ่ขึ้นได้โดยไม่ต้องเขียนใหม่
ท่อ เชื่อมเอาต์พุตของคำสั่งหนึ่งเข้ากับอินพุตของอีกคำสั่งหนึ่ง คิดเหมือนการส่งโน้ตต่อกัน: เครื่องมือตัวหนึ่งผลิตข้อความ เครื่องมือต่อมาบริโภคมัน
เครื่องมือ UNIX มักใช้ช่องทางมาตรฐานสามช่อง:
เพราะช่องทางเหล่านี้คงที่ คุณจึง “เดินสาย” โปรแกรมเข้าด้วยกันโดยที่แต่ละโปรแกรมไม่ต้องรู้จักกันเลย
ท่อส่งเสริมให้เครื่องมือมีขนาดเล็กและมุ่งหน้าที่ หากโปรแกรมสามารถรับ stdin และส่ง stdout มันจะนำกลับมาใช้ได้ในหลายบริบท: การใช้งานแบบโต้ตอบ งานแบตช์ งานตามเวลา และสคริปต์ นี่คือเหตุผลว่าทำไมระบบที่คล้าย UNIX จึงเหมาะกับการเขียนสคริปต์: การอัตโนมัติมักคือ “เชื่อมชิ้นส่วนเหล่านี้”
การประกอบนี้เป็นเส้นตรงจาก UNIX ยุคแรกสู่การประกอบเวิร์กโฟลว์คลาวด์ปัจจุบัน
UNIX ทำให้เรียบง่ายอย่างกล้าหาญ: ให้ทรัพยากรต่าง ๆ ทำตัวเหมือนไฟล์ ไม่ใช่เพราะไฟล์บนดิสก์กับคีย์บอร์ดเหมือนกัน แต่เพราะการให้พวกมันมี อินเทอร์เฟซร่วมกัน (open, read, write, close) ทำให้ระบบเข้าใจง่ายและอัตโนมัติง่าย
เมื่อทรัพยากรแบ่งปันอินเทอร์เฟซหนึ่ง คุณจะได้เลเวอเรจ: ชุดเครื่องมือเล็ก ๆ สามารถทำงานได้ข้ามบริบทมากมาย ถ้า “ผลลัพธ์เป็นไบต์” และ “อินพุตเป็นไบต์” ยูทิลิตี้เรียบง่ายก็สามารถประกอบกันในวิธีต่าง ๆ มากมายโดยไม่ต้องให้ความรู้พิเศษเกี่ยวกับอุปกรณ์ เครือข่าย หรือเคอร์เนล
นี่ยังส่งเสริมความเสถียร ทีมสามารถสร้างสคริปต์และนิสัยปฏิบัติการรอบปริมณฑลพื้นฐานไม่กี่อย่าง (สตรีมอ่าน/เขียน, เส้นทางไฟล์, สิทธิ์) และเชื่อว่าพื้นฐานเหล่านี้จะไม่เปลี่ยนทุกครั้งที่เทคโนโลยีพื้นฐานเปลี่ยน
การปฏิบัติการคลาวด์สมัยใหม่ยังยึดแนวคิดนี้ไว้ โลก์คอนเทนเนอร์มักถูกปฏิบัติเป็นสตรีมที่สามารถ tail และส่งต่อได้ Linux's /proc เปิดเผยโทรเมทรีของกระบวนการและระบบเป็นไฟล์ ดังนั้นเอเยนต์การมอนิเตอร์สามารถ “อ่าน” CPU, หน่วยความจำ และสถิติของกระบวนการเหมือนข้อความธรรมดา อินเทอร์เฟซรูปแบบไฟล์นี้ทำให้การสังเกตและการอัตโนมัติจับต้องได้แม้ในสเกลใหญ่
โมเดลสิทธิ์ของ UNIX ดูเรียบง่าย: ทุกไฟล์ (และทรัพยากรบางอย่างที่ทำหน้าที่เหมือนไฟล์) มี เจ้าของ, กลุ่ม, และชุด สิทธิ์ สำหรับสามกลุ่ม—ผู้ใช้, กลุ่ม, และ ผู้อื่น ด้วยเพียงบิตอ่าน/เขียน/รัน UNIX สร้างภาษาร่วมสำหรับใครทำอะไรได้บ้าง
ถ้าคุณเคยเห็นอย่าง -rwxr-x--- คุณได้เห็นโมเดลทั้งหมดในบรรทัดเดียว:
โครงสร้างนี้ปรับขนาดได้ดีเพราะเข้าใจและตรวจสอบง่าย มันชี้ชวนทีมให้หลีกเลี่ยงการ “เปิดทั้งหมด” เพียงเพื่อให้บางอย่างทำงาน
Least privilege คือการให้บุคคล กระบวนการ หรือบริการ เฉพาะ สิทธิ์ที่ต้องใช้เท่านั้น ในทางปฏิบัติ มักหมายถึง:
แพลตฟอร์มคลาวด์และรันไทม์คอนเทนเนอร์สะท้อนแนวคิดเดียวกันด้วยเครื่องมือที่ต่างออกไป:
สิทธิ์ของ UNIX มีค่า—แต่ไม่ใช่กลยุทธ์ความปลอดภัยสมบูรณ์ พวกมันไม่ป้องกันการรั่วไหลของข้อมูลทั้งหมด ไม่หยุดโค้ดที่มีช่องโหว่จากการถูกโจมตี และไม่ทดแทนการควบคุมเครือข่ายหรือการจัดการความลับ คิดว่ามันคือรากฐาน: จำเป็น เข้าใจได้ และมีประสิทธิผล—แต่ไม่เพียงพอโดยลำพัง
UNIX ให้ความสำคัญกับ กระบวนการ—อินสแตนซ์ที่กำลังรันของบางสิ่ง—เป็นบล็อกการสร้างหลัก นั่นฟังดูเป็นนามธรรมจนกระทั่งคุณเห็นว่ามันกำหนดความน่าเชื่อถือ การทำงานพร้อมกัน และวิธีที่เซิร์ฟเวอร์สมัยใหม่ (และคอนเทนเนอร์) แบ่งปันเครื่องอย่างไร
โปรแกรม เหมือน การ์ดสูตรอาหาร: บอกวิธีทำ
กระบวนการ เหมือน เชฟที่กำลังทำอาหารตามสูตรนั้น: มีขั้นตอนปัจจุบัน วัตถุดิบตั้งอยู่ เตาและนาฬิกาจับเวลา คุณสามารถมีเชฟหลายคนใช้สูตรเดียวกันพร้อมกัน—แต่ละคนเป็นกระบวนการแยกกันมีสถานะของตนเอง
ระบบ UNIX ออกแบบให้แต่ละกระบวนการมี “ฟอง” ของการรัน: หน่วยความจำของตัวเอง มุมมองไฟล์เปิดของตัวเอง และขอบเขตชัดเจนเกี่ยวกับสิ่งที่แตะต้องได้
การแยกนี้สำคัญเพราะความล้มเหลวมักถูกกักกัน หากกระบวนการหนึ่งล้ม มันมักจะไม่ลากกระบวนการอื่นลงไปด้วย เหตุผลใหญ่ที่เซิร์ฟเวอร์สามารถรันบริการหลายอย่างบนเครื่องเดียวได้คือ: เว็บเซิร์ฟเวอร์, ฐานข้อมูล, ตัวจัดตารางงาน, ตัวส่งล็อก—แต่ละตัวเป็นกระบวนการแยกที่สามารถเริ่ม หยุด รีสตาร์ท และมอนิเตอร์ได้อย่างอิสระ
บนระบบที่แชร์ การแยกยังสนับสนุนการแชร์ทรัพยากรที่ปลอดภัย: OS สามารถบังคับขีดจำกัด (เช่น เวลา CPU หรืิอหน่วยความจำ) และป้องกันไม่ให้กระบวนการหนึ่งวิ่งไม่หยุดจนทำให้คนอื่นอด
UNIX ยังให้ สัญญาณ (signals) ซึ่งเป็นวิธีเบา ๆ ที่ระบบหรือคุณจะส่งข้อความถึงกระบวนการ คิดว่าเป็นการเคาะที่ไหล่:
การควบคุมงาน ในการใช้งานแบบโต้ตอบช่วยให้คุณหยุดงาน, resume ต่อหน้า, หรือให้มันรันเบื้องหลัง จุดประสงค์ไม่ใช่แค่ความสะดวก—แต่กระบวนการถูกออกแบบมาให้จัดการเป็นหน่วยที่มีชีวิต
เมื่อการสร้าง แยก และควบคุมกระบวนการง่าย การรันโหลดงานหลายอย่างบนเครื่องเดียวก็เป็นเรื่องปกติ แบบคิดนี้—หน่วยเล็กที่สามารถถูกดูแล รีสตาร์ท และจำกัด—เป็นบรรพบุรุษโดยตรงของการทำงานของตัวจัดการบริการและรันไทม์คอนเทนเนอร์สมัยใหม่
UNIX ไม่ชนะเพราะมันมีฟีเจอร์ทุกอย่างเป็นคนแรก แต่มันคงทนเพราะมันทำให้อินเทอร์เฟซบางอย่างน่าเบื่อ—และเก็บให้เป็นเช่นนั้น เมื่อผู้พัฒนาสามารถพึ่งพาการเรียกระบบ พฤติกรรมบรรทัดคำสั่ง และข้อตกลงของไฟล์เดิม ๆ ปีแล้วปีเล่า เครื่องมือจะสะสมแทนที่จะถูกเขียนใหม่
อินเทอร์เฟซคือข้อตกลงระหว่างโปรแกรมกับระบบ: “ถ้าคุณขอ X คุณจะได้รับ Y” UNIX รักษาข้อตกลงหลักไว้ให้คงที่ (กระบวนการ, ตัวบ่งชี้ไฟล์, ท่อ, สิทธิ์) ซึ่งให้แนวทางให้แนวคิดใหม่เติบโตขึ้นโดยไม่ทำลายซอฟต์แวร์เก่า
คนมักพูดถึง “ความเข้ากันได้ของ API” แต่มีสองชั้น:
ความเสถียรของ ABI เป็นเหตุผลสำคัญที่ระบบนิเวศอยู่ได้นาน: มันปกป้องโปรแกรมที่คอมไพล์แล้ว
POSIX เป็นความพยายามมาตรฐานที่รวบรวม user-space แบบ UNIX-like: การเรียกระบบ, ยูทิลิตี้, พฤติกรรมเชลล์ และข้อตกลง มันไม่ทำให้ทุกระบบเหมือนกัน แต่สร้างพื้นที่ทับซ้อนใหญ่ที่ซอฟต์แวร์สามารถสร้างและใช้ได้ข้าม Linux, BSDs และระบบที่สืบทอดมาจาก UNIX อื่น ๆ
อิมเมจคอนเทนเนอร์พึ่งพาพฤติกรรม UNIX-like ที่เสถียร หลายอิมเมจสมมติว่า:
คอนเทนเนอร์รู้สึกพกพาไม่ใช่เพราะรวมทุกอย่างไว้ แต่เพราะมันวางบนสัญญาที่ใช้ร่วมกันและเสถียร ซึ่งเป็นหนึ่งในผลงานที่ยั่งยืนของ UNIX
Ken Thompson และทีมที่ Bell Labs มุ่งออกแบบระบบที่ เข้าใจได้ และ ปรับปรุงได้: แกนหลักเล็ก ๆ ข้อกำหนดเรียบง่าย และเครื่องมือที่นำมาประกอบกันได้ ทางเลือกเหล่านี้ยังสอดคล้องกับความต้องการสมัยใหม่ เช่น การทำงานอัตโนมัติ, การแยกขอบเขต และการดูแลระบบขนาดใหญ่เมื่อเวลาผ่านไป.
การเขียน UNIX ใหม่เป็นภาษา C ช่วยลดการพึ่งพาฮาร์ดแวร์หรือ CPU แบบใดแบบหนึ่ง ทำให้การย้ายระบบปฏิบัติการ (และซอฟต์แวร์ที่รันบนมัน) ข้ามเครื่องเป็นไปได้จริงจังขึ้น ซึ่งมีอิทธิพลต่อความคาดหวังด้านพกพาในระบบที่คล้าย UNIX และมาตรฐานอย่าง POSIX.
POSIX รวบรวมพฤติกรรมแบบ UNIX ที่ใช้ร่วมกัน (การเรียกระบบ, ยูทิลิตี้, พฤติกรรมเชลล์) มันไม่ทำให้ระบบทุกระบบเหมือนกัน แต่สร้างโซนความเข้ากันได้ขนาดใหญ่ ทำให้ซอฟต์แวร์สามารถสร้างและรันบน UNIX หรือระบบที่คล้ายกันได้โดยมีความประหลาดใจน้อยลง.
เครื่องมือขนาดเล็กหมายถึงโปรแกรมที่เข้าใจง่าย ทดสอบได้ และแทนที่ได้ เมื่อแต่ละเครื่องมือมีสัญญาอินพุต/เอาต์พุตที่ชัดเจน คุณสามารถแก้ปัญหาใหญ่ด้วยการประกอบมันเข้าด้วยกันโดยไม่ต้องแก้ไขเครื่องมือเหล่านั้น
ท่อ (pipe) (|) เชื่อม stdout ของคำสั่งหนึ่งไปยัง stdin ของคำสั่งถัดไป ทำให้คุณต่อขั้นตอนของการแปลงข้อมูลได้ การแยก stderr ช่วยให้เอาต์พุตปกติประมวลผลได้ในขณะที่ข้อผิดพลาดยังคงมองเห็นหรือเปลี่ยนทางได้แยกต่างหาก.
UNIX ใช้อินเทอร์เฟซเดียวกัน—open, read, write, close—กับทรัพยากรหลากหลายไม่ใช่แค่ไฟล์บนดิสก์ นั่นทำให้เครื่องมือและพฤติกรรมเดียวกันใช้ได้กว้าง (แก้ไขคอนฟิก, tail โลก์, อ่านข้อมูลระบบ)
ตัวอย่างเช่นไฟล์อุปกรณ์ใน /dev และไฟล์โทรเมทรีใน /proc
โมเดลเจ้าของ/กลุ่ม/ผู้อื่น กับบิตอ่าน/เขียน/รัน ทำให้สิทธิ์เข้าใจง่ายและตรวจสอบได้ การปฏิบัติแบบ least privilege คือการให้สิทธิ์เฉพาะที่จำเป็นเท่านั้น
ขั้นตอนปฏิบัติได้แก่:
โปรแกรมคือโค้ดแบบนิ่ง; กระบวนการคืออินสแตนซ์ที่กำลังรันมีสถานะของตัวเอง กระบวนการที่แยกขอบเขตช่วยให้ความล้มเหลวถูกจำกัด: ถ้ากระบวนการหนึ่งล้มเหลว มันมักจะไม่ลากให้ตัวอื่นล้มตาม
โมเดลนี้ยังรองรับการควบคุมทรัพยากร (CPU, หน่วยความจำ) และการจัดการด้วยสัญญาณ เช่น เริ่ม/หยุด/รีโหลด
อินเทอร์เฟซที่เสถียรคือสัญญาระยะยาว (การเรียกระบบ, ตัวบ่งชี้ไฟล์, สตรีม, สัญญาณ) ที่ทำให้เครื่องมือสะสมแทนที่จะถูกเขียนใหม่
ความเสถียรของ ABI เป็นเหตุผลใหญ่ที่ระบบนิเวศยังคงอยู่ได้ยาว เพราะมันปกป้องซอฟต์แวร์ที่คอมไพล์แล้วไม่ให้พัง
คอนเทนเนอร์พึ่งพาพฤติกรรม UNIX-like ที่สม่ำเสมอ เช่น โครงสร้างไฟล์ การทำงานของยูทิลิตี้ และสตรีมมาตรฐาน
คอนเทนเนอร์คือการรวมการแพ็กเกจกับการแยกกระบวนการ ไม่ใช่ VM เบา ๆ — กระบวนการภายในคอนเทนเนอร์เป็นกระบวนการปกติบนโฮสต์ แต่ถูกแยกให้รู้สึกเหมือนอยู่คนเดียว ความต่างสำคัญคือคอนเทนเนอร์แชร์เคอร์เนลของโฮสต์ ขณะที่ VM รันเคอร์เนลของตัวเอง
กลไกเคอร์เนลหลักได้แก่:
ข้อจำกัดคือเพราะการแชร์เคอร์เนล การแยกยังไม่สมบูรณ์ ความเปราะบางของเคอร์เนลหรือการกำหนดค่าผิดพลาด (รันเป็น root, ให้ความสามารถกว้าง, มอนต์พาธสำคัญของโฮสต์) สามารถเจาะผ่านขอบเขตได้.