Nhiều RUN so với RUN chuỗi đơn trong Dockerfile, cái gì tốt hơn?


132

Dockerfile.1thực thi nhiều RUN:

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 tham gia cùng họ:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

Mỗi RUNlớp tạo ra một lớp, vì vậy tôi luôn cho rằng ít lớp hơn là tốt hơn và do đó Dockerfile.2tốt hơn.

Điều này rõ ràng là đúng khi RUNloại bỏ một cái gì đó được thêm vào trước đó RUN(ví dụ yum install nano && yum clean all), nhưng trong trường hợp mỗi lần RUNthêm một cái gì đó, có một vài điểm chúng ta cần xem xét:

  1. Các lớp được cho là chỉ thêm một khác biệt so với lớp trước, vì vậy nếu lớp sau không loại bỏ thứ gì đó được thêm vào trong lớp trước, thì sẽ không có nhiều lợi thế tiết kiệm không gian đĩa giữa cả hai phương thức ...

  2. Các lớp được kéo song song từ Docker Hub, vì vậy Dockerfile.1, mặc dù có thể lớn hơn một chút, về mặt lý thuyết sẽ được tải xuống nhanh hơn.

  3. Nếu thêm câu thứ 4 (nghĩa là echo This is the D > d) và xây dựng lại cục bộ, Dockerfile.1sẽ xây dựng nhanh hơn nhờ bộ đệm, nhưng Dockerfile.2sẽ phải chạy lại cả 4 lệnh.

Vì vậy, câu hỏi: Cách nào tốt hơn để làm Dockerfile?


1
Nói chung không thể được trả lời vì nó phụ thuộc vào tình huống và việc sử dụng hình ảnh (tối ưu hóa kích thước, tốc độ tải xuống hoặc tốc độ xây dựng)
Henry

Câu trả lời:


99

Khi có thể, tôi luôn hợp nhất các lệnh tạo các tệp với các lệnh xóa cùng các tệp đó thành một RUNdòng. Điều này là do mỗi RUNdòng thêm một lớp vào hình ảnh, đầu ra hoàn toàn theo nghĩa đen của các thay đổi hệ thống tập tin mà bạn có thể xem docker difftrên bộ chứa tạm thời mà nó tạo ra. Nếu bạn xóa một tệp được tạo trong một lớp khác, tất cả hệ thống tệp kết hợp sẽ đăng ký thay đổi hệ thống tệp trong một lớp mới, tệp vẫn tồn tại trong lớp trước đó và được chuyển qua mạng và được lưu trữ trên đĩa. Vì vậy, nếu bạn tải xuống mã nguồn, giải nén nó, biên dịch nó thành một tệp nhị phân, sau đó xóa các tệp tgz và nguồn ở cuối, bạn thực sự muốn tất cả điều này được thực hiện trong một lớp duy nhất để giảm kích thước hình ảnh.

Tiếp theo, cá nhân tôi chia ra các lớp dựa trên tiềm năng của chúng để tái sử dụng trong các hình ảnh khác và sử dụng bộ nhớ đệm dự kiến. Nếu tôi có 4 hình ảnh, tất cả đều có cùng một hình ảnh cơ bản (ví dụ: debian), tôi có thể kéo một tập hợp các tiện ích chung cho hầu hết các hình ảnh đó vào lệnh chạy đầu tiên để các hình ảnh khác được hưởng lợi từ bộ nhớ đệm.

Thứ tự trong Dockerfile rất quan trọng khi xem xét sử dụng lại bộ đệm hình ảnh. Tôi xem xét bất kỳ thành phần nào sẽ hiếm khi cập nhật, có thể chỉ khi hình ảnh cơ sở cập nhật và đưa những thứ đó lên cao trong Dockerfile. Đến cuối Dockerfile, tôi bao gồm bất kỳ lệnh nào sẽ chạy nhanh và có thể thay đổi thường xuyên, ví dụ: thêm người dùng với UID cụ thể của máy chủ hoặc tạo thư mục và thay đổi quyền. Nếu vùng chứa bao gồm mã được giải thích (ví dụ JavaScript) đang được phát triển tích cực, mã đó sẽ được thêm vào càng muộn càng tốt để việc xây dựng lại chỉ chạy thay đổi duy nhất đó.

