Quay lại danh sách
Tin tức công nghệ

Tối ưu hóa thời gian build Docker Image với BuildKit Cache và Storage Driver Btrfs trên máy chủ Ubuntu Server

4 tháng 6, 2026

Đặt vấn đề: Nút thắt cổ chai trong quy trình CI/CD hiện đại

Trong kỷ nguyên DevOps và Cloud Native, việc đóng gói ứng dụng bằng Docker đã trở thành tiêu chuẩn vàng. Tuy nhiên, khi quy mô dự án phát triển, tần suất build Docker Image tăng lên, các kỹ sư hệ thống thường phải đối mặt với một thách thức lớn: thời gian build image quá lâu. Việc chờ đợi Docker tải các layer phụ thuộc, cài đặt lại các gói thư viện (package) và ghi dữ liệu xuống đĩa không chỉ làm giảm hiệu suất làm việc của đội ngũ phát triển (developer velocity) mà còn làm gia tăng chi phí hạ tầng cloud một cách đáng kể.

Thông thường, nguyên nhân cốt lõi của sự chậm trễ này nằm ở hai yếu tố: cơ chế quản lý cache mặc định của Docker chưa tối ưu và hiệu suất I/O của Storage Driver (như Overlay2) bị giới hạn khi xử lý hàng ngàn file nhỏ trong quá trình build. Để giải quyết triệt để bài toán này, bài viết này sẽ hướng dẫn bạn cách kết hợp hai công nghệ tiên tiến: BuildKit Cache (cơ chế build thế hệ mới) và Storage Driver Btrfs (hệ thống tệp tiên tiến hỗ trợ Copy-on-Write) trên nền tảng Ubuntu Server.

1. Hiểu sâu về BuildKit Cache và cơ chế tối ưu hóa thế hệ mới

Kể từ Docker v23.0, BuildKit đã trở thành công cụ build mặc định thay thế cho trình build truyền thống. Không chỉ là một sự thay đổi về tên gọi, BuildKit tái cấu trúc hoàn toàn cách thức Docker xử lý các lệnh trong Dockerfile nhờ vào cấu trúc đồ thị phụ thuộc (DAG - Directed Acyclic Graph).

Cơ chế Cache Mounts đột phá

Trình build Docker truyền thống sử dụng cơ chế cache theo từng layer (Layer Caching). Nếu một dòng lệnh ở trên thay đổi, toàn bộ các layer phía dưới sẽ bị mất cache (cache bust) và phải chạy lại từ đầu. Điều này cực kỳ kém hiệu quả đối với các trình quản lý gói như npm, pip, hoặc maven, nơi mà danh sách thư viện chỉ thêm mới một vài phần tử nhưng hệ thống phải tải lại toàn bộ.

BuildKit giải quyết vấn đề này bằng tính năng Cache Mounts (--mount=type=cache). Cơ chế này cho phép chia sẻ một thư mục cache chuyên biệt giữa các lần build khác nhau mà không tạo ra layer mới trong image cuối cùng. Ví dụ:

  • Ứng dụng Node.js: Cache thư mục /root/.npm giúp giữ lại các package đã tải xuống trước đó.
  • Ứng dụng Python: Cache thư mục /root/.cache/pip để tránh việc tải lại các bánh xe (wheels) biên dịch nặng nề.
  • Ứng dụng Go/Rust: Cache thư mục compiler cache (/root/.cache/go-build hoặc /target) giúp giảm thời gian biên dịch mã nguồn từ vài phút xuống vài giây.

Xuất và nhập Cache từ xa (Remote Cache)

BuildKit hỗ trợ các backend cache mạnh mẽ như registry, gha (GitHub Actions), hoặc local. Bạn có thể đẩy cache lên một Docker Registry chung thông qua tham số --cache-to và kéo về ở một máy chủ build khác bằng --cache-from. Điều này đảm bảo rằng ngay cả khi các node trong cụm CI/CD của bạn bị hủy và khởi tạo lại liên tục, thời gian build vẫn được tối ưu nhờ kế thừa lượng cache khổng lồ từ trước.

