Chạy 100 container từ cùng một image 200MB — chúng có tốn 20GB đĩa không? Không, chúng tốn khoảng 200MB cộng một chút cho mỗi container. Đây là một trong những phần khéo léo nhất của Docker: image layers và overlayfs. Image không phải một khối đơn mà là chồng nhiều lớp chỉ-đọc dùng chung được, còn mỗi container chỉ thêm một lớp ghi mỏng chồng lên trên. Bài này (phần 6 loạt Docker) chạy thật để phơi bày các layer, tự dựng overlayfs, và đo mức tiết kiệm đĩa.
Image = chồng nhiều layer chỉ-đọc
Mỗi lệnh trong Dockerfile (RUN, COPY, ADD) tạo ra một layer — một tập hợp các thay đổi của hệ thống file. Image cuối là các layer đó xếp chồng lên nhau. docker history phơi bày chúng cùng kích thước:
docker history python:3.11-alpine
# 55.6MB RUN apk add python... (layer cài Python)
# 2.94MB RUN apk add ca-certs...
# 0B ENV/CMD ... (metadata, không tốn đĩa)
Điểm mấu chốt: layer được chia sẻ. Image python:3.11-alpine dựa trên alpine, nên layer alpine của nó là chính layer của image alpine — đo thật: python:3.11-alpine có 4 layer, alpine có 1 layer, và layer alpine đó không bị nhân đôi trên đĩa. Mọi image dựa trên alpine chia sẻ chung một bản.

Hình 1: Image là chồng layer chỉ-đọc (mỗi lệnh Dockerfile một layer); overlayfs gộp lowerdir (layer image chỉ đọc, dùng chung) + upperdir (lớp ghi riêng) thành merged; copy-on-write khiến lower không bao giờ đổi.
overlayfs và copy-on-write, dựng bằng tay
Cơ chế gộp các layer là overlayfs — một union filesystem. Nó xếp chồng:
lowerdir: các layer image (chỉ đọc, dùng chung giữa mọi container).upperdir: lớp ghi của container (riêng mỗi container).merged: cây file container thấy — là lower và upper chồng lên nhau.
Mình tự dựng một overlay để thấy copy-on-write hoạt động:

Hình 2: Chạy thật — docker history phơi bày layer (55.6MB cài Python...), python 4 layer / alpine 1 layer; overlay tự dựng: đọc base.txt lấy từ lower ("tu-image-goc"), sửa qua merged thì lower giữ nguyên còn upper có bản copy ("sua-boi-container"); ba container cùng image mỗi cái thêm 4.1kB, 67.9MB dùng chung.
- Đọc lấy từ lower:
cat merged/base.txttrả vềtu-image-goc— file nằm ở lower (chỉ đọc), không tốn thêm đĩa. - Ghi thì copy-up sang upper: sửa
merged/base.txtthànhsua-boi-container. Kết quả đo thật:lower/base.txtvẫn làtu-image-goc(không đổi!), cònupper/base.txtlà bản copy đã sửa. File mớinew.txtcũng nằm ở upper.mergedthấy phiên bản đã sửa. Đây là copy-on-write: chỉ khi ghi, một bản sao mới được tạo ở lớp trên; lớp dưới (image) không bao giờ thay đổi — nên nhiều container chia sẻ được nó an toàn.
Đo thật mức tiết kiệm
Chạy ba container từ cùng python:3.11-alpine, xem kích thước lớp ghi của từng cái: mỗi container SizeRw = 4.1kB, với virtual = 67.9MB (kích thước "ảo" gồm cả image dùng chung). Nghĩa là ba container không tốn 3 × 67.9MB; chúng dùng chung một bản 67.9MB và mỗi cái chỉ thêm 4.1kB cho những gì nó ghi. Với 100 container, tiết kiệm còn ngoạn mục hơn.
Đây cũng là lý do container khởi động gần như tức thì: tạo một container mới chỉ cần tạo một upperdir rỗng rồi overlay lên các layer image có sẵn — không copy gì cả.
Đánh đổi cần cân nhắc
Ghi nhiều vào lớp container thì mất lợi thế — dùng volume. Copy-on-write rẻ khi container chủ yếu đọc image và ghi ít. Nhưng nếu app ghi khối lượng lớn (database, log) vào lớp ghi của container, mỗi lần sửa file lớn phải copy-up cả file, và dữ liệu đó mất khi container bị xoá. Với dữ liệu bền/ghi nhiều, dùng volume hoặc bind mount (chủ đề bài sau) — chúng nằm ngoài overlayfs.
Thứ tự và cách gộp layer ảnh hưởng kích thước image. Vì mỗi lệnh là một layer cộng dồn, xoá file ở một layer sau không làm image nhỏ đi — file vẫn nằm ở layer trước. Ví dụ RUN apt install ... rồi RUN apt clean ở hai layer để lại rác ở layer đầu. Gộp vào một RUN (dùng &&) hoặc dùng multi-stage build (bài sau) mới giảm được kích thước thật.
overlayfs có giới hạn và điểm lạ. Đổi tên file lớn qua ranh giới lower/upper, một số thao tác trên thư mục có thể chậm hoặc có hành vi khác hệ thống file thường. Với hầu hết ứng dụng không sao, nhưng workload I/O đặc thù (database) nên đặt dữ liệu trên volume thay vì lớp overlay để có hiệu năng và ngữ nghĩa đĩa chuẩn.
Ba ý mang về
- Image là chồng layer chỉ-đọc dùng chung: đo thật
docker historyphơi bày từng layer + kích thước;python:3.11-alpine4 layer,alpine1 layer, và layer alpine dùng chung cho mọi image dựa trên nó — không nhân đôi trên đĩa. - overlayfs + copy-on-write: đo thật tự dựng overlay, sửa file qua
mergedthìlower(image) giữ nguyên còn bản copy nằm ởupper— image không bao giờ đổi nên nhiều container chia sẻ an toàn. - Tiết kiệm đĩa và khởi động tức thì: đo thật 3 container cùng image mỗi cái chỉ thêm 4.1kB (67.9MB dùng chung); tạo container = tạo
upperdirrỗng — nhưng ghi nhiều nên dùng volume, và gộp layer để giảm kích thước image.
Nguồn
- Kernel docs — Overlay Filesystem: https://docs.kernel.org/filesystems/overlayfs.html
- Docker docs — About storage drivers / overlayfs: https://docs.docker.com/engine/storage/drivers/overlayfs-driver/
- Docker docs — Images and layers: https://docs.docker.com/get-started/docker-concepts/building-images/understanding-image-layers/
Phần sau ta xem cách image được dựng: Dockerfile và cache layer — vì sao thứ tự các lệnh quyết định tốc độ build, và đặt COPY . . sai chỗ làm hỏng cache khiến build lại từ đầu mỗi lần.