blogbyAndrew

gVisor vs Firecracker: Hai triết lý sandbox container

May 27, 2026

Red neon SECURITY sign, representing the workload isolation gVisor and Firecracker provide

Photo by Peter Conrad on Unsplash

Mở đầu — Vì sao container truyền thống chưa đủ?

Container "kiểu runc" (namespace + cgroup + seccomp) gần như là chuẩn de facto của workload hiện đại. Nhưng có một nhược điểm không thể né: tất cả container trên cùng một host đều chia sẻ duy nhất một kernel Linux. Toàn bộ ranh giới giữa "code của tôi" và "code của khách hàng khác" nằm gọn trong khoảng 350 syscall của kernel host. Một lỗ hổng nâng quyền trong kernel (CVE-2022-0185, Dirty Pipe, CVE-2017-7308 AF_PACKET, …) là đủ để một container thoát ra và đụng tới các workload khác.

Với 95% workload nội bộ, đánh đổi này chấp nhận được. Nhưng có ba lớp use case mà nó không chấp nhận được:

Hai cách tiếp cận đối lập nhau xuất hiện để giải bài toán này, đều ra mắt năm 2018 và đều mã nguồn mở:

gVisor (Google): thay vì để container gọi syscall trực tiếp xuống kernel host, dựng một "application kernel" trong userspace (viết bằng Go) — gọi là Sentry — đứng giữa. Sentry tự reimplement ~237 syscall Linux bằng code Go memory-safe, và chỉ gọi xuống host khoảng 68 syscall (53 nếu không có network). Attack surface co từ 350 xuống 68. (Nguồn: gVisor Security Basics Part 1)

Firecracker (AWS): thay vì tìm cách "lọc" syscall, ép mỗi workload vào một microVM riêng — một VM thực sự dùng KVM nhưng cực kỳ trimmed (~50K dòng Rust, không PCI, không USB, không BIOS, không graphics). Boot trong dưới 125ms với overhead RAM chỉ ~5MB. Mỗi function Lambda chạy trong một microVM Firecracker riêng. (Nguồn: Firecracker NSDI 2020 paper)

Cả hai đều giải cùng một bài toán — cô lập workload không tin tưởng, ở mật độ cao, với cost thấp — nhưng chọn hai triết lý kỹ thuật trái ngược: gVisor đi từ trên xuống (intercept syscall ở tầng application), Firecracker đi từ dưới lên (đưa workload vào VM riêng). Bài viết này mổ xẻ chi tiết từng cái, rồi đặt chúng cạnh nhau.

Nếu bạn đã đọc bài Container the Hard Way, section này có thể coi như phần "mở rộng deep dive" cho phần Beyond runc — chúng ta sẽ đi sâu vào từng kiến trúc, thay vì chỉ điểm qua.

gVisor — Application kernel trong userspace

Triết lý: "Eo thắt" syscall

Linux phiên bản 5.3.11 có khoảng 350 syscall. Trong một container runc thông thường, tất cả 350 syscall đó đều là attack surface — chỉ cần một lỗ hổng trong bất kỳ syscall nào là một process trong container có thể leo quyền lên host.

gVisor đặt một "application kernel" tên là Sentry đứng giữa container và host kernel. Sentry tự implement lại 237 syscall Linux bằng Go memory-safe. Khi container gọi open() hay read(), Sentry intercept syscall đó và xử lý hoàn toàn trong userspace. Bản thân Sentry — toàn bộ Sentry, phục vụ mọi syscall của container — chỉ gọi xuống host kernel 53 syscall (hoặc 68 nếu bật networking). Tỷ lệ là ~5:1: 350 syscall "tiềm năng" co lại thành 68 syscall "thực sự" reach được host. (Nguồn: gVisor Security Basics Part 1)

Sentry: Một application kernel viết bằng Go, chạy hoàn toàn trong userspace. Đảm nhiệm vai trò của một kernel mini — implement syscall, memory management, signal delivery, scheduling, page faulting, threading. Container "thấy" Sentry như là kernel Linux thật.

