[Docker] General guidelines and recommendations

Tài liệu sau mô tả những gợi ý best practices và các các thức để xây dựng các images có hiệu quả.

Phần này lấy từ 1 phần của https://docs.docker.com/develop/develop-images/dockerfile_best-practices/. Còn 1 phần best practice cho Docker instructions, bạn tham khảo tại bài Docker instructions nhé.

Docker build các image tự động bằng cách đọc các lệnh từ Dockerfile - một file text chứa tất cả các lệnh, theo thứ tự cần thiết để build 1 image. Một Dockerfile tuân thủ cấu trúc riêng và có tập các instructions nhất định.

Một Docker image bao gồm các tầng chỉ đọc (read-only layers), mỗi tầng có 1 nhiệm vụ riêng của 1 Dockerfile instruction. Những tầng này được xếp chồng lên nhau và mỗi tầng có thay đổi với các tầng trước.
.

Xem xét Dockerfile:

1
2
3
4
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

Mỗi instruction tạo 1 layer:

  • FROM tạo 1 layer từ ubuntu:18.04 image.
  • COPY thêm files từ thư mục Docker hiện tại.
  • RUN build ứng dụng của bạn với make.
  • CMD chỉ ra lệnh nào cần chạy trong container này.

Khi bạn chạy 1 image và sinh ra container, bạn thêm 1 layer có thể ghi (writable layer (the “container layer”)), trên cùng của các layers bên dưới. Tất cả thay đổi khi chạy container như là ghi files mới, định nghĩa file đã tồn tại và xóa files được ghi lại trong tầng container có thể ghi này.

General guidelines and recommendations

Create ephemeral containers

Image định nghĩa bởi Dockerfile nên sinh ra các container “càng phù du càng tốt”. Phù du ở đây chúng ta hiểu là các contianer có thể
được stopped và destroyed và rebuild và replaced với thiết lập và cấu hình tối thiểu tuyệt đối.

=> Chưa hiểu ý lắm :v

Understand build context

Vấn đề khi bạn chạy docker build command, đường dẫn làm việc hiện tại của bạn được gọi là build context. Mặc định, Dockerfile sẽ được giả định là từ đây nhưng bạn có thể xác định 1 đường location khác với flag file (-f). Bất kể khi nào Dockerfile đang hoạt động, tất cả nội dung file, tệp đệ quy trong thư mục hiện tại được gửi vào Docker daemon như là build context

Pipe Dockerfile through stdin

Docker có thể build các images bằng cách đường ống dữ liệu vào Dockerfile qua stdin với 1 local or remote build context. Kĩ thuật đường ống dữ liệu vào Dockerfile qua stdin có thể hữu
ích hiệu năng đối với các bản build 1 lần (one-off builds) mà không cần viết Dockerfile vào ổ đĩa hoặc trong các tình huống mà Dockerfile được tạo ra mà không nên tồn tại sau đó
(Trường hợp này thực tế không sử dụng nên tạm thời bỏ qua chưa đọc)

Exclude with .dockerignore

Để loại trừ các file không liên quan khi build, sử dụng file .dockerignore. File này hỗ trợ các mẫu loại bỏ tương tự như .gitignore

Use multi-stage builds

Multi-stage builds cho phép bạn giảm nhiều kích thước image cuối cùng của bạn mà không đấu tranh để giảm số lượng các tầng và files trung gian.

Bởi vì 1 image được build trong giai đoạn cuối cuối của quá trình build, bạn có thể giảm thiểu image layer bằng leveraging build cache (phần sau).

Ví dụ, nếu build của bạn có 1 vài layer, bạn có thể sắp xếp chúng từ ít thay đổi thường xuyên (để chắc chắn rằng cache khi build có thể hữu ích) đến phần thay đổi thường xuyên

  • Install tools bạn cần để build ứng dụng
  • Install và cập nhật các thư viện phụ thuộc
  • Sinh ra ứng dụng của bạn

Một Dockerfile từ ứng dụng Go sẽ như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM golang:1.11-alpine AS build

# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep

# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only

# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project

# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

Don’t install unnecessary packages

Tất nhiên, việc giảm thiểu các package không cần thiết để giảm thiểu độ phức tạo, sự phụ thuộc, kích thước file và thời gian build rồi =)) bởi vì nó có
nghĩa là “nice to have”. Ví dụ, bạn không cần cài các text editor trong database image.

Decouple applications

Mỗi container nên chỉ có 1 quan hệ. Phân tách ứng dụng thành nhiều containers làm nó dễ dàng mở rộng theo chiều ngang và dễ dàng sử dụng lại
các container. Ví dụ, một ngăn xếp của ứng dụng web có thể chứa 3 container tách biệt, mỗi chúng có 1 image duy nhất để quản lý ứng dụng web, database và
im-memory cache.

