FreeBSD trên Firecracker
(usenix.org)- Việc khởi động nhân FreeBSD 14 trên Firecracker VMM, một microVM thiên về Linux dành cho AWS Lambda, là một ví dụ cho thấy môi trường ảo hóa tối giản có thể phơi bày các giả định ẩn và điểm nghẽn trong đường khởi tạo HĐH
- Lần khởi động đầu tiên bị chặn bởi khác biệt về định dạng ELF Note của PVH boot mode, hypercall chỉ dành cho Xen và khác biệt trong cách bố trí bộ nhớ của Firecracker; vấn đề được giải quyết bằng cách sửa mã PVH của FreeBSD
- Do Firecracker không cung cấp ACPI, đường lấy thông tin CPU và ngắt đã thay đổi, và tùy chọn
MPTABLE_LINUX_BUG_COMPATđược thêm vào để tương thích với lỗi xử lý MPTable của Linux - Trong quá trình kết nối console nối tiếp và thiết bị Virtio, các ràng buộc về hoạt động UART, phân tích dòng lệnh kernel và I/O đĩa không căn hàng đã lộ rõ; FreeBSD áp dụng
hw.broken_txfifo, cách обход xả FIFO và xử lý bounce dựa trênbusdma - Nếu tính cả các bản vá chưa được commit, nhân FreeBSD có thể khởi động trong dưới 20ms trên VM 1 CPU và 128MB RAM; các việc còn lại là hợp nhất hỗ trợ PVH, tách mã Xen, tạo cấu hình kernel nhỏ hơn và xem xét port Firecracker sang FreeBSD
Vì sao muốn chạy FreeBSD trên Firecracker
- Firecracker là VMM tạo và quản lý microVM với overhead thấp trên Linux KVM cho các môi trường thực thi serverless như AWS Lambda
- Công việc port FreeBSD bắt đầu từ tháng 6/2022
- Động lực của công việc là cùng lúc kiểm tra giới hạn của cả FreeBSD lẫn Firecracker
- Trong khi tiếp tục cải thiện tốc độ khởi động của FreeBSD, mục tiêu là kiểm tra xem với một hypervisor tối giản thì có thể nhanh đến mức nào
- Port FreeBSD sang một nền tảng mới thường làm lộ ra lỗi ở cả FreeBSD lẫn chính nền tảng đó
- AWS Lambda hiện chỉ hỗ trợ Linux; bất kể có định dùng Lambda hay không, việc FreeBSD hỗ trợ Firecracker vẫn là điều kiện tiên quyết cần có
- Bản thân Firecracker là một nền tảng thú vị nên cũng muốn xác nhận xem nó có thực sự chạy được hay không
Trở ngại đầu tiên trước khi chạy được kernel
- Firecracker ban đầu được thiết kế để chạy kernel Linux, nhưng đến năm 2020 đã có bản vá hỗ trợ PVH boot mode bên cạnh
linuxboot - Vì FreeBSD đã hỗ trợ khởi động PVH trên Xen, cùng đường này được thử trên Firecracker
- Vấn đề đầu tiên là sau khi nạp kernel FreeBSD vào bộ nhớ, Firecracker không tìm được kernel entry point
- Giao thức khởi động PVH lưu giá trị này trong ELF Note
- ELF Note có
PT_NOTEvàSHT_NOTE, còn FreeBSD không cung cấp đúng định dạng mà Firecracker đang tìm - Chỉ với một chỉnh sửa nhỏ trong linker script của kernel FreeBSD, Firecracker đã có thể bắt đầu chạy kernel FreeBSD
- Nhưng việc chạy kernel lại dừng tiếp sau khoảng 1 micro giây
Gỡ lỗi ban đầu và loại bỏ phụ thuộc Xen
- Nếu kernel chết trước khi debugger kernel và console nối tiếp được khởi tạo thì các tính năng debug của FreeBSD gần như không giúp được gì
- Thông tin Firecracker cung cấp chỉ là guest FreeBSD đã gặp triple-fault
- Một quá trình “kernel bisection” được thực hiện bằng cách chèn lệnh
hltvào giữa mã khởi động kernel để thu hẹp vị trí lỗi- Nếu chạy đến
hlt, Firecracker vẫn tiếp tục chạy nhưng mức dùng CPU của host về 0% - Nếu Firecracker thoát thì có thể kết luận đã crash trước điểm đó
- Nếu chạy đến
- Nguyên nhân đầu tiên là Xen hypercall
- Entry point PVH của FreeBSD thực chất là đoạn mã để khởi động trên Xen và giả định đang chạy bên trong Xen
- KVM mà Firecracker dùng không cung cấp Xen hypercall, nên VM bị crash ngay khi gọi
- Ban đầu hypercall Xen bị comment out; về sau được sửa để chỉ gọi sau khi kiểm tra signature Xen trong
CPUID
- Việc truy vấn bản đồ bộ nhớ vật lý là chức năng bắt buộc vốn do Xen hypercall đảm nhận
- Từ PVH version 1, con trỏ bản đồ bộ nhớ được truyền qua trang
start_info - FreeBSD được thay đổi để dùng bản đồ bộ nhớ PVH version 1 thay vì Xen hypercall
- Từ PVH version 1, con trỏ bản đồ bộ nhớ được truyền qua trang
- Khác biệt trong cách bố trí bộ nhớ giữa Firecracker và Xen cũng gây ra vấn đề
- Xen nạp kernel trước rồi đặt trang
start_infoở cuối - Firecracker đặt trang
start_infoở một địa chỉ thấp cố định rồi mới nạp kernel sau đó - Mã PVH của FreeBSD giả định vùng ngay sau
start_infolà scratch space, nhưng trên Firecracker điều này ghi đè lên kernel stack ban đầu - Vấn đề được giải quyết bằng cách cấp scratch space ở phía sau mọi vùng nhớ đã được hypervisor khởi tạo
- Xen nạp kernel trước rồi đặt trang
Thiếu ACPI và tương thích MPTable
- Trên x86, FreeBSD thường lấy thông tin đĩa, bộ điều hợp mạng, CPU và interrupt controller qua ACPI
- Firecracker cố ý theo đuổi hiện thực tối giản nên không cung cấp ACPI
- Thay vào đó, FreeBSD có thể dùng cấu trúc MPTable từ Intel MultiProcessor Specification cũ
- Nó không được включить mặc định trong cấu hình kernel
GENERIC - Trong cấu hình kernel gọn nhẹ cho Firecracker, có thể dùng bằng cách thêm
device mptable
- Nó không được включить mặc định trong cấu hình kernel
- MPTable mà Firecracker cung cấp không theo chuẩn mà theo cách Linux chấp nhận
- Linux có lỗi trong cách tìm và parse MPTable
- Firecracker được thiết kế để boot Linux nên cung cấp bố trí phi chuẩn mà Linux hỗ trợ
- FreeBSD, với phần hiện thực độc lập theo chuẩn, không tìm được MPTable đặt sai chỗ, và dù tìm được cũng không parse được MPTable không hợp lệ
- FreeBSD được thêm tùy chọn kernel
options MPTABLE_LINUX_BUG_COMPAT- Dùng khi cần tương thích bug-for-bug với cách Linux xử lý MPTable
- Tùy chọn này cho phép FreeBSD tiếp tục boot xa hơn trên Firecracker
Hỗ trợ console nối tiếp và thiết bị Virtio
- Một trong số ít thiết bị giả lập mà Firecracker cung cấp là cổng nối tiếp
- Trong cấu hình thông thường, đầu vào/ra chuẩn của tiến trình Firecracker trở thành đầu vào/ra của cổng nối tiếp trong VM
- Kernel FreeBSD đã boot với đĩa root được nhúng trong image kernel, và có thể đọc được output console của kernel
- Khi chuyển sang khởi động user space, output console dừng lại ở 16 ký tự
- Đây là triệu chứng giống lỗi UART từng có trong QEMU
- Khi transmit FIFO của UART rỗng thì không có ngắt, khiến FreeBSD không thể ghi thêm sau 16 byte
- Vấn đề được xử lý bằng cách biên dịch biến môi trường kernel
hw.broken_txfifo="1", một workaround sẵn có của kernel FreeBSD
- Input console cũng không hoạt động
- Firecracker cho rằng receive FIFO của UART giả lập đang đầy nên không đọc console
- Trong lúc khởi tạo UART, FreeBSD cố đo kích thước FIFO bằng cách đổ các giá trị rác vào receive FIFO rồi xóa bằng FIFO Control Register
- Firecracker không hiện thực FIFO Control Register nên FIFO cứ ở trạng thái đầy
- FreeBSD được sửa để sau khi xả FIFO, nếu
LSR_RXRDYvẫn còn thì đọc bỏ từng ký tự để làm trống FIFO
- Muốn dùng đĩa và mạng thì cần thiết bị Virtio block/network
- Firecracker lộ các thiết bị này dưới dạng thiết bị
mmio - Cấu hình kernel Firecracker của FreeBSD được thêm
device virtio_mmio
- Firecracker lộ các thiết bị này dưới dạng thiết bị
- Ban đầu FreeBSD kỳ vọng sẽ phát hiện thiết bị
mmioqua FDT, nhưng Firecracker lại truyền chỉ thị nhưvirtio_mmio.device=4K@0x1001e000:5trong dòng lệnh kernel- FreeBSD được thêm mã để parse chỉ thị này và tạo node thiết bị
virtio_mmio - Khi đã có node thiết bị, quy trình probe thiết bị sẵn có của FreeBSD sẽ xác định loại thiết bị Virtio và gắn đúng driver
- FreeBSD được thêm mã để parse chỉ thị này và tạo node thiết bị
- Khi có nhiều thiết bị Virtio, lại phát sinh vấn đề parse dòng lệnh kernel
- Firecracker truyền nhiều cặp
key=valuetheo kiểu Linux - FreeBSD parse dòng lệnh kernel thành biến môi trường, nên nếu có hai
virtio_mmio.device=cùng tên thì chỉ còn giữ lại một - Mã parse môi trường kernel ban đầu được sửa để gắn hậu tố số cho biến trùng lặp, như
virtio_mmio.device=vàvirtio_mmio.device_1=
- Firecracker truyền nhiều cặp
- Sau khi tắt bất thường, nếu lần boot sau chạy
fsckthì xảy ra kernel panicfscklà một trong số ít trường hợp ở FreeBSD tạo ra I/O đĩa không căn hàng theo trang- Phần hiện thực Virtio của Firecracker chỉ nhận một data buffer duy nhất và không hỗ trợ cách Virtio thông thường là chia buffer băng qua ranh giới trang thành nhiều segment
- Driver Virtio block của FreeBSD được sửa để dùng
busdma, và các request không căn hàng được xử lý bounce qua bộ đệm tạm để phù hợp với giới hạn của Firecracker
Tối ưu khởi động mà Firecracker làm lộ ra
- Khi FreeBSD bắt đầu chạy được trên Firecracker, những điểm cần tối ưu thời gian khởi động và mức dùng bộ nhớ trở nên rất rõ ràng
- Trên VM 128MB RAM, gần một nửa bộ nhớ hệ thống ở trạng thái
wiredvà tiến trình thường xuyên bị kill- Điều tra cho thấy
busdmađang dành trước 32MB cho bounce page - Với giới hạn của Firecracker, mỗi I/O đĩa chỉ cần tối đa một bounce page 4KB
- Một bản vá giới hạn việc dự trữ bounce page cho thiết bị chỉ hỗ trợ số lượng nhỏ I/O segment đã giảm mức dùng bộ nhớ xuống còn 512KB
- Điều tra cho thấy
- Tối ưu bộ sinh số ngẫu nhiên của kernel giúp rút ngắn thời gian boot
- Trong VM, entropy từ thiết bị phần cứng có thể không hiệu quả
- x86 dùng
RDRANDnhư nguồn entropy dự phòng, nhưng lượng entropy cho mỗi lần yêu cầu nhỏ và chỉ được yêu cầu mỗi 100ms một lần - Việc đổi sang yêu cầu đủ để seed hoàn toàn bộ sinh số ngẫu nhiên Fortuna đã giảm 2,3 giây
- Việc xử lý Host ID cũng nhanh hơn
- Thông thường boot loader sẽ đặt
smbios.system.uuiddựa trên thông tin BIOS hoặc UEFI - Firecracker không có boot loader nên không cung cấp ID này
- Hệ thống được sửa để nếu phần cứng cung cấp ID sai thì cảnh báo và chờ 2 giây, còn nếu hoàn toàn không có ID thì tiếp tục nhanh và im lặng
- Thông thường boot loader sẽ đặt
- Điều kiện chờ IPv6 DAD được thu hẹp
- FreeBSD vốn chờ Duplicate Address Detection nếu một interface mạng có IPv6 được bật
- Interface loopback luôn bật IPv6
- Việc đổi sang chỉ chờ DAD khi có IPv6 trên interface không phải loopback đã tiết kiệm 2 giây
- Các khoảng chờ cố định trong quá trình reboot và shutdown đã bị loại bỏ
- Khi reboot, hành vi chờ 1 giây sau thông báo
Rebooting...đểprintfhoàn tất và có thời gian đọc đã được thay bằng sysctlkern.reboot_wait_time, với giá trị mặc định là 0 - Khi shutdown hoặc reboot, hành vi BSP chờ thêm 1 giây sau khi nhận tín hiệu dừng các CPU khác cũng bị loại bỏ
- Khi reboot, hành vi chờ 1 giây sau thông báo
- TSLOG được dùng để phân tích flame chart khởi động
- Môi trường tối giản của Firecracker có ít nhiễu nên dễ nhìn thấy các điểm nghẽn còn lại
- Việc chạy VM rất nhanh, nên từ build kernel mới đến chạy và tạo flame chart thường mất chưa tới 30 giây
- Phân tích TSLOG đã giúp giảm nhiều điểm nghẽn ở mức mili giây
- Giảm vòng lặp hiệu chuẩn 100000 lần của
lapic_initxuống 1000 lần đã tiết kiệm 10ms - Đổi
ns8250_draintừ chỗ gọiDELAYcho mỗi ký tự sang chỉ trì hoãn khi cần sau khi kiểm traLSR_RXRDYđã tiết kiệm 27ms - Khiến Firecracker hiện thực CPUID leaf thông báo tần số TSC và local APIC clock đã tiết kiệm 20ms
- Đổi
kern.nswbuftừ giá trị luôn là 256 sang32 * mp_ncpusđã tiết kiệm 5ms trên VM 1 CPU - Thay bubblesort trong
mi_startupbằng quicksort có thể tiết kiệm 2ms, nhưng tính đến ngày 22/08/2023 vẫn chưa được commit - Đổi
vm_memtừ khởi tạo ngay cấu trúcvm_pagecho toàn bộ bộ nhớ vật lý sang khởi tạo lazy có thể tiết kiệm 2ms, và đến cùng ngày vẫn chưa được commit - Thêm
MAP_POPULATEvàommapbộ nhớ guest của Firecracker có thể giảm chi phí Linux tạo cấu trúc trang ở lần truy cập trang đầu tiên, tiết kiệm 2ms, và đến cùng ngày cũng chưa được commit
- Giảm vòng lặp hiệu chuẩn 100000 lần của
Trạng thái hiện tại và phần việc còn lại
- FreeBSD hiện đã boot được trên Firecracker và chạy rất nhanh
- Nếu tính cả các bản vá chưa được commit ở FreeBSD và Firecracker, kernel FreeBSD có thể boot trong dưới 20ms trên VM 1 CPU và 128MB RAM
- Công việc còn lại chủ yếu tập trung vào hoàn thiện hỗ trợ PVH và thu gọn cấu hình kernel
- Cần commit các bản vá đã nhắc ở trên
- Cần hợp nhất hỗ trợ PVH boot mode vào mainline của Firecracker
- Mã PVH booting hiện đang trộn lẫn với hỗ trợ Xen nên cần tách riêng
- Kernel arm64 của FreeBSD hiện chưa thể build nếu không có hỗ trợ PCI hoặc ACPI; nếu loại bỏ các phụ thuộc sai này thì có thể tạo kernel FreeBSD/Firecracker nhỏ hơn nữa
- Việc kiểm tra xem có cần dành trước bộ nhớ cho GPU Intel hay không đang tốn 25µs, nên cấu hình kernel nhỏ hơn có thể giúp giảm thêm vài micro giây thời gian boot
- Về dài hạn hơn, cũng có khả năng port Firecracker để chạy trên FreeBSD
- Firecracker được viết với giả định dùng Linux KVM
- Không thấy có lý do căn bản nào khiến nó không thể được điều chỉnh để dùng phần kernel hypervisor bhyve của FreeBSD
- Nếu muốn thử nghiệm, có thể build kernel FreeBSD 14.0 cho amd64 với cấu hình kernel
FIRECRACKER, rồi dùngfeature/pvhbranch của dự án Firecracker- Nếu branch đó không còn nữa thì có nghĩa là mã đã được hợp nhất vào cây mainline của Firecracker
1 bình luận
Ý kiến trên Hacker News
Tôi không biết rõ rằng Firecracker VM không chỉ là một công nghệ container Linux đơn giản, mà là máy ảo hoàn chỉnh
Ban đầu nghe có vẻ kém hiệu quả, nhưng nhìn vào các trường hợp sử dụng thực tế như fly.io thì thật ngạc nhiên khi thấy micro VM rất nhỏ mà vẫn đủ mạnh
Nhờ KVM và phần hỗ trợ phần cứng tối thiểu (không có PCI, ACPI, v.v.), mã nguồn Firecracker khá đơn giản và tương đối dễ đọc ngay cả với người không chuyên
Đó chính là lý do Firecracker tồn tại
ACPI được nêu trong bài là một ví dụ. Dù vậy, đúng là nó ảo hóa phần cứng chứ không phải kernel, và vì khởi tạo cực nhanh nên tôi nghĩ hầu hết người dùng sẽ không cảm nhận được khác biệt nếu thay containerd thông thường bằng firecracker-containerd
Ngoài Firecracker, nhiều micro VM như crosvm, cloud-hypervisor và Dragonball của Kata hiện cũng đang được phát triển trên KVM
Tôi không có ấn tượng rằng việc triển khai một target mới cho ngôn ngữ lập trình khó đến thế, nên nếu tạo một target kiểu hệ điều hành tương tự WASI/WASM và gửi PR cho các ngôn ngữ được hỗ trợ, có lẽ có thể giảm phần lớn overhead. Phần khó nhất chắc sẽ là mô phỏng không gian người dùng Linux đủ chính xác, nhưng vì bề mặt quá rộng nên con đường tạo một target kiểu hệ điều hành tương tự có vẻ lại là tốt nhất
Khi bản vá của Colin được đưa vào FreeBSD và Firecracker, thời gian khởi động toàn bộ kernel sẽ dưới 20ms
Chúng ta thật sự đang sống trong một thời đại khó tin
Tìm nhanh thì có vài con số, nhưng đó là dữ liệu từ vài năm trước, và cũng không rõ cách đo thời gian khởi động hay định nghĩa “thời gian khởi động” có giống nhau không, nên khó biết có so sánh được không
Đây là bài trình bày BSDCan gần đây của Colin, được đăng vài ngày trước
https://youtu.be/MT3cdeuRTzs?si=l6baNriUjcvy0ZOE
Sau bài trình bày ở BSDCan, FreeBSD Journal nói “bài trình bày hay, anh có thể chuyển nó thành bài viết không?”, và sau khi bài trên FreeBSD Journal xuất hiện thì ;login: hỏi liệu họ có thể đăng lại hay không
qemu có microvm lấy cảm hứng từ firecracker
https://qemu.readthedocs.io/en/latest/system/i386/microvm.ht...
Tất nhiên một số là sửa lỗi của FreeBSD nên chắc chắn vẫn cần
Thú vị là phần lớn trong số 1 giây chờ hóa ra thực ra không thật sự cần thiết
Tôi tự hỏi có bao nhiêu quản trị viên hệ thống đã thực sự làm được điều gì có ý nghĩa khi hệ thống dừng lại vì UUID máy không hợp lệ
Ngược lại, trường hợp “in thông báo cho người dùng rằng sẽ khởi động lại, đợi 1 giây để họ đọc được console, rồi khởi động lại” thì hơi khác
Không có ý tỏ ra ta đây, nhưng tôi tò mò instance Firecracker phù hợp với những trường hợp sử dụng nào
Tôi dùng FreeBSD cho mọi thứ, từ máy chủ colocated đến PC cá nhân, và tôi giống một quản trị viên Unix lâu năm hơn là lập trình viên. Tôi thích bare metal, nhưng hoan nghênh các công nghệ tương lai đóng góp cho hệ điều hành. Tuy vậy, dù đã nghe các buzzword như Lambda hay Firecracker, tôi vẫn không thật sự biết chúng được dùng vào đâu. Tôi hiểu Docker và container, còn k8s thì chỉ vừa đủ hiểu, nhưng không hiểu tại sao phải khởi chạy VM rồi loại bỏ ngay, trong khi có thể cứ chạy một VM và dùng khi cần. Đây hoàn toàn là để có trải nghiệm cloud hay nhằm tiết kiệm chi phí sao?
Làm như vậy cho phép bất kỳ nút nào trong compute plane xử lý bất kỳ lưu lượng ứng dụng nào. Một ứng dụng có thể mở rộng để tiêu thụ động tài nguyên tính toán còn dư của plane theo thay đổi trong mẫu lưu lượng, và không dùng tài nguyên khi không xử lý lưu lượng. Tăng dung lượng của compute plane nghĩa là đưa thêm nút lên online. Ngoài việc quản lý nhiều triển khai quy mô lớn, tôi không nghĩ ra trường hợp sử dụng cụ thể nào khác; trong các môi trường không ở “quy mô” lớn, đây sẽ là công nghệ ẩn bên dưới ranh giới của nhà cung cấp
Nếu bạn vận hành một dịch vụ có API không được dùng thường xuyên nhưng khi được gọi thì phải phản hồi nhanh, Lambda hoặc cách tương tự rất phù hợp. Thực tế khá nhiều API cho ứng dụng điện thoại nằm trong nhóm này, và bạn sẽ không muốn duy trì một máy rảnh rỗi 99% thời gian chỉ để phản hồi những lệnh gọi API đó
Phần lớn không làm chỉ vì công sức bỏ ra lớn hơn số tiền tiết kiệm, nhưng tiềm năng thì có. Những thứ như Lambda và Firecracker khiến việc đó dễ hơn rất nhiều
Thật tiếc là cả AWS lẫn macOS trên ARM đều không hỗ trợ ảo hóa lồng nhau
Nếu có hỗ trợ, việc phát triển và triển khai công nghệ dựa trên Firecracker đã dễ dàng hơn rất nhiều
Thực ra có thể ảo hóa trên bất kỳ loại instance nào, nhưng theo tôi biết chỉ instance .metal mới dùng được tăng tốc phần cứng
Firecracker rất ấn tượng, nhưng có nhiều trường hợp ngoại lệ cần được ghi chép tài liệu
Rất cảm ơn Colin Percival đã chia sẻ điều này. Tôi đặc biệt thích câu “sau khi đã hái hết những quả ở tầm thấp”; với Colin, đó là các bản vá bus_dma tùy chỉnh. Giờ thì ai cũng có thể miễn phí tận hưởng việc “kernel FreeBSD khởi động trong chưa đến 20ms trên 1 CPU và 128MB RAM”. Nếu bạn quen với DevOps dùng cụm k8s hay nhiều Docker, đây thật sự là một điều đáng kinh ngạc
Tôi đã thử nghịch Firecracker một chút; thời gian khởi động đúng như cam kết, nhưng trải nghiệm sử dụng thì khá gian nan
Ví dụ, sau khi khởi động thành công và reo lên mừng rỡ, tôi lại hụt hẫng khi biết rằng để thiết lập networking còn phải làm theo một tutorial dài nữa
Sẽ thật tuyệt nếu có thể tải xuống một binary duy nhất rồi chạy, cả giao diện web lẫn API đều bật lên, có thể cấu hình nhanh và tự động tải về những thứ cần thiết
“Kernel FreeBSD có thể khởi động trong chưa đến 20ms trên một máy ảo có 1 CPU và 128MB RAM”
Trời ạ, làm sao đạt được điều tương tự trên phần cứng thật mà không dùng VM nhỉ ;)
Chậm là mọi thứ còn lại. Ví dụ máy của tôi là
Startup finished in 14.552s (firmware) + 2.885s (loader) + 741ms (kernel) + 23.116s (initrd) + 11.191s (userspace) = 52.488s