FreeBSD jail vs Docker container: khác nhau gì?
So sánh jail và Docker về userland, image layer, state, networking và giới hạn. Hiểu khi nào cần userland FreeBSD đầy đủ thay vì một process từ registry.
FreeBSD jail và Docker container, trong một đoạn
FreeBSD jail và Docker container 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à máy ảo. Điểm khác nhau nằm ở nội dung chạy bên trong. Docker container chạy một process từ image dạng layer được pull từ registry. Jail chạy một userland FreeBSD hoàn chỉnh: có /etc riêng, các script khởi động rc riêng, database pkg riêng và có thể chạy bao nhiêu 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 hướng dẫn cài đặt cho một máy 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, nên có trước cgroups và sớm hơn Docker khoảng một thập kỷ. Cơ chế này chỉ dùng một kernel call. jail(8) nhận một cây thư mục, khởi động các process bên trong đó và gắn vào chúng một jail ID. Sau đó, kernel từ chối một tập hợp thao tác cố định đối với mọi process mang ID đó. Process trong jail không thể thấy 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 chưa được cấp. Bạn không cần học một loại namespace riêng và cũng không cần bật từng tính năng riêng lẻ. Các giới hạn được áp dụng như một đơn vị duy nhất và được điều chỉnh bằng các parameter trong config 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 sẽ làm việc đó cho bạn:
sudo bsdinstall jail /usr/local/jails/containers/webLệnh này tải bộ base distribution tương ứng với release của bạn và chạy các bước post-install 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 bạn đưa phần mềm vào hệ thống: image và registry so với userland tự cài đặt
Đây là khác biệt bạn sẽ cảm nhận ngay từ ngày đầu.
Với Docker, bạn chỉ định phần mềm rồi nhận được phần mềm đó. docker pull nginx tải về một image phân lớp, đượ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 đã gắn vào. 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ờ đó, chạy Docker trên VPS trở thành một công việc ngắn thay vì một dự án.
FreeBSD không có 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ư thiết lập một bare server. Cách này cần nhập nhiều lệnh hơn. Đổi lại, nó minh bạch hơn, vì những gì chạy trong jail chính là những gì pkg đã đưa vào, từ cùng một tập package mà host sử dụng.
Tooling giúp quy trình này ngắn hơn. BastilleBSD là jail manager phổ biến và được phát hành 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 tháng 6 năm 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à đưa phần mềm vào đó cần thêm một lệnh:
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 cho bạn một login shell bên trong jail, còn bastille list hiển thị những gì đang tồn tại trên host. Để lặp lại một build, Bastille template lưu các bước trong một file rồi áp dụng chúng vào 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 đưa vào dưới dạng build sẵn.
Tóm lại, vấn đề rất rõ ràng. Docker cung cấp cho bạn các build của người khác. Jail cung cấp cho bạn các bản cài đặt do chính bạn tạo. 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ó hình thức nào khác, thì câu trả lời đã rõ 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 riêng trạng thái. Filesystem của container có thể loại bỏ, còn dữ liệu nằm trong named volume hoặc bind mount, và quy trình nâng cấp là docker compose pull rồi docker compose up -d. Container bị thay thế, mọi thứ không được đặt trong volume sẽ mất. Đây là một tính năng nếu bạn tuân thủ quy tắc này, nhưng sẽ gây mất dữ liệu nếu bạn quên. Vì vậy, lựa chọn giữa bind mount và named volume có vai trò rất quan trọng trong một Compose stack.
Jail không tách riêng trạng thái, và ZFS là lý do mô hình 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. Snapshot được tạo trong 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 bản 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 tích hợp sẵn vì mô hình của Docker giả định rằng bạn không 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 nâng cấp package. 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 này. Thin jail mount một base dùng chung, chỉ đọc, qua nullfs, đồng thời cấp cho mỗi jail một writable layer riêng có kích thước nhỏ. Nhờ đó, 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ề mô hì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 đặt trên một bridge, có thể 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 packet filter rule để 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 yêu cầu bạn chọn mô hình ngay từ đầu. 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ỉ: khi socket trong jail yêu cầu 0.0.0.0, kernel sẽ rewrite yêu cầu đó thành địa chỉ riêng của jail. Hai jail không thể cùng listen trên port 80 của cùng một địa chỉ. Vì vậy, bạn phải cấp cho mỗi jail một địa chỉ riêng hoặc đặt mộ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, tức một virtual cable có một đầu ở mỗi bên, rồi đặt đầu phía host lên một bridge. Đây là mô hình gần nhất với cách Docker cung cấp mạng, đồng thời là mô hình đứ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 redirect rule pf. Bastille cung cấp wrapper cho rule 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 lưu lượng nào có thể đến jail nếu địa chỉ của jail hoặc redirect rule không cho phép. Cách này khởi động chậm 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 ngay nơi container được định nghĩa: --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 nó và được giữ cùng service đó trong git.
FreeBSD dùng rctl. Đây là một subsystem mà bạn phải bật. Mặc định, việc accounting tài nguyên bị tắt vì mỗi lần cấp phát đều phát sinh 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 nó đang gần giới hạn đến đâu 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 được thêm bằng rctl -a sẽ mất sau lần reboot tiếp theo. Service rctl của FreeBSD sẽ nạp lại 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 thuận 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 nằm ngoài phạm vi của nó. Jail không thể chạy 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 Linux system call và không phải 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ật 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ự trên Linux khi chọn giữa container và full virtual machine. Quyết định này xác định 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 cung cấp Docker Hub và GHCR, docker compose, Kubernetes khi một máy không còn đủ, các CI runner đã tích hợp sẵn hỗ trợ container, cùng hướng dẫn khởi động nhanh bằng một lệnh trong README của gần như mọi project. Jails cung cấp FreeBSD ports tree lớn và được duy trì cẩn thận, cùng một tập nhỏ hơn nhiều các application bundle có thể chạy ngay. Khi một project chỉ phát hành container image mà không cung cấp tùy chọn nào khác, cách dùng FreeBSD là đọc tài liệu 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 dùng 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à 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à tài liệu được duy trì cùng nhau như một hệ thống. Ý cuối cùng này là điều người ta muốn nói khi gọi FreeBSD là một hệ thống nhất quán. Nội dung này được trình bày rộng hơn trong phần so sánh Linux và FreeBSD trên 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 đủ gần nhau để cấu hình của bạn có ảnh hưởng lớn hơn. Hãy chuyển đổi vì bạn muốn rollback toàn bộ service dựa trên ZFS, hoặc vì bạn đã sử dụng FreeBSD.
FAQ
Tôi có thể chạy Docker image trên FreeBSD không?
Không chạy được Linux image theo cách được hỗ trợ. FreeBSD có hỗ trợ container OCI: sudo pkg install -y podman-suite cài Podman, công cụ này chạy container thông qua ocijail, một runtime tạo jail thực bên dưới. Công cụ này cần fdescfs được mount tại /dev/fd cho container monitor, và cần pf cho NAT của container (network address translation). OCI image native cho FreeBSD hoạt động tốt nhất. Linux image còn cần lớp tương thích Linux, và tính đến tháng 8 năm 2026, port Podman của 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 nó trên Linux.
FreeBSD jail có bảo mật hơn Docker container không?
Cả hai đều dùng chung kernel của host, nên một lỗi kernel sẽ gây rủi ro cho cả hai. Không mô hình 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 hợp lớn operation 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, trong đó một số capability đã bị loại bỏ; các biện pháp hardening bổ sung là tùy chọn. Trong thực tế, cấu hình quyết định nhiều hơn mô hình: một jail bật allow.mount và allow.raw_sockets 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 như thế 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ẽ ghi lại 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 đã sửa thủ công. Điều này ngược với cách làm phổ biến của 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à cũng là nơi tốt hơn để bắt đầu. jail.conf, jls, jexec và service jail start bao quát toàn bộ mô hình. Khi đã biết các lệnh này, bạn có thể đọc hiểu 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 lớp tiện ích phía trên: công cụ này bootstrap release, tạo thin jail, áp dụng template và ghi rule redirect pf thay cho bạn. Hãy học các lệnh base system trước, rồi thêm Bastille khi số lượng jail khiến việc nhập lệnh trở nên mất thời gian.