Gofer: Một process host riêng được spawn cùng container, đóng vai trò trusted filesystem proxy. Sentry không được phép open() file trên host (seccomp chặn) — mọi truy cập filesystem phải đi qua Gofer. Đây là một thanh chắn quan trọng: nếu Sentry bị compromise, attacker vẫn không tự open("/etc/shadow") được, mà phải xin Gofer.

runsc: Binary entrypoint, OCI-compliant. Đóng gói Sentry + Platform + Gofer thành một runtime mà Docker / containerd / Kubernetes có thể plug-in qua --runtime=runsc hoặc RuntimeClass.

Sơ đồ kiến trúc

gVisor architecture diagram showing application calling into Sentry which then makes restricted host syscalls

Layer model của gVisor. Nguồn: gvisor.dev/docs/

Diagram trên minh hoạ ý tưởng "eo thắt": container phía trên có thể gọi 350 syscall (Application Calls), nhưng Sentry chỉ chuyển một tập rất nhỏ trong số đó xuống host (Limited System Calls). Phần bên trái trong bản đầy đủ trên gvisor.dev là Gofer — proxy filesystem riêng. Cả Sentry lẫn Gofer đều chạy với seccomp filter cực hẹp và bị pivot_root vào thư mục rỗng để không thể path-traverse.

Platform — cách Sentry bắt syscall của container

Một câu hỏi cơ bản: khi container gọi getpid(), làm sao Sentry chặn được? gVisor có ba "platform" tương ứng với ba cơ chế intercept khác nhau (Nguồn: Platform Guide):

PlatformCơ chế bắt syscallTrạng thái
ptracePTRACE_SYSEMU trap mỗi syscallDeprecated (chậm)
KVMSentry vừa là guest OS vừa là VMM, dùng hardware virtualizationTốt trên bare-metal, kém trong nested-virt
Systrapseccomp SECCOMP_RET_TRAP gửi SIGSYSDefault (từ giữa 2023)

ptrace là cách đơn giản nhất: kernel "pause" thread của container mỗi khi nó issue syscall, đẩy nó cho Sentry xử lý, rồi resume. Vấn đề: mỗi syscall = 4 context switch user/kernel/user/kernel. Workload syscall-heavy (Redis, web server nhỏ) chậm thê thảm. Đã bị deprecate.

KVM dùng hardware virtualization: Sentry tự đóng vai trò guest kernel VMM, container chạy trong "ring 3" của một mini-VM, mỗi syscall trap qua VMEXIT. Nhanh khi chạy trên bare-metal hoặc machine có hardware virt enabled, nhưng trong VM của cloud (nested virtualization) thì chậm và nhiều khi không khả dụng.

Systrap là default mới từ giữa 2023. Kernel cài seccomp filter trên thread container với SECCOMP_RET_TRAP — khi thread gọi syscall, kernel raise SIGSYS cho thread đó, signal handler đẩy context sang Sentry. Đây là sự hợp lý giữa ptrace (universal nhưng chậm) và KVM (nhanh nhưng đòi hardware): Systrap chạy ổn trên cả bare-metal lẫn cloud VM.

Sentry — kernel mini bằng Go

Sentry implement bằng pure Go (không CGo), và phần unsafe được tách riêng vào các file được audit kỹ. Tại sao Go? Memory safety. Một lỗ hổng kiểu use-after-free hay buffer overflow trong implementation của syscall — vốn là nguồn của hầu hết kernel CVE — trở nên khó/không thể xảy ra trong Go. Một CVE kernel ảnh hưởng đến code C của Linux gần như chắc chắn không ảnh hưởng đến Sentry, vì Sentry là code Go viết lại từ đầu.

Một ví dụ thực tế: CVE-2020-14386 là một bug leo quyền trong AF_PACKET của kernel Linux. gVisor không bị ảnh hưởng vì Sentry tự implement packet socket code bằng Go — code Linux có bug, code Sentry hoàn toàn khác.

