Multi-Stage Build: Tối Ưu Hóa Docker Image

·

7 min read

Trong quá trình phát triển phần mềm, việc tạo ra các Docker image tối ưu là một yếu tố quan trọng giúp đảm bảo ứng dụng của bạn chạy hiệu quả trên môi trường sản xuất. Một Docker image nhẹ sẽ giúp cải thiện hiệu suất, giảm chi phí lưu trữ và tốc độ triển khai. Đây là lúc multi-stage build trong Docker phát huy tác dụng.

Multi-Stage Build là gì?

Multi-stage build là một tính năng của Docker cho phép bạn sử dụng nhiều giai đoạn (stages) trong quá trình tạo Docker image. Thay vì phải tạo nhiều Dockerfile hoặc build các image phụ lớn và không cần thiết, bạn có thể chia quá trình build thành nhiều bước logic, và chỉ giữ lại những gì cần thiết cho ứng dụng chạy trong môi trường sản xuất.

Multi-stage build giúp:

  • Giảm kích thước của Docker image cuối cùng.

  • Tăng cường bảo mật bằng cách loại bỏ các file không cần thiết.

  • Đơn giản hóa quy trình build và deploy.

Tại sao lại cần Multi-Stage Build?

Thông thường, khi build một Docker image, bạn cần cài đặt các công cụ phát triển và dependencies mà chỉ cần cho quá trình build, nhưng lại không cần cho môi trường sản xuất. Điều này sẽ làm tăng kích thước của Docker image nếu chúng không bị loại bỏ. Multi-stage build cho phép chúng ta tách biệt quá trình build ứng dụng và chạy ứng dụng trong hai (hoặc nhiều hơn) giai đoạn.

Hãy xem xét ví dụ sau:

# Stage 1: Build Stage
FROM golang:1.20-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o myapp .

# Stage 2: Production Stage
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]

Trong ví dụ trên, Dockerfile này được chia thành 2 giai đoạn:

  1. Build Stage: Sử dụng image golang:1.20-alpine để build ứng dụng Go. Sau khi build thành công, chúng ta có một file binary của ứng dụng.

  2. Production Stage: Sử dụng image alpine rất nhẹ, chỉ copy file binary đã build từ giai đoạn đầu tiên và không cần mang theo các file thừa như mã nguồn hay các công cụ build. Điều này giúp image cuối cùng có kích thước nhỏ hơn rất nhiều.

Ưu điểm của Multi-Stage Build

  1. Tối ưu kích thước Docker image

    • Khi sử dụng multi-stage build, bạn chỉ cần giữ lại các thành phần thực sự cần thiết cho môi trường sản xuất như binary hoặc các thư viện cần thiết. Các thành phần như mã nguồn, dependencies và công cụ phát triển sẽ không được đưa vào image cuối cùng.

    • Ví dụ: Một image build từ Node.js với các dependencies thường có kích thước vài trăm MB, nhưng khi chỉ giữ lại các file tĩnh trong giai đoạn cuối, kích thước có thể giảm xuống còn vài chục MB.

  2. Bảo mật tốt hơn

    • Khi loại bỏ các file không cần thiết như mã nguồn hoặc các công cụ build, bạn cũng giảm được khả năng rò rỉ thông tin hoặc lỗ hổng bảo mật có thể xảy ra do các thành phần thừa.
  3. Quy trình build rõ ràng

    • Việc chia quá trình build thành nhiều giai đoạn giúp quy trình trở nên dễ hiểu và rõ ràng hơn. Bạn có thể dễ dàng quản lý các phần build khác nhau và có thể thêm hoặc bớt từng giai đoạn một cách linh hoạt.

Multi-Stage Build với 3 Giai Đoạn

Hãy xem một ví dụ nâng cao hơn, sử dụng 3 giai đoạn: build, test, và production.

# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Test
FROM node:18-alpine AS tester
WORKDIR /app
COPY --from=builder /app ./
RUN npm run test

# Stage 3: Production
FROM nginx:alpine AS production
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Giai đoạn 1: Build

  • Sử dụng image Node.js để cài đặt dependencies và build ứng dụng.

  • Sau đó, mã nguồn và các dependencies được sử dụng để build các file tĩnh (như trong ứng dụng React hoặc Angular).

Giai đoạn 2: Test

  • Sau khi build, chúng ta chạy các unit test để đảm bảo mọi thứ hoạt động như mong đợi.

