Dùng Dive soi từng layer Docker Image: Cách mình ‘ép cân’ Image từ 1.5GB xuống 120MB

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Khi Docker Image trở thành “hố đen” dung lượng lúc 2 giờ sáng

2 giờ sáng, điện thoại mình rung liên hồi. Hệ thống giám sát Prometheus báo động đỏ rực: Disk Pressure trên cluster production của dự án e-commerce. Kiểm tra nhanh, mình phát hiện một nghịch lý. Bản microservice vừa deploy chiều nay chiếm tới 1.5GB dung lượng. Trong khi đó, mã nguồn thực tế chỉ nặng vài chục MB.

Cảm giác lúc đó thực sự bế tắc. Mình biết chắc chắn có “kẻ gian” đang ẩn nấp bên trong Docker Image, nhưng làm sao để lôi nó ra ánh sáng? Lệnh docker images chỉ cho biết tổng dung lượng. Lệnh docker history thì hiện danh sách lệnh thực thi khô khan, không chỉ đích danh file nào đang ngốn dung lượng ở layer nào.

Tình huống này làm mình nhớ đến lần debug lỗi memory leak kéo dài 2 ngày trước đây. Cả hai đều có chung một điểm yếu: sự thiếu minh bạch bên trong container. Đó là lúc mình tìm đến Dive. Đây là công cụ mà mọi DevOps hay Backend Engineer nên có nếu không muốn thức trắng đêm vì những file rác vô dụng.

Tại sao những cách kiểm tra thông thường lại vô tác dụng?

Thông thường, khi thấy Image nặng, anh em hay dùng lệnh này:

docker history my-app:latest

Kết quả trả về thường sẽ như thế này:

IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
7f928e345b1c   2 hours ago    COPY . . # buildkit                             850MB     
<missing>      2 hours ago    RUN npm install # buildkit                      450MB     
...

Nhìn vào đây, bạn thấy layer COPY . . nặng tận 850MB. Nhưng 850MB đó cụ thể là gì? Là thư mục node_modules phình to? Là file log vô tình bị copy vào? Hay là mớ tài liệu PDF, hình ảnh test bạn quên cho vào .dockerignore? Lệnh docker history hoàn toàn im lặng trước những câu hỏi này.

Vấn đề cốt lõi nằm ở tính cộng dồn của layer trong Docker. Giả sử tại Layer 1, bạn tải một file nặng 500MB. Sang Layer 2, bạn dùng lệnh rm để xóa nó đi. Kết quả là Image cuối cùng vẫn “cõng” thêm 500MB đó. File chỉ bị ẩn đi ở tầng cuối cùng nhưng vẫn nằm chình ình trong lịch sử Image. Đây là cái bẫy kinh điển mà nhiều người mới dùng Docker thường sập bẫy.

Dive – “Kính hiển vi” soi từng ngóc ngách của Docker Image

Dive là công cụ mã nguồn mở giúp bạn khám phá nội dung Docker Image theo từng tầng. Nó không chỉ liệt kê file đơn thuần. Dive chỉ rõ file nào được thêm mới (A – Added), file nào bị sửa đổi (M – Modified) và file nào bị xóa (D – Removed) giữa các layer.

Cách cài đặt Dive trong một nốt nhạc

Trên Linux (Ubuntu/Debian), mình thường cài qua file .deb cho ổn định:

wget https://github.com/wagoodman/dive/releases/download/v0.10.0/dive_0.10.0_linux_amd64.deb
sudo apt install ./dive_0.10.0_linux_amd64.deb

Anh em dùng Mac thì cứ dùng brew cho rảnh tay:

brew install dive

Hoặc chạy trực tiếp bằng chính Docker (một cách dùng khá thú vị):

docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest <your-image-tag>

Phân tích Image thực tế

Để soi con Image 1.5GB lúc nãy, mình gõ lệnh:

dive my-app:latest