Gofer và filesystem — sự tiến hoá

Filesystem là phần phức tạp nhất của gVisor. Sentry không có quyền open() file host — vậy làm sao container mở được file?

Trả lời: Gofer. Khi runsc start một sandbox, nó cũng spawn một process Gofer riêng. Gofer có quyền truy cập rootfs của container (thông qua bind mount), Sentry thì không. Sentry và Gofer giao tiếp qua một Unix socket.

Giao thức giao tiếp đã thay đổi 3 lần:

Thế hệNămCơ chếTrade-off
9P2018+Mỗi path component = 1 RPC. stat("/a/b/c") = 3+ RPCĐơn giản, nhưng chatty và chậm
LISAFS2022Linux Sandbox FS protocol, walk nhiều component trong 1 RPCGofer memory giảm 30-60%, cold start App Engine nhanh hơn 25%
Directfs2023 (DEFAULT)Gofer pass raw file descriptor sang Sentry qua SCM_RIGHTS. Sentry gọi openat() trực tiếp trên FD đóstat nhanh hơn 2x, Ruby load nhanh hơn 17%

Directfs là cú breakthrough: Sentry vẫn không có quyền open path tuỳ ý, nhưng được Gofer cấp sẵn FD vào rootfs. Sentry dùng openat(fd, "relative/path") đi tiếp — vẫn an toàn vì bị pivot_root vào thư mục rỗng, không có cách nào "leo" ra khỏi FD đó. (Nguồn: Directfs blog, LISAFS announcement)

gVisor resource model showing sandbox containing Sentry, with Gofer outside as filesystem proxy

Resource model: sandbox bao quanh Sentry, Gofer đứng ngoài làm filesystem proxy. Nguồn: gvisor.dev/docs/architecture_guide/resources/

Networking — Netstack TCP/IP bằng Go

gVisor mặc định dùng Netstack — một stack TCP/IP đầy đủ viết bằng Go. Container gọi socket(), connect(), send() — Sentry chuyển những syscall đó cho Netstack xử lý. Netstack tự ráp packet TCP/UDP, IP header, rồi đẩy frame raw xuống host qua một AF_PACKET socket.

Hệ quả: một CVE TCP stack Linux không đụng được Netstack. Cái giá: Netstack chưa support đầy đủ các cơ chế recovery cao cấp như Linux (CUBIC variants, BBR tweaks, …) và CPU efficiency thấp hơn ~20-30% so với kernel Linux.

Có chế độ thay thế passthrough (--network=host): Sentry chuyển syscall socket xuống thẳng host kernel. Nhanh hơn nhưng đánh đổi isolation — gần như trở về như container runc về mặt network attack surface. (Nguồn: Networking guide)

Security model — ba lớp phòng thủ

gVisor security threat model showing isolation layers between container and host kernel

Threat model. Nguồn: gvisor.dev/docs/architecture_guide/security/

Theo doc chính thức (Security Model), gVisor có ba nguyên tắc defense-in-depth:

  1. No syscall bypass — Mỗi syscall có một implementation Go độc lập. CVE syscall X trong Linux không tự động đụng được syscall X trong gVisor.
  2. Limited functionality — Chỉ implement những phần phổ biến. Phần "exotic" (xattrs, raw socket, hầu hết ioctl, io_uring đầy đủ) bị bỏ. Càng ít feature = càng ít attack surface.
  3. Restricted host exposure — Syscall mà Sentry được phép gọi xuống host được enumerate cứng và enforce bằng seccomp filter ngay khi Sentry start.

Khi nào gVisor chậm, khi nào nhanh

Hiểu rõ cost model là chìa khoá khi quyết định dùng gVisor (Nguồn: Performance guide).

Nhanh (gần như zero overhead):

Chậm (đáng kể):

Sử dụng thực tế