Trong mỗi nhóm thay đổi này, tôi hợp nhất hết mức có thể để giảm thiểu các lớp. Vì vậy, nếu có 4 thư mục mã nguồn khác nhau, chúng được đặt trong một thư mục duy nhất để có thể thêm nó vào bằng một lệnh. Bất kỳ gói cài đặt nào từ apt-get đều được hợp nhất thành một RUN duy nhất khi có thể để giảm thiểu lượng chi phí quản lý gói (cập nhật và dọn dẹp).


Cập nhật cho các bản dựng nhiều giai đoạn:

Tôi lo lắng rất nhiều về việc giảm kích thước hình ảnh trong các giai đoạn không phải là cuối cùng của một bản dựng nhiều giai đoạn. Khi các giai đoạn này không được gắn thẻ và chuyển đến các nút khác, bạn có thể tối đa hóa khả năng tái sử dụng bộ đệm bằng cách chia mỗi lệnh thành một RUNdòng riêng biệt .

Tuy nhiên, đây không phải là một giải pháp hoàn hảo để xóa các lớp vì tất cả những gì bạn sao chép giữa các giai đoạn là các tệp và không phải là phần còn lại của dữ liệu meta hình ảnh như cài đặt biến môi trường, điểm nhập và lệnh. Và khi bạn cài đặt các gói trong bản phân phối linux, các thư viện và các phụ thuộc khác có thể nằm rải rác trong hệ thống tệp, khiến việc sao chép tất cả các phụ thuộc trở nên khó khăn.

Do đó, tôi sử dụng các bản dựng nhiều giai đoạn để thay thế cho việc xây dựng nhị phân trên máy chủ CI / CD, do đó máy chủ CI / CD của tôi chỉ cần có công cụ để chạy docker buildvà không có jdk, nodejs, go và bất kỳ công cụ biên dịch khác được cài đặt.


30

Câu trả lời chính thức được liệt kê trong thực tiễn tốt nhất của họ (hình ảnh chính thức PHẢI tuân thủ những điều này)

Giảm thiểu số lượng lớp

Bạn cần tìm sự cân bằng giữa khả năng đọc (và do đó có thể duy trì lâu dài) của Dockerfile và giảm thiểu số lượng lớp mà nó sử dụng. Hãy chiến lược và thận trọng về số lượng các lớp bạn sử dụng.

Kể từ Docker 1.10 các COPY, ADDRUNbáo cáo thêm một lớp mới để hình ảnh của bạn. Hãy thận trọng khi sử dụng các tuyên bố này. Cố gắng kết hợp các lệnh thành một RUNcâu lệnh duy nhất . Chỉ tách riêng điều này nếu cần thiết cho khả năng đọc.

Thông tin thêm: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minizes-the-number-of-layers

Cập nhật: Nhiều giai đoạn trong docker> 17.05

Với các bản dựng nhiều giai đoạn, bạn có thể sử dụng nhiều FROMcâu lệnh trong Dockerfile của mình. Mỗi FROMtuyên bố là một giai đoạn và có thể có hình ảnh cơ sở riêng của nó. Trong giai đoạn cuối, bạn sử dụng một hình ảnh cơ sở tối thiểu như alpine, sao chép các thành phần xây dựng từ các giai đoạn trước và cài đặt các yêu cầu thời gian chạy. Kết quả cuối cùng của giai đoạn này là hình ảnh của bạn. Vì vậy, đây là nơi bạn lo lắng về các lớp như được mô tả trước đó.

Như thường lệ, docker có tài liệu tuyệt vời về các bản dựng nhiều giai đoạn. Đây là một đoạn trích nhanh:

Với các bản dựng nhiều giai đoạn, bạn sử dụng nhiều câu lệnh TỪ trong Dockerfile của mình. Mỗi lệnh TỪ có thể sử dụng một cơ sở khác nhau và mỗi lệnh bắt đầu một giai đoạn mới của quá trình xây dựng. Bạn có thể sao chép có chọn lọc các tạo tác từ giai đoạn này sang giai đoạn khác, bỏ lại mọi thứ bạn không muốn trong hình ảnh cuối cùng.

Một bài đăng blog tuyệt vời về điều này có thể được tìm thấy ở đây: https://blog.alexellis.io/mutli-stage-docker-builds/

