Khi chúng ta build 1 image docker, dung lượng của image ảnh hưởng khá nhiều đến việc vận hành. Đặc biệt là khi chúng ta muốn scale service với Kubernetes ( k8s ), Docker Swarm, … Image càng nhỏ gọn thì deploy và scale càng nhanh.
Vậy làm sao để tối ưu image docker
Để làm được điều này, chúng ta cần làm những việc sau đây:
1/ Thay đổi base image
Đầu tiên, chúng ta cần chọn 1 base image có dung lượng nhỏ, điển hình là images “alpine”. Đây là những base image có kích thước nhỏ gọn, tối ưu cho việc lưu trữ mà vẫn đủ điều kiện để service chạy mượt mà.
ví dụ:
Thay vì sử dụng FROM node:10, chúng ta có thể chuyển qua sử dụng FROM node:10-alpine.
Các image dựng trên alpine có dung lượng rất nhẹ, với nodejs là giảm từ 900MB của bản mặc định xuống chỉ còn 70MB của alpine.
2/ Tận dụng layer caching
Layer caching là một tính năng rất quan trọng giúp giảm thời gian của quá trình build bằng cách tận dụng những layer đã được build từ lần trước đó . Để có thể tận dụng tối đa tính năng này, ta cần sắp xếp lại và tách lệnh build sao cho phần lệnh ít thay đổi sẽ ở trên, phần thay đổi thường xuyên sẽ ở dưới. Để hiểu rõ hơn về việc này, chúng ta cần xét ví dụ dưới đây.
TRƯỜNG HỢP 1
1 FROM alpine
2 WORKDIR /app
3 RUN apt-get update
4 RUN apt-get install -y git
5 RUN yarn global add node-gyp
5 ADD . /app
6 RUN yarn --pure-lockfile
7 RUN yarn build
8 EXPOSE 3000
TRƯỜNG HỢP 2
10 FROM alpine
11 EXPOSE 3000
12 WORKDIR /app
13 RUN apt-get update
14 RUN apt-get install -y git
15 RUN yarn global add node-gyp
16 ADD package.json yarn.lock /app/
17 RUN yarn --pure-lockfile
18 ADD . /app
19 RUN yarn build
Như bạn thấy, 2 trường hợp mà chúng tôi đã minh họa phía trên, trường hợp 2 đã sắp xếp lại các câu lệnh sao cho, những câu lệnh nào thường xuyên thay đổi thì sẽ được để phía dưới, ít thay thay đổi được xếp lên trên. Vì sao lại vậy ?.
Như chúng ta đã biết, mỗi câu lệnh chúng ta build sẽ tương ứng với 1 lớp ( layer), khi chúng ta build lại image, docker sẽ kiểm tra xem đã có những lớp đã được build trước hay chưa, nếu có thì chúng sẽ tận dụng lại những lớp này mà không cần phải build lại, điều này sẽ tiết kiệm được thời gian build. Tuy nhiên, những lớp phía sau từ lớp được thay đổi sẽ phải build lại.
ví dụ, ở trường hợp thứ 2, chúng ta thay đổi câu lệnh tại dòng số 14 , tôi bổ sung thêm gói cài đặt “vim” :
điều này nghĩa là từ lớp 10 đến 13, docker sẽ dùng cache, không cần build lại. Tuy nhiên, từ lớp 14 đến 19, docker sẽ build lại mới. Đó là lý do vì sao chúng ta cần phải sắp xếp hợp lý thứ tự các câu lênh.
10 FROM alpine
11 EXPOSE 3000
12 WORKDIR /app
13 RUN apt-get update
14 RUN apt-get install -y git vim
15 RUN yarn global add node-gyp
16 ADD package.json yarn.lock /app/
17 RUN yarn --pure-lockfile
18 ADD . /app
19 RUN yarn build
3/ Giảm bớt số lượng layer
Như đã đề cập bên trên, Docker được build dựa trên các lớp layer xếp chồng lên nhau. Nếu có càng nhiều lớp thì image docker sẽ càng có dung lượng lớn, nặng nề. Do đó, chúng ta tận dụng càng ít layer càng lớp.
xét ví dụ sau để hiểu rõ hơn:
FROM alpine
ADD . /app
ADD package.json yarn.lock /app/
ADD entrypoint.sh /entrypoint.sh
WORKDIR /app
RUN apt-get update
RUN apt-get install -y git
RUN yarn global add node-gyp
RUN yarn --pure-lockfile
RUN yarn build
EXPOSE 3000
CMD ["/entrypoint.sh]
Thay vì sử dụng quá nhiều dòng lệnh lặp lại “RUN”, có tới 5 dòng lệnh “RUN” tương ứng với 5 lớp, quá nhiều. Chúng ta sẽ gom lại thành 1 dòng “RUN” như sau.
FROM alpine
ADD . /app
ADD package.json yarn.lock /app/
ADD entrypoint.sh /entrypoint.sh
WORKDIR /app
RUN apt-get update -y \
&& apt-get install -y git \
&& yarn global add node-gyp \
&& yarn --pure-lockfile \
&& yarn build
EXPOSE 3000
CMD ["/entrypoint.sh]
Việc này sẽ làm giảm đáng kể dung lượng images.
4/ Sử dụng multi-stage trong lúc build
Multi-stage build được giới thiệu từ docker v17.05, đây là tính năng khá hữu ích, nó sẽ bỏ hết những thứ linh tinh trong quá trình chúng ta build images, ví dụ đơn giản thế này, khi chúng ta cần build một service từ source, chúng ta tất nhiên cần phải cài đặt những package cần thiết, nhưng vấn đề là images docker của chúng ta không cần phải có những package đó, chúng ta chỉ muốn giữ lại bộ source sao khi đã build xong, còn mấy package linh tinh cần bỏ hết. Chính vì vậy, multi-stage ra đời để giải quyết vấn đề này. Cùng xét ví dụ sau để hiểu rõ hơn.
FROM node:10-alpine AS builder
WORKDIR /app
RUN apk --no-cache add \
g++ make python git \
&& yarn global add node-gyp \
&& rm -rf /var/cache/apk/*
ADD package.json yarn.lock /app/
RUN yarn --pure-lockfile
# Runtime image from here
FROM node:10-alpine
EXPOSE 3000
WORKDIR /app
# Copy node_modules from builder image
COPY --from=builder /app .
ADD . /app
RUN yarn build
CMD ["yarn", "docker:start"]
Trong Dockerfile phía trên, chúng tôi đã tách việc build docker thành 2 phần, phần 1 đóng vai trò là builder, phần 2 mới là service cuối cùng mà chúng ta cần. Kích thước của Images Docker sẽ là thành phần được build cuối cùng.
Kết luận
để tối ưu image docker, chúng ta cần làm những bước sau:
- Chọn base image có kích thước nhỏ gọn, alpine là điển hình
- Sắp xếp Dockerfile để tận dụng layer caching giúp giảm thời gian build
- Giảm số lượng Layer bằng cách gom lại các câu lệnh “RUN”, “ADD”, “COPY”.
- Sử dụng multi-stage build để giảm dung lượng image
Người Viết:
congdonglinux.com
Đăng ký liền tay Nhận Ngay Bài Mới
Subscribe ngay
Cám ơn bạn đã đăng ký !
Lỗi đăng ký !
Add Comment