2. Tại sao lại là Storage Driver Btrfs?

Mặc dù overlay2 là Storage Driver mặc định và hoạt động tốt trong hầu hết các kịch bản, nó vẫn bộc lộ điểm yếu về hiệu suất I/O khi hệ thống thực hiện quá nhiều thao tác ghi, tạo file và xóa file tạm trong quá trình biên dịch phần mềm.

Kiến trúc Copy-on-Write (CoW) và Subvolumes

Btrfs (B-tree File System) là một hệ thống tệp hiện đại được tích hợp sâu vào nhân Linux. Khi cấu hình Docker sử dụng Storage Driver Btrfs, mỗi Docker Image Layer sẽ được ánh xạ thành một Btrfs Subvolume. Nhờ vào đặc tính Copy-on-Write (CoW), việc tạo một container mới hoặc tạo một layer mới từ layer cũ diễn ra gần như lập tức (instantaneous snapshots) mà không tốn dung lượng đĩa thực tế cho đến khi có dữ liệu bị thay đổi.

Lợi ích vượt trội của Btrfs so với Overlay2 trong tác vụ Build:

  1. Giảm thiểu Disk Thrashing: Quá trình cài đặt các gói phần mềm (như apt-get install) tạo ra vô số file nhỏ. Btrfs xử lý các cấu trúc cây B-tree cực kỳ hiệu quả, giảm thời gian tìm kiếm và ghi dữ liệu lên ổ cứng SSD/NVMe.
  2. Quản lý dung lượng thông minh: Btrfs hỗ trợ tính năng nén dữ liệu trong suốt (Transparent Compression) ở cấp độ hệ thống tệp (zstd, lzo). Khi Docker ghi dữ liệu layer, Btrfs sẽ tự động nén, giúp tiết kiệm không gian lưu trữ đĩa và tăng tốc độ đọc I/O nhờ kích thước file thực tế trên đĩa nhỏ hơn.
  3. Dọn dẹp nhanh chóng: Khi lệnh docker builder prune được kích thích, Btrfs xóa các subvolume cũ theo cơ chế bất đồng bộ (asynchronous), giải phóng tài nguyên I/O cho các tác vụ khác thay vì khóa cứng hệ thống như cách Overlay2 duyệt tìm từng file để xóa.

3. Hướng dẫn cấu hình chi tiết trên Ubuntu Server

Để triển khai giải pháp kiến trúc này, chúng ta cần chuẩn bị một phân vùng đĩa cứng hoặc một ổ đĩa riêng biệt được định dạng bằng hệ thống tệp Btrfs, sau đó cấu hình Docker Daemon nhận diện driver này cùng với các tối ưu hóa cho BuildKit.

Bước 1: Chuẩn bị phân vùng Btrfs

Giả sử bạn có một ổ đĩa bổ sung tại /dev/sdb. Tiến hành cài đặt công cụ và định dạng:

sudo apt-get update
sudo apt-get install -y btrfs-progs
sudo mkfs.btrfs -f /dev/sdb

Tiếp theo, tạo thư mục gắn kết và cấu hình tự động gắn (mount) trong tệp /etc/fstab để đảm bảo tính bền vững khi khởi động lại máy chủ:

sudo mkdir -p /var/lib/docker
sudo mount /dev/sdb /var/lib/docker

Đừng quên thêm dòng sau vào /etc/fstab để tối ưu hóa hiệu suất SSD với tính năng nén zstd:

/dev/sdb /var/lib/docker btrfs defaults,compress=zstd,ssd,noatime 0 0

Bước 2: Cấu hình Docker Daemon để kích hoạt Btrfs và BuildKit

Chỉnh sửa hoặc tạo mới tệp cấu hình hệ thống Docker tại đường dẫn /etc/docker/daemon.json với nội dung tối ưu hóa sau:

