สร้างเครื่องมือดีพลอยที่ฉันอยากได้
(ruuda.nl)- Deptool เป็นเครื่องมือดีพลอยที่สร้างขึ้นเพื่อดูแลการตั้งค่า DNS และเว็บเซิร์ฟเวอร์ด้วยตัวเอง โดยจะแสดงแผนการเปลี่ยนแปลงก่อน แล้วจึงนำไปใช้กับโฮสต์เป้าหมายหลังจากยืนยัน
- มันเรนเดอร์การตั้งค่าของทั้งคลัสเตอร์ล่วงหน้าแล้วจัดการด้วย Git จากนั้นสร้างไดเรกทอรีแยกตาม commit ใต้
/var/lib/deptoolของแต่ละโฮสต์ และสลับเวอร์ชันแบบอะตอมมิกด้วยการเปลี่ยนsymbolic linkcurrent - ก่อนดีพลอย จะล็อกบนแต่ละโฮสต์และเปรียบเทียบ commit ที่เครื่องโลคัลรู้จักกับสถานะดีพลอยจริง เพื่อยกเลิกแผนที่ล้าสมัย และจะดำเนินการต่อเมื่อสามารถล็อกโฮสต์ที่ได้รับผลกระทบทั้งหมดได้เท่านั้น
- บริการจะรันเป็น systemd unit และรีสตาร์ตเมื่อมีการเปลี่ยนการตั้งค่า หากเริ่มต้นไม่สำเร็จ ก็จะย้อนลิงก์กลับไปยังเวอร์ชันก่อนหน้าที่ known-good แล้วรีสตาร์ตอีกครั้ง ทำให้เกิดการ rollback อัตโนมัติในระดับมิลลิวินาที
- การสั่งงานระยะไกลใช้ SSH เป็นเพียงชั้นขนส่งในรูปแบบเอเจนต์แบบ static ทำให้ติดตั้งอัตโนมัติได้ด้วย coreutils เท่านั้น แม้ในสภาพแวดล้อมอย่าง Flatcar Linux ที่ไม่มี Python และตัวจัดการแพ็กเกจ
ที่มาของการสร้าง Deptool
- จุดเริ่มต้นมาจากการย้ายบล็อกไปยุโรป เพื่อหลีกเลี่ยงความย้อนแย้งของการเขียนเรื่องอธิปไตยดิจิทัลของยุโรป แต่ไปโฮสต์ไว้บนผู้ให้บริการอเมริกันและ hyperscaler ที่อยู่ภายใต้การควบคุมของสหรัฐฯ
- เนื่องจาก DNS ก็ยังพึ่งพา Cloudflare อยู่ จึงจำเป็นต้องดูแลเซิร์ฟเวอร์ DNS ด้วยตัวเอง
- เว็บเซิร์ฟเวอร์เดิมรัน Nginx และ Lego สำหรับต่ออายุใบรับรองบน VM ขนาดเล็ก โดยสร้างการตั้งค่า Nginx ด้วย Nix แล้วคัดลอกไปยังเซิร์ฟเวอร์ด้วยสคริปต์ Python ขนาดเล็กก่อนรีสตาร์ต Nginx
- แต่การรัน DNS server ต้องใช้อย่างน้อยสองเครื่อง, systemd unit เพิ่มขึ้น, ไฟล์คอนฟิก และ zonefile มากขึ้น ทำให้สคริปต์เดิมไม่เพียงพอ
- แม้จะมีทางเลือกอย่างการย้ายไป NixOS แต่ผู้เขียนเลือกคงแนวทางเดิมไว้ คือใช้ระบบปฏิบัติการฐานขั้นต่ำและรันบริการใน chroot แบบอ่านอย่างเดียวที่มีเฉพาะไบนารีที่จำเป็น พร้อมสร้างเครื่องมือดีพลอยใหม่ขึ้นมา
วิธีใช้งาน Deptool
- Deptool จะแสดงแผนการเปลี่ยนแปลงการตั้งค่าคลัสเตอร์ก่อน แล้วค่อยนำไปใช้กับโฮสต์เป้าหมายหลังได้รับการยืนยัน
- ตัวอย่างการอัปเดต DNS record เมื่อรัน
deptool deployจะแสดงแผนว่าจะเปลี่ยนไฟล์คอนฟิกnsdและรีสตาร์ตnsd.serviceบนs4.ruuda.nlและs5.ruuda.nl - หากดีพลอยล้มเหลว จะมีการ rollback อัตโนมัติ โดยในตัวอย่างจะถามยืนยันก่อนว่าจะนำไปใช้กับ 2 โฮสต์ในคลัสเตอร์
prodหรือไม่ แล้วดำเนินการสำเร็จภายใน 0.99 วินาที - เอาต์พุตจะแยกให้เห็นโฮสต์เป้าหมาย, แอปพลิเคชันที่เปลี่ยน, ไฟล์ที่เปลี่ยน และ systemd unit ที่จะรีสตาร์ต ทำให้ตรวจสอบงานที่จะเกิดขึ้นจริงได้ก่อนดีพลอย
คุณสมบัติของเครื่องมือดีพลอยที่ต้องการ
-
เร็ว
- การอัปเดตคอนฟิกควรใช้เวลาน้อยกว่า 1 วินาที และเมื่อ ping ข้ามมหาสมุทรแอตแลนติกยังอยู่ราว 100ms ก็ไม่มีเหตุผลเชิงแก่นแท้ที่มันต้องช้ากว่านั้น
-
คาดการณ์ได้
- เครื่องมือต้องแสดงงานที่จะทำก่อน แล้วค่อยทำตามนั้นจริง
- ต้องการแนวทางแยกขั้นตอนplan และ apply แบบ OpenTofu
- มองว่า check mode ของ Ansible ไม่น่าเชื่อถือ เพราะการเปลี่ยนแปลงแบบลูกโซ่อาจจะเห็นได้ก็ต่อเมื่อขั้นตอนเชิงคำสั่งถูกดำเนินไปแล้ว และยังป้องกันไม่ได้ด้วยว่าสถานะโฮสต์จะเปลี่ยนไประหว่างช่วง check กับการรันจริง
-
ปลอดภัย
- แม้คอนฟิก Nginx จะเสีย เครื่องมือก็ควร rollback อัตโนมัติในระดับมิลลิวินาที เพื่อไม่ให้เว็บเซิร์ฟเวอร์ล่มอยู่นานหลายนาที
-
เรียบง่าย
- สิ่งที่จำเป็นมีเพียงการคัดลอกไฟล์คอนฟิกจากโน้ตบุ๊กไปยังเซิร์ฟเวอร์ แล้วรีสตาร์ต systemd unit ไม่กี่ตัว
- ไม่จำเป็นต้องแก้ทุกปัญหาของการดีพลอย หรือมี control flow กับการรันโค้ดตามอำเภอใจ
- การประมวลผลเทมเพลตไฟล์คอนฟิกสามารถแยกให้เครื่องมืออื่นทำได้ โดยประเด็นเรื่อง YAML template ถูกแยกไว้ใน generate และ เครื่องมือสร้างไฟล์แยกต่างหาก
-
ต้องเป็นแบบ declarative
- ถ้าลบไฟล์หรือแอปพลิเคชันออกจากคอนฟิก มันก็ควรถูกลบออกจากเซิร์ฟเวอร์ด้วย
- ไม่ควรต้องเพิ่มขั้นตอน cleanup แบบชัดเจน และไม่ควรเกิด drift หรือมีไฟล์ตกค้างเพราะลืมลบ
-
ไม่ต้องมีการตั้งค่าเริ่มต้น
- ควรเริ่มจัดการเซิร์ฟเวอร์ได้ทันทีหลัง provisioning เสร็จ
- ถ้าต้องติดตั้งเอเจนต์, daemon, dependency หรือมีขั้นตอนลงทะเบียนโฮสต์ด้วยมือ ก็จะกลายเป็นปัญหาว่าต้องทำให้ขั้นตอนนั้นเป็นอัตโนมัติอีกทอดหนึ่ง
การแยกการสร้างคอนฟิกกับการดีพลอย
- แนวคิดหลักคือแยกการสร้างคอนฟิกออกจากการดีพลอย
- Unsible ที่ David สร้างในที่ทำงานใช้แนวทางไม่รัน Ansible playbook ทีละขั้น แต่สร้าง tarball ในเครื่องโลคัลก่อนแล้วส่งไปยังโฮสต์เพื่อวางไฟล์
- สคริปต์ดีพลอยแบบง่ายเดิมก็สร้างคอนฟิกจากภายนอกอยู่แล้ว และตัวสคริปต์ทำหน้าที่ใกล้เคียงกับการคัดลอกไฟล์
- จะมองว่า NixOS ใช้แนวคิดนี้กับระบบโลคัลก็ได้ โดยสิ่งที่เรียนรู้ได้จาก Nix คือเก็บผลลัพธ์ที่สร้างแล้วไว้ในตำแหน่งที่หลายเวอร์ชันอยู่ร่วมกันได้ และจำกัดส่วนเชิงคำสั่งของ system administration ให้เหลือเพียง activation step ขนาดเล็กที่สลับ symbolic link ไม่กี่ตัว
- การออกแบบนี้เหมาะทั้งกับการจัดการแพ็กเกจและการตั้งค่าระบบ
วิธีการทำงานของ Deptool
-
เรนเดอร์การตั้งค่าทั้งคลัสเตอร์ล่วงหน้า
- สร้างไฟล์คอนฟิกของทั้งคลัสเตอร์ล่วงหน้าแล้วเก็บไว้ในไดเรกทอรีบนดิสก์
- โครงสร้างไดเรกทอรีมีความลึกสองชั้น โดยชั้นบนสุดเป็นไดเรกทอรีตามโฮสต์เป้าหมาย และชั้นถัดมาเป็นไดเรกทอรีตามแอปพลิเคชัน
-
ใส่ลงใน Git repository
- เมื่อนำไดเรกทอรีคอนฟิกใส่ใน Git repository ก็สามารถเปรียบเทียบความต่างระหว่างเวอร์ชันได้ และดูได้ว่าทั้งคลัสเตอร์มีอะไรเปลี่ยนไปบ้าง
- ใช้ diffstat เพื่อดูได้ว่าโฮสต์ไหนและแอปไหนได้รับผลกระทบ พร้อมดู diff ที่แม่นยำของแต่ละไฟล์คอนฟิกได้
-
materialize ไฟล์ลงในไดเรกทอรีแยกของโฮสต์
- ไฟล์ทั้งหมดจะถูกวางไว้ใต้
/var/lib/deptoolเพื่อไม่ให้รบกวนองค์ประกอบอื่น - จะสร้างไดเรกทอรีตามชื่อ commit ที่จะดีพลอย จึงสามารถมีหลายเวอร์ชันอยู่ร่วมกันบนดิสก์ได้
- symbolic link
currentจะชี้ไปยังเวอร์ชันที่ดีพลอยอยู่ ทำให้สลับเวอร์ชันแบบอะตอมมิกได้ - ไฟล์ที่ถูกลบจะไม่ถูก materialize ในเวอร์ชันถัดไป จึงไม่เกิดไฟล์ตกค้าง
- สำหรับแอปพลิเคชันที่ต้องการไฟล์ในตำแหน่งเฉพาะ ก็สามารถสร้าง symbolic link จากตำแหน่งที่จำเป็นใน filesystem ให้ชี้มายัง
/var/lib/deptoolได้ - การสร้างหรือลบ symbolic link ไม่ได้เป็นอะตอมมิก แต่จะจำเป็นเฉพาะตอนเพิ่มหรือลบลิงก์ ไม่ใช่ตอนแก้ไขเนื้อหาไฟล์
- หลังจากนั้น ถ้า symbolic link นั้นไม่อยู่ในเวอร์ชันที่ดีพลอยแล้ว ก็จะรู้จาก diff ว่าต้องลบออก จึงไม่เหลือไฟล์ค้าง
- ไฟล์ทั้งหมดจะถูกวางไว้ใต้
-
บันทึกสถานะการดีพลอยด้วย remote-tracking ref
- ติดตาม commit ที่ดีพลอยอยู่บนแต่ละโฮสต์จากโน้ตบุ๊กของผู้ดูแล
- สถานะการดีพลอยไม่ใช่คุณสมบัติของทั้งคลัสเตอร์ แต่เป็นคุณสมบัติรายโฮสต์
- ถ้าการเปลี่ยนแปลงใดไม่กระทบโฮสต์หนึ่ง ๆ ก็ไม่จำเป็นต้องดีพลอย commit ใหม่ไปยังโฮสต์นั้น
- ข้อมูลนี้ทำให้คำนวณ diff ของคลัสเตอร์แบบออฟไลน์ได้ และ diff นี้เองจะกลายเป็นแผนการดีพลอยที่แสดงได้ในระดับมิลลิวินาที
-
ขอ lock จากโฮสต์เป้าหมายก่อนดีพลอย
- เชื่อมต่อผ่าน SSH เพื่อส่งคำขอ lock โดยในคำขอจะใส่ commit ที่เชื่อว่าถูกดีพลอยอยู่บนโฮสต์นั้นมาด้วย
- ถ้าได้ lock แสดงว่าแผนยังใช้ได้ และจนกว่าจะปล่อย lock ก็จะไม่มีการดีพลอยอื่นเกิดขึ้นบนโฮสต์นั้น ทำให้แผนยังคงใช้ได้ต่อไป
- จะดีพลอยก็ต่อเมื่อถือ lock ของทุกโฮสต์ที่ได้รับผลกระทบจากการเปลี่ยนแปลงไว้ครบแล้วเท่านั้น
- ถ้า ref เก่าและมีอย่างอื่นถูกดีพลอยลงโฮสต์ไปแล้ว แผนจะถือว่าล้าสมัยและถูกยกเลิก
- หลังจากอัปเดต ref ในเครื่องโลคัลแล้ว การรันครั้งถัดไปก็จะแสดงแผนล่าสุดได้
-
รีสตาร์ต systemd unit
- ทุกบริการรันเป็น systemd unit และเริ่มทำงานได้เร็ว จึงเลือกรีสตาร์ตเมื่อไม่แน่ใจ
- เมื่อคอนฟิกของแอปพลิเคชันเปลี่ยน ก็จะรีสตาร์ต systemd unit ที่ได้รับผลกระทบ
- หาก unit เริ่มไม่สำเร็จ จะย้อน symbolic link กลับไปยังเวอร์ชัน known-good ก่อนหน้า แล้วรีสตาร์ตใหม่ ทำให้ rollback อัตโนมัติในระดับมิลลิวินาทีเป็นไปได้
โมเดล optimistic concurrency
- การดีพลอยของ Deptool มีองค์ประกอบของoptimistic concurrency
- มันวางแผนโดยสมมติว่ารู้สถานะปัจจุบันของคลัสเตอร์ และถ้าสมมตินั้นผิด ก็ต้องลองใหม่
- เมื่อไม่มีการแย่งกันใช้งาน มันจะเร็วมาก ซึ่งเหมาะกับกรณีโครงสร้างพื้นฐานส่วนตัวที่มีคนเดียวดีพลอยจากโน้ตบุ๊กเครื่องเดิม
- แต่ในสภาพแวดล้อมที่หลายคนพยายามดีพลอยอย่างต่อเนื่อง จะมีเพียงคนเดียวที่สำเร็จ และที่เหลือต้องลองใหม่ ทำให้ประสิทธิภาพแย่มากได้
- โมเดลนี้เหมือนกับ
git pushและแม้จะไม่สเกลถึงระดับคนหลายร้อยหรือเซิร์ฟเวอร์หลายพันเครื่อง แต่ก็เพียงพอสำหรับโครงสร้างพื้นฐานส่วนตัว - เมื่อสร้างเครื่องมือเอง ก็สามารถปรับให้เหมาะกับ use case ของตัวเองได้อย่างตรงจุด
การสร้างเอเจนต์
-
Flatcar Linux และข้อจำกัดของโฮสต์เริ่มต้น
- เว็บเซิร์ฟเวอร์รันอยู่บน Flatcar Linux
- Flatcar Linux เป็นระบบปฏิบัติการแบบ image-based ที่มี userspace ขนาดเล็กมาก มี coreutils กับ Bash แต่ไม่มีตัวจัดการแพ็กเกจและ Python
- สิ่งนี้ดีต่อการลด attack surface แต่ไม่เอื้อต่อการติดตั้งอะไรเพิ่มเติม
- ถ้าเครื่องมือต้องติดตั้งบางอย่างก่อนถึงจะทำงานได้ ก็จะเกิดปัญหาใหม่ว่าต้องทำให้กระบวนการติดตั้งนั้นเป็นอัตโนมัติ
-
ใช้ SSH เป็นเพียงชั้นขนส่ง
- เพราะต้องจัดการโฮสต์ใหม่จากภายนอก จึงสามารถใช้ SSH และ passwordless sudo ได้
- การรันคำสั่งผ่าน SSH โดยตรงไม่เพียงแต่ handshake ช้า แต่ argv ยังข้ามขอบเขตของ SSH ได้ไม่ปลอดภัย และต้องรับมือกับปัญหา word splitting กับ escaping ของ shell-over-SSH
- Deptool จะรันโปรแกรมเดียวที่ไม่มีอาร์กิวเมนต์ ณ ตำแหน่งที่คาดเดาได้ และใช้โปรแกรมนี้เป็นเอเจนต์
- เอเจนต์จะอ่านข้อความจาก stdin และส่งคำตอบกลับทาง stdout
- SSH จึงถูกใช้เป็นเพียงช่องทางขนส่งเหมือน socket โดยไม่มีอินพุตที่ผู้ใช้ควบคุมได้ถูกใส่เข้าไปในคำสั่ง SSH หรือ shell ทำให้หลีกเลี่ยงปัญหา escaping ได้
-
ใช้ static binary
- เอเจนต์ถูก build เป็น static binary
- จึงไม่ต้องสมมติว่ามีอะไรอยู่นอกจาก kernel และไม่ต้องมี interpreter ที่ต้อง parse โค้ดหลาย MB ก่อนเริ่มทำงานที่มีประโยชน์
- แม้ Ansible จะ mitigate ปัญหาที่เลวร้ายที่สุดแล้ว ก็ยังต้องส่ง Python module หลาย MB ทุกครั้งที่เชื่อมต่อ และบน Flatcar ก็ไม่มี Python อยู่แล้ว
-
วางไบนารีไว้ในพาธตาม commit
- ไบนารีของเอเจนต์จะถูกเก็บไว้ในพาธที่มี commit ที่ใช้ build รวมอยู่ด้วย
- วิธีนี้ทำให้มั่นใจว่าทั้งสองฝั่งรันเวอร์ชันเดียวกัน จึงไม่เกิดปัญหาความเข้ากันได้ของโปรโตคอล
- พาธอยู่ในรูปแบบ
/var/lib/deptool/bin/deptool-<version>-<commit>
-
สมมติว่ามีไบนารีอยู่ก่อน
- เพราะ SSH handshake มีต้นทุนสูง จึงไม่เสียเวลากับขั้นตอน probe หรือการติดตั้งแบบ idempotent
- ไบนารีของเอเจนต์มีขนาดประมาณ 1.6MB ซึ่งไม่ใหญ่ถึงขั้นห้ามส่ง แต่ก็ไม่ใช่ว่าฟรี
- การเปลี่ยนคอนฟิกคลัสเตอร์เกิดบ่อยกว่าการอัปเดต Deptool มาก ดังนั้นโดยทั่วไปจึงสมมติว่าไบนารีมีอยู่แล้ว
-
ถ้ารันไม่สำเร็จค่อยติดตั้งไบนารี
- หากเริ่มรันไบนารีไม่สำเร็จ ก็จะติดตั้งผ่านการเชื่อมต่อ SSH ครั้งที่สอง
- คำสั่งรันมีดังนี้
uname -sm
&& sudo mkdir -p /var/lib/deptool/{bin,apps,store}
&& sudo dd status=none of=<remote_bin_path>
&& sudo chmod +x <remote_bin_path>
&& sudo sha256sum <remote_bin_path>
- ก่อนอื่นจะอ่าน stdout หนึ่งบรรทัดเพื่อรับผลลัพธ์
unameแล้วใช้ข้อมูลนั้นตรวจสอบ OS กับสถาปัตยกรรม CPU เพื่อส่งไบนารีเอเจนต์สำหรับแพลตฟอร์มนั้น - เมื่อเขียนไบนารีลง stdin แล้ว
ddฝั่งรีโมตจะบันทึกมันลงดิสก์ - จากนั้นจะอ่าน stdout อีกหนึ่งบรรทัดเพื่อตรวจสอบ shasum ที่คำนวณบนรีโมต และยืนยันว่าการส่งสำเร็จ
- กระบวนการนี้พึ่งพาเพียงโปรแกรม coreutils มาตรฐานเท่านั้น
- หลังจากนั้น เมื่อลองรันเอเจนต์อีกครั้งก็ควรสำเร็จ และเอเจนต์จะล้างเวอร์ชันเก่าเพื่อไม่ให้ดิสก์เต็ม
ผลลัพธ์และต้นทุนของแนวทางเอเจนต์
- ได้วิธีรันและสื่อสารกับเอเจนต์บนโฮสต์ระยะไกล
- ติดตั้งอัตโนมัติบนโฮสต์ระยะไกลได้โดยไม่ต้องการอะไรนอกจาก coreutils
- เนื่องจากทั้งสองฝั่งรันเวอร์ชันเดียวกัน ความเข้ากันได้ของโปรโตคอลจึงถูกการันตีโดยโครงสร้าง
- อินพุตที่ผู้ใช้ควบคุมได้จะถูกส่งผ่านเพียง socket บน SSH เท่านั้น โดยไม่เข้าไปอยู่ในคำสั่ง SSH หรือ shell จึงหลีกเลี่ยงปัญหา escaping และข้อจำกัดด้านความยาวได้
- ในกรณีทั่วไปต้องใช้เพียง SSH handshake ครั้งเดียว จึงมี latency ต่ำ
- ในกรณีที่ไม่บ่อย เช่น ดีพลอยลงเครื่องใหม่หรือหลังอัปเดตเครื่องมือ จะต้องมีการเชื่อมต่อเพิ่มอีก 2 ครั้งและการส่งข้อมูล 1.6MB แบบครั้งเดียว
- หากใช้
ControlMasterก็ข้าม overhead ของการเชื่อมต่อครั้งถัดไปได้เกือบทั้งหมด ทำให้ต้นทุนรวมอยู่ในระดับไม่กี่วินาที - แม้ในกรณีนี้จะไม่ใช่การดีพลอยต่ำกว่า 1 วินาที แต่ก็ยังดีกว่า Ansible ตามมุมมองของผู้เขียน
- ใน workflow แบบดีพลอยคอนฟิก แล้วแก้เล็กน้อยก่อนดีพลอยอีกครั้ง SSH สามารถคงการเชื่อมต่อพื้นฐานไว้ได้ ทำให้การดีพลอยให้ความรู้สึกแทบจะทันที
ผลการใช้งานและการเปิดเผยสู่สาธารณะ
- Deptool ถูกใช้ดูแลโครงสร้างพื้นฐานส่วนตัวมาตลอดหนึ่งเดือนที่ผ่านมา
- จุดเด่นคือสามารถเห็นแผนที่ถูกต้องได้ทันทีตั้งแต่ก่อนเชื่อมต่อ และมี rollback อัตโนมัติ แต่การเปลี่ยนแปลงที่ใหญ่ที่สุดคือการดีพลอยต่ำกว่า 1 วินาที
- ถ้าวิธีดีพลอยที่ถูกต้องใช้เวลาหลายนาที ก็ย่อมอยากแก้ไฟล์บนเซิร์ฟเวอร์โดยตรงเพื่อลด feedback loop แต่กับ Deptool การแก้ในเครื่องโลคัลแล้วดีพลอยกลับเร็วกว่าการ SSH เข้าไปเปิดตัวแก้ไขบนเซิร์ฟเวอร์
- วิธีที่มีแรงเสียดทานน้อยที่สุดจึงกลายเป็นวิธีที่ถูกต้อง และทุกการแก้ไขที่นำไปใช้จะถูกบันทึกไว้ในประวัติ Git
- แม้จะทำบางอย่างพังก็ตาม Deptool ก็ rollback กลับก่อนที่ผู้ใช้จะทันรู้ตัวว่ามันพัง
- Deptool ถูกสร้างมาเพื่อแก้ปัญหาส่วนตัวอย่างตรงจุด และการที่มันไม่ได้พยายามแก้ทุกปัญหาการดีพลอยของทุกคน คือสิ่งที่ทำให้มันโดดเด่นใน use case นี้
- มันอาจมีประโยชน์อย่างยิ่งกับระบบปฏิบัติการแบบ image-based โดยเปิดเผยโค้ดไว้บน Codeberg และ GitHub พร้อมมี manual แบบละเอียดให้ใช้งาน
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
รู้สึกดีใจมากที่โปรเจกต์นี้ ประกาศชัดว่าไม่ได้ใส่ข้อความที่สร้างโดย LLM ลงไป: not putting LLM-generated text anywhere near this
ตัวเครื่องมือเองก็ดูขัดเกลาดีและออกแบบมาดี แต่ฉันคงยังใช้ NixOS ต่อไปอีกสักพัก
ตั้งใจว่าจะลองใช้แน่นอน ดูเหมือนเป็นเวอร์ชันที่ขัดเกลามากกว่าของระบบที่ทำเองเพื่อ deploy บริการบน systemd
ดูจากทิวทอเรียลแล้วน่าสนใจ แต่อยากรู้ว่าควรจัดการ local state ยังไง เช่น ฐานข้อมูล sqlite ของแอปควรเก็บไว้ที่ไหน หาไม่เจอในเอกสาร
แล้วก็สงสัยว่ามีวิธีส่ง app binary ไปยังเซิร์ฟเวอร์เพื่อให้ systemd unit ใช้งานได้ไหม ถ้าไม่มี ก็อยากรู้ว่าจัดการ deployment ของ binary กันอย่างไร
/var/lib/<yourapp>ถ้ารันแอปเป็น systemd unit ก็ใช้
StateDirectory=เพื่อให้ systemd สร้างไดเรกทอรีด้วย owner ที่ถูกต้องได้แอปที่ฉันดูแลจะ build เป็นอิมเมจ EROFS ขนาดเล็กด้วย สคริปต์ที่อิง Nix นี้ และสคริปต์นั้นก็มีฟังก์ชัน push อิมเมจไปยังเซิร์ฟเวอร์ด้วย เดิมทีเคยเป็นคนละขั้นตอน แต่ตอนนี้รวม build กับ push เป็นขั้นตอนเดียวแล้ว และจะเข้าไปอยู่ในไดเรกทอรีเฉพาะ ทำให้หลายเวอร์ชันอยู่ร่วมกันได้
ผลลัพธ์จากการ build ยังมี JSON ที่บรรจุ path ของไฟล์ ซึ่งจะถูกนำเข้าไปยังการตั้งค่าคลัสเตอร์ แล้ว render เป็น systemd unit ก่อนจะ deploy ด้วย Deptool สรุปคือ เครื่องมือหนึ่งรับหน้าที่ deploy อิมเมจ และ Deptool รับหน้าที่ activate
ถ้าใช้คอนเทนเนอร์ ปกติก็ push ไปที่ registry และบนเซิร์ฟเวอร์จะมีแค่ไฟล์คอนฟิกที่ระบุว่าจะดึงอะไรมา ดังนั้นส่วนนั้นจัดการด้วย Deptool อย่างเดียวได้
อีกแนวทางหนึ่งคือใช้ bootable containers ซึ่งก็ค่อนข้างดีเหมือนกัน
สิ่งที่ยังขาดอยู่คือยังไม่มีอะไรที่คอยไปรัน
bootc update --applyบนโฮสต์ที่เหมาะสมจริง ๆ แม้จะมีกลไกอัปเดตอัตโนมัติ แต่ไม่ได้มีการประสานงานกัน จึงไม่ใช่พฤติกรรมที่ต้องการในคลัสเตอร์ตอนนี้ยังทำด้วยมืออยู่ แต่สุดท้ายสิ่งที่ต้องรันก็มีแค่คำสั่ง bootc เดียว เลยดูเหมือนจะทำเป็นสคริปต์ทีหลังได้ไม่ยาก
ทุกครั้งที่มีเครื่องมือ deploy ใหม่ออกมา ฉันมักจะมองด้วยความกังขาเล็กน้อย แต่ตัวนี้ดู ออกแบบมาดีและขัดเกลาดี
การใช้คำสั่ง
sshตรง ๆ ก็ดูเป็นทางเลือกที่ถูกต้อง คุณรู้ว่าsshที่ผู้ใช้มีอยู่นั้นใช้งานได้จริง และผู้ใช้บางคนอาจมีการตั้งค่าพิเศษมาก ๆ หรือใช้ ssh binary ที่ถูกแพตช์มาเครื่องมือที่พยายามทำ ssh เองผ่านไลบรารีภายนอกมีโอกาสจะกลายเป็นอุปสรรคสำหรับผู้ใช้บางกลุ่ม
อยากรู้เพิ่มเติมว่าใช้ EROFS อย่างไร และทำไมถึงใช้
Flatcar ไม่มี package manager ดังนั้นจึงต้องหาวิธีเอาซอฟต์แวร์กับ dependency ขึ้นไปเอง และอิมเมจไฟล์ซิสเต็มแบบ self-contained ก็เป็นหนึ่งในวิธีนั้น
OCI image ต้องอาศัยเครื่องมือแยกต่างหากอย่าง Podman หรือ Docker มาแตก tar ไว้ที่ไหนสักแห่งและจัดสแต็ก overlay mount แต่ถ้าเป็นอิมเมจไฟล์ซิสเต็มอยู่แล้ว ก็สามารถรันจาก systemd unit ได้ตรง ๆ ด้วย
RootImage=ฉัน build อิมเมจด้วย Nix ให้มีเฉพาะสิ่งขั้นต่ำจริง ๆ มีแค่ Nginx binary, LibreSSL, libc และ shared library อีกไม่กี่ตัว ไม่มีแม้แต่ Bash
นี่เป็นส่วนหนึ่งของ defense in depth ต่อให้ Nginx มีช่องโหว่ remote code execution ผู้โจมตีก็จะรันอยู่ใน filesystem namespace ที่แทบไม่มีวัตถุดิบให้ใช้ต่อยอด exploit ขั้นถัดไป และทั้งไฟล์ซิสเต็มก็เป็นแบบอ่านอย่างเดียว ไม่ใช่แค่เพราะ mount เป็น read-only แต่เพราะ EROFS เขียนไม่ได้ตั้งแต่แรก
เมื่อก่อนฉันใช้ Squashfs ซึ่งก็ทำงานได้ดี แต่ไฟล์ซิสเต็มนั้นออกแบบมาโดยยึดบริบทยุค live CD เป็นหลัก ส่วน EROFS เลือกจุดสมดุลที่เหมาะกับระบบปัจจุบันมากกว่า แต่พูดตรง ๆ สำหรับ use case ของฉันก็คงไม่มีความต่างที่วัดได้
อิมเมจมีขนาดเล็กกว่าจริง แต่ก็เพราะใช้การตั้งค่า compression คนละแบบ ในทางทฤษฎี EROFS ยังเหมาะกว่าสำหรับ content-defined chunking หากต้องการนำข้อมูลกลับมาใช้ซ้ำระหว่างอิมเมจคนละเวอร์ชัน แต่ตอนนี้ฉันยังไม่ได้ใช้มันกับการส่งอิมเมจจริง ๆ
พอดีกำลังคุยกับเพื่อนเรื่องกลยุทธ์ deployment แบบเรียบง่ายอยู่ แล้วบทความนี้ก็โผล่มาพอดี ซึ่งค่อนข้างใกล้กับข้อสรุปที่เรากำลังไปถึง
แต่อยากรู้ว่าในเซ็ตอัปแบบนี้จัดการ secrets กันอย่างไร
เขาบอกว่า “Prompting the deployment tool I wish I had” แต่
https://codeberg.org/ruuda/deptool/…
ในแง่หนึ่งก็น่าทึ่งดีที่เหล่าเลขทศนิยมแบบ floating-point สามารถ โน้มน้าวให้ใช้ Rust ได้
ถ้าจะพูดในแง่ดี Rust เป็นภาษาที่ “มีวินัย” และมีทั้งขนบปฏิบัติที่แข็งแรงกับระบบเครื่องมือที่ชัดเจน ซึ่งทั้งสองอย่างช่วย LLM ได้
แปลกดีที่ LLM มักจะสร้าง โปรแกรมที่สั้นกว่า ใน Rust มากกว่าบางภาษา อย่างน้อยถ้ามีการชี้นำมันสักหน่อย ยังไงฉันก็คิดจะอ่านและแก้ทุกบรรทัดอยู่แล้ว สำหรับฉันแบบที่สั้นกว่าจึงดีกว่า
อยากรู้ว่าใช้ตัวนี้จัดการ secrets ยังไง มี workflow ที่ชอบเป็นพิเศษไหม ใส่ไว้ในอิมเมจ EROFS หรือ inject ผ่าน systemd
ไดเรกทอรีนั้นจะถูก mount แบบอ่าน-เขียนสำหรับยูนิตของ Lego และ mount แบบอ่านอย่างเดียวสำหรับยูนิตของ Nginx