Nội dung bài viết
Hi các bạn 👋. Mình là Hùng Anh.
Đây là bài thứ tư trong series Kubernetes Fundamentals. Ở bài trước, mình kết thúc bằng 1 câu hỏi: nếu phần lớn VM trên 1 server thực ra chỉ cần dùng chung 1 loại OS, thì có nhất thiết mỗi VM phải chứa riêng hẳn 1 kernel đầy đủ không? Cùng mình xem container trả lời câu hỏi đó như nào.
Bắt đầu thôi.
1. Container là gì
Container đơn giản là 1 tiến trình bình thường trên host — không có gì huyền bí hơn thế. Thứ khiến nó trông như đang chạy trong 1 máy riêng biệt là kernel "nhốt" nó lại bằng đúng những cơ chế cô lập có sẵn: namespace và cgroups — chính 2 thứ mình đã tự tay dựng lên ở bài đầu tiên của series này.
Khác với VM, container không có kernel riêng. Nó cũng không đi qua hypervisor — nó chạy thẳng trên kernel thật của host, chỉ khác là bị cô lập với mọi thứ khác cũng đang chạy ở đó.
2. Cách Container hoạt động
Khi 1 container khởi động, hệ thống làm 1 việc khá đơn giản: tạo 1 tiến trình mới trên host — nhanh, y hệt như khởi động bất kỳ chương trình nào khác — rồi áp 2 cơ chế của kernel vào đúng tiến trình đó:
- Namespace — container không thấy được tiến trình, file, hay mạng của host hay của container khác.
- cgroups — container bị giới hạn tối đa được dùng bao nhiêu CPU, RAM, disk I/O.
Tiến trình đó gọi syscall thẳng tới kernel thật của host — không qua lớp giả lập nào. Đây chính là khác biệt cốt lõi so với VM, và cũng là lý do container nhẹ và khởi động nhanh hơn hẳn.
3. Container đã giải quyết bài toán ở bài trước như nào
Vì container bỏ qua hoàn toàn hypervisor và guest OS, nó tránh được mọi thứ khiến VM trở nên nặng nề: không cần boot cả 1 hệ điều hành (khởi động 1 container chỉ là khởi động 1 tiến trình — dưới 1 giây, không phải vài phút), không tốn vài GB chỉ để nuôi sống 1 guest kernel, và số lượng container chạy được trên cùng 1 phần cứng nhiều hơn hẳn so với VM.
4. Nhược điểm của Container
- Cô lập yếu hơn VM. Vì mọi container trên host đều dùng chung 1 kernel, 1 lỗ hổng ở kernel đó có thể ảnh hưởng tới mọi container đang chạy trên đó — khác với VM, nơi mỗi cái có hẳn 1 kernel tách biệt hoàn toàn.
- Phụ thuộc vào kernel của host. 1 container Linux cần có Linux kernel bên dưới mới chạy được. Muốn chạy container Windows? Bạn cần đúng Windows kernel/host tương ứng.
Vậy Docker Desktop chạy container Linux trên Windows như nào? Tuyệt đại đa số image container ngoài đời (nginx, postgres, node...) đều là container Linux, mà Windows kernel thì đơn giản là không hiểu chúng. Vì vậy khi cài Docker Desktop trên Windows, nó âm thầm chạy 1 VM Linux nhẹ phía sau qua WSL2 — đúng mô hình đã nói ở bài trước — và mọi "container Linux" bạn chạy thực chất đang chạy bên trong chiếc VM ẩn đó, không phải trực tiếp trên Windows kernel. Docker chỉ khiến nó trông như native mà thôi.
5. Container Runtime
Namespace và cgroups chỉ là cơ chế có sẵn trong kernel — vẫn cần 1 bên thực sự đứng ra gọi đúng các cơ chế đó để tạo, khởi động, dừng, xóa container. Đó chính là nhiệm vụ của container runtime.
Kubernetes không muốn bị khóa cứng vào 1 runtime cụ thể, nên nó định nghĩa sẵn 1 chuẩn giao tiếp trước — Container Runtime Interface (CRI) — rồi để các runtime khác nhau tự implement theo chuẩn đó:
- containerd — tách ra từ Docker, hiện là runtime mặc định phổ biến nhất trong Kubernetes.
- CRI-O — sinh ra ngay từ đầu để tuân theo đúng chuẩn CRI.
Vì kubelet (agent của Kubernetes sẽ nói ở bài sau) chỉ nói chuyện theo đúng chuẩn CRI, việc đổi runtime bên dưới không bao giờ đòi hỏi phải sửa lại kubelet.
6. Tổng kết
Container chỉ đơn giản là 1 tiến trình bình thường, bị cô lập bằng namespace và cgroups, chạy thẳng trên chính kernel của host — không hypervisor, không guest OS. Đó là lý do nó nhẹ và nhanh, đổi lại là cô lập yếu hơn và phụ thuộc cứng vào kernel của host. Container runtime là phần mềm thực sự đứng ra tạo và quản lý chúng.
Giờ thì thư giãn một chút — bài tiếp theo: khi container rải rác trên hàng chục, hàng trăm máy, ai sẽ là người quyết định container nào chạy ở đâu? Đó chính xác là bài toán mà Kubernetes sinh ra để giải quyết.
Happy reading! 🍵
Tài liệu tham khảo
- What even is a container: namespaces and cgroups - Julia Evans
- Container Runtime Interface (CRI) - Kubernetes documentation
Bài viết liên quan
Kubernetes: Kiến trúc và các thành phần
Khi container rải rác trên hàng chục, hàng trăm máy, ai sẽ là người quyết định container nào chạy ở đâu, tự khởi động lại khi nó chết, hay scale thêm khi tải tăng? Đó chính xác là bài toán Kubernetes sinh ra để giải quyết — cùng mình vẽ lại bên trong 1 cluster có những gì.
Các thuật ngữ Linux cơ bản
Trước khi chạm đến Kubernetes, câu chuyện phải bắt đầu từ thứ mà mọi thứ còn lại trong series này được xây trên nền của nó: Linux kernel. Nếu bạn chưa từng thực sự ngồi nghĩ kỹ kernel làm gì, driver là gì, hay "sandbox" trong Linux thực chất là gì, bài này dành cho bạn.
Server vật lý và các giới hạn
Server vật lý thực sự trông như thế nào, và vì sao không thể cứ deploy mãi lên cùng 1 server?