Để trả lời quan điểm của bạn:

  1. Vâng, các lớp là loại khác nhau như. Tôi không nghĩ có những lớp được thêm vào nếu hoàn toàn không có thay đổi. Vấn đề là một khi bạn cài đặt / tải xuống thứ gì đó ở lớp 2, bạn không thể xóa nó ở lớp 3. Vì vậy, một khi một cái gì đó được viết trong một lớp, kích thước hình ảnh không thể giảm nữa bằng cách loại bỏ nó.

  2. Mặc dù các lớp có thể được kéo song song, làm cho nó có khả năng nhanh hơn, nhưng mỗi lớp chắc chắn sẽ làm tăng kích thước hình ảnh, ngay cả khi chúng đang xóa các tệp.

  3. Có, bộ nhớ đệm rất hữu ích nếu bạn đang cập nhật tệp docker của mình. Nhưng nó hoạt động theo một hướng. Nếu bạn có 10 lớp và bạn thay đổi lớp # 6, bạn vẫn sẽ phải xây dựng lại mọi thứ từ lớp # 6- # 10. Vì vậy, không quá thường xuyên rằng nó sẽ tăng tốc quá trình xây dựng, nhưng nó được đảm bảo để tăng kích thước hình ảnh của bạn một cách không cần thiết.


Cảm ơn @Mohan đã nhắc nhở tôi cập nhật câu trả lời này.


1
Điều này hiện đã lỗi thời - xem câu trả lời dưới đây.
Mohan

1
@Mohan cảm ơn đã nhắc nhở! Tôi đã cập nhật bài viết để giúp người dùng.
Menzo Wijmenga

19

Có vẻ như các câu trả lời ở trên đã lỗi thời. Các tài liệu lưu ý:

Trước Docker 17.05 và thậm chí nhiều hơn, trước Docker 1.10, điều quan trọng là giảm thiểu số lượng lớp trong hình ảnh của bạn. Những cải tiến sau đây đã giảm bớt nhu cầu này:

[...]

Docker 17.05 và cao hơn hỗ trợ thêm cho các bản dựng nhiều giai đoạn, cho phép bạn chỉ sao chép các tạo phẩm bạn cần vào hình ảnh cuối cùng. Điều này cho phép bạn bao gồm các công cụ và thông tin gỡ lỗi trong các giai đoạn xây dựng trung gian của bạn mà không làm tăng kích thước của hình ảnh cuối cùng.

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minizes-the-number-of-layers

Lưu ý rằng ví dụ này cũng nén hai lệnh RUN một cách giả tạo bằng toán tử Bash &&, để tránh tạo thêm một lớp trong ảnh. Đây là dễ thất bại và khó để duy trì.

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

Thực tiễn tốt nhất dường như đã thay đổi để sử dụng các bản dựng nhiều tầng và giữ cho Dockerfiles có thể đọc được.


Mặc dù các bản dựng nhiều tầng có vẻ là một lựa chọn tốt để giữ cân bằng, nhưng cách khắc phục thực sự cho câu hỏi này sẽ đến khi docker image build --squashtùy chọn nằm ngoài thử nghiệm.
Yajo

2
@Yajo - Tôi hoài nghi về squashviệc vượt qua thử nghiệm. Nó có nhiều mánh lới quảng cáo và chỉ có ý nghĩa trước khi xây dựng nhiều giai đoạn. Với nhiều giai đoạn xây dựng, bạn chỉ cần tối ưu hóa giai đoạn cuối rất dễ dàng.
Menzo Wijmenga

1
@Yajo Để mở rộng về điều đó, chỉ các lớp trong giai đoạn cuối tạo ra bất kỳ sự khác biệt nào đối với kích thước của hình ảnh cuối cùng. Vì vậy, nếu bạn đặt tất cả các gubbins của trình xây dựng của mình vào các giai đoạn trước và có giai đoạn cuối cùng chỉ cần cài đặt các gói và sao chép trên các tệp từ các giai đoạn trước, mọi thứ đều hoạt động tốt và không cần squash.
Mohan

3

Nó phụ thuộc vào waht bạn bao gồm trong các lớp hình ảnh của bạn.

Điểm mấu chốt là chia sẻ càng nhiều lớp càng tốt:

Ví dụ xấu:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

Ví dụ tốt:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

Một đề xuất khác là xóa không chỉ hữu ích nếu nó xảy ra trên cùng một lớp với hành động thêm / cài đặt.


2 người này thực sự sẽ chia sẻ RUN yum install big-packagetừ bộ đệm?
Yajo

Vâng, họ sẽ chia sẻ cùng một lớp, miễn là họ bắt đầu từ cùng một cơ sở.
Ondra ižka
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.