Tại Google: App Engine standard, Cloud Functions, Cloud ML Engine, Cloud Run 1st-gen (đời sau dùng microVM kiểu Firecracker, nhưng 1st-gen vẫn dùng gVisor). Ngoài Google: nhiều CI runner cho code không tin tưởng, sandbox AI agent, OSS Fuzz.

Tích hợp Kubernetes:

bash
# Cách 1: GKE Sandbox managed
gcloud container node-pools create sandbox-pool \
  --cluster=my-cluster \
  --sandbox=type=gvisor
 
# Trong Pod manifest:
spec:
  runtimeClassName: gvisor
  containers: [...]
bash
# Cách 2: Self-managed (containerd shim)
# Install containerd-shim-runsc-v1, register RuntimeClass:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc

Docker chỉ cần sudo runsc install && docker run --runtime=runsc ubuntu. (Nguồn: gVisor Quick Start)

Firecracker — Hypervisor tối giản cho serverless

Triết lý: "Container VM"

Trong khi gVisor tránh né hardware virtualization (vì lý do cloud nested-virt), Firecracker đi ngược lại: ôm chặt KVM. Nhưng nó cắt bỏ tất cả những phần "rườm rà" của một hypervisor truyền thống. AWS đặt câu hỏi đúng: "a virtual machine would look like if it was designed for today's world of containers and functions?" (AWS announcement 2018)

Câu trả lời: một VMM chỉ 50.000 dòng Rust (so với QEMU >1.4 triệu dòng C). Không PCI, không USB, không graphics, không sound, không BIOS, không ACPI enumeration, không CPU instruction emulation. Chỉ giữ những thứ tối thiểu để guest kernel boot và phục vụ một workload kiểu container: virtio block, virtio net, virtio vsock, một virtio balloon, một serial console, và một i8042 keyboard controller bé tí — chỉ để guest có thể gửi tín hiệu reset/shutdown.

Firecracker: Một Virtual Machine Monitor (VMM) viết bằng Rust, chạy trên KVM. Mỗi process Firecracker quản lý đúng một microVM. Boot guest kernel trong dưới 125ms, overhead RAM khoảng 5MB, có thể tạo 150 microVM/giây mỗi host. Là engine đằng sau AWS Lambda và AWS Fargate.

microVM: Một VM thực sự (dùng KVM, có guest kernel riêng) nhưng được trimmed cực nhỏ — không PCI, không BIOS, chỉ có virtio device tối thiểu. Mục tiêu: chạy một container-like workload với boot time và density gần với container, mà cô lập ở tầng hardware như VM.

Jailer: Process wrapper khởi chạy Firecracker. Cài chroot, cgroup, namespace, seccomp filter, drop privilege, rồi mới exec() vào Firecracker binary. Đây là lớp phòng thủ thứ hai — nếu guest thoát được KVM, vẫn rơi vào một jail unprivileged.

Tại sao nhỏ thế?

Firecracker được fork từ crosvm của Chromium OS (cũng là một minimal VMM Rust cho Chrome OS). Bước đầu xoá hơn nửa code — bỏ tất cả phần liên quan đến desktop, GPU, audio. Sau đó rewrite core. Rust được chọn vì memory safety: hầu hết CVE hypervisor lịch sử đến từ bug C kiểu use-after-free, buffer overflow trong device emulation. Rust loại được cả lớp lỗi đó. (Nguồn: LWN, Micah Lerner summary)

Việc bỏ PCI nghe có vẻ kinh khủng nhưng nhìn kỹ rất hợp lý: serverless function không cần plug-and-play device discovery. Guest biết trước nó có virtio-net, virtio-block ở địa chỉ cố định. Bỏ BIOS — kernel boot trực tiếp từ entry point, không cần real-mode setup. Bỏ ACPI — không có power management dynamic. Tất cả những gì còn lại là một stub guest tối giản, đủ để Linux kernel boot và chạy.

Sơ đồ kiến trúc