Cố gắng giữ các container của bạn sạch sẽ và phân hóa tính module nhất có thể. Nếu containers của bạn phụ thuộc các container khác, bạn có thể sử dụng
Docker container network để chắc chắn rằng chúng có thể nói chuyện được với nhau =))

Minimize the number of layers

Trong các phiên bản Docker cũ, thực sự quan trọng cho bạn giảm thiểu số lượng layers trong image của bạn để chắc chắn rằng chúng có hiệu năng tốt.
Các tính năng sau đây đã được thêm vào để giảm thiểu các giới hạn này:

  • Chỉ các instructions RUN, COPY, ADD tạo ra layers. Các instruction khác tạo ra các image trung gian tạm thời, và không làm tăng kích thước của quá trình build.
  • Bất cứ khi nào có thể, sử dụng multi-stage builds, và chỉ copy các thứ bạn cần vào image cuối cùng. Điều này cho phép bạn có thể có các tools và thông tin debug trong các stage tạm thời
    của bạn nhưng không làm tăng kích thước trong image cuối cùng

Ý kiến cá nhân: Bạn có thể xem quá trình build ra images đó sinh ra các layers và các image trung gian nào thông qua docker history

1
2
3
4
5
6
7
FROM ubuntu:latest

COPY Hello.txt /tmp

RUN apt-get update && apt-get install vim -y

CMD [ "sh", "-c", "echo $HOME" ]

1
2
3
4
5
6
7
8
9
10
C:\Users\Nguyen Van Minh\Desktop\Docker>docker history d978393ef1c7
IMAGE CREATED CREATED BY SIZE COMMENT
d978393ef1c7 33 seconds ago /bin/sh -c #(nop) CMD ["sh" "-c" "echo $HOM… 0B
de55aff93a8d 23 minutes ago /bin/sh -c apt-get update && apt-get install… 88.3MB
c5a143208697 36 minutes ago /bin/sh -c #(nop) COPY file:6ffa4dcbbe5403e6… 16B
4e5021d210f6 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 4 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B
<missing> 4 weeks ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 987kB
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:594fa35cf803361e6… 63.2MB

Bạn có thể thấy rất rõ, mỗi instruction đều tạo 1 layer (để đơn giản hóa, hãy xem các image trung gian tạm thời OB kia cũng là các layer) và tổng kích thước image chính là đống layer này cộng vào :D.

Hiểu nôm na các image trung gian (0B) chỉ là copy lại image cũ, chưa làm gì nên chưa tăng kích thước, nếu dùng RUN, COPY, ADD trên các image trung gian đó thì sinh 1 layer mới làm tăng kích thước images.

Sort multi-line arguments

Bất cứ khi nào có thể, dễ dàng thay đổi về sau bằng cách sắp xếp các đối số nhiều dòng. Điều này tránh bị trùng lặp các package và làm danh sách sẽ dễ dàng cập nhật hơn. Điều này cũng làm PRs dễ dàng đọc và review. Thêm space và mỗi backslash(\) để làm việc này :D.

Đây là một ví dụ:

1
2
3
4
5
6
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

Leverage build cache

Hiểu 1 chút về các khái niệm trong Docker và cache:

  • Dockerfile bắt đầu với 1 parent image (từ FROM).
  • Sau đó trong quá trình build sẽ sinh ra nhất nhiều image trung gian, ta gọi là childrent image.
  • Base image thường nói đến các image từ các parent image (các image trống trơn, chưa cài đặt gì), và thêm các cài đặt phục vụ bạn (như cài thêm npm để build js, cài thêm composer
    để build thêm PHP) được đẩy lên repo Docker hoặc trên local của bạn

Ví dụ xem xét Dockerfile sau:

1
2
3
FROM ubuntu:latest

COPY Hello.txt /tmp

Dockerfile lấy từ parent image ubuntu:latest, sau lệnh COPY này, nó sẽ sinh ra 1 childrent image. Khi bạn build, bạn sẽ nhận được danh sách images sau:
1
2
3
4
C:\Users\Nguyen Van Minh\Desktop\Docker>docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> c5a143208697 11 seconds ago 64.2MB
ubuntu latest 4e5021d210f6 4 weeks ago 64.2MB

Như ví dụ trên tôi hiểu

  • ubuntu::lastest: parent image => image ubuntu không có cái vẹo gì
  • c5a143208697: là image con của ubuntu:lastest
  • c5a143208697 cũng được gọi là base image của bạn. Tuy nhiên nó chưa cài thêm cái gì, base image này có vẻ hơi tù và gọi là base image có vẻ chưa ngon lắm.
  • Base image ví dụ laravel-workspace gọi base image thì sẽ chuẩn hơn, nó cung cấp không gian làm việc với ứng dụng.

Ref: https://github.com/FramgiaDockerTeam

