uncategorized

dockerfile best practice

Dockerfile Best Practice

도커 컨테이너를 만들기 위해 사용되는 도커 이미지는 Dockerfile 을 통해서 생성할 수 있다. 물론 commit 명령어를 이용하여 동적으로 컨테이너로부터 이미지를 생성할 수도 있지만, 이미지의 효율적인 관리를 위해서 commit을 사용하는 것보다는 Dockerfile 을 이용하여 이미지를 생성하는 것이 좋다. 여기서는 Dockerfile을 작성할 때 유의해야 하는 사항들에 대하여 정리한다. (해당 내용은 https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ 의 링크를 참조하였습니다.)

설치 구성의 최소화

컨테이너는 효율적인 리소스 사용 및 배포의 자동화를 위해, 사용량을 고려하여 손쉽게 scale in/out 될 수 있는 구조다. 따라서 해당 컨테이너가 필요에 따라 생성되거나 삭제될 수 있다는 점을 고려하여, 컨테이너에 필요 이상의 설정을 하지 않고 최소한의 설정/구성으로 구동할 것을 권장한다. 복잡성/의존성을 최소화하기 위하여 해당 이미지에서 구동되는 어플리케이션에 필요한 최소한의 library 만을 설치하는 것이 좋다. 또한 컨테이너가 삭제될 경우 컨테이너 내부의 데이터도 함께 삭제되기 때문에, 사용자에 의해 변경되거나 기록될 수 있는 데이터는 VOLUME 명령어를 이용하여 외부에 적재하는 것이 좋다.

‘.dockerignore’ 파일의 활용

빌드 작업을 수행할 때 docker engine은 build script 파일(Dockerfile)이 있는 경로와 모든 하위 경로의 파일/디렉토리를 임시 공간(Docker context)에 저장한 후 build 작업을 수행한다. 이 경우 docker build 에 직접적으로 사용되지 않더라도 해당 경로 하위에 있는 파일/디렉토리가 context에 포함될 수 있다. 이를 방지하기 위하여 .dockerignore 파일을 이용하여 빌드 시에 제외할 파일/디렉토리를 지정할 수 있다. 빌드 되는 컨테이너 이미지의 기능 차이는 없으나, 빌드 성능을 높이기 위하여 build 가 이루어지는 디렉토리에서 .dockerignore 파일을 생성하여 활용하는것을 권장한다. 물론 가장 좋은 방법은 빈 디렉토리를 생성한 후에 Dockerfile과 build에 반드시 필요한 파일/디렉토리만을 해당 디렉토리 내부에 위치하여 container 이미지를 생성하는 방법이다.

1 application per 1 container

컨테이너 안에 2개 이상의 어플리케이션을 구동하는 것도 물론 가능하다. 예를 들어 web, was 어플리케이션을 하나의 컨테이너 안에서 구동되도록 할 수도 있다. 하지만 이렇게 1개의 컨테이너 안에 2개 이상의 어플리케이션을 구동하여 사용하는 것은, 컨테이너로 구동되어 있는 어플리케이션 간의 결합성을 높이고 확장성을 떨어트린다. 가령 앞의 경우 web 어플리케이션만 scaling이 되어도 충분한 상황에서 was까지 추가적으로 구동이 될 수 밖에 없다. 자원의 효율적인 확장을 위하여 1개의 컨테이너 안에 웹서버, 데이터베이스 등을 모두 설치하여 사용하는 방식을 지양해야 한다.

Dockerfile 스크립트 최소화

Docker 이미지의 구성 레이어 수는 Dockerfile 의 예약어 수에 비례한다. 가독성을 높이기 위하여 Dockerfile의 예약어를 여러 라인에 걸쳐 사용할 수 있지만, 레이어의 수가 증가하여 파일크기, 빌드시간 등이 늘어날 수 있으므로 가독성과 이미지 크기를 적절히 고려하여 Dockerfile 스크립트를 구성해야 한다.

Caching 기능 활용

빌드 시간을 단축하기 위하여 Docker build 엔진의 캐싱 기능을 활용할 수 있다. build engine은 한번 이상 빌드한 Dockerfile의 이미지를 Dockerfile 기준 스크립트 라인 단위로 캐싱 한다. 변경되지 않은 라인의 경우 기존의 캐싱 데이터를 이용하므로, 자주 변경될 수 있는 예약어의 경우 Dockerfile의 하단에 위치하는 것이 좋다.

또한 이와는 반대로 캐싱 기능을 아예 사용하지 않을 경우도 있다. 이 경우에는 FROM 절 바로 밑의 하단에 ENV 변수로 CACHE_TEMP 등의 변수명을 지정해주고 빌드를 할 때, 이 변수의 값을 임의의 난수로 지정해주면 된다. 해당 변수는 컨테이너 이미지 안에서 사용되지 않고, 의미없는 변수로 남게 되지만 Dockerfile의 해당 라인이 변경되었으므로 build engine은 FROM 절 바로 밑 라인부터 캐시를 사용하지 않고 다시 빌드를 수행할 수 있다.

Command instruction

FROM