Firecracker host integration diagram showing Firecracker process, KVM, microVM with guest kernel, jailer, and virtio devices

Firecracker host integration. Nguồn: firecracker-microvm.github.io

Threading model

Mỗi Firecracker process có ba loại thread (design doc):

text
+--------------------- Firecracker process ----------------------+
|  Unix socket (REST API, control plane) <----+                  |
|                                             |                  |
|  +-------------+  +--------------+  +----------------------+   |
|  | API thread  |  | VMM thread   |  | vCPU thread(s)       |   |
|  | (HTTP/UDS,  |  | (devices,    |  | (loop:               |   |
|  |  control)   |  |  IRQ chip,   |  |   ioctl(KVM_RUN))    |   |
|  |             |  |  PIO/MMIO)   |  |   -> guest code runs |   |
|  +-------------+  +--------------+  +----------------------+   |
|         ^                ^                     |               |
|         +----------------+------ ioctl KVM_* --+               |
+----------------------------------------------------------------+

Control plane (REST API qua Unix socket) tách bạch với data plane (vCPU và virtio queue). Một orchestrator gọi API để config CPU, RAM, attach drive, network, snapshot — sau đó vCPU thread tự chạy guest code qua ioctl(KVM_RUN). Có token-bucket rate limiter built-in per-device cho ops/sec và bandwidth.

Jailer — defense in depth

Firecracker tự nó đã rất nhỏ (TCB chỉ ~50K LoC Rust), nhưng AWS vẫn không tin. Threat model giả định: "vCPU thread chạy malicious code ngay từ giây thứ nhất sau boot". Vậy nếu một CVE trong virtio device cho phép guest đọc bộ nhớ host thì sao?

Jailer là câu trả lời. Trước khi exec() vào binary Firecracker, jailer làm tuần tự (Nguồn: design doc):

text
1. Tạo cgroup (v1/v2): giới hạn CPU, memory, PIDs
2. unshare(CLONE_NEW{PID,NET,MNT,IPC,UTS}): tách namespace
3. chroot(/firecracker/jail/<id>): khoá filesystem vào jail
4. setresuid()/setresgid(): drop về UID unprivileged
5. Load seccomp-bpf filter: whitelist ~24 syscall + ~30 ioctl
6. exec("firecracker"): chuyển sang binary chính

Sau bước 6, ngay cả nếu Firecracker bị hack từ bên trong, attacker chỉ thấy: một jail rỗng, không có UID 0, không có quyền sang namespace khác, không có syscall nào ngoài 24 cái được whitelist. Hai lớp barrier: KVM + jailer.

Snapshot/restore — bí mật của Lambda SnapStart

Một trong những feature kỹ thuật ấn tượng nhất của Firecracker là snapshot: dump toàn bộ state của microVM (memory page, device state, CPU register, vCPU state) ra disk, rồi restore lại trong vài mili-giây. (Nguồn: Marc Brooker — SnapStart blog)

AWS Lambda SnapStart dùng ý tưởng này cho cold start:

text
Cold start truyền thống:
  boot kernel (100ms) -> init runtime (500ms-3s) -> load code -> chạy
  = vài giây cho lần đầu

Cold start với SnapStart:
  [build-time, offline]
    boot kernel -> init runtime -> chạy customer init code -> SNAPSHOT
  [request time]
    RESTORE từ snapshot -> chạy handler  (4-10ms)

Một số chi tiết tinh tế:

SnapStart hiện GA cho Java 11+, Python 3.12, .NET 8 — AWS quảng bá giảm cold start tới 10x. (Nguồn: AWS SnapStart docs)

Performance numbers

Lấy từ paper NSDI '20 (Agache et al.) và Adrian Colyer's summary:

MetricFirecrackerCloud HypervisorQEMU
Memory overhead / VM~3-5 MiB~13 MB~131 MB
Boot time tới userspace~100-125 ms~200 msseconds
VM creation rate~150 / giâythấp hơnnhiều thấp hơn
Block I/O 4KB (max)~13,000 IOPS / 52 MB/scao hơnnative 340k+