Khái niệm base image và parent image dễ gây nhầm lẫn, mà thôi đừng quan tâm sâu quá về nó. Hãy liên hệ khi bạn code, bạn có thể sử dụng Controller có sẵn trong Laravel là các parent image, bạn thêm các hàm (whereLike chẳng hạn) thì xây dựng lên BaseController, từ BaseController này sinh ra các image con HomeController, ProductController (mà gọi là con của Controller cũng không sai nhỉ =_=)

Rồi, tản mạn về Base image, Parent Image và Childrent Image vậy thôi, bây giờ tôi sẽ đề cập đến cache.

Khi build Docker, chỗ nào sử dụng cache được báo lại rất rõ. Ví dụ với Dockerfile trên, build lại lần nữa, cache sẽ được sử dụng từ lệnh COPY, bạn có thể thấy rõ từ thông báo:

1
2
3
4
5
6
7
8
C:\Users\Nguyen Van Minh\Desktop\Docker>docker build .
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM ubuntu:latest
---> 4e5021d210f6
Step 2/2 : COPY Hello.txt /tmp
---> Using cache
---> c5a143208697
Successfully built c5a143208697

Khi build 1 image, các bước Dokcker qua các instructions trong Dockerfile, thực thi từng instruction một được chỉ định. Mỗi instruction được kiểm tra, Docker tìm 1 image đã tồn tại trong cache của nó để sử dụng lại, thay vì tạo 1 (nhiều) image.

Nếu bạn không muốn sử dụng cache trong tất cả trường hợp, bạn có thể sử dụng tùy chọn --no-cache=true khi chạy docker build. Tuy nhiên, nếu bạn để Docker sử dụng cache, thực sự quan trọng để hiểu khi nào nó có thể dùng, và khi nào không thể, tìm kiếm image trùng. Các nguyên tắc cơ bản sau Docker sẽ tuân theo:

  • Bắt đầu với parent image, cái đã có trong cache, các instruction tiếp thep sẽ được so sánh với tất cả các image con có nguồn gốc từ base image để xem nếu 1 trong số chúng đã được build sử dụng instruction giống nhau. Nếu không, cache sẽ bị vô hiệu.
  • Trong đa số các trường hợp, sự so sánh đơn giản instruction trong Dockerfile với 1 trong những child images là vừa đủ. Tuy nhiên, 1 số instructions sẽ yêu cầu nhiều sự xem xét và giải thích hơn.
  • Đối với lệnh ADDCOPY, nội dung của file(s) trong image được xem xét và checksum sẽ được tính toán cho mỗi file. Mỗi last-modified and last-accessed time của file(s) không được xem xét trong checksum này. Trong khi cache tìm kiếm, checksum sẽ so sánh với các checksum của image đã tồn tại. Nếu có 1 vài sự thay đổi trong file(s), như là nội dung và metadata, cache sẽ bị vô hiệu hóa.
  • Aside from the ADD and COPY commands, cache checking does not look at the files in the container to determine a cache match. For example, when processing a RUN apt-get -y update command the files updated in the container are not examined to determine if a cache hit exists. In that case just the command string itself is used to find a match.

Một khi cache bị vô hiệu hóa, tất cả các lệnh Dockerfile tiếp theo sẽ sinh ra 1 image mới và cache sẽ không được sử dụng.

Tổng kết

Khi xây dựng Dockerfile, hi vọng 1 số tips sau giúp bạn xây dựng tốt hơn:

  • Build context có thể lấy cứ local hoặc remote, khi build 1 lần, hãy xem xét sử dụng stdin để không cần lưu Dockerfile trên máy của bạn luôn.
  • Exclude with .dockerignore.
  • Use multi-stage builds: giảm kích thước image qua việc Dockerfile chỉ sử dụng image cuối cùng.
  • Don’t install unnecessary packages.
  • Decouple applications.
  • Minimize the number of layers:
    • Mỗi instruction tạo ra 1 layer (tính cả layer 0B gọi là các image trung gian tạm thời đi)
    • ADD, COPY, RUN là các instruction làm tăng kích thước images. Ngoài ra, khi copy cả thư mục, hãy xem xét các file thường xuyên thay đổi thì nên tách ra. Chi tiết xem thêm tại Docker instructions.
    • Xem kích thước các instruction với docker history.
  • Sort multi-line arguments: sử dụngbackslash (\) để PRs của bạn dễ review hơn và tránh lặp các package.
  • Tận dụng cache khi build.
  • Parent image (Controller), base image => base image: image bạn tự custom lại, nên tích hợp thêm cái gì đó hữu ích (BaseController with whereLike) và childrent image: ProductController, HomeController.

Tài liệu tham khảo:

Author

Ming

Posted on

2020-04-19

Updated on

2021-04-10

Licensed under

Comments