So sánh build bình thường và multi stage build của Docker

Multi stage build phù hợp cho những người muốn tối ưu Dockerfiles cũng như Docker Image và đảm bảo nó vẫn dễ đọc dễ bảo trì. Nếu bạn chỉ muốn làm một file kiểu chạy được là được thì có vẻ dành thời gian vào điều này là không cần thiết rồi. Dùng multi-stage builds […]

So sánh build bình thường và multi stage build của Docker

Multi stage build phù hợp cho những người muốn tối ưu Dockerfiles cũng như Docker Image và đảm bảo nó vẫn dễ đọc dễ bảo trì. Nếu bạn chỉ muốn làm một file kiểu chạy được là được thì có vẻ dành thời gian vào điều này là không cần thiết rồi.

Dùng multi-stage builds

Multi stage nghĩa là dùng nhiều FROM trong một Dockerfile. Không giới hạn phải dùng chung một base, nó giúp chia nhỏ việc build ra thành từng phần từng giai đoạn khác nhau. Giúp loại bỏ những phần không cần thiết trong lúc có thể copy các artifacts từ stage này qua stage khác

Ví dụ như một project NextJS. Mình cần build với dev dependencies, nhưng khi start thì không cần. Nếu như thông thường sẽ dư ra nhiều deps không cần thiết, thay vào đó

ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine AS base
WORKDIR /usr/src/app

FROM base AS deps
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

FROM deps AS build
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

FROM base AS final

ENV NODE_ENV=production
COPY package.json .
COPY .env .
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY --from=build /usr/src/app/.next ./.next
COPY --from=build /usr/src/app/next.config.js .

EXPOSE 3000

CMD [ "npm", "start" ]
Code language: Dockerfile (dockerfile)

Vậy cuối cùng ta được gì?

Một image với:

  • Chỉ có các dependencies cần thiết
  • Chỉ chứa code được build ra, không hề có những file không cần thiết ( như thư mục /src )
  • Tất nhiên là dung lượng sẽ nhỏ gọn hơn nhiều so với việc build thông thường ( npm ci, npm run build, npm start )

Trong thực tế thì nó như thế này. Mình sẽ thử build cái Frontend của website này (NextJS) với 2 kiểu và xem thử thời gian và dung lượng.
Các bài kiểm tra này đã được dùng buildkit và prune trước khi thực hiện

Thử nghiệm build bình thường

ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm ci && npm run dockerbuild
EXPOSE 3000
CMD [ "npm", "start" ]Code language: Dockerfile (dockerfile)
docker buildx build -t regular-dockerbuild -f regular-build.Dockerfile .Code language: Bash (bash)

Và kết quả, mình mất 2 phút 9 giây và tạo ra một image nặng 1.77 GB

Nếu tách 2 layer npm ci và npm run build thì nó kiểu

=> [2/5] WORKDIR /usr/src/app                                                                 0.1s
=> [3/5] COPY . .                                                                 0.1s
=> [4/5] RUN npm ci                                                                 21.7s
=> [5/5] RUN npm run dockerbuild                                                                 66.6s
=> exporting to image                                                                 24.7sCode language: Bash (bash)

Thử nghiệm multi stage build

Cái này thì quên xoá node:18-alpine image nên công thêm 5s cho công bằng.

Có một điểm thú vị, là với BuildKit thì bạn không cần đợi stage trước hoàn thành mà nó sẽ chạy song song nếu như các step không phụ thuộc nhau

Kết quả:

Cũng là 2 phút 9 giây (chưa +5) nhưng sẽ tạo ra một image chỉ nặng 1.17 GB

Với lần lượt 17s và 18s cho 2 lần install deps. Có lẽ nếu mình bỏ qua việc cài riêng từng phần thì sẽ nhanh hơn?

Kết luận

Việc sử dụng thứ gì cách gì thì tuỳ nhu cầu trường hợp mà ta dùng thôi.
Không thể nói build bình thường chậm mà multi nhanh, hay multi tối ưu hơn được.

Nhưng mình có lời khuyên:
Kèm theo node_modules nên nặng nề thôi, chứ build standalone hay binary thì copy mỗi cái file vào final stage và export ra thì rất nhẹ
Một image gọn nhẹ không chưa rác và source code là điều cần thiết khi upload lên các dịch vụ lưu trữ, kể cả private. Nên nếu được, hãy sử dụng dụng multi stage đi.

Bạn sẽ thấy cảm giác pull/push một file image với dung lượng lớn nó ra sao khi mạng không ổn định, chậm.

A
WRITTEN BY

ArcLight

Responses (0 )