Giai đoạn 3: Production

  • Giai đoạn cuối cùng sử dụng NGINX (image rất nhẹ) để chạy ứng dụng đã được build sẵn từ giai đoạn trước.

  • Các file build được copy từ giai đoạn build vào NGINX để phục vụ ứng dụng trong môi trường production.

Multi-stage build là một kỹ thuật mạnh mẽ giúp tối ưu hóa Docker image của bạn. Nó không chỉ giúp giảm kích thước image, tăng cường bảo mật mà còn giúp quy trình build trở nên rõ ràng và dễ quản lý hơn. Đặc biệt, đối với những ứng dụng lớn có nhiều bước xử lý, việc tách biệt từng giai đoạn sẽ giúp dễ dàng phát hiện lỗi và điều chỉnh quy trình build.

Khi phát triển ứng dụng Docker, hãy cân nhắc sử dụng multi-stage build để mang lại lợi ích tối đa cho quá trình phát triển, kiểm thử và triển khai ứng dụng.

Dưới đây là một ví dụ về một Dockerfile multi-stage build, trong đó các file hoặc kết quả từ từng giai đoạn (stage) được copy tuần tự qua từng giai đoạn. Mỗi stage có một vai trò cụ thể và file được truyền từ stage này sang stage khác.

Dockerfile Multi-Stage với Các Giai Đoạn Copy Liên Tiếp:

# Stage 1: Build application
FROM node:18-alpine AS build-stage
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json to install dependencies
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy all source files into the image
COPY . .
# Build the application (output goes to /app/dist)
RUN npm run build

# Stage 2: Run tests
FROM node:18-alpine AS test-stage
# Set working directory
WORKDIR /app
# Copy the built app from the build stage
COPY --from=build-stage /app/dist ./dist
# Copy dependencies from build stage (to use them for testing)
COPY --from=build-stage /app/node_modules ./node_modules
# Run tests
COPY test ./test
RUN npm run test

# Stage 3: Create production image
FROM nginx:alpine AS production-stage
# Set working directory for NGINX
WORKDIR /usr/share/nginx/html
# Copy built app from the test stage (since it's already built and tested)
COPY --from=test-stage /app/dist .
# Expose port 80
EXPOSE 80
# Start NGINX
CMD ["nginx", "-g", "daemon off;"]

Giải thích các giai đoạn:

Stage 1: Build Application (build-stage)

  • FROM node:18-alpine AS build-stage: Sử dụng image Node.js 18 để build ứng dụng.

  • COPY package.json ./*: Copy các file package.jsonpackage-lock.json vào container để cài đặt dependencies.

  • RUN npm install: Cài đặt dependencies cần thiết cho dự án.

  • COPY . .: Copy toàn bộ mã nguồn vào container.

  • RUN npm run build: Build ứng dụng và tạo ra các file tĩnh trong thư mục /app/dist.

Stage 2: Run Tests (test-stage)

  • FROM node:18-alpine AS test-stage: Sử dụng lại image Node.js để chạy các bài kiểm tra (test).

  • COPY --from=build-stage /app/dist ./dist: Copy các file build đã được tạo ra từ build-stage vào test-stage để test.

  • COPY --from=build-stage /app/node_modules ./node_modules: Copy các dependencies đã được cài đặt từ build-stage để sử dụng cho quá trình test.

  • COPY test ./test: Copy các file test từ mã nguồn vào container.

  • RUN npm run test: Chạy các bài test. Nếu có lỗi, build sẽ dừng tại đây.

Stage 3: Production Image (production-stage)

  • FROM nginxAS production-stage: Sử dụng NGINX (image rất nhẹ) để deploy ứng dụng.

  • COPY --from=test-stage /app/dist .: Copy các file build từ test-stage (đã qua kiểm tra) sang NGINX để phục vụ ứng dụng.

  • EXPOSE 80: Mở cổng 80 để ứng dụng có thể truy cập từ bên ngoài.

  • CMD ["nginx", "-g", "daemon off;"]: Khởi động NGINX.

Lưu đồ quá trình:

  1. Build Stage: Ứng dụng được build và tạo ra các file tĩnh (frontend) trong thư mục /app/dist.

  2. Test Stage: Các file build từ build-stage được copy qua để chạy unit tests, đảm bảo rằng các file được tạo ra chính xác và không có lỗi.

  3. Production Stage: Các file đã build và test được chuyển qua production-stage, nơi chúng được phục vụ bởi NGINX.