Docker đóng gói ứng dụng và tất cả các phụ thuộc vào một container, giúp đảm bảo tính nhất quán giữa các môi trường khác nhau. Tuy nhiên, một trong những thách thức lớn khi làm việc với Docker là tạo ra Docker image gọn nhẹ và tối ưu hóa về tốc độ.
Trong bài viết này, mình sẽ hướng dẫn các bạn cách tối ưu hóa Docker image cách hiệu quả để giúp bạn giảm dung lượng, tăng tốc độ build và đảm bảo bảo mật tốt hơn cho container của bạn.
1. Tại sao phải tối ưu dung lượng docker image?
- Tối ưu hóa Docker image không chỉ giúp transfer image nhanh hơn. Khi Docker image được tối ưu và nhỏ gọn, server có thể pull image "nhanh như chớp", giảm thời gian chờ đợi trong quá trình triển khai.
Điều này đặc biệt hữu ích khi dự án có áp dụng quy trình CI/CD tự động, bạn chắc hẳn không muốn mỗi lần push code lên reposity phải đợi từ từ 10-15m để server mới hoàn tất việc build/pull image của mình đâu đúng không. - Trong môi trường Cloud, không gian lưu trữ là tài nguyên quý giá - ai lại không muốn tiết kiệm chi phí, phải không nào? Giảm dung lượng Docker image không chỉ tiết kiệm không gian lưu trữ mà còn giảm băng thông khi truyền tải image giữa các hệ thống. Điều này đặc biệt quan trọng khi bạn quản lý nhiều image và phải cập nhật thường xuyên.
- Về bảo mật, sử dụng Docker image nhỏ gọn là một sự lựa chọn thông minh. Với ít thành phần và phụ thuộc hơn, giảm thiểu các package và library không cần thiết, thu nhỏ bề mặt tấn công và giảm nguy cơ lỗ hổng bảo mật. Việc này cũng giúp quá trình kiểm tra và duy trì hệ thống trở nên đơn giản hơn nhiều.
- Docker image với dung lượng nhỏ giúp việc khởi động container nhanh hơn. Trong k8s, nơi mà các container được khởi động và dừng thường xuyên, điều này sẽ đem lại sự khác biệt lớn trong việc phản hồi của hệ thống.
2. Tối ưu dung lượng Image
2.1 Minimal Base Images
Một trong những bước quan trọng nhất để tối ưu hóa Docker image là chọn base image (image nền) phù hợp.
Base image cơ bản được sử dụng để xây dựng Docker image của bạn. Chọn một base image nhẹ sẽ giúp bạn giảm đáng kể kích thước của image cuối cùng.
- Alpine Linux: đây là một image Linux rất nhẹ, chỉ khoảng 5MB. Alpine là options được lựa chọn phổ biến cho ứng dụng, không yêu cầu nhiều thành phần hệ thống.
- Scratch: đây là một image trống, thích hợp với các ứng dụng được biên dịch thành file nhị phân tĩnh như: Go hoặc Rust. Nếu ứng dụng của bạn không cần bất kỳ thứ gì ngoài file nhị phân, scratch là lựa chọn dành cho bạn.
- Distroless Images: chỉ bao gồm các thư viện cần thiết để chạy ứng dụng, không có shell hay package manager.
Khi bạn sử dụng base image tối thiểu, bạn có thể giảm kích thước tổng thể của Docker image và cải thiện bảo mật.
2.2 Multi-stage Builds: The Secret Sauce
Multi-stage builds là kỹ thuật tối ưu hóa Docker image bằng cách sử dụng nhiều giai đoạn để tách việc xây dựng và chạy ứng dụng. Điều này cho phép bạn chỉ sao chép những phần cần thiết từ giai đoạn build vào image cuối cùng, giúp giảm kích thước image đáng kể.
Ví dụ, đối với ứng dụng Go, bạn có thể sử dụng multi-stage builds như sau:
FROM golang:1.16-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]
Trong ví dụ trên, giai đoạn đầu sẽ biên dịch ứng dụng Go, chỉ có file nhị phân cuối cùng được sao chép vào image production. Nhờ có multi-stage builds, bạn tránh được việc bao gồm các công cụ build không cần thiết vào image cuối cùng.
Mình sẽ lấy thêm ví dụ đối với ứng dụng Nodejs:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --only=production
CMD ["node", "dist/index.js"]
Giai đoạn build (builder):
- Sử dụng Node.js phiên bản alpine.
- Sao chép
package.json
,package-lock.json
vàtsconfig.json
để cài đặt dependencies và cấu hình TypeScript. - Chạy
npm install
để cài đặt dependencies (cả devDependencies). - Sao chép toàn bộ mã nguồn và chạy
npm run build
để biên dịch TypeScript sang JavaScript (thường sẽ nằm trong folder dist).
Giai đoạn chạy:
- Sử dụng Node.js phiên bản alpine.
- Sao chép folder dist từ giai đoạn build.
- Sao chép
package.json
vàpackage-lock.json
để cài đặt dependencies cần thiết cho runtime. - Chạy
npm install --only=production
để chỉ cài đặt các dependencies cần cho runtime, bỏ qua devDependencies. - Chạy ứng dụng với câu lệnh
node dist/index.js
.
2.3 Layer Optimization: Every Line Counts
Docker xây dựng image thành từng lớp (layer), mỗi lệnh trong Dockerfile sẽ tạo ra một lớp mới. Nên là, việc tối ưu hóa cách mà bạn viết Dockerfile cũng cực kỳ quan trọng để giảm thiểu số lớp và kích thước image.
- Mỗi lệnh RUN sẽ tạo ra một lớp mới, vì vậy kết hợp nhiều lệnh trong một RUN có thể giúp giảm số lớp.
Đây là một ví dụ về không tối ưu trong Go và TypeScript:
//Go
RUN apt-get update
RUN apt-get install -y python
RUN apt-get clean
//TypeScript
RUN npm install -g typescript
RUN npm install -g ts-node
RUN npm install
Và sau khi mình áp dụng tối ưu sẽ như này:
//Go
RUN apt-get update && \
apt-get install -y python && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
//TypeScript
RUN npm install -g typescript ts-node && \
npm install
- Thứ tự các lệnh trong Dockerfile cũng ảnh hưởng đến bộ nhớ đệm (cache) của Docker. Nếu bạn thay đổi file ở lớp trên cùng, các lớp bên dưới sẽ không cần build lại.
Ví dụ, nếu bạn thường xuyên thay đổi mã nguồn nhưng không thay đổi các phụ thuộc (package.json,...), hãy sao chép file phụ thuộc trước đó.
Đây là một ví dụ chưa được tối ưu:
//Go
COPY . .
RUN pip install -r requirements.txt
//TypeScript
COPY . .
RUN npm install
Trong trường hợp này, nếu bạn thay đổi bất kỳ file nào, Docker sẽ phải chạy lại lệnh pip install
đối với Go và npm install
đối với TypeScript.
//Go
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
//TypeScript
COPY package*.json ./
RUN npm install
COPY . .
Bằng cách này, nếu code thay đổi nhưng requirements.txt
hay package.json
không đổi, Docker sẽ sử dụng cache cho bước cài đặt thư viện, giúp tăng tốc độ build.
Việc này sẽ đảm bảo bước cài đặt phụ thuộc được cache lại, giúp quá trình build nhanh hơn khi chỉ có code thay đổi.
2.4 Sử dụng .dockerignore
File .dockerignore
cũng tương tự như .gitignore
, giúp loại bỏ những file và thư mục không cần thiết khỏi quá trình build. Điều này không chỉ giúp giảm kích thước build context mà còn bảo vệ các file nhạy cảm khỏi việc vô tình được thêm vào image.
Ví dụ .dockerignore
node_modules
.git
.env
Dockerfile
README.md
Việc loại bỏ các folder có dung lượng lớn như node_modules
hoặc .git
khỏi build context sẽ làm cho quá trình build nhanh và gọn nhẹ hơn.
2.5 Sử dụng Docker's BuildKit
Docker BuildKit là tính năng mới của Docker giúp cải thiện hiệu suất build và cung cấp các options mới cho việc tối ưu Dockerfile. Để kích hoạt BuildKit, bạn chỉ cần thêm biến môi trường DOCKER_BUILDKIT=1 khi chạy lệnh build.
DOCKER_BUILDKIT=1 docker build .
Một vài lợi ích của BuildKit bao gồm:
- Parallel builds: BuildKit có thể chạy các bước trong Dockerfile song song.
- Secret management: BuildKit cho phép quản lý các thông tin nhạy cảm an toàn hơn trong quá trình build.
- Caching improvements: bộ nhớ đệm được quản lý tốt hơn, giúp build nhanh hơn.
2.6 Loại bỏ file và folder không cần thiết
Trong quá trình build, có thể sẽ phát sinh các file tạm, cache hoặc log không cần thiết cho image cuối cùng. Bạn nên kiểm tra và loại bỏ giúp giảm kích thước image.
Xoá cache và file tạm:
RUN apt-get update && \
apt-get install -y package && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Xoá file log và docs:
RUN rm -rf /var/log/* /usr/share/doc/*
2.7 Giảm số lượng Dependencies
Cài đặt ít dependencies hơn sẽ tối ưu về mặt kích thước image.
Chỉ cài đặt những gì cần thiết:
RUN apt-get install -y --no-install-recommends package
--no-install-recommends
sẽ chỉ cài đặt những package cần thiết, bỏ qua các package khuyến nghị không bắt buộc.
3. Kết luận
Tối ưu hóa là một quá trình liên tục. Hy vọng qua những chia sẻ trong bài viết, bạn có thể áp dụng tối ưu docker image của cá nhân bạn. Bên cạnh đó bạn nên thường xuyên cập nhật các kiến thức và công cụ mới để đảm bảo Docker image của mình luôn trong trạng thái tốt nhất.
Các bài viết liên quan:
Bài viết liên quan
SQL Injection là gì? Mối Đe Dọa Đối Với Cơ Sở Dữ Liệu
Oct 25, 2024 • 10 min read
So sánh hiệu suất Query của PostgreSQL và MySQL
Oct 24, 2024 • 9 min read
Ubuntu là gì? Vì sao hệ điều hành Ubuntu phổ biến
Oct 15, 2024 • 14 min read
CI/CD là gì? Lợi ích của việc thành thạo CI/CD trong DevOps
Oct 07, 2024 • 8 min read
Linux là gì? So sánh Hệ điều hành Linux và Windows
Oct 03, 2024 • 8 min read
Full bộ source code: Simple Task Microservices
Oct 02, 2024 • 1 min read