Cô lập và Runtime Container (Container Isolation & Runtime)
1. Tổng quan
A. Định nghĩa
Công nghệ cô lập và runtime container là việc kết hợp các cơ chế cô lập ở cấp hệ điều hành trong nhân Linux — namespace, control group (cgroups), giảm đặc quyền (capabilities), lọc lời gọi hệ thống (seccomp) — để làm cho nhiều tiến trình chạy trên cùng một nhân trông như chạy trong những môi trường thực thi độc lập, cùng với lớp phần mềm (runtime) tạo, chạy và kết thúc chúng theo chuẩn OCI (Open Container Initiative).
Khác với máy ảo dựa trên hypervisor vốn ảo hóa phần cứng và nạp một nhân riêng cho mỗi guest OS, container chia sẻ nhân của máy chủ và chỉ cô lập ở cấp tiến trình. Do đó không có quá trình khởi động và không cần ảnh guest OS hàng trăm MB, nên container khởi động trong vòng vài chục mili giây và có thể xếp dày đặc hàng trăm đến hàng nghìn trên một node. Nhưng việc chia sẻ nhân cũng có nghĩa là "cường độ cô lập phụ thuộc vào tính đúng đắn của các tính năng nhân và chất lượng cấu hình", và đây là điểm khởi đầu của bảo mật container và thiết kế runtime.
B. Bối cảnh ra đời và sự cần thiết
Thứ nhất, máy ảo sao chép toàn bộ một hệ điều hành chỉ để cô lập một ứng dụng, dẫn đến hiệu suất tài nguyên thấp và tốc độ triển khai chậm. Trong xu hướng chia nhỏ khối lượng công việc thành hàng chục đến hàng trăm microservice, chi phí khởi động một máy ảo nhiều GB cho mỗi dịch vụ trở nên khó gánh. Container giải quyết điều này bằng cách cung cấp một đơn vị nhẹ "chỉ đóng gói ứng dụng và các phụ thuộc của nó".
Thứ hai, vấn đề không nhất quán môi trường "trên laptop của tôi thì chạy nhưng trên môi trường vận hành thì không" rất nghiêm trọng.
Ảnh container cố định thư viện, runtime và cấu hình thành một artifact bất biến (immutable) duy nhất, bảo đảm khả năng tái lập giữa môi trường phát triển, kiểm thử và vận hành.
Khả năng tái lập này trở thành điều kiện tiên quyết cho tự động hóa CI/CD, tức [[ci-cd-pipeline]] và [[gitops]].
Thứ ba, năm 2013 Docker đã phổ cập các tính năng nhân Linux bằng cách gói chúng trong một CLI và định dạng ảnh dễ dùng, và năm 2015 OCI ra đời và chuẩn hóa định dạng ảnh, runtime và quy cách phân phối, hình thành một hệ sinh thái không bị khóa nhà cung cấp.
Nhờ đó Docker, containerd, CRI-O và Podman tương thích lẫn nhau, và [[kubernetes]] có thể xem runtime là thành phần thay thế được thông qua CRI (Container Runtime Interface).
C. Đặc điểm
Cô lập container đồng thời mang ba đặc điểm: (1) nhân chia sẻ — nguồn gốc của hiệu năng và mật độ, đồng thời là điểm yếu của ranh giới bảo mật, (2) ảnh bất biến mang tính khai báo — nền tảng của khả năng tái lập và truy vết chuỗi cung ứng, và (3) khả năng thay thế dựa trên chuẩn — tính trung lập nhà cung cấp thông qua OCI/CRI. Tùy vào điểm cân bằng của ba yếu tố này mà người ta chọn các mức cô lập đa dạng, từ container thuần túy đến runtime sandbox và VM nhẹ.
2. Các thành phần cốt lõi của cô lập container
Container không phải một công nghệ đơn lẻ mà là sự kết hợp của nhiều tính năng nhân. Trước hết ta khảo sát toàn bộ cấu trúc, rồi giải thích nguyên lý của từng thành phần.
graph TD
subgraph Host["Máy chủ (một nhân chia sẻ duy nhất)"]
K["Nhân Linux"]
subgraph C1["Container A"]
P1["Tiến trình ứng dụng"]
end
subgraph C2["Container B"]
P2["Tiến trình ứng dụng"]
end
end
K -->|"Namespace (thấy được gì)"| C1
K -->|"Namespace (thấy được gì)"| C2
K -->|"cgroups (dùng được bao nhiêu)"| C1
K -->|"cgroups (dùng được bao nhiêu)"| C2
K -->|"capabilities/seccomp/LSM (làm được gì)"| C1
K -->|"capabilities/seccomp/LSM (làm được gì)"| C2
A. Namespace — cô lập khả năng nhìn thấy
Namespace phân vùng "tiến trình thấy được gì". Namespace PID khiến tiến trình bên trong container nhận mình là PID 1, nên không thấy các tiến trình khác của máy chủ. Namespace NET cung cấp một ngăn xếp mạng độc lập (giao diện, bảng định tuyến, luật tường lửa), nên mỗi container có thể có không gian IP và cổng riêng. Ngoài ra còn có các namespace MNT (mount hệ thống tệp), IPC (bộ nhớ chia sẻ/semaphore), UTS (tên máy), USER (ánh xạ ID người dùng/nhóm), CGROUP và TIME — tổng cộng tám loại trong nhân tính đến năm 2024.
Đặc biệt namespace USER là cốt lõi về bảo mật. Ánh xạ root bên trong container (UID 0) sang một người dùng không đặc quyền trên máy chủ (ví dụ UID 100000) nghĩa là ngay cả khi xảy ra thoát container, kẻ tấn công cũng chỉ có được quyền người dùng thường trên máy chủ, giảm mạnh thiệt hại. Đây là nền tảng của container rootless được trình bày sau.
B. Control group (cgroups) — cô lập tài nguyên
cgroups giới hạn và đo lường "một nhóm tiến trình dùng được bao nhiêu tài nguyên". Chúng kiểm soát hạn ngạch CPU (ví dụ 0.5 vCPU), giới hạn bộ nhớ (ví dụ 512 MiB), băng thông I/O khối, số lượng PID, v.v., ngăn việc một container chạy loạn làm tê liệt cả node — vấn đề hàng xóm ồn ào (noisy neighbor). Chẳng hạn khi vượt giới hạn bộ nhớ, OOM Killer sẽ kết thúc các tiến trình của container đó để bảo vệ các khối lượng công việc khác.
Hiện nay chuẩn là cgroups v2, vốn đơn giản hóa cấu trúc phân cấp và quản lý thống nhất các loại tài nguyên. Kết hợp với systemd, nó hoạt động như một hệ phân cấp hợp nhất duy nhất; cgroups v2 đạt GA từ Kubernetes 1.25, cho phép kiểm soát tải chính xác dựa trên tín hiệu áp lực bộ nhớ (PSI, Pressure Stall Information).
C. Giảm đặc quyền và lọc lời gọi hệ thống — cô lập hành vi
capabilities chia đặc quyền root vốn theo lối "tất cả hoặc không có gì" thành khoảng hơn bốn mươi đặc quyền chi tiết hơn.
Runtime container mặc định chỉ cấp một tập tối thiểu, sau khi đã loại bỏ các đặc quyền nguy hiểm như CAP_NET_RAW và CAP_SYS_ADMIN.
seccomp (secure computing) dùng bộ lọc BPF để hạn chế theo danh sách cho phép các lời gọi hệ thống mà tiến trình được phát, giảm bề mặt tấn công nhân — hồ sơ mặc định của Docker chặn 44 lời gọi hệ thống nguy hiểm trong khoảng hơn 300.
Nếu chồng thêm một LSM (Linux Security Module) như SELinux hay AppArmor để kiểm soát truy cập tệp và mạng ở mức kiểm soát truy cập bắt buộc, thì ngay cả khi namespace bị phá vẫn có tuyến phòng thủ thứ hai hoạt động.
| Phân loại | Câu hỏi kiểm soát | Chức năng chính | Ảnh hưởng khi cô lập thất bại |
|---|---|---|---|
| Namespace | thấy gì | PID/NET/MNT/USER, 8 loại | lộ tài nguyên của container khác hoặc máy chủ |
| cgroups | dùng bao nhiêu | giới hạn CPU/bộ nhớ/I/O/PID | cạn kiệt tài nguyên, từ chối dịch vụ |
| capabilities | làm gì | chia nhỏ đặc quyền root | leo thang đặc quyền, chiếm máy chủ |
| seccomp/LSM | gọi nhân thế nào | hạn chế lời gọi hệ thống/MAC | khai thác lỗ hổng nhân, thoát container |
Như bảng cho thấy, ba phạm trù trả lời những câu hỏi khác nhau và phải phối hợp với nhau như phòng thủ theo chiều sâu (defense in depth). Hàm ý cốt lõi của thiết kế bảo mật container là không một yếu tố đơn lẻ nào trong số đó tự thân tạo nên sự cô lập hoàn chỉnh.
3. Kiến trúc runtime container và quy trình thực thi
Runtime được chia theo vai trò thành runtime cấp cao (điều phối quản lý ảnh, mạng, lưu trữ) và runtime cấp thấp (runtime OCI thực sự thiết lập namespace và cgroups rồi khởi chạy tiến trình). Ta giải thích các lớp này qua luồng gọi khi Kubernetes khởi chạy một Pod.
sequenceDiagram
participant K as kubelet
participant HL as containerd / CRI-O (cấp cao)
participant S as containerd-shim
participant LL as runc / crun (OCI cấp thấp)
participant KN as Nhân Linux
K->>HL: Lời gọi CRI gRPC (RunPodSandbox/CreateContainer)
HL->>HL: Pull/giải nén ảnh OCI, dựng rootfs
HL->>S: tạo tiến trình shim (tách vòng đời container)
S->>LL: chuyển gói OCI (config.json + rootfs)
LL->>KN: clone() + thiết lập namespace/cgroups/capabilities
KN-->>LL: khởi chạy tiến trình PID 1 đã cô lập
LL-->>S: trả kết quả rồi kết thúc
S-->>HL: báo cáo trạng thái container (shim thường trú)
A. Runtime cấp cao và CRI
Để không bị ràng buộc vào một runtime cụ thể, kubelet giao tiếp qua một chuẩn gRPC gọi là CRI (Container Runtime Interface).
Khi Kubernetes loại bỏ lớp kết nối riêng cho Docker (dockershim), gỡ bỏ nó ở phiên bản 1.24 vào năm 2020, containerd và CRI-O trở thành runtime cấp cao chuẩn trên thực tế.
Runtime cấp cao kéo ảnh OCI từ registry ảnh, hợp nhất các lớp (layer), kết nối mạng (CNI) và lưu trữ (CSI), rồi dựng gói OCI (rootfs + config.json).
B. shim và runtime OCI cấp thấp
containerd-shim thường trú và đảm nhận vai trò tiến trình cha của tiến trình container, để ngay cả khi daemon containerd khởi động lại thì container đang chạy cũng không chết — nó tách vòng đời.
Việc thiết lập cô lập thực sự do runtime cấp thấp runc (Go) thực hiện. runc tạo namespace bằng các lời gọi hệ thống clone()/unshare(), áp dụng cgroups, capabilities và seccomp rồi exec tiến trình người dùng.
Gần đây các runtime thay thế tương thích OCI như crun (viết bằng C để giảm mức dùng bộ nhớ và độ trễ khởi động) và youki (viết bằng Rust) đã xuất hiện và được áp dụng ngày càng nhiều trong môi trường mật độ cao, độ trễ thấp.
C. Ảnh và chuỗi cung ứng
Khả năng tái lập của quy trình thực thi dựa trên quy cách ảnh OCI (các lớp định địa chỉ theo nội dung cộng với một manifest).
Một ảnh có cùng digest (SHA-256) là đồng nhất ở mọi nơi, nên kết hợp ký ảnh, [[sbom]] và quét lỗ hổng cho phép kiểm chứng tính toàn vẹn của chuỗi cung ứng phần mềm.
Nói cách khác, runtime đang mở rộng để chịu trách nhiệm không chỉ cho "thực thi đã cô lập" mà còn cho "thực thi các artifact đã được kiểm chứng".
4. So sánh runtime tăng cường và sandbox
Để bù đắp giới hạn của nhân chia sẻ, các runtime nâng cao cường độ cô lập đã xuất hiện. Thay vì liệt kê đơn thuần, ta so sánh tập trung vào lý do mỗi lựa chọn được đưa ra.
| Phương thức | Ranh giới cô lập | Chia sẻ nhân | Tốc độ khởi động | Mật độ/hiệu quả | Áp dụng tiêu biểu |
|---|---|---|---|---|---|
| Container chuẩn (runc) | namespace/cgroups | chia sẻ trực tiếp nhân máy chủ | rất nhanh (vài chục ms) | cao nhất | microservice thông thường |
| gVisor (runsc) | nhân guest ở không gian người dùng | chặn lời gọi hệ thống | nhanh | cao | PaaS đa khách thuê |
| Kata Containers | VM nhẹ (phần cứng) | tách nhân guest | trung bình (vài trăm ms) | trung bình | khách thuê có ranh giới tin cậy khác nhau |
| Firecracker microVM | microVM (phần cứng) | tách nhân guest | nhanh (~125 ms) | cao | serverless (FaaS) |
gVisor, do Google phát hành, có một nhân guest tự tạo (Sentry) chạy ở không gian người dùng chặn và xử lý các lời gọi hệ thống của container. Nó giảm mạnh bề mặt lời gọi hệ thống chạm trực tiếp vào nhân máy chủ, hạ thấp nguy cơ thoát container qua một lỗ hổng nhân. Tuy nhiên vì chuyển tiếp lời gọi hệ thống nên nó gây suy giảm hiệu năng với khối lượng công việc thiên về I/O — một đánh đổi khiến nó phù hợp cho môi trường đa khách thuê chạy cô lập mã ít tin cậy.
Kata Containers gói mỗi Pod trong một VM nhẹ trên hypervisor như QEMU hay Cloud Hypervisor, cung cấp sự tách nhân ở cấp phần cứng.
Vì giữ nguyên các giao diện container chuẩn (OCI/CRI), ta có thể chọn mức cô lập mạnh hơn chỉ bằng cách đổi runtimeClassName mà không sửa ứng dụng.
[[confidential-containers]] là một mở rộng bổ sung mã hóa bộ nhớ TEE lên trên, cho thấy sự cô lập VM nhẹ dựa trên Kata trở thành nền tảng của điện toán bảo mật.
Firecracker là một microVM mà AWS xây dựng cho Lambda và Fargate; nó hoàn tất khởi động trong khoảng 125 ms và hạ chi phí bộ nhớ trên mỗi VM xuống khoảng 5 MiB, chạy dày đặc hàng nghìn thực thể hàm đã cô lập trên một máy chủ. Là trường hợp kết hợp sự cô lập mạnh của VM với khởi động nhanh của container, nó là ví dụ thực tiễn làm dịu niềm tin cũ rằng "cường độ cô lập và hiệu năng tất yếu xung đột" bằng thiết kế tối thiểu hóa bề mặt tấn công.
5. Chuyên sâu: Xu hướng và áp dụng thực tiễn
A. Sự lan rộng của container rootless
Theo truyền thống, daemon container chạy bằng root, nên nếu daemon bị chiếm thì cả máy chủ gặp rủi ro. Phương thức rootless, vốn tận dụng namespace USER, chạy daemon/runtime bằng một người dùng không đặc quyền và ánh xạ root bên trong container sang một người dùng thường của máy chủ. Podman áp dụng cấu trúc rootless không daemon (daemonless) theo mặc định, và Kubernetes cũng hỗ trợ kubelet rootless ở dạng thử nghiệm, nhờ đó nguyên tắc "đặc quyền tối thiểu" đang dịch chuyển thành giá trị mặc định của runtime.
B. Kết hợp với runtime Wasm
Cũng có một xu hướng rõ rệt muốn đưa WebAssembly ([[webassembly]]) vào hệ sinh thái container.
Dùng shim runwasi của containerd, một mô-đun Wasm có thể được đóng gói thành ảnh OCI và triển khai như một Pod.
Wasm khởi động dưới mili giây rất nhanh và bảo đảm cô lập bộ nhớ ở cấp ngôn ngữ, nên nó thu hút sự chú ý như một bổ sung cho container nhẹ trong các khối lượng công việc cực kỳ độ trễ thấp của serverless và biên ([[edge-computing]]).
C. Khả năng quan sát và bảo mật cấp nhân — eBPF
Các công cụ dựa trên [[ebpf]] (Cilium, Falco, Tetragon) quan sát và cưỡng chế trực tiếp trong nhân các lời gọi hệ thống và luồng mạng của container, thực hiện phát hiện mối đe dọa runtime và áp dụng chính sách mạng.
Chẳng hạn trong ngành tài chính, ngày càng phổ biến việc phát hiện hành vi bất thường đối với runtime container (thực thi shell không mong đợi, nỗ lực leo thang đặc quyền) bằng eBPF và triển khai bảo mật runtime (một dạng tự bảo vệ khối lượng công việc tương tự [[rasp]]).
Điều này cho thấy sự cô lập đang mở rộng từ "cấu hình tĩnh" sang "phát hiện và ứng phó động".
6. Cân nhắc và hàm ý
Từ góc nhìn Kỹ sư chuyên nghiệp, việc áp dụng cô lập/runtime container phải được đánh giá tổng hợp theo những điểm sau.
- Thiết kế đánh đổi giữa mức cô lập và hiệu năng: Áp dụng cô lập cấp VM cho mọi khối lượng công việc làm xấu đi chi phí và hiệu năng. Một hướng tiếp cận thực tế là chiến lược cô lập phân tầng dựa trên
runtimeClassNamechỉ áp dụng chọn lọc gVisor/Kata cho khối lượng công việc ít tin cậy, lấy ranh giới tin cậy (mã bên trong cùng tổ chức so với mã khách thuê bên ngoài) làm tiêu chí. - Biến đặc quyền tối thiểu thành mặc định: Loại bỏ capabilities, cưỡng chế hồ sơ seccomp/AppArmor, hệ thống tệp gốc rootless và chỉ đọc, cùng ánh xạ namespace USER nên được đặt làm chính sách mặc định. Hồ sơ Restricted của
[[kubernetes-pod-security-standards]]nên được cưỡng chế ở giai đoạn admission để ngăn thiếu sót cấu hình. - Kiểm soát bao gồm cả niềm tin chuỗi cung ứng: Chỉ thực thi đã cô lập không ngăn được ảnh độc hại hay dễ tổn thương. Xác minh chữ ký ảnh, tạo
[[sbom]], quét lỗ hổng và kiểm soát truy cập registry nên được kết nối qua CI/CD và[[policy-as-code]]để cưỡng chế "chỉ chạy các artifact đã kiểm chứng". - Liên kết khả năng quan sát với ứng phó mối đe dọa runtime: Bên cạnh cấu hình cô lập tĩnh, phát hiện runtime dựa trên eBPF (
[[edr]], Falco) và ghi log tập trung/[[observability]]nên được kết hợp để xây một hệ thống phát hiện và ứng phó sớm vào đúng thời điểm sự cô lập bị phá vỡ. - Bảo đảm chuẩn và tính trung lập nhà cung cấp: Chọn một runtime tuân thủ chuẩn OCI/CRI để tránh khóa vào một nhà cung cấp cụ thể và, như trường hợp loại bỏ dockershim cho thấy, duy trì một kiến trúc thay thế được sẵn sàng cho sự thay đổi của hệ sinh thái.
Tóm lại, cô lập container không phải một tính năng đơn lẻ mà là một hệ thống thực thi tin cậy kết hợp các yếu tố cô lập nhân, các lớp runtime, chuỗi cung ứng và khả năng quan sát; Kỹ sư chuyên nghiệp được yêu cầu năng lực thiết kế điểm cân bằng giữa bảo mật, hiệu năng và hiệu quả vận hành sao cho phù hợp với mô hình tin cậy của tổ chức.
Tài liệu tham khảo
- Open Container Initiative, "Runtime/Image/Distribution Specifications", https://opencontainers.org/
- Kubernetes Documentation, "Container Runtime Interface (CRI)", https://kubernetes.io/docs/concepts/architecture/cri/
- Linux man-pages, "namespaces(7) / cgroups(7) / capabilities(7) / seccomp(2)", https://man7.org/linux/man-pages/man7/namespaces.7.html
- AWS, "Firecracker: Lightweight Virtualization for Serverless Applications", https://firecracker-microvm.github.io/
- gVisor Documentation, "What is gVisor?", https://gvisor.dev/docs/
Tóm tắt một câu: Cô lập/runtime container cô lập các tiến trình trên một nhân chia sẻ thông qua namespace Linux (khả năng nhìn thấy), cgroups (tài nguyên) và capabilities/seccomp (hành vi) và chạy chúng qua các runtime chuẩn OCI/CRI (containerd→runc), bù đắp giới hạn bảo mật của nhân chia sẻ bằng các VM sandbox/nhẹ như gVisor, Kata và Firecracker cùng rootless và quan sát eBPF để thiết kế điểm cân bằng giữa cường độ cô lập và hiệu năng.