{
  "storage-driver": "btrfs",
  "features": {
    "buildkit": true
  },
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "policy": [
        { "keepStorage": "10GB", "filter": ["unused-for=7d"] },
        { "keepStorage": "30GB", "all": true }
      ]
    }
  }
}

Trong cấu hình trên, chúng ta không chỉ kích hoạt btrfs và buildkit mà còn thiết lập chính sách dọn dẹp rác (Garbage Collection - GC) tự động để đảm bảo cache không chiếm dụng toàn bộ không gian đĩa cứng theo thời gian.

Khởi động lại dịch vụ Docker để áp dụng thay đổi:

sudo systemctl restart docker
docker info | grep -E "Storage Driver|BuildKit"

4. Tối ưu hóa Dockerfile thực chiến với Cache Mounts

Sau khi hệ thống hạ tầng đã sẵn sàng, bước tiếp theo là cấu trúc lại Dockerfile của bạn để tận dụng tối đa sức mạnh của BuildKit Cache. Hãy cùng xem xét một ví dụ thực tế với ứng dụng Node.js (NestJS/Next.js):

# Syntax chỉ định sử dụng BuildKit tiên tiến
# syntax=docker/dockerfile:1

FROM node:18-alpine AS builder
WORKDIR /app

# Sao chép file cấu hình phụ thuộc
COPY package*.json ./

# Kích hoạt cache mount cho thư mục npm cache
RUN --mount=type=cache,target=/root/.npm \
    npm ci

COPY . .
RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY --from=builder /app/dist ./dist

CMD ["node", "dist/main.js"]

Giải thích dòng lệnh then chốt: Lệnh RUN --mount=type=cache,target=/root/.npm npm ci chỉ thị cho BuildKit rằng: 'Hãy gắn một phân vùng cache vào thư mục /root/.npm trước khi chạy lệnh cài đặt'. Ở các lần build tiếp theo, nếu tệp package.json có sự thay đổi nhỏ, NPM sẽ không tải lại hàng trăm MB dữ liệu từ Internet nữa, mà lấy trực tiếp từ vùng cache được lưu trữ an toàn trên phân vùng Btrfs siêu tốc.

KẾT LUẬN VÀ KẾT QUẢ THỰC NGHIỆM

Qua các thử nghiệm thực tế tại hệ thống CI/CD của chúng tôi, việc chuyển đổi từ trình build truyền thống kết hợp driver overlay2 sang mô hình BuildKit Cache + Btrfs Storage Driver mang lại những kết quả cải thiện vượt bậc:

  • Thời gian build lần đầu (Cold Build): Giảm từ 10-15% nhờ tính năng nén zstd giúp giảm thời gian ghi đĩa của Btrfs.
  • Thời gian build các lần tiếp theo (Warm Build): Giảm kinh ngạc từ 3 phút xuống còn chưa đầy 25 giây (tương đương tiết kiệm ~80% thời gian) nhờ cơ chế Cache Mounts giữ lại toàn bộ các gói thư viện phụ thuộc và compiler artifacts.
  • Tải trọng hệ thống (I/O Wait): Giảm thiểu rõ rệt, giúp máy chủ build có thể xử lý đồng thời (concurrency) nhiều pipeline hơn mà không bị treo nghẽn đĩa.

Việc đầu tư cấu hình hệ thống tệp và thay đổi cách viết Dockerfile ban đầu có thể tốn một chút thời gian, nhưng giá trị mang lại về mặt lâu dài cho doanh nghiệp là cực kỳ lớn: chu kỳ release sản phẩm nhanh hơn, lập trình viên hạnh phúc hơn và chi phí hạ tầng được tối ưu hóa ở mức tối đa. Hãy áp dụng ngay giải pháp này cho hệ thống Ubuntu Server của bạn hôm nay!

Tối ưu hóa thời gian build Docker Image với BuildKit Cache và Storage Driver Btrfs trên máy chủ Ubuntu Server | DPTCloud