Base 이미지의 경우 docker hub에서 제공하는 official 이미지를 사용하는 것을 권장한다. official 이미지는 docker principle 에 따라 최소한의 설치 요소로 구성되어 있다. 이 외에도 자주 사용하는 라이브러리가 있을 경우, 자신에게 커스터마이징 된 이미지를 미리 생성해놓고 해당 이미지를 base 이미지로 사용하는 것도 좋은 방법이다.

ENV

ENV 예약어는 구동 되어질 컨테이너 내부의 환경변수를 정의한다. WORKDIR, USER 등의 명령어를 조합하여 사용하지 않는 경우, 환경변수 PATH에 어플리케이션의 경로를 추가하여 구동 시 임의의 위치에서 명령어가 실행 될 수 있도록 지정할 수 있다. 또한 환경변수로 설치된 어플리케이션의 major, minor version을 명시하거나, 이미지 생성일시 등을 기록해두면 직관성을 높이고 유지보수에도 도움이 된다. 또한 위에 서술한 내용과 같이 Dockerfile 의 cache를 없애고 싶을 때도 활용할 수 있다.

ADD, COPY 명령어

ADD, COPY는 모두 이미지 build가 이뤄지는 호스트 환경의 파일을 컨테이너 내부로 복사한다는 점에서는 동일한 기능을 수행하지만, 세부적인 기능에서 차이가 있으므로 이를 구분하고 적절하게 사용하는 것이 좋다. COPY 예약어는 host 환경의 파일, 디렉토리를 대상 컨테이너 이미지 안으로 복사하는 기능만을 수행한다. 이에 비해 ADD 예약어는 2가지 기능을 추가로 제공한다. 첫번째로 제공하는 기능은 ‘Auto-extraction’ 기능이며 이는 복사하는 대상 파일이 압축 파일(tar, tar.gz)일 경우, 해당 파일의 압축을 해제하여 복사해준다. 또한 ‘Remote-URL’ 기능을 제공하여 wget등을 통하여 원격지의 파일을 복사할 파일로 지정할 수 있다. 그러나 직관성이 떨어지므로, 2가지 기능을 반드시 사용해야하는 경우가 아니라면 상대적으로 직관성이 더 높은 COPY 명령어를 권장한다.

RUN 명령어

RUN 명령어는 컨테이너 안에서 쉘 명령어를 사용할 수 있는 예약어다. 이미지를 커스터마이징하기 위해서 매우 높은 빈도로 사용되며, Dockerfile 을 작성하면서 RUN 명령어를 사용하는 대부분의 경우는 apt-get(debian,ubuntu), yum(centos) 등을 라이브러리를 설치하기 위해 사용된다. 이 경우, 최소 구성을 위하여 자동 update 기능을 지양하는 것이 좋다. apt-get 명령어를 사용할 경우 불필요한 apt-get upgrade 등의 명령어를 사용하지 않을 것을 권장한다. upgrade 명령어를 같이 사용할 경우, 최소 구성을 위한 설치 이외의, 권장되지만(nice to have) 반드시 필요하지는 않는 라이브러리 또한 함께 설치되어 이미지 레이어 크기가 증가한다. 또한 여러 개의 라이브러리를 설치할 경우, 라이브러리 간 판별성을 높이기를 위하여 알파벳순으로 정렬한 후 개행문자()를 이용하여 여러라인으로 표시하는 것이 좋다.

cachinig 방식 유의(cache busting)

Docker engine은 ADD, COPY 이외의 명령어에 대하여 string 값을 비교하여 캐싱 작업을 수행한다.

예를 들어,

1
2
14: RUN apt-get update 
15: RUN apt-get install -y curl

위의 빌드 스크립트를 포함한 docker image를 생성하여 사용한 후에, 사용 뒤의 용도 변경으로 인하여

1
2
14: RUN apt-get update 
15: RUN apt-get install -y curl nginx

위와 같이 변경하였을 경우, docker engine은 14번째 라인까지 캐싱 값을 사용하여 15번째 라인부터 명령어를 수행한다. 이 경우 최신 update를 사용하지 않게 되어, nginx 가 latest버전으로 설치 되지 않을 수 있다. 설치하는 library, middle ware 의 최신화를 위화여 update 구문은 항상

1
14: RUN apt-get update && apt-get install something...

위와 같이 파이프라인을 통하여 함께 수행하는것을 권장한다.

apt, yum 캐시 제거

apt-get update, apt-get install 명령어를 수행한 후에 파이프라인을 통하여 apt cache를 제거하면 레이어의 크기를 줄일 수 있다.
위의 instruction을 정리하여 아래와 같은 방식으로 apt-get 명령어를 수행하는 것을 권장한다.

1
2
3
4
5
RUN     apt-get update \ 
&& apt-get install -y --no-install-recommends libjemalloc1 \
curl \
nginx \
&& rm -rf /var/lib/apt/lists/*

Using pipes

1
RUN wget -O - https://some.site | wc -l / number

위의 구문 같은 경우 wget 의 성공/실패 여부와 상관없이 그 결과 출력의 wc -l 구문 결과가 /number에 성공적으로 저장되면서 docker build engine은 이 경우 언제나 성공으로 인식한다. build 초기에 잡히는 구문 오류가 아닌, 이러한 논리적 오류을 없애기 위해선

1
RUN set -o pipefail && wget -O - https://some.site | wc -l / number

이런식으로 set -o option을 같이 적용해줘야 한다.

Share