Động Cơ Cài Đặt Hệ Điều Hành (OS Provisioning Engines)
Tài liệu này giải thích chi tiết cơ chế hoạt động bên trong của các động cơ cài đặt hệ điều hành tự động (Automated OS Engines) được tích hợp trong dự án: Ubuntu Server 24.04 (Subiquity / Cloud-Init), Talos Linux (MachineConfig), và openSUSE Leap Micro (Combustion). Đồng thời, tài liệu cung cấp hướng dẫn mở rộng để lập trình viên tự thêm các bản phân phối Linux mới vào hệ thống.
1. Ubuntu Server 24.04 LTS: Casper & Subiquity Autoinstall
Ubuntu Server từ phiên bản 20.04 LTS trở đi đã loại bỏ hoàn toàn bộ cài đặt cũ Debian-Installer (Preseed) và chuyển sang bộ cài đặt mới mang tên Subiquity kết hợp với Casper live boot environment và Cloud-Init.
1.1. Luồng Khởi Động Casper & Kernel Arguments
Khi iPXE nạp Linux Kernel (vmlinuz) và Ramdisk (initrd), nó truyền vào dòng lệnh Kernel Command Line (định nghĩa tại [src/providers/ubuntu/ipxe.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/ubuntu/ipxe.ts)):
kernel ${base_url}/assets/ubuntu/24.04/vmlinuz initrd=initrd \
boot=casper \
ip=dhcp \
autoinstall \
ds=nocloud-net;s=${base_url}/os/ubuntu/${mac}/ \
[rootfs-loading-args]Giải mã các tham số cốt lõi:
boot=casper: Báo hiệu cho initrd biết hệ thống đang khởi động môi trường Live ISO của Ubuntu. Casper chịu trách nhiệm tìm kiếm và gắn kết (mount) hệ thống tệp nénrootfs(filesystem.squashfs).autoinstall: Kích hoạt cơ chế cài đặt tự động không chạm của Subiquity, ngăn chặn hiển thị màn hình cài đặt tương tác (bàn phím, ngôn ngữ, timezone).ds=nocloud-net;s=...: Chỉ thị cho Cloud-Init nạp dữ liệu từ DataSourcenocloud-nettại URL máy chủ Bun.- Subiquity sẽ tự động truy vấn 2 endpoint HTTP:
${base_url}/os/ubuntu/${mac}/meta-data: Chứainstance-idvàlocal-hostname.${base_url}/os/ubuntu/${mac}/user-data: Chứa toàn bộ cấu hình cài đặt YAML#cloud-config.
- Subiquity sẽ tự động truy vấn 2 endpoint HTTP:
1.2. So Sánh Hai Phương Thức Nạp Rootfs: HTTP Boot vs. NFS Boot
Đây là một trong những quyết định kiến trúc quan trọng nhất trong việc tối ưu tài nguyên Homelab:
| Tiêu Chí | HTTP Boot (boot_method: http) | NFS Boot (boot_method: nfs) |
|---|---|---|
| Tham số Boot | url=${base_url}/assets/.../ubuntu-24.04-live-server.iso | netboot=nfs nfsroot=192.168.250.4:/srv/nfs/ubuntu-24.04 |
| Cơ chế nạp | Tải toàn bộ file ISO dung lượng 2.6GB lưu vào bộ nhớ RAM (tmpfs). | Gắn kết thư mục ISO đã trích xuất từ xa qua giao thức NFS v3/v4. |
| Yêu cầu RAM tối thiểu | ≥ 8GB RAM. Nếu RAM 4GB–5GB, Subiquity sẽ bị Linux Kernel OOM Killer tiêu diệt giữa chừng. | 4GB RAM (Thậm chí 3GB RAM vẫn cài đặt thành công). |
| Hạ tầng phụ trợ | Không cần (Bun HTTP Server tự phục vụ toàn bộ). | Cần 1 máy chủ NFS nội bộ (Synology NAS, TrueNAS hoặc Linux NFS). |
| Phù hợp nhất cho | Máy chủ vật lý Bare-Metal RAM dồi dào. | Máy ảo Proxmox VE, cụm Cluster ảo hóa tài nguyên hạn chế. |
1.3. Cơ Chế HTTP Range Requests (Mã 206) Cho File Lớn
Để nạp file ISO kích thước nhiều Gigabytes một cách tin cậy, máy chủ tĩnh Bun [src/core/static-server.ts](file:///Users/timi/lab/lab-ipxe-os/src/core/static-server.ts) được lập trình để xử lý chuẩn HTTP Header Range: bytes=start-end:
// Trích đoạn từ src/core/static-server.ts
const rangeHeader = req.headers.get("range");
if (rangeHeader && rangeHeader.startsWith("bytes=")) {
const parts = rangeHeader.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1;
const chunkLength = end - start + 1;
const slicedFile = file.slice(start, end + 1);
return new Response(slicedFile, {
status: 206,
headers: {
"Content-Range": `bytes ${start}-${end}/${stat.size}`,
"Accept-Ranges": "bytes",
"Content-Length": chunkLength.toString(),
"Content-Type": file.type || "application/octet-stream",
},
});
}Nhờ hỗ trợ mã phản hồi 206 Partial Content, Linux Casper có thể đọc trực tiếp các sector và header ISO mà không bắt buộc phải tải toàn bộ file cùng một lúc, đồng thời cho phép tiếp tục tải (resume) khi đường truyền mạng có sự cố chập chờn.
1.4. Phân Tích Cấu Trúc Subiquity #cloud-config (user-data)
File cấu hình do Bun server sinh động tại [src/providers/ubuntu/autoinstall.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/ubuntu/autoinstall.ts) bao gồm các khối chức năng quan trọng:
- Định danh & Truy cập (
identity&ssh):- Khởi tạo user mặc định (
homelab) với password hash SHA-512. - Bơm SSH Public Keys được cấu hình trong
config/hosts.yamlvào file~/.ssh/authorized_keys.
- Khởi tạo user mặc định (
- Lưu trữ (
storage):- Chế độ
layout.name: directtự động sử dụng toàn bộ dung lượng ổ đĩa không thông qua LVM phức tạp. - Cho phép chỉ định chính xác tên thiết bị đích bằng
layout.match.path(/dev/sda,/dev/vda, hoặc/dev/nvme0n1).
- Chế độ
- Mạng (
network):- Sinh cấu hình Netplan phiên bản 2 chuẩn.
- Hỗ trợ cả DHCP động lẫn IP tĩnh (Static IP, Subnet CIDR, Gateway, DNS Nameservers).
- Bộ lệnh hậu kỳ (
late-commands):- Đây là nơi hệ thống thực thi các tinh chỉnh sâu vào hệ điều hành vừa cài đặt trước khi máy khởi động lại.
- Lệnh được thực thi bằng tiện ích
curtin in-target --target=/target -- <command>(chroot trực tiếp vào ổ đĩa vừa ghi của máy đích).
1.5. Mổ Xẻ Chuyên Sâu Profile k3s-single-node
Profile k3s-single-node được module hóa độc lập tại [src/providers/ubuntu/profiles/k3s-single-node.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/ubuntu/profiles/k3s-single-node.ts), biến một máy Ubuntu trắng thành một cụm Kubernetes Single Node Production-ready:
lateCommands: [
// 1. Tắt vĩnh viễn phân vùng Swap trong /etc/fstab (bắt buộc cho Kubernetes Kubelet)
`curtin in-target --target=/target -- sed -i '/ swap / s/^\\(.*\\)$/#\\1/g' /etc/fstab || true`,
// 2. Tinh chỉnh Sysctl cho mạng ảo Kubernetes (Bridge Netfilter & IP Forward)
`curtin in-target --target=/target -- sh -c 'cat <<EOF > /etc/sysctl.d/99-kubernetes.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF'`,
// 3. Tự động nạp các Kernel Modules cốt lõi khi khởi động
`curtin in-target --target=/target -- sh -c 'cat <<EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF'`,
// 4. Tạo thư mục và cấu hình khai báo K3s chuẩn (/etc/rancher/k3s/config.yaml) với dynamic TLS SAN
`curtin in-target --target=/target -- mkdir -p /etc/rancher/k3s`,
`curtin in-target --target=/target -- sh -c 'cat <<EOF > /etc/rancher/k3s/config.yaml
write-kubeconfig-mode: "0644"
tls-san:
${sanEntries}
EOF'`,
// 5. Dynamic fallback: bổ sung IP thực tế vào tls-san nếu máy nhận IP qua DHCP
`curtin in-target --target=/target -- sh -c 'NODE_IP=$(ip -4 route get 1.1.1.1 2>/dev/null | awk "{print \\$7}"); if [ -n "$NODE_IP" ] && ! grep -q "$NODE_IP" /etc/rancher/k3s/config.yaml; then echo " - \\"$NODE_IP\\"" >> /etc/rancher/k3s/config.yaml; fi'`,
// 6. Cài đặt K3s binary & systemd service (hỗ trợ ghim phiên bản qua host.custom.k3s_version)
`curtin in-target --target=/target -- sh -c 'curl -sfL https://get.k3s.io | ${k3sVersionEnv}INSTALL_K3S_SKIP_START=true sh -'`,
// 7. Kích hoạt systemd unit k3s để tự chạy ngay khi máy boot lần đầu
`curtin in-target --target=/target -- systemctl enable k3s || true`,
// 8. Cấu hình biến môi trường KUBECONFIG toàn hệ thống và symlink ~/.kube/config cho user
`curtin in-target --target=/target -- sh -c 'echo "KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /etc/environment'`,
`curtin in-target --target=/target -- sh -c 'echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" > /etc/profile.d/k3s.sh'`,
`curtin in-target --target=/target -- mkdir -p /home/${defaultUser}/.kube /root/.kube`,
`curtin in-target --target=/target -- ln -sf /etc/rancher/k3s/k3s.yaml /home/${defaultUser}/.kube/config`,
`curtin in-target --target=/target -- ln -sf /etc/rancher/k3s/k3s.yaml /root/.kube/config`,
`curtin in-target --target=/target -- chown -R ${defaultUser}:${defaultUser} /home/${defaultUser}/.kube || true`,
// 9. Kích hoạt QEMU Guest Agent để Proxmox VE theo dõi IP và tình trạng máy ảo
`curtin in-target --target=/target -- systemctl enable qemu-guest-agent || true`,
// 10. Gửi Webhook Phone-Home về Bun Server để xác nhận hoàn tất & khóa Boot Loop!
`curtin in-target --target=/target -- curl -s -X POST "${baseUrl}/api/installed?mac=..." || true`,
]IMPORTANT
Lưu ý kỹ thuật: Tham số INSTALL_K3S_SKIP_START=true là bắt buộc vì tại thời điểm late-commands thực thi, hệ điều hành đích vẫn đang nằm trong môi trường chroot (/target), systemd PID 1 thực sự của máy chưa chạy, nếu cố khởi động service K3s tại đây sẽ dẫn đến lỗi cài đặt Subiquity bị fail! Do đó lệnh systemctl enable k3s được gọi để kích hoạt service cho lần boot đầu tiên.
Ngoài ra, các lệnh cơ sở hạ tầng dùng chung (như kích hoạt qemu-guest-agent, đồng bộ efibootmgr, và gọi Webhook /api/installed) được gom tập trung vào [src/providers/ubuntu/profiles/base.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/ubuntu/profiles/base.ts) và tự động nối vào cuối danh sách lateCommands ở tầng Dispatcher.
2. Talos Linux: Immutable Kubernetes Operating System
Khác biệt với Ubuntu, Talos Linux là một hệ điều hành bất biến (Immutable), không có shell, không có SSH, không có trình quản lý gói (apt/yum). Mọi thao tác cấu hình đều thông qua tệp khai báo YAML MachineConfig.
2.1. Tham Số Boot Talos Kernel
Định nghĩa tại [src/providers/talos/ipxe.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/talos/ipxe.ts):
kernel ${base_url}/assets/talos/v1.14.0/vmlinuz-amd64 \
talos.platform=metal \
talos.config=${base_url}/os/talos/${mac}/config.yaml \
init_on_alloc=1 slab_nomerge pti=on \
console=tty0 console=ttyS0 printk.devkmsg=on ip=dhcp
initrd ${base_url}/assets/talos/v1.14.0/initramfs-amd64.xz
boot2.2. Sinh Động Talos MachineConfig
Tại [src/providers/talos/config.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/talos/config.ts), hệ thống tạo tài liệu cấu hình theo chuẩn Talos v1alpha1:
- Định cấu hình hostname theo khai báo trong
hosts.yaml. - Thiết lập phân vai trò node:
type: controlplanehoặctype: join(worker node). - Tự động nạp danh sách SSH keys hoặc token chứng thực của cluster.
3. openSUSE Leap Micro: Combustion Engine
openSUSE Leap Micro sử dụng công cụ cấu hình ban đầu mang tên Combustion (thực thi trước khi hệ thống systemd khởi động):
- Bun server cung cấp kịch bản Shell tại endpoint:
GET /os/suse-micro/:mac/combustion/script. - Kịch bản Combustion tự động:
- Thiết lập mật khẩu root và hostname.
- Ghi SSH public keys vào
/root/.ssh/authorized_keys. - Thiết lập network tĩnh hoặc DHCP (qua NetworkManager connection).
- Tự động mở rộng Btrfs root filesystem (
btrfs filesystem resize max /). - Phục hồi thứ tự khởi động UEFI (
efibootmgr) và gửi Webhook Phone-Home/api/installed.
Hệ thống hỗ trợ 2 profile cho openSUSE Leap Micro qua thư mục [src/providers/suse-micro/profiles/](file:///Users/timi/lab/lab-ipxe-os/src/providers/suse-micro/profiles/):
generic([generic.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/suse-micro/profiles/generic.ts)): Cài các gói cơ bản (curl,git,qemu-guest-agent) và mở rộng Btrfs root filesystem.rke2-single-node([rke2-single-node.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/suse-micro/profiles/rke2-single-node.ts)): Tự động cài đặt Rancher RKE2 bằng RPM method chính thức, cấu hình SELinux permissive, tắt swap & firewalld, bật sysctl/modules Kubernetes (vm.max_map_count = 262144), tắt auto-reboot ban đêm (rebootmgr strategy=off), cấu hình crictl socket, cấu hình CNI (Canal / Cilium), Ingress (Traefik / NGINX), và tạo symlink kubeconfig.
TIP
Hướng dẫn chi tiết về vận hành, cập nhật hệ điều hành định kỳ (transactional-update), quản lý snapshot Btrfs (snapper), rollback sự cố và chính sách reboot xem tại: [docs/suse-micro-update-guide.md](file:///Users/timi/lab/lab-ipxe-os/docs/suse-micro-update-guide.md).
4. Mô Hình Kiến Trúc 2 Tầng Registry: Provider Registry & Profile Registry Map
Hệ thống được thiết kế theo nguyên lý Separation of Concerns và Open-Closed Principle (OCP) thông qua mô hình Registry 2 tầng:
4.1. Tại sao sử dụng Registry Map thay vì Switch-Case?
| Tiêu Chí | Switch-Case Trước Đây | Registry Map Hiện Tại |
|---|---|---|
| Nguyên lý Open-Closed (OCP) | Vi phạm: Mỗi lần thêm profile đều phải can thiệp trực tiếp vào thân hàm dispatcher. | Tuân thủ triệt để: Hàm dispatcher là pure function, chỉ cần thêm 1 dòng đăng ký vào Map. |
| Boilerplate Code | Nhiều khối lệnh case "..." : return ...; break; lặp đi lặp lại. | Khai báo dạng dữ liệu thuần túy (Declarative Data): Record<string, ProfileHandler>. |
| Khả năng Nội suy (Introspection) | Không thể liệt kê danh sách profile nếu không hardcode. | Dễ dàng lấy Object.keys(PROFILES) phục vụ API listing hoặc validate cấu hình hosts.yaml. |
| Độ tin cậy & Fallback | Dễ sót nhánh default hoặc xử lý hoa/thường không đồng nhất. | Luôn chuẩn hóa .toLowerCase() và fallback có cảnh báo console.warn về profile generic. |
| Độ nhất quán (Consistency) | Mỗi OS một kiểu viết (Ubuntu kiểu khác, SUSE kiểu khác). | Toàn bộ các Provider đều đồng bộ theo cùng 1 chuẩn cấu trúc module. |
4.2. Cấu Trúc Module Chuẩn Của Một Thư Mục Profile
Cả src/providers/ubuntu/profiles/ và src/providers/suse-micro/profiles/ đều tuân theo cấu trúc 4 thành phần:
src/providers/<os>/profiles/
├── types.ts # Interface ProfileSpec và Type ProfileHandler
├── base.ts # (Tùy chọn) Các late-commands/snippets nền tảng dùng chung
├── generic.ts # Profile cơ sở mặc định (Standard base utilities)
├── <custom-profile>.ts # Profile chuyên biệt (k3s-single-node, rke2-single-node, ...)
└── index.ts # Pure Dispatcher sử dụng Registry MapVí dụ mã nguồn triển khai Registry Map (src/providers/suse-micro/profiles/index.ts):
import type { HostConfig } from "../../../types.ts";
import type { SuseProfileSpec, SuseProfileHandler } from "./types.ts";
import { getGenericProfile } from "./generic.ts";
import { getRke2SingleNodeProfile } from "./rke2-single-node.ts";
export * from "./types.ts";
// Registry Map khai báo tập trung các profile
const PROFILES: Record<string, SuseProfileHandler> = {
generic: getGenericProfile,
"rke2-single-node": getRke2SingleNodeProfile,
};
// Pure Dispatcher Function
export function getSuseMicroProfile(
profileName: string,
host: HostConfig,
baseUrl: string
): SuseProfileSpec {
const normalizedKey = profileName.toLowerCase();
let handler = PROFILES[normalizedKey];
if (!handler) {
console.warn(
`[openSUSE Leap Micro Profile] Unknown profile "${profileName}", falling back to "generic".`
);
handler = getGenericProfile;
}
return handler(host, baseUrl);
}4.3. Quy trình 3 bước thêm một Profile mới
Khi bạn muốn thêm một profile mới (ví dụ: k3s-worker cho Ubuntu hoặc microos-desktop cho SUSE):
- Tạo file profile độc lập: Tạo
src/providers/<os>/profiles/<tên-profile>.tsvà xuất hàmget...Profile(host: HostConfig, baseUrl: string): ProfileSpec. - Đăng ký vào Registry Map: Mở
src/providers/<os>/profiles/index.ts, import hàm vừa tạo và thêm 1 dòng vào đối tượngPROFILES:typescriptconst PROFILES: Record<string, ProfileHandler> = { generic: getGenericProfile, "k3s-single-node": getK3sSingleNodeProfile, "k3s-worker": getK3sWorkerProfile, // <-- Thêm tại đây }; - Sử dụng trong
config/hosts.yaml: Khai báoprofile: k3s-workercho máy đích. Hệ thống sẽ tự động điều hướng mà không cần sửa bất kỳ dòng code routing nào khác!
5. Hướng Dẫn Mở Rộng: Tự Thêm OS Provider Mới
Kiến trúc của dự án được thiết kế theo mẫu Strategy / Registry Pattern, cho phép bạn dễ dàng tích hợp thêm các bản phân phối Linux khác (ví dụ: Debian, Alpine Linux, Fedora CoreOS, Arch Linux).
Quy trình 3 bước tích hợp OS mới:
Bước 1: Tạo thư mục Provider và kế thừa BaseProvider
Tạo thư mục mới src/providers/debian/index.ts:
import { BaseProvider } from "../base.ts";
import type { BootContext, HostConfig } from "../../types.ts";
export class DebianProvider extends BaseProvider {
public readonly id = "debian";
public readonly name = "Debian GNU/Linux";
public readonly defaultVersion = "12";
// 1. Sinh kịch bản iPXE nạp Kernel & Preseed
public renderIpxe(ctx: BootContext): string {
const { hostConfig, baseUrl, mac } = ctx;
const version = hostConfig.version || this.defaultVersion;
return `#!ipxe
echo Starting Debian ${version} Netboot...
kernel ${baseUrl}/assets/debian/${version}/linux initrd=initrd.gz auto=true priority=critical preseed/url=${baseUrl}/os/debian/${mac}/preseed.cfg
initrd ${baseUrl}/assets/debian/${version}/initrd.gz
boot
`;
}
// 2. Xử lý các endpoint trả về cấu hình tự động (vd: preseed.cfg)
public async handleConfigRoute(
subpath: string,
req: Request,
ctx: { hostConfig: HostConfig; baseUrl: string }
): Promise<Response> {
if (subpath === "preseed.cfg") {
const preseedContent = `
d-i debian-installer/locale string en_US
d-i netcfg/get_hostname string ${ctx.hostConfig.hostname}
d-i preseed/late_command string in-target curl -X POST "${ctx.baseUrl}/api/installed?mac=${ctx.hostConfig.mac}"
`;
return new Response(preseedContent, {
headers: { "Content-Type": "text/plain" },
});
}
return new Response("Not Found", { status: 404 });
}
}Bước 2: Đăng ký vào Provider Registry
Mở [src/providers/registry.ts](file:///Users/timi/lab/lab-ipxe-os/src/providers/registry.ts) và đăng ký class mới:
import { DebianProvider } from "./debian/index.ts";
export class ProviderRegistry {
constructor() {
this.register(new UbuntuProvider());
this.register(new TalosProvider());
this.register(new SuseMicroProvider());
this.register(new DebianProvider()); // <-- Đăng ký thêm tại đây
}
}Bước 3: Khai báo host trong config/hosts.yaml
hosts:
"00:11:22:33:44:55":
hostname: "debian-node-01"
os: debian
version: "12"
profile: genericKhởi động lại server hoặc lưu file, hệ thống sẽ lập tức nhận diện và phục vụ OS mới!