Dockerfile 역할
- 도커는 완성된 이미지를 생성하기 위해 컨테이너에 설치해야 하는 패키지, 추가해야 하는 소스 코드, 실행해야 하는 명령어와 쉘 스크립트 등을 하나의 파일에 기록해두면 도커는 이 파일을 읽어 컨테이너에서 작업을 수행한 뒤 이미지로 만들어 낸다.
- 이러한 작업을 기록한 파일의 이름을 Dockerfile이라 한다.
- 빌드 명령어는 Dockerfile을 읽어 이미지를 생성한다.
- Dockerfile을 활용하면 애플리케이션의 빌드 및 배포를 자동화할 수 있다.
- Docker hub에 이미지 자체를 배포하는 대신 이미지 생성 방법을 작성한 Dockerfile을 배포할 수도 있다.
Dockerfile 작성
- 예제: 웹 서버 이미지 생성
- test.html 생성
$ echo test >> test.html
- Dockerfile 생성
$ vi Dockerfile FROM ubuntu:14.0.4 MAINTAINER devHTak LABEL "purpose"="practice" RUN apt-get update RUN apt-get install apache2 -y ADD test.html /var/www/html WORKDIR /var/www/html RUN ["/bin/bash", "-c", "echo hello >> test2.html"] EXPOSE 80 CMD apachectl -DFOREGROUND
- 한줄에 하나의 명령어로 이루어져 있으며, 소문자로 작성해도 되나 보통 대문자로 작성한다.
-
명령어는 위에서 아래로 한줄씩 실행된다.
- FROM
- 생성할 이미지의 베이스가 될 이미지를 뜻한다.
- FROM 명령어는 Dockerfile을 작성할 때 반드시 한 번 이상 입력해야 하며, 이미지 이름의 포맷은 docker run 명령어에서 이미지 이름과 동일하다.
- 사용하려는 이미지가 없는 경우 hub에서 pull한다.
- MAINTAINER
- 이미지를 생성한 개발자의 정보(이메일 주소)를 나타낸다.
- 도커 1.13.0 버전 이후로 사용하지 않는다. 대신, LABEL을 사용한다.
LABEL maintainer "devHTak <devHTak@gmail.com>"
- LABEL
- 이미지에 메타 데이터를 추가한다.
- 메타데이터는 key value 형태로 저장되며, 여러 개의 메타데이터를 생성할 수 있다.
- docker inspect 명령어로 이미지의 정보를 구해서 확인할 수 있다.
- RUN
- 이미지를 만들기 위해 컨테이너 내부에서 명령어를 실행한다.
- 예제에서는 아파치 웹 서버가 설치된 이미지가 생성된다.
- RUN [“실행 가능한 파일”, “명령줄 인자1”, “명령줄 인자2”, …]
- 이와 같은 방식으로 JSON 배열 형태로 명령어를 입력할 수 있다.
- ADD
- 파일을 이미지에 추가한다.
- 추가하는 파일은 Dockerfile이 위치한 디렉터리인 컨텍스트(Context)에서 가져온다.
- 예제에서는 test.html 파일을 이미지 내에 /var/www/html에 추가한다.
- ADD [“추가할 파일 이름”, …, “컨테이너에 추가될 위치”]
- JSON 배열 형태로 파일을 추가할 수 있다.
- WORKDIR
- 명령어를 실행할 디렉터리를 나타낸다.
- /bin/bash에서 cd 명령어와 같은 역할을 한다.
- EXPOSE
- Dockerfile의 빌드로 생성된 이미지에서 노출할 포트를 설정한다.
- 이미지를 컨테이너로 만들 때 포트 포워딩을 -P(Publish)로 하면 해당 expose로 설정된 포트로 접근할 수 있다.
- CMD
- 컨테이너가 실행될 때마다 실행할 명령어(커맨드)를 설정한다.
- Dockerfile에서 한번만 사용할 수 있다.
- 예제에서는 컨테이너가 실행될 때 마다 apache가 실행되도록 했다.
- CMD [“실행 가능한 파일”, “명령줄 인자1”, …] 형태로도 사용 가능하다.
- CMD 대신 ENTRYPOINT를 사용할 수 있다.
- 빌드
$ docker build -t mybuild:0.0 ./
- -t 옵션을 이용하여 빌드된 이미지의 이름을 지정할 수 있다.
- mybuild:0.0 라는 이름의 이미지 생성
- -t 옵션을 사용하지 않으면 16진수로 된 임의의 이미지명이 생성되어 사용하는 것이 좋다.
- ./ 현재 디렉터리에 Dockerfile이 있다는 것을 명시했다.
- -t 옵션을 이용하여 빌드된 이미지의 이름을 지정할 수 있다.
- test.html 생성
빌드 과정
- 빌드 컨텍스트
- 이미지 빌드를 시작하면, 도커는 가장 먼저 빌드 컨텍스트를 읽어들인다.
- 빌드 컨텍스트란, Dockerfile이 위치한 디렉터리
- 빌드 컨텍스트는 이미지를 생성하는 데 필요한 각종 파일, 소스코드, 메타데이터 등을 담고 있는 디렉터리를 의미한다.
- 빌드 컨텍스트 하위 폴더도 포함되기 때문에 불필요한 파일이 있으면 성능이 떨어진다.
- .dockerignore 파일을 작성하면 빌드 시 명시된 이름의 파일을 컨텍스트에서 제외한다.
$ vi .dockerignore test2.html *.html */*.html !test.html?
- *: 모든 파일을 뜻한다.
- ?: 임의의 1자리 문자가 들어가는 파일
- !: 해당 파일은 제외에서 제외한다.
- 이미지 빌드를 시작하면, 도커는 가장 먼저 빌드 컨텍스트를 읽어들인다.
- Dockerfile을 이용한 컨테이너 생성과 커밋
- Dockerfile에서 명령어 한 줄이 실행될 때마다 이전 Step에서 생성된 이미지에 의해 새로운 컨테이너가 생성되며, Dockerfile에 적힌 명령어를 수행하고 다시 새로운 이미지 레이어로 저장된다.
- 따라서, 이미지가 생성되면 Dockerfile에 명령어 줄 수만큼 레이어가 존재하게 되며, 중간에 컨테이너도 같은 수만큼 생성되고 삭제된다.
- 캐시를 이용한 이미지 빌드
- 한번 이미지 빌드를 마치고 난 뒤 다시 같은 빌드를 진행하면 이전의 이미지 빌드에서 사용했던 캐시를 사용한다.
$ cp Dockerfile Dockerfile2 $ docker build -f Dockerfile2 -t mycache:0.0 ./ # step 별로 Using Cache 내용을 확인할 수 있다.
- 하지만 캐시가 불필요한 경우가 있다.
- git에서 clone해 오는 경우, 캐시에 남아있는 소스를 그대로 사용하면 안된다.
- –no-cache옵션을 추가하면 된다.
$ docker build --no-cache -t mybuild:0.1
- 캐시를 사용할 이미지를 직접 지정할 수도 있다.
$ docker build --cache-from nginx -t my_extend_nginx:0.0
- 한번 이미지 빌드를 마치고 난 뒤 다시 같은 빌드를 진행하면 이전의 이미지 빌드에서 사용했던 캐시를 사용한다.
- 멀티 스테이지를 이용한 Dockerfile 빌드하기
- 개발한 어플리케이션을 사용하기 위해서는 해당 언어에 맞는 의존성 패키지와 라이브러리가 필요하다.
- 17.0.5 버전 이상을 사용하는 도커 엔진이라면, 이미지의 크기를 줄이기 위한 멀티 스테이지 빌드 방법을 사용할 수 있다.
- 멀티 스테이지 빌드는 하나의 Dockerfile 안에 여러개의 FROM 이미지를 정의함으로써 빌드 완료 시 최종적으로 생성될 이미지의 크기를 줄이는 역할을 한다.
- 멀티 스테이지 빌드는 반드시 필요한 실행 파일만 최종 이미지 결과물에 포함시킴으로써 이미지를 크게 줄일 때 유용하게 사용할 수 있다.
FROM golang ADD main.go /root WORKDIR /root RUN go build -o /root/mainApp /root/main.go FROM alpine:latest WORKDIR /root COPY --from=0 /root/mainApp . CMD ["./mainApp"]
- 2개의 FROM을 통해 2개의 이미지가 명시되었다.
- 첫 번째 FROM을 통해 명시된 golang 이미지는 이전과 동일하게 main.go 파일을 /root/mainApp으로 빌드하였다.
- 두 번째 FROM 아래에서 사용된 COPY 명령어는 첫 번째 FROM에서 사용된 이미지의 최종 상태에 존재하는 /root/mainApp 파일을 두 번째 이미지인 alpine에 복사한다.
- alpine 이미지는 매우 작지만 프로그램 실행에 필요한 런타임 요소가 포함되어 있는 리눅스 배포판 이미지이다.
- 이 때, –from=0은 첫 번째 FROM에서 빌드된 이미지의 최종 상태를 의미한다.
- 즉, 첫 번째 FROM 이미지에서 빌드한 /root/mainApp 파일을 두번째의 FROM 절에 명시된 이미지를 alpine:latest 이미지에 복사하는 것
$ docker build . -t go_helloworld:multi-stage $ docker images
- 이미지의 크기가 줄어든 것을 확인할 수 있다.
기타 Dockerfile 명령어
- ENV
- Dockerfile에 사용될 환경변수를 지정한다.
- ${ENV_NAME}, $ENV_NAME 형태로 사용할 수 있다.
ENV test /home
- test라는 변수에 /home이라는 값을 설정하였다.
- run 명령어에서 -e 옵션을 사용해 같은 이름의 환경변수를 사용하면 기존 값은 덮어진다.
- VOLUME
- 빌드된 이미지로 컨테이너를 생성했을 때 호스트와 공유할 컨테이너 내부의 디렉터리를 설정한다.
- JSON 형식으로 여러개를 사용하거나 나열하여 사용할 수 있다.
- VOLUME [“/home/dir”, “/home/dir2”]
- VOLUME /home/dir /home/dir2
- 컨테이너를 생성하고 볼륨의 목록을 확인해 보면 볼륨이 생성된 것을 알 수 있다.
- ARG
- build 명령어를 실행할 때 추가로 입력을 받아 Dockerfile 내에서 사용될 변수의 값을 설정한다.
- 기본값을 지정할 수도 있다.
FROM ubuntu:14.04 ARG my_arg ARG my_arg2=value2 RUN touch ${my_arg2}/mytouch
- 빌드할 때 –build-arg 옵션을 사용하여 key=value 형태로 입력할 수 있다.
$ docker build --build-arg my_arg=/home -t my_arg:0.0 .
- ARG, ENV의 값을 사용하는 방법은 ${}으로 같으므로 Dockerfile에서 ARG로 설정한 변수를 ENV에서 같은 이름으로 다시 정의하면 –build-arg 옵션에서 설정하는 값은 ENV에 의해 덮어진다.
- USER
- USER 컨테이너 내에서 사용될 사용자 계정의 이름이나 UID를 설정하면 그 아래의 명령어는 해당 사용자 권한으로 실행된다.
- 기본적으로 root 권한으로 실행되는 데 필요 없는 경우 사용할 수 있다.
//... RUN groupadd -r author && useradd -r -g author devhtak USER devhtak //...
- OnBuild
- 빌드된 이미지를 기반으로 하는 다른 이미지가 Dockerfile로 생성될 때 실행할 명령어를 추가한다.
FROM ubuntu:14.04 RUN echo "this is onbuild test!" ONBUILD RUN echo "onbuild!" >> /oubuild_file
- 처음으로 빌드를 할 때에는 해당 명령어로 이뤄진 레이어는 생기지만 onbuild_file이 생성되지는 않는다.
$ docker build -t onbulid_test:0.0 .
- 해당 이미지를 기반으로 빌드를 하면 onbuild_file이 생성되는 것을 확인할 수 있다.
FROM onbuild_test:0.0 RUN echo "this is child image!"
$ docker build -t onbuild_test:0.1 . $ docker run -it --rm onbuild_test0.1 ls /onbuild_file onbuild_file
- 처음으로 빌드를 할 때에는 해당 명령어로 이뤄진 레이어는 생기지만 onbuild_file이 생성되지는 않는다.
- 빌드된 이미지를 기반으로 하는 다른 이미지가 Dockerfile로 생성될 때 실행할 명령어를 추가한다.
- Stopsignal
- 컨테이너가 정지될 때 사용될 시스템 콜의 종류를 지정한다.
- 아무것도 설정하지 않으면 기본적으로 SIGTERM으로 설정되지망 Dockerfile에 STOPSIGNAL을 정의해 컨테이너가 종료되는 사용될 신호를 선택할 수 있다.
- Dockerfile의 STOPSIGNAL은 docker run 명령어에서 –stop-signal 옵션으로 개별적으로 설정할 수 있다.
- HealthCheck
- 이미지로부터 생성된 컨테이너에서 동작하는 애플리케이션의 상태를 체크하도록 설정
- 컨테이너 내부에서 동작중인 애플리케이션의 프로세스가 종료되지는 않았으나 애플리케이션이 동작하고 있지 않은 상태를 방지하기 위해 사용
FROM nginx RUN apt-get update -y && apt-get install curl -y HEALTHCHECK --interval=1m --timeout=3s --retrieve=3 CMD curl -f http://localhost || exit 1
- –interval로 설정한 시기마다, CMD curl 부분이 상태를 체크하는 명령어가 된다.
- –timeout으로 설정한 시간을 초과하면 실패한 것으로 간주하고 실패한 경우 –retries의 횟수만큼 명령어를 반복한다.
- –retries에 설정된 횟수만큼 상태 체크에 실패하면 해당 컨테이너는 unhealthy 상태로 설정
- Shell
- Dockerfile에서 기본적으로 사용하는 쉘은 리눅스에서 “/bin/sh -c”, 윈도우에서 “cmd /S /C”이다.
- 사용하고자 하는 다른 쉘을 사용하고자 할 때, SHELL을 사용한다.
- ADD 와 COPY
- ADD는 외부 URL 및 tar 파일에서도 파일을 추가할 수 있다.
ADD https://raw.githubsercontent.com/alicek106/mydockerrepo/matser/test.htmlk /home/ ADD test.tar /home
- COPY는 로컬 디렉터리에서 읽어 들인 컨텍스트로부터 이미지에 파일을 복사하는 역할을 한다.
COPY test.html /home/ COPY ["test.html", "/home/"]
- ADD 보다는 COPY를 선호한다. ADD를 했을 경우 정확히 어떤 파일인지 확인하기 어렵기 때문이다.
- ADD는 외부 URL 및 tar 파일에서도 파일을 추가할 수 있다.
- ENTRYPOINT와 CMD
- 같은 점
- ENTRYPOINT는 CMD와 동일하게 컨테이너가 시작될 때 수행할 명령을 지정한다.
- 차이점
- ENTRYPOINT는 커맨드를 인자로 받아 사용할 수 있는 스크립트의 역할을 할 수 있다는 점에서 다르다.
- ENTRYPOINT를 활용한 스크립트 실행
$ docker run -it --name entrypoint_sh --entrypoint='/test.sh' ubuntu:14.04 /bin/bash
- 실행할 스크립트는 컨테이너 내부에 존재해야 한다.
- 없는 경우 COPY, ADD를 사용하여 복사한다.
FROM ubuntu:14.04 RUN apt-get update RUN apt-get install apache2 -y ADD entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
- 이미지 빌드 시 이미지 작동 단계
- 어떤 설정 및 실행이 필요한지에 대해 스크립트로 정리
- ADD 또는 COPY로 스크립트를 이미지로 복사
- ENTRYPOINT를 이 스크립트로 설정
- 이미지를 빌드해 사용
- 스크립트에서 필요한 인자는 docker run 명령어에서 cmd로 entrypoint의 스크립트에 전달
- JSON 배열 형태와 일반 형식의 차이점
- ENTRYPOINT, CMD 에서 사용할 명령어를 /bin/sh로 사용할 수 없으면, JSON 벼열의 형태로 사용해야 한다.
- 같은 점
Dockerfile로 빌드할 때 주의점
- 좋은 습관
- 하나의 명령어를 (역슬래시)로 나누어 가독성을 높일 수 있도록 작성하자.
- 사용하지 않는 파일은 .dockerignore 파일을 작성해 불필요한 파일을 빌드 컨텍스트에 포함하지 않도록 하자.
- 빌드 캐시를 이용해 기존에 사용했던 이미지 레이어를 재사용하자.
- 주의점
FROM ubuntu:14.04 RUN mkdir /test RUN fallocate -l 100m /test/dummy RUN rm /test/dummy
- 레이어(명령어)를 살펴보면 100m 크기의 /test/dummy를 만들었다가 삭제한다.
- 파일을 삭제했다 하더라도 레이어로 만든 기록이 있기 때문에 여전히 이미지의 크기가 높다.
- && 으로 실행할 내용을 하나로 묶어서 사용하면 된다.
FROM ubuntu:14.04 RUN mkdir /test && \ fallocate -l 100m /test/dummy && \ rm /test/dummy
- RUN이 하나의 이미지 레이어가 된다는 것을 생각해보면 간단한 해결책이다.
- 묶어서 사용하는 명령어는 하나이 레이어로 되며, 레이어 수를 줄일 수 있다.
** 참고: 용찬호 님의 시작하세요! 도커/쿠버네티스