Containerizing Applications – Vì sao Docker trở thành nền tảng của Cloud Native?
Một Virtual Machine có thể mất 30 giây, một phút hoặc lâu hơn để khởi động.
Một Container có thể sẵn sàng chỉ trong vài giây.
Một VM Image có thể chiếm nhiều GB. Một Container Image đơn giản đôi khi chỉ dưới 100 MB.
Nhưng lợi ích quan trọng nhất của Container không chỉ nằm ở chuyện "nhẹ hơn VM".
Container giúp chúng ta đóng gói Application + Dependencies thành một đơn vị nhất quán từ Development → Testing → Staging → Production.
Đây chính là lý do Docker trở thành một công nghệ quan trọng trong DevOps, CI/CD và Cloud Native.
Vấn đề của Virtual Machine
Virtual Machine vẫn là một công nghệ rất quan trọng và có khả năng cách ly tốt.
Nhưng nếu mục tiêu đơn thuần là triển khai ứng dụng, VM có một số overhead đáng kể.
Mô hình VM thường như sau:
Physical Server
│
Host OS
│
Hypervisor
│
┌────┴──────────────┐
│ │
VM 1 VM 2
│ │
Guest OS Guest OS
Libraries Libraries
Application Application
Mỗi VM phải mang theo một Guest Operating System riêng.
Điều đó đồng nghĩa với việc mỗi VM cần thêm:
CPU + RAM + Storage + thời gian Boot.
Một Production VM có thể cần hàng GB RAM ngay cả trước khi tính đến tài nguyên mà ứng dụng thực sự sử dụng.
Container thay đổi điều gì?
Container không mang theo toàn bộ Guest OS.
Thay vào đó, các Container chia sẻ kernel của Host OS và được cách ly thông qua Container Runtime.
Physical Server
│
Host OS
│
Docker Engine
│
┌────┼────┐
▼ ▼ ▼
C1 C2 C3
Bên trong mỗi Container chủ yếu là:
Application + Libraries + Dependencies cần thiết
Vì vậy Container thường nhỏ hơn, khởi động nhanh hơn và cho phép chạy nhiều workload hơn trên cùng một hạ tầng.
Một lợi ích cực kỳ quan trọng: Dependency Isolation
Giả sử:
Application A → Library v1
Application B → Library v2
Nếu hai ứng dụng cùng chạy trực tiếp trên một OS, việc quản lý nhiều phiên bản dependency có thể trở nên phức tạp.
Với Container:
Container A
App A + Library v1
Container B
App B + Library v2
Mỗi ứng dụng có môi trường riêng.
Đây chính là nguyên tắc Declare and Isolate Dependencies mà chúng ta đã đề cập trong chuỗi 12-Factor App.
"Chạy trên máy em được" cũng giảm đáng kể
Một trong những giá trị lớn nhất của Container là tạo ra môi trường ứng dụng nhất quán.
Developer có thể chạy:
docker run myapp:v1
CI Pipeline cũng kiểm thử:
myapp:v1
Staging triển khai:
myapp:v1
Production cũng chạy:
myapp:v1
Môi trường Development và Production có thể khác về CPU, RAM, networking hay infrastructure, nhưng Application Environment bên trong Container Image vẫn nhất quán.
Đây chính là Dev/Prod Parity, yếu tố thứ 10 của 12-Factor App mà chúng ta vừa học.
Dockerfile – "Công thức" tạo Container Image
Cách phổ biến để tạo Docker Image là mô tả quá trình Build trong một file:
Dockerfile
Dockerfile xác định:
Ví dụ đơn giản:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Sau đó:
docker build -t myapp:v1 .
Docker sẽ tạo ra một Container Image.
Image này có thể được kiểm thử, version hóa và đưa vào CI/CD Pipeline.
Một lưu ý quan trọng từ chuỗi 12-Factor trước: các cấu hình thay đổi theo môi trường và đặc biệt là credentials/secrets không nên bake trực tiếp vào Image.
Container Registry – "Kho hàng" của Docker Image
Sau khi Image được tạo, thông thường chúng ta sẽ Push nó lên Container Registry.
Source Code
│
▼
Dockerfile
│
docker build
▼
Container Image
│
docker push
▼
Container Registry
Từ Registry, các môi trường Testing, Staging hay Production có thể Pull đúng Image cần triển khai.
Registry có thể là:
Docker Image sử dụng Layer
Một đặc điểm rất quan trọng của Docker Image là kiến trúc Layer.
Ví dụ:
Application Code
───────────────
Python Dependencies
───────────────
Python Runtime
───────────────
Base Linux Image
Các Layer có thể được cache và tái sử dụng.
Nếu phiên bản mới chỉ thay đổi Application Code, Docker không nhất thiết phải tải hoặc tạo lại toàn bộ các Layer không thay đổi.
Điều này giúp:
Docker + CI/CD là một sự kết hợp rất tự nhiên
Một Pipeline cho ứng dụng Containerized có thể hoạt động như sau:
Developer Commit
│
▼
Static Analysis
│
▼
Unit Test
│
▼
docker build
│
▼
Container Image
│
▼
Security / Dynamic Testing
│
▼
Container Registry
│
▼
Staging
│
▼
Production
Điểm quan trọng là chúng ta có thể Build Image một lần và đưa chính Image đã được kiểm thử đó đi qua các môi trường tiếp theo.
Điều này phù hợp trực tiếp với nguyên tắc Build – Release – Run của 12-Factor App.
Container cũng giúp Scale Out rất nhanh
Giả sử hệ thống đang chạy:
Container
App v1
Traffic tăng đột biến.
Nền tảng orchestration có thể tạo thêm:
Load Balancer
│
┌────────┼────────┐
▼ ▼ ▼
App v1 App v1 App v1
Vì Container thường khởi động nhanh nên hệ thống có thể Scale Out nhanh hơn đáng kể so với việc phải Boot thêm một VM hoàn chỉnh.
Đây chính là mối liên hệ với hai yếu tố chúng ta vừa học:
Concurrency → Scale Out
và
Disposability → Fast Startup + Graceful Shutdown
Container không phải "VM nhỏ"
Đây là điểm rất quan trọng.
VM ảo hóa hardware và mỗi VM có Guest OS riêng.
Container chủ yếu cung cấp process isolation ở cấp hệ điều hành và chia sẻ kernel phù hợp với nền tảng container.
Chính vì chia sẻ kernel nên Container có overhead thấp hơn, nhưng điều đó cũng có nghĩa rằng Container Security phải được cấu hình đúng.
Trong Linux, khả năng cách ly và bảo vệ Container dựa trên nhiều cơ chế như namespaces, cgroups, Linux capabilities, seccomp, SELinux/AppArmor và các lớp bảo vệ khác.
Vì vậy:
Nhìn lại toàn bộ chuỗi 12-Factor App vừa qua, chúng ta sẽ thấy Docker không xuất hiện một cách tình cờ.
Container hỗ trợ rất tự nhiên cho nhiều nguyên tắc chúng ta đã học: Dependency Isolation, Build–Release–Run, Stateless Processes, Port Binding, Concurrency, Disposability, Dev/Prod Parity và Logs as Event Streams.
Đó chính là lý do Containerization trở thành một trong những mảnh ghép quan trọng nối Software Development → CI/CD → DevSecOps → Cloud Native → Kubernetes.
Một Virtual Machine có thể mất 30 giây, một phút hoặc lâu hơn để khởi động.
Một Container có thể sẵn sàng chỉ trong vài giây.
Một VM Image có thể chiếm nhiều GB. Một Container Image đơn giản đôi khi chỉ dưới 100 MB.
Nhưng lợi ích quan trọng nhất của Container không chỉ nằm ở chuyện "nhẹ hơn VM".
Container giúp chúng ta đóng gói Application + Dependencies thành một đơn vị nhất quán từ Development → Testing → Staging → Production.
Đây chính là lý do Docker trở thành một công nghệ quan trọng trong DevOps, CI/CD và Cloud Native.
Vấn đề của Virtual Machine
Virtual Machine vẫn là một công nghệ rất quan trọng và có khả năng cách ly tốt.
Nhưng nếu mục tiêu đơn thuần là triển khai ứng dụng, VM có một số overhead đáng kể.
Mô hình VM thường như sau:
Physical Server
│
Host OS
│
Hypervisor
│
┌────┴──────────────┐
│ │
VM 1 VM 2
│ │
Guest OS Guest OS
Libraries Libraries
Application Application
Mỗi VM phải mang theo một Guest Operating System riêng.
Điều đó đồng nghĩa với việc mỗi VM cần thêm:
CPU + RAM + Storage + thời gian Boot.
Một Production VM có thể cần hàng GB RAM ngay cả trước khi tính đến tài nguyên mà ứng dụng thực sự sử dụng.
Container thay đổi điều gì?
Container không mang theo toàn bộ Guest OS.
Thay vào đó, các Container chia sẻ kernel của Host OS và được cách ly thông qua Container Runtime.
Physical Server
│
Host OS
│
Docker Engine
│
┌────┼────┐
▼ ▼ ▼
C1 C2 C3
Bên trong mỗi Container chủ yếu là:
Application + Libraries + Dependencies cần thiết
Vì vậy Container thường nhỏ hơn, khởi động nhanh hơn và cho phép chạy nhiều workload hơn trên cùng một hạ tầng.
Một lợi ích cực kỳ quan trọng: Dependency Isolation
Giả sử:
Application A → Library v1
Application B → Library v2
Nếu hai ứng dụng cùng chạy trực tiếp trên một OS, việc quản lý nhiều phiên bản dependency có thể trở nên phức tạp.
Với Container:
Container A
App A + Library v1
Container B
App B + Library v2
Mỗi ứng dụng có môi trường riêng.
Đây chính là nguyên tắc Declare and Isolate Dependencies mà chúng ta đã đề cập trong chuỗi 12-Factor App.
"Chạy trên máy em được" cũng giảm đáng kể
Một trong những giá trị lớn nhất của Container là tạo ra môi trường ứng dụng nhất quán.
Developer có thể chạy:
docker run myapp:v1
CI Pipeline cũng kiểm thử:
myapp:v1
Staging triển khai:
myapp:v1
Production cũng chạy:
myapp:v1
Môi trường Development và Production có thể khác về CPU, RAM, networking hay infrastructure, nhưng Application Environment bên trong Container Image vẫn nhất quán.
Đây chính là Dev/Prod Parity, yếu tố thứ 10 của 12-Factor App mà chúng ta vừa học.
Dockerfile – "Công thức" tạo Container Image
Cách phổ biến để tạo Docker Image là mô tả quá trình Build trong một file:
Dockerfile
Dockerfile xác định:
- Base Image
- Dependencies cần cài đặt
- Application Files
- Các bước Build
- Command khởi động ứng dụng
Ví dụ đơn giản:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Sau đó:
docker build -t myapp:v1 .
Docker sẽ tạo ra một Container Image.
Image này có thể được kiểm thử, version hóa và đưa vào CI/CD Pipeline.
Một lưu ý quan trọng từ chuỗi 12-Factor trước: các cấu hình thay đổi theo môi trường và đặc biệt là credentials/secrets không nên bake trực tiếp vào Image.
Container Registry – "Kho hàng" của Docker Image
Sau khi Image được tạo, thông thường chúng ta sẽ Push nó lên Container Registry.
Source Code
│
▼
Dockerfile
│
docker build
▼
Container Image
│
docker push
▼
Container Registry
Từ Registry, các môi trường Testing, Staging hay Production có thể Pull đúng Image cần triển khai.
Registry có thể là:
- Docker Hub
- GitHub Container Registry
- GitLab Container Registry
- Amazon ECR
- Azure Container Registry
- Google Artifact Registry
- Private Registry
Docker Image sử dụng Layer
Một đặc điểm rất quan trọng của Docker Image là kiến trúc Layer.
Ví dụ:
Application Code
───────────────
Python Dependencies
───────────────
Python Runtime
───────────────
Base Linux Image
Các Layer có thể được cache và tái sử dụng.
Nếu phiên bản mới chỉ thay đổi Application Code, Docker không nhất thiết phải tải hoặc tạo lại toàn bộ các Layer không thay đổi.
Điều này giúp:
- Build nhanh hơn.
- Push/Pull nhanh hơn.
- Tiết kiệm Storage.
- Tăng hiệu quả của CI/CD Pipeline.
Docker + CI/CD là một sự kết hợp rất tự nhiên
Một Pipeline cho ứng dụng Containerized có thể hoạt động như sau:
Developer Commit
│
▼
Static Analysis
│
▼
Unit Test
│
▼
docker build
│
▼
Container Image
│
▼
Security / Dynamic Testing
│
▼
Container Registry
│
▼
Staging
│
▼
Production
Điểm quan trọng là chúng ta có thể Build Image một lần và đưa chính Image đã được kiểm thử đó đi qua các môi trường tiếp theo.
Điều này phù hợp trực tiếp với nguyên tắc Build – Release – Run của 12-Factor App.
Container cũng giúp Scale Out rất nhanh
Giả sử hệ thống đang chạy:
Container
App v1
Traffic tăng đột biến.
Nền tảng orchestration có thể tạo thêm:
Load Balancer
│
┌────────┼────────┐
▼ ▼ ▼
App v1 App v1 App v1
Vì Container thường khởi động nhanh nên hệ thống có thể Scale Out nhanh hơn đáng kể so với việc phải Boot thêm một VM hoàn chỉnh.
Đây chính là mối liên hệ với hai yếu tố chúng ta vừa học:
Concurrency → Scale Out
và
Disposability → Fast Startup + Graceful Shutdown
Container không phải "VM nhỏ"
Đây là điểm rất quan trọng.
VM ảo hóa hardware và mỗi VM có Guest OS riêng.
Container chủ yếu cung cấp process isolation ở cấp hệ điều hành và chia sẻ kernel phù hợp với nền tảng container.
Chính vì chia sẻ kernel nên Container có overhead thấp hơn, nhưng điều đó cũng có nghĩa rằng Container Security phải được cấu hình đúng.
Trong Linux, khả năng cách ly và bảo vệ Container dựa trên nhiều cơ chế như namespaces, cgroups, Linux capabilities, seccomp, SELinux/AppArmor và các lớp bảo vệ khác.
Vì vậy:
Container nhẹ hơn không đồng nghĩa với việc chúng ta được phép xem nhẹ Security.
Nhìn lại toàn bộ chuỗi 12-Factor App vừa qua, chúng ta sẽ thấy Docker không xuất hiện một cách tình cờ.
Container hỗ trợ rất tự nhiên cho nhiều nguyên tắc chúng ta đã học: Dependency Isolation, Build–Release–Run, Stateless Processes, Port Binding, Concurrency, Disposability, Dev/Prod Parity và Logs as Event Streams.
Đó chính là lý do Containerization trở thành một trong những mảnh ghép quan trọng nối Software Development → CI/CD → DevSecOps → Cloud Native → Kubernetes.