Đây là điểm yếu thừa nhận: Firecracker không thay thế hypervisor general-purpose. Workload I/O block performance critical (database VM) thì QEMU + virtio-blk-vhost vẫn nhanh hơn nhiều lần. Firecracker tối ưu cho densityboot time, không cho throughput.

Density trong sản xuất:

API — REST qua Unix socket

Firecracker không có CLI flag phức tạp. Toàn bộ lifecycle qua REST:

bash
# Start daemon (jailed)
firecracker --api-sock /tmp/fc.sock
 
# Set kernel + rootfs
curl --unix-socket /tmp/fc.sock -X PUT 'http://localhost/boot-source' \
  -d '{"kernel_image_path": "vmlinux", "boot_args": "console=ttyS0 root=/dev/vda"}'
 
curl --unix-socket /tmp/fc.sock -X PUT 'http://localhost/drives/rootfs' \
  -d '{"drive_id": "rootfs", "path_on_host": "rootfs.ext4", "is_root_device": true}'
 
# CPU / RAM
curl --unix-socket /tmp/fc.sock -X PUT 'http://localhost/machine-config' \
  -d '{"vcpu_count": 1, "mem_size_mib": 256}'
 
# Network
curl --unix-socket /tmp/fc.sock -X PUT 'http://localhost/network-interfaces/eth0' \
  -d '{"iface_id": "eth0", "host_dev_name": "tap0"}'
 
# Start
curl --unix-socket /tmp/fc.sock -X PUT 'http://localhost/actions' \
  -d '{"action_type": "InstanceStart"}'

Đơn giản, declarative. Đây là điểm mạnh khi build platform — orchestrator chỉ cần gọi HTTP, không phải parse log/output.

Production use beyond AWS

Liên hệ với Nitro Hypervisor

Câu hỏi hay gặp: Firecracker có phải Nitro Hypervisor không? Không. Nitro Hypervisor là một hypervisor riêng (cũng dựa trên KVM), chạy trên Nitro Card (silicon AWS thiết kế) để cô lập EC2 instance. Firecracker chạy bên trong EC2 instance (thường là Nitro-based) để cung cấp lớp isolation thứ hai cho Lambda/Fargate workload. (Nguồn: AWS Nitro)

Phân lớp: Nitro cô lập customer (instance to instance), Firecracker cô lập workload (function to function trong cùng một customer).

So sánh trực tiếp gVisor vs Firecracker

Hai công nghệ giải cùng một bài toán bằng hai triết lý ngược nhau. Section này đặt chúng cạnh nhau theo từng góc.

Bản đồ kiến trúc — vị trí của ranh giới isolation

text
            gVisor                                Firecracker
  +-----------------------+            +-----------------------+
  | Container app         |            | Guest userspace (app) |
  | (Linux syscalls)      |            |                       |
  +-----------------------+            +-----------------------+
             |                                     |
             v  (syscall intercepted)              v
  +-----------------------+            +-----------------------+
  | Sentry (Go userspace) |            | Guest kernel (Linux)  |
  | - reimpl 237 syscalls |            | (full kernel, yours)  |
  | - Netstack TCP/IP     |            +-----------------------+
  +-----------------------+                        |
             |                                     v  (VMEXIT)
             v  (53-68 host syscalls)  +-----------------------+
  +-----------------------+            | Firecracker VMM (Rust)|
  | Host Linux kernel     |            | + KVM ioctls          |
  +-----------------------+            +-----------------------+
                                                   |
                                                   v
                                       +-----------------------+
                                       | Host Linux kernel+KVM |
                                       +-----------------------+

Điểm cốt lõi: ở đâu là ranh giới giữa "code không tin tưởng" và "host kernel"?

Bảng so sánh đầy đủ

