Viết Dockerfile hiệu quả
Viết Dockerfile là một trong những quá trình quan trọng trong quá trình develop cũng như trước khi deploy. Một file Dockerfile hiệu quả được định nghĩa là:
- Có cấu trúc được phân tách layer rõ ràng theo thứ tự sau: layer dành cho môi trường, ngôn ngữ, framework; layer dành cho quá trình build, cài đặt các thành phần cần thiết; layer dành cho quá trình run.
- Các layer có tính module tốt, có khả năng dùng lại được cho nhiều Dockerfile khác nhau.
- Layer được tối ưu tốt nhất để đảm bảo dung lượng nhỏ nhất có thể giúp tăng khả năng tái sử dụng của image.
Chia tách layer rõ ràng
Dưới đây là ví dụ về Dockerfile cho Rails app:
# environment layers
FROM ruby:2.5.1-alpine# build layers - install requirement system libraries
RUN apk add --update --no-cache build-base linux-headers tzdata \
ruby-dev zlib-dev yaml-dev libxml2-dev libxslt-dev curl-dev && \
rm -rf /var/cache/apk/*# build layers - install specific application libraries
RUN apk add mysql-dev redis# build layers - install web framework libraries
COPY Gemfile Gemfile.lock ./
RUN gem install bundler && \
bundle config build.nokogiri --use-system-libraries && \
bundle install --binstubs --without development test && \
bundle clean# build layers - copy source code to work directory
ADD . /home/rails/nova# run layers - initialize run process
RUN mkdir -p /var/log/unicorn /home/unicorn/pids
WORKDIR /home/rails/nova
EXPOSE 80
USER rails
ENV RAILS_LOG_TO_STDOUT true
ENTRYPOINT bundle exec
CMD unicorn -c config/unicorn.rb
Như ở Dockerfile trên, các layer đã được chia tách khá rõ ràng. Có 3 layer chính là Enviroment Layers, Build Layers, Run Layers:
- Environment Layers: Layer khởi tạo OS cho hệ thống. Với Rails app thường OS để build sẽ chính là OS để run. Với những ứng dụng khác có thể dùng Multi-stage-build trong Docker để tách biệt OS build (thường sẽ là ubuntu, debian) và OS run (thường là centos, linux alpine, phusion/baseimage). Với những ứng dụng như Rails chỉ sử dụng 1 images cho cả việc build và run thì lời khuyên là nên chọn những images tối ưu cho việc run như alpine, phusion, centos vì một số ưu điểm liên quan đến độ ổn định, bảo mật, kích thước images hay khả năng tối ưu process mà các image này đem lại.
- Build Layers: Layer setup cho app. Trong layer này cũng nên tách biệt ra thành layer cài đặt thư viện hệ thống (
apt-get install
,apk add
, ... ), layer cài đặt các thành phần riêng của app (database, redis, node, ...) , layer cài đặt thư viện cho từng loại ứng dụng, framework (bundle install
,npm install
,mvn build
, ...) và layer đẩy code app vào images. Khi build docker là với thao tác ADD sẽ không caching, vì vậy để caching hiệu quả nhất nên thực hiện COPY các file config cần thiết phục vụ quá trình build, tiến hành RUN các cài đặt rồi sau đó mới đẩy layer ADD sau cùng trong cả quá trình build. - Run Layers: Layer khởi chạy app.
Module hóa Build Layers
Build Layers là layer xử lý lâu nhất, dễ lỗi nhất và khó debug nhất trong cả quá trình build images bằng Dockerfile. Ở ví dụ trên theo cách viết Dockerfile thông thường sẽ như thế này:
# environment layers
FROM ruby:2.5.1-alpine #1# build layers
RUN apk update #2
RUN apk add --no-cache build-base linux-headers tzdata \
ruby-dev zlib-dev yaml-dev libxml2-dev libxslt-dev \
curl-dev mysql-dev redis #3
RUN rm -rf /var/cache/apk/* #4
ADD . /home/rails/nova #5
WORKDIR /home/rails/nova #6
RUN gem install bundler #7
RUN bundle config build.nokogiri --use-system-libraries #8
RUN bundle install --binstubs --without development test #9# run layers
RUN mkdir -p /var/log/unicorn /home/unicorn/pids #10
EXPOSE 80 #11
USER rails #12
ENV RAILS_LOG_TO_STDOUT true #13
ENTRYPOINT bundle exec #14
CMD unicorn -c config/unicorn.rb #15
Dockerfile trên không được mudule hóa Build Layers, khi có sự thay đổi liên quan tới các thành phần của app (thay MySQL bằng PostgresSQL, dùng External Redis thay cho Internal Redis, ...) sẽ khiến cả layer #3 phải build lại. Bản thân layer #3 cũng không có tính dùng lại tốt, giả sử cần build 1 con service có kiến trúc tương tự nhưng chỉ khác database thì phải build 1 layer mới.
Việc module hóa layer giúp tiết kiệm thời gian build cũng như debug, tăng khả năng caching trong Docker. Đặc biệt trong một hệ thống đi theo kiến trúc Microservice gồm nhiều service có chung môi trường, ngôn ngữ, kiến trúc thì việc module hóa giúp tăng khả năng dùng lại các layer trong Dockerfile, giúp quá trình development cũng như deployment nhiều service cùng lúc trở nên hiệu quả hơn.
Tối ưu kích thước image
Giảm số lượng layer là điều đầu tiên cần nghĩ khi muốn giảm kích thước images. Cùng thử build 2 Dockerfile trên và so sánh kích thước images (lần lượt build 2 Dockefile ở trên):
REPOSITORY TAG IMAGE ID CREATED SIZE
nova dockerfile1 019463671797 2 minutes ago 554MB
nova dockerfile2 bd4e1b3de006 9 minutes ago 559MB
Dockerfile đầu tiên do kết hợp các layer RUN lại khiến cho kích thước images giảm đi so với Dockefile thứ 2 tách nhỏ các layer RUN. Việc kết hợp nhiều layer có thể gây khó khăn cho việc debug ban đầu, nhưng xét về lâu dài giúp giảm số lượng layer sử dụng từ đó giảm kích thước image. Càng nhiều layer RUN mà đặc biệt là các layer có kích thước lớn khi kết hợp lại sẽ càng làm kích thước images giảm đi đáng kể.
Để tối ưu kích thước image ngoài việc giảm số lượng layer còn có 1 cách nữa là loại bỏ hoặc không sử dụng những packages không cần thiết. Ví dụ để không cài đặt những thư viện không cần thiết có thể thêm--no-install-recommends
khi chạy apt-get install
, thêm --no-cache
khi chạy apk add
; hoặc để loại bỏ cache packages sau mỗi layer RUN có thể chạy rm -rf /var/cache/apk/*
sau khi apk add
, chạyrm -rf /var/lib/apt/lists/*
sau khi apt-get install
, chạybundle clean
sau khi bundle install
, chạy rm -rf /node_modules
sau khi npm build
. Những packages phục vụ cho quá trình development, test cũng nên được loại bỏ hoặc tránh sử dụng khi build images, ví dụ bundle install --without development test
.
Giải pháp cuối cùng có thể nghĩ tới nhằm tối ưu kích thước images là sử dụng base image có kích thước nhỏ như linux alpine hay minideb chẳng hạn.
Kết luận
Viết Dockerfile là một công việc thú vị và không kém phần quan trọng trong quá trình development. Một file Dockerfile tốt giúp tăng khả năng tái sử dụng images, tiết kiệm thời gian phát triển phần mềm cho developer, tiết kiệm thời gian cho quá trình CI-CD và rất nhiều hệ quả tích cực khác.