Dockerfile의 다중 RUN 대 단일 체인 RUN은 무엇입니까?


132

Dockerfile.1여러 실행 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 그들과 합류 :

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

각각 RUN은 레이어를 생성하므로 항상 적은 레이어가 더 좋을 것이라고 생각했습니다 Dockerfile.2.

이것은 RUN이전에 추가 된 것을 제거 할 때 RUN(즉 yum install nano && yum clean all) 분명한 것이지만, 모든 RUN것이 무언가를 추가하는 경우 고려해야 할 몇 가지 사항이 있습니다.

  1. 레이어는 이전 레이어 위에 diff를 추가하기 만하므로, 나중에 레이어가 이전 레이어에 추가 된 것을 제거하지 않으면 두 방법 사이에 디스크 공간 절약 이점이 많지 않아야합니다 ...

  2. Docker Hub에서 레이어를 병렬로 Dockerfile.1가져 오기 때문에 약간 더 크더라도 이론적으로 다운로드 속도가 더 빠릅니다.

  3. 4 번째 문장 (예 :)을 추가 echo This is the D > d하고 로컬로 재 구축 Dockerfile.1하면 캐시 덕분에 더 빨리 빌드되지만 Dockerfile.24 개의 명령을 모두 다시 실행해야합니다.

그렇다면 질문 : Dockerfile을 수행하는 더 좋은 방법은 무엇입니까?


1
상황과 이미지 사용 (크기, 다운로드 속도 또는 건물 속도에 최적화)에 따라 일반적으로 대답 할 수 없음
Henry

답변:


99

가능하면 항상 동일한 파일을 한 RUN줄로 삭제하는 명령으로 파일을 만드는 명령을 병합합니다 . 각 RUN라인이 이미지에 레이어를 추가 하기 때문에 출력은 문자 그대로 docker diff생성 된 임시 컨테이너에서 볼 수있는 파일 시스템 변경 사항입니다 . 다른 계층에서 작성된 파일을 삭제하면 모든 통합 파일 시스템이 파일 시스템 변경 사항을 새 계층에 등록하기 만하면 파일이 여전히 이전 계층에 존재하며 네트워크를 통해 배송되고 디스크에 저장됩니다. 따라서 소스 코드를 다운로드하여 압축을 풀고 바이너리로 컴파일 한 다음 마지막에 tgz 및 소스 파일을 삭제하면 이미지 크기를 줄이기 위해 단일 계층에서이 작업을 수행해야합니다.

다음으로 다른 이미지에서 재사용 할 가능성과 캐싱 사용량을 기반으로 레이어를 개인적으로 분할했습니다. 기본 이미지 (예 : 데비안)가 모두 같은 4 개의 이미지가있는 경우 대부분의 이미지에 공통 유틸리티 모음을 첫 번째 실행 명령으로 가져 와서 다른 이미지가 캐싱의 이점을 얻을 수 있습니다.

이미지 캐시 재사용을 볼 때 Dockerfile의 순서가 중요합니다. 기본 이미지가 업데이트 될 때만 매우 드물게 업데이트되는 구성 요소를보고 Dockerfile에 높은 이미지를 넣습니다. Dockerfile의 끝 부분에는 호스트 특정 UID를 가진 사용자를 추가하거나 폴더를 만들고 권한을 변경하는 등 빠르게 실행되고 자주 변경 될 수있는 명령이 포함되어 있습니다. 컨테이너에 활발히 개발중인 해석 된 코드 (예 : JavaScript)가 포함 된 경우 재 구축이 단일 변경 사항 만 실행하도록 가능한 한 늦게 추가됩니다.

이러한 각 변경 그룹에서 레이어를 최소화 할 수 있도록 최선을 다합니다. 따라서 4 개의 다른 소스 코드 폴더가있는 경우 단일 폴더 안에 배치되므로 단일 명령으로 추가 할 수 있습니다. apt-get과 같은 패키지 설치는 가능한 경우 단일 RUN으로 병합되어 패키지 관리자 오버 헤드 양 (업데이트 및 정리)을 최소화합니다.


다단계 빌드 업데이트 :

다단계 빌드의 최종 단계에서 이미지 크기를 줄이는 것에 대해 훨씬 덜 걱정합니다. 이러한 단계에 태그가 지정되지 않고 다른 노드로 배송되지 않으면 각 명령을 별도의 RUN줄로 분할하여 캐시 재사용 가능성을 최대화 할 수 있습니다 .

그러나 스테이지간에 복사하는 모든 파일은 환경 변수 설정, 진입 점 및 명령과 같은 나머지 이미지 메타 데이터가 아니라 파일이기 때문에 레이어 스 쿼싱에 대한 완벽한 솔루션은 아닙니다. Linux 배포판에 패키지를 설치할 때 파일 시스템 전체에 라이브러리 및 기타 종속성이 흩어져서 모든 종속성의 복사본을 만들기가 어려울 수 있습니다.

이 때문에 CI / CD 서버에서 바이너리를 빌드하는 대신에 다단계 빌드를 사용하므로 CI / CD 서버는 툴링 만 실행 docker build하면되고 jdk, nodejs, go 및 설치된 다른 컴파일 도구


30

모범 사례에 나열된 공식 답변 (공식 이미지는 반드시 준수해야 함)

레이어 수를 최소화

Dockerfile의 가독성 (및 장기 유지 관리 성)과 사용하는 계층 수를 최소화하는 것 사이의 균형을 찾아야합니다. 사용하는 레이어 수에 대해 전략적이고 신중해야합니다.

고정 표시기 1.10 이후 COPY, ADD그리고 RUN문은 이미지에 새 레이어를 추가합니다. 이 진술을 사용할 때는주의하십시오. 명령을 단일 RUN명령문 으로 결합하십시오 . 가독성이 필요한 경우에만 분리하십시오.