Giao diện Dive hiện lên với hai bảng điều khiển trực quan:

  • Bên trái (Layers): Danh sách các tầng của Image. Bạn dùng phím mũi tên để di chuyển qua lại.
  • Bên phải (Current Layer Contents): Toàn bộ cấu trúc file system tại layer đang chọn.

Kinh nghiệm xương máu: Hãy tập trung vào hệ thống màu sắc ở khung bên phải.

  • Màu vàng: File bị thay đổi nội dung.
  • Màu xanh lá: File mới xuất hiện ở layer này.
  • Màu đỏ: File đã bị xóa (nhưng vẫn chiếm dung lượng ở các layer phía trước).

Case study: Truy tìm 500MB “mất tích” bí ẩn

Khi soi Image dự án e-commerce bằng Dive, mình phát hiện một sai lầm ngớ ngẩn. Tại layer chạy lệnh npm install, dung lượng tăng vọt 500MB. Nhìn sang khung bên phải, thư mục /root/.npm chiếm gần hết con số đó.

Hóa ra, trong Dockerfile cũ mình viết như sau:

RUN npm install

Lệnh này mặc định lưu cache của npm vào thư mục home của root. Dù sau đó mình có xóa node_modules thừa, đống cache kia vẫn nằm lại vĩnh viễn trong layer đó.

Bên cạnh đó, Dive còn cung cấp chỉ số Image Efficiency Score. Image của mình lúc đó chỉ đạt 65%. Điều này đồng nghĩa với việc 35% dung lượng là “Wasted Space” (không gian lãng phí). Dive liệt kê chính xác từng file gây lãng phí, giúp mình biết cần phải “phẫu thuật” ở đâu.

Chiến lược tối ưu: Để Docker Image luôn nhẹ nhàng

Sau khi dùng Dive để bắt bệnh, mình áp dụng quy trình 3 bước để ép cân cho Image:

1. Triển khai Multi-stage Build

Đây là kỹ thuật quan trọng nhất. Thay vì dùng một Image cho cả build và chạy code, hãy chia làm hai giai đoạn. Giai đoạn đầu dùng Image đầy đủ công cụ để build. Giai đoạn sau chỉ copy file thực thi sang một Image siêu nhẹ như Alpine.

# Giai đoạn Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Giai đoạn Run
FROM node:18-alpine
WORKDIR /app
# Chỉ copy những thứ thực sự cần thiết
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

2. Gộp các lệnh RUN và dọn dẹp tức thì

Nếu cần cài đặt package từ hệ điều hành, hãy cài và xóa cache trong cùng một lệnh RUN. Khi đó, lệnh xóa mới thực sự có tác dụng giải phóng bộ nhớ cho layer.

RUN apt-get update && apt-get install -y \
    git \
    && rm -rf /var/lib/apt/lists/*

3. Đừng bỏ quên .dockerignore

Hãy coi .dockerignore quan trọng như .gitignore. Đừng bao giờ để thư mục .git, node_modules local hay các file tài liệu test lọt vào Docker Context. Mỗi MB bỏ sót sẽ bị nhân lên gấp nhiều lần khi build qua các layer.

Kết quả ngọt ngào

Sau khi dùng Dive phân tích và áp dụng Multi-stage build, mình đã ép cân thành công Image từ 1.5GB xuống còn vỏn vẹn 120MB. Hệ thống chạy mượt mà hơn hẳn. Thời gian pull Image từ Registry về server giảm từ vài phút xuống chỉ còn vài giây.

Đừng coi nhẹ kích thước Docker Image. Một Image gọn nhẹ không chỉ tiết kiệm chi phí lưu trữ đám mây. Nó còn giúp quy trình CI/CD nhanh hơn và hạn chế lỗ hổng bảo mật đáng kể. Lần tới nếu thấy Image có dấu hiệu “béo phì”, hãy bật Dive lên và soi ngay nhé!

Share: