Tối ưu docker image

tl;dr

  • tối ưu .dockerignore.
  • dùng minimal base image, ví dụ alpine, slim, distroless.
  • giảm số lượng command / layer.
  • install production dependency thôi.
  • dùng remote build system, ví dụ nx hay turborepo, để cache lại build.
  • sử dụng multistage build.

mục đích và ngữ cảnh

  • mục đích: tối ưu dung lượng và thời gian build docker image để tăng hiệu suất triển khai.
  • ngữ cảnh: build một full-stack JavaScript web app dùng React và Nest.js.

gốc

FROM node:18

EXPOSE 8081
WORKDIR /web

# copy source files
COPY package.json ./
COPY package-lock.json ./
COPY turbo.json ./
COPY packages ./packages
COPY apps/client ./apps/client
COPY apps/server ./apps/server

# install dependencies
RUN npm ci

# build
RUN npm run build

# run
CMD ["npm", "run", "serve", "-w", "server"]

tối ưu .dockerignore

  • ignore hết mấy file không cần thiết khỏi docker image như README, ci, git, logs, env, dist, …
.env
logs
*.log
npm-debug.log*
docs
*.md
coverage
node_modules
.npm
dist
.git
.DS_Store

dùng minimal base image

  • dùng image nhẹ làm base image. image này được lược bỏ tối đa tính năng không cần thiết để tối ưu dung lượng.
  • một vài minimal base image tiêu biểu là alpine, slimdistroless.
  • trong 3 base images trên, thì alpine là image thông dụng nhất, còn nhẹ nhất thì là distroless.
  • vì bị lược bỏ tối đa tính năng nên bạn phải tự cài tay những thư việc bạn muốn. ví dụ, sử dụng distroless image, phải tự cài thêm npm nếu muốn cài thêm thư viện.
  • vào việc. ở bước này mình sẽ dùng node:alpine (alpine cài thêm node.js).
FROM node:18-alpine
  • một lưu ý nhỏ là Alpine không phải Ubuntu, nên các câu lệnh có thể khác biệt. điển hình là câu lệnh quản lý thư viện, trong Alpineapk còn trong Ubuntuapt-get.

giảm số lượng câu lệnh / layer

  • hiểu đơn giản thế này, docker image được tạo từ nhiều layer khác nhau. mỗi câu lệnh (RUN, COPY, FROM, ADD, …) tạo thêm một layer cho docker image, càng nhiều câu lệnh thì docker image càng nặng.
  • do đó, nên gộp chung các câu lệnh để giảm số lượng layer.
  • vào việc. ở bước này mình sẽ gộp các cậu lệnh COPY ở trên thành 2 câu lệnh.
COPY package*.json turbo.json ./
COPY packages apps ./

install production dependency thôi

  • thay vì install tất cả dependency, bạn chỉ nên install các dependency giúp app build và chạy.
  • vào việc. ở bước này mình thêm omit=dev vào câu lệnh npm ci.
# install dependencies
RUN npm ci --omit=dev

dùng remote build system

  • cache lại output sau mỗi lần build, nếu ở lần build tiếp theo code không đổi thì lấy từ cache ra thay vì phải build lại.
  • vào việc. ở bước này mình dùng turborepo, bạn có thể dùng nx cái gì khác thay thế. lưu ý, bạn cần setup file setting turbo.json trước.
{
	"$schema": "https://turbo.build/schema.json",
	"pipeline": {
		"dev": {
			"cache": false,
			"persistent": true
		},
		"build": {
			"cache": true,
			"persistent": false,
			"dependsOn": ["^build"],
			"outputs": ["dist/**"]
		}
	}
}
{
	"scripts": {
		"build": "turbo run build --remote-only"
	}
}
ENV TURBO_TEAM=xxx
ARG TURBO_TOKEN
ENV TURBO_TOKEN=$TURBO_TOKEN

sử dụng multistage build

  • tách stage build và stage run riêng. mục đích là để loại bỏ những layer chỉ cần thiết ở stage build mà không cần thiết ở stage run để giảm dung lượng image.
  • vào việc. ở bước này mình tách ra thành 2 stage run và build. stage run chỉ copy thư mục dist từ stage build. tiện tay, mình cũng dùng image distroless thay cho node:18-alpine.
# BUILD
FROM node:20 AS build
WORKDIR /app
...
# build
RUN npm run build

# RUN
FROM gcr.io/distroless/nodejs20-debian11
EXPOSE 8081
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app /app
CMD ["apps/server/index.js"]

nếu bạn vẫn rảnh

  • bạn có thể dùng depot.dev để giảm thời gian build. bên này quảng cáo giảm ~ 20 lần thời gian build trong một số trường hợp. nếu rảnh bạn có thể dùng thử và chia sẻ trải nghiệm giúp mình, chứ mình chưa dùng 🙈.

  • trong trường hợp mà bạn vẫn muốn tối ưu docker image lên một tầm cao mới (hoặc bạn quá rảnh), bạn có thể cân nhắc dùng một vài tool để audit docker image, ví dụ Dive, Docker Slim hay Docker Squash. mấy tool này mình cũng chưa dùng đâu, ai dùng rồi thì lại chia sẻ trải nghiệm nhé.

kết luận

  • trên đây là một vài kinh nghiệm để tối ưu docker image mình tự đúc rút (cũng như copy trên mạng). lý thuyết vẫn chỉ là lý thuyết, bạn cần áp dụng chúng một cách hợp lý cho dự án của mình để đạt kết quả tốt.

tham khảo

mời bình luận 🗣️

bình luận 😨

chờ chút, đang tải bình luận...