ContainerVì sao thứ này thay đổi hẳn cách chúng ta giao phần mềm cho thế giới
Bài này không bắt đầu bằng định nghĩa. Nó bắt đầu bằng một tối thứ Sáu hỏng việc — rồi đi ngược lại để tìm ra nguyên nhân, bằng sáu bản vẽ bạn tự bấm để mổ xẻ.
Tối thứ Sáu, và câu nói kinh điển
Bạn vừa xong tính năng upload ảnh. Trên laptop mọi thứ mượt: ảnh resize đúng, watermark đóng đúng góc, chạy đi chạy lại chục lần không lỗi. Commit, push, báo team lên staging.
Mười lăm phút sau, tin nhắn từ tester: "Anh ơi, upload ảnh lỗi 500."
Bạn mở log. Một dòng lạnh lùng: ImageMagick: command not found. Máy bạn có. Server không có. Và bạn nói ra câu mà mọi lập trình viên đều đã từng nói ít nhất một lần trong đời:
"Nhưng trên máy em nó chạy được mà?"
Chuyện này không phải xui xẻo. Nó là hệ quả tất yếu của cách ta đóng gói phần mềm suốt mấy chục năm: ta chỉ chuyển đi mã nguồn, còn môi trường để mã nguồn chạy được thì bỏ lại phía sau.
Vấn đề này không giải quyết được bằng "viết tài liệu cài đặt cẩn thận hơn". Tài liệu sẽ lạc hậu, người mới sẽ cài thiếu bước, và server production thứ ba sẽ lại khác server thứ hai. Cần một cơ chế máy móc tự đảm bảo, không dựa vào kỷ luật con người.
Ý tưởng đã cứu cả ngành vận tải biển
Trước 1956, xếp hàng lên tàu biển là một cơn ác mộng. Mỗi món hàng một hình dạng: bao gạo, thùng rượu, kiện vải, máy móc. Công nhân bốc vác xếp thủ công từng món, nhét chèn cho khỏi xê dịch. Một con tàu mất cả tuần để bốc dỡ, và chi phí bốc xếp chiếm gần một nửa giá vận chuyển.
Rồi Malcom McLean đặt câu hỏi ngược:
Thay vì tối ưu việc xếp hàng, sao không chuẩn hoá cái hộp đựng hàng?
Ông làm ra thùng thép kích thước cố định. Bên trong đựng gì cũng được — gạo, rượu, vải, máy móc, không ai quan tâm. Bên ngoài thì luôn giống hệt nhau: cùng kích thước, cùng bốn góc khoá chuẩn. Nhờ vậy cần cẩu ở bất kỳ cảng nào cũng nhấc được, xe tải nào cũng chở được, toa tàu nào cũng xếp được. Thời gian bốc dỡ rơi từ một tuần xuống vài giờ.
Container là suất cơm hộp mang đi
Bạn không mang theo cái bếp, cũng không mang theo đầu bếp. Bạn mang theo một hộp đã có sẵn cơm, thức ăn, nước chấm, đũa — đúng khẩu phần, đúng thứ tự, đóng kín. Mở ra ở văn phòng, ở công viên hay trên tàu thì món ăn vẫn y hệt lúc đóng hộp.
Container phần mềm cũng vậy: nó gói ứng dụng cùng với runtime, thư viện, cấu hình và biến môi trường mà ứng dụng cần. Mở ra ở máy nào cũng cho đúng một kết quả.
Máy ảo và container khác nhau ở đúng một chỗ
Trước khi có container, cách phổ biến để "đóng gói cả môi trường" là dùng máy ảo. Bạn tạo một máy tính ảo hoàn chỉnh, cài hệ điều hành riêng, cài ứng dụng vào đó, rồi bê nguyên máy ảo đi. Cách này giải quyết được vấn đề — nhưng rất tốn kém.
Máy ảo và container khác nhau ở độ sâu của lớp ảo hoá. Máy ảo ảo hoá phần cứng: mỗi máy ảo tin rằng nó có CPU, RAM, ổ cứng riêng, nên nó cần một hệ điều hành đầy đủ của riêng mình. Container ảo hoá hệ điều hành: mọi container dùng chung nhân Linux của máy chủ, chỉ bị cách ly về góc nhìn — nó tưởng mình có filesystem riêng, danh sách tiến trình riêng, mạng riêng.
Kéo thanh chọn trên bản vẽ để thấy chi phí phình lên thế nào khi số ứng dụng tăng.
| Tiêu chí | Máy ảo | Container |
|---|---|---|
| Ảo hoá ở tầng | Phần cứng | Hệ điều hành |
| Hệ điều hành riêng | Có — mỗi máy ảo một bản đầy đủ | Không — dùng chung kernel máy chủ |
| Dung lượng điển hình | 1 – 20 GB | 5 – 300 MB |
| Thời gian khởi động | 30 giây – vài phút | dưới 1 giây |
| Mức cách ly | Rất mạnh — ranh giới ở phần cứng ảo | Khá mạnh — ranh giới ở namespace & cgroup |
| Chạy được OS khác | Có — Windows trên host Linux | Không — container Linux cần kernel Linux |
| Hợp với việc gì | Cách ly nhiều khách hàng, chạy OS khác | Đóng gói & giao ứng dụng, CI/CD, co giãn |
"Container là máy ảo nhẹ hơn." Nghe thì tiện, nhưng nó dẫn tới ba sai lầm rất tốn thời gian: chạy nhiều dịch vụ trong một container như thể nó là server; dùng docker exec sửa trực tiếp rồi tưởng thay đổi còn mãi; và tin rằng container cách ly mạnh ngang máy ảo. Container không phải máy nhỏ — nó là một tiến trình bị kernel giới hạn tầm nhìn.
Thực tế phổ biến nhất hiện nay là container chạy bên trong máy ảo: nhà cung cấp cloud cho thuê một máy ảo, bạn cài Docker lên đó và chạy hàng chục container. Máy ảo lo cách ly giữa các khách hàng, container lo đóng gói ứng dụng.
Bên trong một lệnh docker run
Bạn gõ một dòng lệnh, mười giây sau có một web server đang chạy. Giữa hai thời điểm đó là một chuỗi việc khá thú vị. Hiểu được luồng này thì phần lớn lỗi Docker sau này bạn sẽ tự đoán được nguyên nhân.
$ docker run -d -p 8080:80 --name web nginx:1.27-alpine Unable to find image 'nginx:1.27-alpine' locally 1.27-alpine: Pulling from library/nginx a0d0a0d46f8b: Pull complete e7b6b1ef4a0e: Pull complete Status: Downloaded newer image for nginx:1.27-alpine 3f1a9c04b7e2d8a5c6f0b31e7d9a4c28f5b6e1d0a3c7b249e8f5d1a6c0b3e7f42
Bước 1. Chương trình docker bạn gõ không tự tạo container. Nó dịch lệnh của bạn thành một lời gọi HTTP rồi gửi đi.
Năm chi tiết bạn sẽ gặp mỗi ngày
Docker chỉ có vài danh từ cốt lõi. Nắm chắc năm cái dưới đây là bạn đọc hiểu được hầu hết tài liệu và thông báo lỗi sau này. Bấm vào từng dòng để mở rộng.
Vòng đời của một container
Container không chỉ có "chạy" và "tắt". Nó đi qua một số trạng thái, mỗi lệnh Docker đưa nó sang một trạng thái cụ thể. Rất nhiều nhầm lẫn của người mới đến từ việc không phân biệt được đã dừng và đã xoá.
Bấm thử các lệnh trên bản vẽ để xem container di chuyển — kể cả những lệnh sai.
docker create dừng đúng ở đây, còn docker run làm luôn cả bước tiếp theo.Gõ docker stop db rồi tưởng dữ liệu đã mất. Không hề — container ở trạng thái stopped vẫn giữ nguyên lớp ghi, docker start db là chạy lại như cũ. Chỉ khi docker rm db thì lớp ghi mới thực sự bị xoá. Và nếu dữ liệu nằm trong volume thì ngay cả docker rm cũng không đụng tới.
Vì sao image nhẹ đến vậy
Đến đây bạn có thể thắc mắc: nếu mỗi container mang theo cả môi trường, chạy 20 container thì chẳng phải tốn 20 lần dung lượng sao? Câu trả lời nằm ở cách image được xây: xếp chồng từng lớp, và các lớp được dùng chung.
Mỗi chỉ thị trong Dockerfile tạo một lớp chứa đúng phần khác biệt so với lớp dưới. Các lớp này chỉ đọc. Khi container chạy, Docker đặt thêm một lớp ghi mỏng lên trên cùng — mọi thay đổi lúc chạy chỉ nằm ở lớp đó.
FROM lấy một image có sẵn làm nền. Đây thường là lớp nặng nhất — và cũng là lớp được chia sẻ nhiều nhất giữa các image khác nhau trên cùng máy.$ docker image ls REPOSITORY TAG IMAGE ID SIZE myapp v1 8c2f1a9d3e40 185MB myapp v2 b71e04a5c9d2 186MB node 20-alpine 2ad9f0c81b57 130MB $ docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 3 2 196MB 12.4MB (6%) Containers 3 3 1.1MB 0B # Ba image cộng lại "trên giấy" là 501MB. # Thực tế trên đĩa chỉ 196MB — phần còn lại là layer dùng chung.
Bốn sai lầm tốn thời gian nhất
Coi container như một cái server nhỏ
Vào container bằng docker exec, cài thêm gói, sửa file cấu hình, rồi yên tâm. Container tái tạo lại là mọi thứ bay sạch.
Cách sửaMọi thay đổi phải nằm trong Dockerfile hoặc trong volume. Container phải xoá đi tạo lại được bất cứ lúc nào mà không mất gì.
Lưu dữ liệu quan trọng bên trong container
Chạy Postgres bằng docker run postgres không kèm volume. Đến lúc nâng cấp phiên bản, docker rm — và toàn bộ cơ sở dữ liệu biến mất cùng lớp ghi.
Cách sửaMọi trạng thái cần sống lâu đều gắn volume. Xem như container có thể bị xoá bất cứ lúc nào mà không báo trước.
Dùng tag latest ở production
latest không có nghĩa là "mới nhất" — nó chỉ là tag mặc định, và người khác có thể đẩy nội dung hoàn toàn khác vào đó. Cùng một file compose, hôm nay chạy tốt, tháng sau kéo về một bản khác và hỏng.
Cách sửaGhim phiên bản cụ thể (nginx:1.27-alpine), hoặc chắc chắn hơn nữa là ghim theo mã băm sha256.
Nhồi mọi thứ vào một container
Web server, ứng dụng, cơ sở dữ liệu, cron — tất cả trong một container, khởi động bằng script tự chế. Một dịch vụ chết thì Docker không biết, không restart, log trộn lẫn nhau.
Cách sửaMỗi container một việc, một tiến trình chính. Nhiều dịch vụ thì ghép lại bằng Docker Compose.
Kiểm tra hiểu bài
Năm câu, không tính điểm, không giới hạn thời gian. Mục đích là để bạn tự phát hiện chỗ nào còn mơ hồ — mỗi câu giải thích cả đáp án đúng lẫn vì sao các đáp án kia sai.
docker stop web sẽ xoá mọi dữ liệu mà container web đã ghi ra.Đúng / Saipackage.json vào trước rồi mới COPY . .?Chọn 1docker rm -f web, những thứ nào vẫn còn trên máy?Chọn nhiềuNăm điều mang về
Lỗi "máy tôi chạy được" sinh ra vì ta chỉ chuyển đi mã nguồn mà bỏ lại môi trường. Container đóng gói cả hai vào một thứ.
Container không phải máy ảo nhẹ. Máy ảo ảo hoá phần cứng nên cần OS riêng; container ảo hoá hệ điều hành và dùng chung kernel máy chủ.
Một lệnh docker run đi qua: CLI → REST API → daemon → kho image ở máy → registry nếu thiếu → tạo container → cấp mạng → chạy tiến trình.
Image là khuôn chỉ đọc gồm nhiều lớp dùng chung được; container là image đang chạy, có thêm lớp ghi mỏng sẽ mất khi bị xoá.
stop khác rm. Dữ liệu cần sống lâu thì phải nằm trong volume, không nằm trong container.