Frood: Một hệ thống NAS độc đáo chạy hoàn toàn trên bộ nhớ RAM
Frood là một thiết bị NAS được xây dựng dựa trên Alpine Linux nhưng chạy hoàn toàn trong một initramfs lớn. Cách tiếp cận này giúp hệ thống khởi động nhanh, dễ dàng hoàn nguyên (rollback) khi có lỗi và quản lý cấu hình khai báo qua Git.

Frood, chiếc NAS của tôi, có một cấu hình hơi kỳ lạ. Nó thực chất chỉ là một initramfs khổng lồ chứa toàn bộ hệ thống Alpine Linux. Điều này thực sự thú vị và tôi không hiểu tại sao nó lại chưa phổ biến hơn.
Miễn là bộ khởi động (bootloader) có thể tìm thấy kernel và initramfs, máy sẽ khởi động sạch sẽ. Việc triển khai A/B và hoàn nguyên chỉ đơn giản là chọn một tùy chọn khởi động khác. Hệ thống được định nghĩa theo kiểu khai báo trong kho lưu trữ Git dùng để xây dựng initramfs.
Hệ thống Frood
Tại sao lại chọn cách này?
Tôi luôn thích chạy hệ thống từ bộ nhớ: nó nhanh và ngăn chặn sự hao mòn trên thiết bị lưu trữ hệ thống, vốn thường là những thẻ SD kém chất lượng, bởi vì các ổ đĩa tốt đã được dành cho pool ZFS.
Tuy nhiên, bạn sẽ gặp ngay vấn đề về cách duy trì các thay đổi cấu hình. Câu trả lời của Alpine cho vấn đề này là "chế độ không đĩa" (diskless mode), nơi mọi tùy chỉnh được giữ trong một tệp overlay. Sau khi khởi động, hệ thống tiêu chuẩn sẽ tìm kiếm tệp khớp với *.apkovl trong tất cả các hệ thống tệp có sẵn, áp dụng nó, sau đó cài đặt bất kỳ gói apk nào còn thiếu từ bộ nhớ đệm cục bộ.
Vấn đề đầu tiên với cách này là sự phức tạp: công cụ tạo và quản lý apkovl, lbu(1), khá tốt nhưng quy trình đó có quá nhiều bộ phận chuyển động. Tìm apkovl, áp dụng nó, gắn kết các hệ thống tệp trong fstab mới, cài đặt các apk còn thiếu, tiếp tục quá trình khởi động. Quá trình khởi động phụ thuộc vào trình quản lý gói!
Vấn đề thứ hai là tôi thực sự muốn trạng thái của hệ thống được theo dõi trong Git. Graham Christensen có một bài thuyết trình rất hay về các hệ thống khai báo hoặc bất biến trong "Erase your darlings" (Xóa những đứa con yêu quý của bạn).
Tôi xóa hệ thống của mình tại mỗi lần khởi động. Theo thời gian, một hệ thống sẽ tích lũy trạng thái trên phân vùng gốc của nó. Trạng thái này sống trong các thư mục khác nhau như /etc và /var, và đại diện cho mọi bước tài liệu kém hoặc không đúng trình tự khi đưa các dịch vụ vào hoạt động.
Tôi từng giải quyết điều này bằng cách thực hiện (hầu hết) các thay đổi thông qua Ansible, nhưng sau đó tôi có một tình huống nhiều lớp nơi tôi cần thực hiện thay đổi trong Ansible, sau đó triển khai nó, rồi lưu nó bằng lbu vào apkovl.
Cách thức hoạt động
Khi khởi động, Linux mong đợi một hình ảnh "initramfs". Nó là một kho lưu trữ cpio đơn giản của các tệp tạo nên hệ thống tệp gốc đầu tiên khi khởi động. Thông thường, công việc của hệ thống này là tải đủ các mô-đun để gắn kết rootfs thực sự và chuyển sang nó. Tuy nhiên, không có gì ngăn chúng ta đưa toàn bộ hệ thống vào trong đó! Ai cần rootfs?
Xây dựng một initramfs
Điểm khởi đầu là alpine-make-rootfs, một đoạn script ngắn (~500 dòng) có nghĩa là để xây dựng một hình ảnh container. Nó thực sự là 90% những gì chúng ta cần.
Script này sẽ sao chép các tệp từ thư mục gốc, cài đặt các gói từ tệp packages và chạy script setup.sh trong một môi trường chroot. Sau đó, chúng ta trích xuất thư mục boot và đóng gói phần còn lại vào một kho lưu trữ initramfs.
Điều đó thực sự gần như là tất cả! Thật ấn tượng khi Alpine cho phép làm điều này mà hầu như không cần các thủ thuật (hack).
Cấu trúc hệ thống
Các gói phần mềm
Các gói chúng tôi cài đặt là những thứ thông thường bạn sẽ cài đặt trên máy chủ. Chỉ có một vài gói đáng chú ý như alpine-base, linux-lts (kernel), linux-firmware-i915, intel-ucode và syslinux làm bộ khởi động.
Syslinux đơn giản hơn nhiều so với GRUB, nó cài đặt trong phân vùng hệ thống tệp, sau đó khởi động kernel từ phân vùng đó. Điều này khép kín vòng lặp: miễn là chúng ta khởi động đúng phân vùng, không có cách nào ngoại trừ hệ thống của chúng ta có thể tải được. Không có gì trong quá trình khởi động cần khám phá hoặc thậm chí đặt tên cho một hệ thống tệp.
Triển khai và Rollback
Chúng ta có ba mục khởi động: thường (lts), cũ (old) và mới (new). Khi triển khai phiên bản mới của hệ thống, chúng ta rsync nó qua, sau đó sử dụng extlinux --once để chọn nó cho lần khởi động tiếp theo.
Nếu máy khởi động sạch sẽ, chúng ta chuyển hình ảnh thường sang cũ và mới sang thường. Nếu không, một lần khởi động khác sẽ hoàn nguyên nó về phiên bản cũ.
Một dịch vụ trạng thái đơn giản
Tôi muốn một dịch vụ đơn giản để xem trạng thái của hệ thống một cái nhìn tổng quan. Có hàng triệu cách để làm điều này, nhưng tôi chọn viết một máy chủ Go nhỏ. Nó không cần thiết để hệ thống này hoạt động, nhưng tôi đưa nó vào để cho thấy việc thêm một dịch vụ dễ dàng như thế nào.
Máy chủ Go này lắng nghe trên cổng 80 trên IP Tailscale và phục vụ đầu ra của các tập lệnh tôi đặt trong /etc/monitor.d/.
Toàn bộ thiết lập là mã nguồn mở trong kho lưu trữ mostly-harmless của tôi. Một điều tôi chưa giải quyết được là cách tiêm các bí mật (secrets). Hiện tại chúng chỉ bị .gitignore. Có lẽ tôi sẽ cắm một YubiKey và sử dụng age-plugin-yubikey để giải mã chúng.