ChiềugVisorFirecracker
Loại isolationApplication kernel (userspace)Hardware virtualization (KVM)
Ngôn ngữGo (memory-safe)Rust (memory-safe)
Cô lập syscall quaReimplement trong SentryGuest kernel + KVM
Có guest kernel?KhôngCó (Linux đầy đủ)
Linux ABI compatMột phần (~237/350 syscall)100% (chạy Linux kernel thật)
Boot timeVài chục đến vài trăm ms~125 ms
Memory overhead~15 MB (Sentry + Gofer)~5 MB (chưa tính guest kernel)
Density / hostHàng nghìnHàng nghìn
Syscall overheadĐáng kể (10-30% cho syscall-heavy)Gần native (sau khi boot)
Network performanceNetstack chậm hơn Linux ~20-30%Native (qua TAP)
I/O storageQua Gofer (giờ là Directfs, gần native)virtio-blk (≤ kernel native, OK)
Hardware virt yêu cầuKhông (Systrap không cần)Có (KVM bắt buộc)
Nested virtualizationOKKhả dụng nhưng ít dùng
GPU passthroughNVIDIA supportKhông (không có PCI)
OCI compatibleCó (runsc)Không (cần firecracker-containerd)
Use case chínhSandbox code không tin tưởng, GKE SandboxServerless (Lambda), micro-VM platform
Mã nguồn~600K LoC Go~50K LoC Rust
Open-sourced2018 (Google)2018 (AWS)

Attack surface — khác biệt căn bản

Đây là điểm khác biệt nền tảng nhất.

gVisor giảm attack surface bằng cách thu hẹp số syscall reach được host kernel: 350 -> 68. Container code phải xuyên qua Sentry, và Sentry chỉ "đụng" host qua 68 syscall được whitelist. Một CVE kernel trong syscall không nằm trong 68 đó không tồn tại về mặt thực tế trong môi trường gVisor.

text
[Container code]
   v
   |  ~237 syscalls go through Sentry (Go reimpl)
   v
[Sentry]
   v
   |  53-68 host syscalls (seccomp-enforced)
   v
[Host kernel]

Firecracker giữ toàn bộ syscall surface — nhưng surface đó nằm ở guest kernel của container, không phải host. Code trong guest có thể exploit guest kernel — nhưng để thoát ra host, phải:

  1. Compromise guest kernel (350 syscall, đầy đủ Linux)
  2. Compromise virtio device emulation trong Firecracker VMM (~50K LoC Rust)
  3. Hoặc tìm bug trong KVM (host kernel module)
  4. Sau đó vẫn rơi vào jailer sandbox (seccomp, chroot, namespace)

Hai cách tiếp cận khác nhau triết lý: gVisor hạn chế attack surface, Firecracker xếp lớp attack surface.

Hệ quả thực tế: nếu attacker khai thác được một kernel CVE, gVisor có khả năng cao "miễn nhiễm" (vì Sentry reimplement code). Firecracker thì guest kernel của bạn cũng có CVE đó — nhưng escape ra host vẫn phải qua KVM/jailer barrier.

Compatibility — đâu mới là "Linux thật"?

Firecracker = guest kernel Linux thật. Bạn boot bất kỳ kernel Linux nào (5.10+, 6.x, custom). Mọi syscall hoạt động đúng như Linux, mọi /proc, mọi ioctl, mọi feature mới đều có. Workload chạy "as is".

gVisor = subset của Linux. ~237/350 syscall implement, một số phần:

Với 90% workload web (Node, Python, Go service), không vấn đề gì. Với workload "low-level" (cần perf, cần eBPF, cần io_uring cao cấp): chọn Firecracker.

Performance — khác biệt theo workload

Không có "cái nào nhanh hơn" — phụ thuộc workload:

