FreeBSD jails vs Docker containers: khác gì nhau?
So sánh FreeBSD jails và Docker containers về userland, image nhiều lớp, state, networking và giới hạn. Biết mô hình nào hợp với workload của bạn.
FreeBSD jails và Docker containers trong một đoạn
FreeBSD jails và Docker containers giải quyết cùng một vấn đề theo hai mô hình khác nhau. Cả hai đều chạy userland bị cô lập trên cùng một kernel dùng chung, nên không mô hình nào là virtual machine. Điểm khác biệt nằm ở nội dung bên trong. Docker container chạy một process từ một image nhiều lớp được pull từ registry. Một jail chạy toàn bộ userland FreeBSD: có /etc riêng, các script khởi động rc riêng, cơ sở dữ liệu pkg riêng và số lượng process tùy ý. Gần như mọi khác biệt khác trên trang này đều bắt nguồn từ điểm đó.
SSD Nodes không cung cấp image FreeBSD. Bạn không thể thuê server FreeBSD trên nền tảng này, và phần bên dưới không phải là hướng dẫn cài đặt cho một máy mà bạn có thể mua tại đây. Đây là phần so sánh hai mô hình cô lập, được viết để bạn xác định workload thực sự cần mô hình nào và đọc cấu hình của một team FreeBSD mà không phải đoán.
Jail thực sự là gì
Jail xuất hiện trong FreeBSD 4.0 vào tháng 3 năm 2000, lâu đời hơn cgroups và khoảng một thập kỷ trước Docker. Cơ chế này chỉ dùng một lời gọi kernel. jail(8) nhận một cây thư mục và khởi động các process bên trong đó với một jail ID gắn kèm. Sau đó kernel từ chối một tập thao tác cố định đối với mọi process mang ID đó. Process trong jail không thể xem các process bên ngoài jail, không thể mount hoặc unmount filesystem, không thể load kernel module và không thể bind vào các địa chỉ mạng mà jail không được cấp. Không có loại namespace riêng cần học và cũng không có cơ chế bật từng tính năng; các hạn chế được áp dụng thành một nhóm, rồi điều chỉnh bằng các parameter trong cấu hình của jail.
Trên host, jls liệt kê các jail đang chạy, còn jexec web sh đưa bạn vào shell bên trong jail có tên web.
Bạn tạo jail bằng cách đưa userland của FreeBSD vào một thư mục. Base system thực hiện việc này cho bạn:
sudo bsdinstall jail /usr/local/jails/containers/webLệnh này tải bộ phân phối base tương ứng với release của bạn và chạy các bước sau cài đặt thông thường. Vì vậy, bạn đặt root password và chọn timezone giống như khi cài một server mới. Kết quả là một bản cài đặt FreeBSD nằm trong một thư mục. Sau đó, bạn khai báo jail trong /etc/jail.conf:
web {
host.hostname = "web.example.internal";
path = "/usr/local/jails/containers/web";
ip4.addr = "10.0.0.10";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
mount.devfs;
}Khởi động jail, rồi kiểm tra:
sudo service jail start web
jlsjls lúc này phải liệt kê web cùng với JID, hostname và IP address của jail. Nếu jail không xuất hiện, hãy chạy trực tiếp sudo jail -c web. Lệnh này áp dụng cùng cấu hình ở foreground và in ra parameter mà nó không chấp nhận, thay vì để lỗi chỉ xuất hiện trong output của service.
Dòng cần đọc kỹ hai lần là exec.start = "/bin/sh /etc/rc". Khi khởi động jail, FreeBSD chạy boot script thông thường bên trong jail. Vì vậy, jail khởi động mọi service được enable trong /etc/rc.conf của chính nó. Docker container không có bước tương đương, vì nó chạy process entrypoint của image và dừng khi process đó dừng.
Cách đưa phần mềm vào hệ thống: image và registry so với userland tự cài
Đây là khác biệt bạn nhận thấy ngay từ ngày đầu.
Với Docker, bạn chỉ định tên phần mềm và nhận được phần mềm đó. docker pull nginx tải một image dạng layer, được định danh theo nội dung, do người khác build và kiểm thử; docker compose up -d khởi động image cùng với volume và network của nó. Registry chính là sản phẩm. Phần lớn giá trị của workflow Docker nằm ở việc hàng nghìn dự án công bố image hoạt động được. Nhờ vậy, chạy Docker trên VPS chỉ là một công việc ngắn thay vì một dự án.
FreeBSD không cung cấp sẵn một registry công khai mặc định cho jail image. Bạn tạo một userland trống rồi cài phần mềm vào đó, giống như khi thiết lập một server bare. Cách này cần gõ nhiều lệnh hơn. Nhưng nó minh bạch hơn, vì những gì chạy trong jail chính là những gì pkg đã cài vào đó, từ cùng một bộ package mà host sử dụng.
Tooling giúp rút ngắn quy trình. BastilleBSD là jail manager phổ biến và được cung cấp dưới dạng package:
sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASEbastille setup tự cấu hình network, storage và firewall cho bạn. bastille bootstrap tải một release một lần, sau đó mọi jail bạn tạo đều dùng lại release đó. FreeBSD 15.1 là production release hiện tại, được phát hành vào June 2026; hãy thay bằng release bạn đang chạy.
Sau đó, tạo một jail chỉ cần một lệnh, và cài phần mềm vào đó bằng thêm một lệnh nữa:
sudo bastille create web 15.1-RELEASE 10.17.89.10/24
sudo bastille pkg web install nginx
sudo bastille service web nginx start
sudo bastille console webbastille console web cung cấp login shell bên trong jail, còn bastille list hiển thị những gì đang có trên host. Để lặp lại một bản build, Bastille template lưu các bước trong một file rồi áp dụng chúng cho một jail. Đây là thứ gần với Dockerfile nhất trong hệ sinh thái này. Template được chạy lại trên từng jail. Không có gì được cung cấp dưới dạng build sẵn.
Tóm lại, vấn đề khá rõ. Docker cung cấp các bản build của người khác. Jail cung cấp các bản cài đặt do chính bạn thực hiện. Nếu phần mềm trong danh sách của bạn chỉ được phát hành dưới dạng container image và không có cách nào khác, thì câu hỏi đã được quyết định trước khi các tiêu chí khác được xem xét.
Trạng thái và nâng cấp: phần ZFS thay đổi
Docker cố ý tách trạng thái. Filesystem của container là phần có thể bỏ, còn dữ liệu nằm trong named volume hoặc bind mount. Quy trình nâng cấp là docker compose pull rồi docker compose up -d. Container được thay thế, và mọi thứ không được đặt trong volume sẽ mất. Đây là một tính năng khi bạn tuân thủ quy tắc, nhưng sẽ thành sự cố mất dữ liệu khi bạn quên. Vì vậy, việc chọn bind mount hay named volume có ảnh hưởng rất lớn trong một stack Compose.
Jail không tách trạng thái, và ZFS là lý do cách này hoạt động. Toàn bộ jail là một dataset:
sudo zfs snapshot zroot/jails/containers/web@pre-upgrade
sudo pkg -j web upgrade
sudo zfs rollback zroot/jails/containers/web@pre-upgradeKiểm tra tên dataset thực tế bằng zfs list trước khi chạy lệnh này. Đường dẫn trên là layout được handbook sử dụng. Việc tạo snapshot mất khoảng một giây và gần như không tốn thêm dung lượng cho đến khi nội dung của jail thay đổi. Nếu nâng cấp làm service lỗi, rollback sẽ đưa toàn bộ userland về trạng thái trước đó, bao gồm package database và các file cấu hình bạn đã sửa thủ công lúc 2am. Docker không có chức năng tương đương được tích hợp sẵn, vì model của Docker giả định rằng bạn không bao giờ cần chức năng này.
zfs clone là nửa còn lại. Clone của một snapshot là một jail mới có thể ghi, chia sẻ các block không thay đổi với jail gốc. Vì vậy, một bản staging của jail 3 GB gần như không tốn thêm dung lượng trên disk cho đến khi bạn bắt đầu thay đổi nó. Đây là cách admin FreeBSD tạo một jail “giống production” để diễn tập nâng cấp.
Nâng cấp base system tách biệt với packages. Với jail có bản sao userland riêng:
sudo freebsd-update -b /usr/local/jails/containers/web fetch installThin jail tránh phải lặp lại công việc đó. Thin jail mount một base dùng chung ở chế độ chỉ đọc thông qua nullfs và cấp cho mỗi jail một writable layer riêng. Nhờ vậy, bạn chỉ cần patch base một lần và mọi jail đều thấy kết quả. Bastille mặc định tạo thin jail.
Mạng: port được publish hay quyết định về cách đánh địa chỉ
Docker tự quyết định mô hình mạng và yêu cầu bạn publish các ngoại lệ. Container được đưa vào một bridge, chúng kết nối với nhau bằng service name trên user-defined network, còn -p 8080:80 expose một container ra host. Docker tự ghi các rule packet filter để thực hiện việc này. Đây cũng là lý do port của container được publish có thể đi thẳng qua ufw.
Jail buộc bạn chọn mô hình ngay từ đầu, và có 2 mô hình.
Shared IP. ip4.addr = "10.0.0.10" thêm địa chỉ đó vào một interface hiện có của host và giới hạn jail chỉ dùng địa chỉ này. Jail không có network stack riêng nên không thể chạy firewall riêng. Jail cũng không thể thực sự bind vào mọi địa chỉ: socket trong jail yêu cầu 0.0.0.0 sẽ bị kernel rewrite thành địa chỉ riêng của jail. 2 jail không thể cùng listen trên port 80 của cùng một địa chỉ. Vì vậy, bạn cấp cho mỗi jail một địa chỉ riêng hoặc đặt reverse proxy phía trước.
VNET. Thêm vnet; vào jail để jail có một network stack đầy đủ: interface riêng, routing table riêng và firewall rule riêng. Bạn kết nối jail với host bằng epair, một virtual cable có một đầu ở mỗi phía, rồi đặt đầu phía host vào một bridge. Đây là mô hình gần nhất với cách Docker hoạt động, đồng thời là mode đứng sau các loại jail -V và -B của Bastille.
Forward một port của host vào jail là một rule redirect pf. Bastille đóng gói thao tác này:
sudo bastille rdr web tcp 80 80Không có EXPOSE và cũng không có cơ chế publish tự động. Không có lưu lượng nào đến được jail nếu địa chỉ của jail hoặc một rule redirect không cho phép. Việc khởi đầu lâu hơn nhưng firewall yên tĩnh hơn nhiều.
Giới hạn tài nguyên: cgroups và rctl
Docker giới hạn container bằng cgroups. Các giới hạn nằm cùng nơi định nghĩa container: --memory=1g --cpus=1.5 trên command line hoặc các key tương ứng trong file Compose. Nếu bạn đã lưu stack trong một file Docker Compose trên VPS, giới hạn nằm cạnh service áp dụng giới hạn đó và đi cùng service trong git.
FreeBSD sử dụng rctl. Đây là một subsystem phải được bật thủ công. Mặc định, việc accounting tài nguyên bị tắt vì mỗi lần cấp phát đều chịu thêm một ít overhead. Thêm tunable vào /boot/loader.conf rồi reboot:
kern.racct.enable=1Sau đó đặt một rule và theo dõi rule đó:
sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:webrctl -hu jail:web in mức sử dụng hiện tại của jail theo đơn vị dễ đọc, để bạn biết jail đang gần giới hạn đến mức nào trước khi có lỗi. Action deny khiến lần cấp phát vượt giới hạn bị fail bên trong jail. Nhờ đó, bạn thấy lỗi cấp phát của chính ứng dụng thay vì thông báo kill trên host.
Các rule thêm bằng rctl -a sẽ mất sau lần reboot tiếp theo. Service rctl của FreeBSD sẽ reload chúng từ /etc/rctl.conf. Vì vậy, hãy ghi rule vào file đó và enable service:
sudo sysrc rctl_enable=YESĐây là điểm Docker rõ ràng tiện hơn. Giới hạn trong file Compose được review cùng service mà nó giới hạn. Một rule rctl là một dòng trong file riêng và dòng đó phải chỉ rõ một jail được định nghĩa ở nơi khác.
Khi cần một máy ảo: bhyve
Jail dùng chung kernel với host, nên một số khả năng luôn bị giới hạn. Jail không thể chạy một phiên bản kernel khác, không thể load kernel module và không thể chạy Linux binary theo cách Linux container thực hiện. FreeBSD có lớp tương thích Linux là linuxulator, nhưng lớp này chỉ triển khai một phần system call của Linux và không phải là giải pháp tổng quát cho mọi Linux image.
bhyve là hypervisor của FreeBSD. Đây là công cụ phù hợp khi bạn cần ranh giới thực sự giữa các máy: một hệ điều hành khác, một kernel khác hoặc một tenant mà bạn không muốn dùng chung kernel. Đổi lại, bạn phải dành riêng memory thay vì dùng chung và phải patch thêm một kernel. Đây cũng là quyết định tương tự khi chọn giữa container và full virtual machine trên Linux. Quyết định này xác định liệu bạn có cần một VPS hỗ trợ nested virtualization ở bên dưới hay không.
Hệ sinh thái là lý do thực tế khiến hầu hết đội ngũ dùng Docker
Toàn bộ phần trên nói về mô hình. Với hầu hết đội ngũ, yếu tố quyết định lựa chọn là quy mô của hệ sinh thái xung quanh mỗi mô hình.
Docker có Docker Hub và GHCR, docker compose, Kubernetes khi một máy không còn đủ, các CI runner đã được tích hợp sẵn hỗ trợ container, và hướng dẫn quickstart chạy bằng một lệnh trong README của gần như mọi project. Jails có FreeBSD ports tree lớn và được duy trì cẩn thận, cùng một bộ application bundle sẵn sàng chạy nhỏ hơn nhiều. Khi một project chỉ công bố container image mà không có cách triển khai nào khác, dùng FreeBSD nghĩa là bạn phải đọc documentation rồi tự lắp ráp các thành phần.
Jails phù hợp ở phía bên kia của sự đánh đổi đó. Bạn nên chọn jails khi đã chạy ZFS và coi trọng snapshot, rollback cho toàn bộ service; khi các service của bạn là ứng dụng native trên FreeBSD; khi muốn có một userland đầy đủ cho từng tenant thay vì chỉ một process; hoặc khi muốn kernel, packet filter, filesystem và documentation được duy trì cùng nhau như một hệ thống. Ý cuối cùng này là điều người ta nói đến khi gọi FreeBSD là một hệ thống nhất quán. Nội dung này được phân tích thêm trong so sánh rộng hơn giữa Linux và FreeBSD với vai trò nền tảng server và những thay đổi của FreeBSD 15 đối với việc sử dụng làm server.
Một nhận định cuối cùng. Nếu đội ngũ của bạn đã quen với Docker, chi phí chuyển đổi là có thật và lợi ích nhận được phải cụ thể. Đừng chuyển đổi chỉ vì chất lượng isolation; hai mô hình đủ tương đồng để cấu hình của bạn có ảnh hưởng lớn hơn. Hãy chuyển đổi vì muốn rollback toàn bộ service dựa trên ZFS, hoặc vì bạn đã dùng FreeBSD.
FAQ
Tôi có thể chạy Docker image trên FreeBSD không?
Không phải Linux image, và đây cũng không phải cách triển khai được hỗ trợ. FreeBSD có hỗ trợ OCI container: sudo pkg install -y podman-suite cài Podman, còn Podman chạy container thông qua ocijail, một runtime tạo jail thực sự ở bên dưới. Runtime này cần fdescfs được mount vào /dev/fd để theo dõi container, và cần pf cho container NAT (network address translation). OCI image native cho FreeBSD hoạt động tốt nhất. Linux image còn cần Linux compatibility layer, và tính đến tháng 8 năm 2026, port Podman trên FreeBSD vẫn được mô tả là experimental. Nếu deployment của bạn là một stack gồm Linux image, hãy chạy stack đó trên Linux. Với họ RHEL, bạn có thể cài Docker trên Rocky Linux hoặc AlmaLinux, nơi Podman lại xuất hiện lần thứ hai dưới dạng package đã sở hữu command docker trước khi bạn cài bất kỳ thứ gì.
FreeBSD jail có an toàn hơn Docker container không?
Cả hai đều dùng chung một host kernel, nên một kernel bug có thể gây rủi ro cho cả hai. Không loại nào là boundary phù hợp cho code thực sự không đáng tin cậy. Điểm khác biệt nằm ở trạng thái mặc định. Jail bắt đầu với một tập operations lớn bị từ chối, rồi bạn bật lại từng operation bằng từng parameter. Docker container bắt đầu với root bên trong một tập namespace, đồng thời một số capability đã bị loại bỏ; các bước hardening bổ sung là tùy chọn. Trong thực tế, cấu hình ảnh hưởng nhiều hơn model: một jail chạy với allow.mount và allow.raw_sockets được bật không an toàn hơn một container được cấu hình cẩn thận.
Tôi sao lưu một jail bằng cách nào?
Tạo snapshot cho dataset rồi gửi snapshot đó đi. sudo zfs snapshot zroot/jails/containers/web@backup, sau đó zfs send snapshot đó sang pool khác hoặc vào một file để bạn copy ra khỏi máy. Vì jail lưu toàn bộ userland trong một dataset, snapshot sẽ chứa các package đã cài và dữ liệu tại cùng một thời điểm nhất quán, cùng với mọi file cấu hình bạn đã chỉnh sửa thủ công. Điều này trái ngược với cách làm phổ biến với Docker, trong đó bạn sao lưu named volume và file Compose, rồi dựng lại phần còn lại từ image.
Tôi có cần BastilleBSD không, hay base system là đủ?
Base system là đủ, và đây là nơi nên bắt đầu. jail.conf, jls, jexec và service jail start bao phủ toàn bộ model. Khi đã biết các command này, bạn có thể đọc cấu hình của bất kỳ FreeBSD host nào mà không cần học tooling riêng của host đó trước. Bastille là một convenience layer phía trên base system: nó bootstrap release, tạo thin jail, áp dụng template và tự viết rule redirect pf cho bạn. Hãy học các command của base system trước, sau đó thêm Bastille khi số lượng jail khiến việc gõ lệnh trở nên mất thời gian.