추가 정보 : https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

업데이트 : docker> 17.05의 다단계

다단계 빌드를 사용하면 FROMDockerfile에서 여러 명령문을 사용할 수 있습니다 . 각 FROM문장은 단계이며 자체 기본 이미지를 가질 수 있습니다. 마지막 단계에서는 alpine과 같은 최소 기본 이미지를 사용하고 이전 단계의 빌드 아티팩트를 복사하고 런타임 요구 사항을 설치하십시오. 이 단계의 최종 결과는 이미지입니다. 따라서 여기에서 앞에서 설명한대로 레이어에 대해 걱정할 수 있습니다.

평소와 같이 docker는 다단계 빌드에 대한 훌륭한 문서 를 가지고 있습니다. 빠른 발췌 내용은 다음과 같습니다.

다단계 빌드를 사용하면 Dockerfile에서 여러 FROM 문을 사용합니다. 각 FROM 명령어는 서로 다른베이스를 사용할 수 있으며 각 빌드는 새로운 빌드 단계를 시작합니다. 최종 이미지에서 원하지 않는 모든 것을 남겨두고 한 단계에서 다른 단계로 아티팩트를 선택적으로 복사 할 수 있습니다.

이에 대한 훌륭한 블로그 게시물은 https://blog.alexellis.io/mutli-stage-docker-builds/ 에서 찾을 수 있습니다.

당신의 요점에 대답하려면 :

  1. 그렇습니다. 레이어는 diff와 비슷합니다. 변경 사항이 전혀 없으면 레이어가 추가 된 것으로 생각하지 않습니다. 문제는 일단 계층 # 2에서 무언가를 설치 / 다운로드하면 계층 # 3에서이를 제거 할 수 없다는 것입니다. 따라서 일단 무언가가 층에 쓰여지면, 그것을 제거하여 이미지 크기를 더 이상 줄일 수 없습니다.

  2. 레이어를 병렬로 가져올 수있어 잠재적으로 더 빠를 수 있지만 각 레이어는 파일을 제거하더라도 의심 할 여지없이 이미지 크기를 증가시킵니다.

  3. 예, 도커 파일을 업데이트하는 경우 캐싱이 유용합니다. 그러나 한 방향으로 작동합니다. 레이어가 10 개이고 레이어 # 6을 변경하더라도 레이어 # 6- # 10의 모든 항목을 다시 작성해야합니다. 따라서 너무 자주 빌드 프로세스를 가속화하지는 않지만 이미지 크기를 불필요하게 늘리는 것이 보장됩니다.


이 답변을 업데이트 하도록 알려주@Mohan 에게 감사합니다 .


1
이것은 구식입니다. 아래 답변을 참조하십시오.
Mohan

1
@Mohan은 알림 주셔서 감사합니다! 사용자를 돕기 위해 게시물을 업데이트했습니다.
Menzo Wijmenga

19

위의 답변이 오래된 것 같습니다. 문서 참고 사항 :

Docker 17.05 이전 및 Docker 1.10 이전에는 이미지의 레이어 수를 최소화하는 것이 중요했습니다. 다음과 같은 개선 사항으로 이러한 요구가 완화되었습니다.

[...]

Docker 17.05 이상은 다단계 빌드에 대한 지원을 추가하여 필요한 아티팩트 만 최종 이미지에 복사 할 수 있습니다. 이를 통해 최종 이미지의 크기를 늘리지 않고도 중간 빌드 단계에서 도구 및 디버그 정보를 포함 할 수 있습니다.

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

이 예제는 또한 Bash && 연산자를 사용하여 두 개의 RUN 명령을 인위적으로 압축하여 이미지에 추가 레이어를 생성하지 않도록합니다. 이것은 실패하기 쉽고 유지 관리하기가 어렵습니다.

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

모범 사례는 다단계 빌드를 사용하고 Dockerfile가독성을 유지하는 것으로 변경되었습니다 .


다단계 빌드는 균형을 유지하는 좋은 옵션 인 것처럼 보이지만이 옵션의 실제 수정은 docker image build --squash옵션이 실험을 벗어나면 나타납니다.
Yajo

2
@ 야조-나는 squash과거의 실험에 대해 회의적 이다. 많은 특수 효과가 있으며 다단계 빌드 이전에만 의미가 있습니다. 다단계 빌드를 사용하면 최종 단계 만 최적화하면되며 매우 쉽습니다.
Menzo Wijmenga

1
@Yajo이를 확대하기 위해 마지막 단계의 레이어 만 최종 이미지의 크기와 차이를 만듭니다. 따라서 모든 빌더 gubbin을 초기 단계에 배치하고 최종 단계에서 패키지를 설치하고 이전 단계의 파일을 복사하면 모든 것이 아름답게 작동하고 스쿼시가 필요하지 않습니다.
Mohan

3

이미지 레이어에 포함시켜야합니다.

핵심은 가능한 많은 레이어를 공유하는 것입니다.

나쁜 예 :

도커 파일 .1

RUN yum install big-package && yum install package1

도커 파일 .2

RUN yum install big-package && yum install package2

좋은 예 :

도커 파일 .1

RUN yum install big-package
RUN yum install package1

도커 파일 .2

RUN yum install big-package
RUN yum install package2

삭제 제안은 추가 / 설치 작업과 동일한 계층에서 발생하는 경우에만 유용하지는 않습니다.


이 2가 실제로 RUN yum install big-packagefrom 캐시를 공유 합니까?
Yajo

예, 동일한 기반에서 시작하는 경우 동일한 계층을 공유합니다.
Ondra Žižka
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.