WorkloadgVisorFirecracker
CPU-bound computeGần nativeGần native
Cold start (1 lần)100-300 ms~125 ms
Cold start + snapshotKhông có4-10 ms (SnapStart)
Memory / instance~15 MB (Sentry+Gofer)~5 MB + guest kernel ~20 MB
Network throughputNetstack: -20%; passthrough: 0%Native (qua TAP)
Storage IOPSDirectfs: gần nativevirtio-blk: OK nhưng có cap
Syscall-heavy-10% đến -50%Gần native
Memory pressure / densityTốt (không có guest kernel)Tốn hơn

Nói gọn: Firecracker tốt hơn cho workload cần performance bám sát native nhưng vẫn cần strong isolation. gVisor tốt hơn cho workload mà cost của một guest kernel quá lớn (vd. hàng triệu function ngắn, mỗi cái <50ms), hoặc khi không có hardware virtualization (chạy trong cloud VM nested).

Use case "nên dùng cái nào"

Chọn gVisor khi:

Chọn Firecracker khi:

Cân nhắc Kata Containers nếu bạn muốn microVM nhưng cũng cần OCI compatibility ngay (Kata wrap Firecracker hoặc QEMU thành OCI runtime).

Một góc nhìn lịch sử

Hai dự án open-source gần như đồng thời (cuối 2018), từ hai đối thủ trực tiếp:

text
2018-05  Google open-source gVisor tại KubeCon EU
2018-11  AWS open-source Firecracker tại re:Invent

Trên thực tế, gVisor cũng từng được xem xét cho Lambda (theo lời Marc Brooker trong Seven Years of Firecracker), nhưng AWS chọn Firecracker vì lý do compatibility — họ cần chạy bất kỳ runtime nào khách hàng deploy (Java JVM, .NET, Go, Rust binary tự build), không thể chấp nhận 5% workload bị break vì subset syscall.

Ngược lại, Google chọn gVisor vì App Engine / Cloud Run / Cloud Functions phần lớn là managed runtime — Google kiểm soát interpreter, biết workload không động vào syscall exotic. Trade-off compatibility chấp nhận được.

Hai chọn lựa engineering khác nhau cho hai môi trường khách hàng khác nhau, không phải vì kỹ thuật bên này thắng bên kia.

Kết luận

gVisor và Firecracker xuất hiện cùng năm 2018 vì cùng một áp lực thị trường: chạy code không tin tưởng ở mật độ cao, với cost thấp. Nhưng triết lý giải bài toán thì gần như đối nghịch:

Hai cái không thay thế nhau, chúng bổ sung: một số platform thậm chí dùng cả hai (gVisor cho workload syscall-không-quá-exotic, Firecracker cho workload đòi 100% Linux ABI).

Vài điểm rút ra:

  1. Memory-safe language (Go cho Sentry, Rust cho Firecracker) là điều kiện cần khi viết code trên đường nóng của isolation boundary. Cả hai team đều tránh C có chủ đích.
  2. Defense in depth là chuẩn. Cả hai không tin tưởng layer chính của mình — gVisor có Sentry + seccomp host + Gofer tách quyền filesystem, Firecracker có KVM barrier + jailer + seccomp.
  3. Minimalism là feature. Firecracker chỉ giữ 5 virtio device. Sentry chỉ implement những syscall thật sự cần. Càng ít code = càng ít CVE.
  4. Trade-off là thật. gVisor nhanh hơn cho function ngắn nhưng compat thấp hơn. Firecracker compat 100% nhưng tốn RAM guest kernel. Không có free lunch.

Nếu bạn đang thiết kế hệ thống multi-tenant, câu hỏi đầu tiên không phải "gVisor hay Firecracker", mà là threat model của tôi là gì: ai chạy code không tin tưởng, ai gánh hậu quả nếu thoát container, cost của một CVE đáng giá bao nhiêu. Trả lời câu đó trước, rồi chọn công cụ phù hợp — hoặc thực dụng hơn: dùng Kubernetes RuntimeClass và để workload chọn runtime (runc, runsc, kata) qua label.

Đọc thêm trên blog này

References

Tài liệu chính thức

Paper và blog kỹ thuật

gVisor specifics

Firecracker production stories

So sánh & phân tích