이글은 위키북스의
시작하세요! 도커/쿠버네티스
를 참고하여 쓴 글입니다.
Dockerfile
Make an Image
일반적으로 개발한 애플리케이션을 컨테이너화한다면 다음과 같은 순서로 작업을 시행할 것입니다.
- 아무것도 존재하지 않는 이미지(우분투, CentOS 등)로 컨테이너를 생성
- 애플리케이션을 위한 환경을 설치하고 소스코드 등을 복사해 잘 동작하는지 확인
- 컨테이너를 이미지로 커밋
위와 같은 순서로 애플리케이션이 동작하는 환경을 구성하려면 수작업으로 패키지를 설치하고 소스코드를 복사하고 구동해보고.. 어느세월에 다 하겠냐는 것입니다.
비슷한 예시로 Makefile을 들 수 있습니다.
각각의 컴파일할 파일들을 컴파일할 명령어들을 지정해두고 의존성을 설정하고 make명령을 실행하면 각각의 모든 소스코드들을 컴파일할 필요가 사라지게 되는 것과 같은 이치인 것입니다.
도커는 이러한 빌드 명령어를 제공합니다. 완성된 이미지를 생성하기 위해 컨테이너에 설치해야 하는 패키지, 추가해야 하는 소스코드 실행해야 하는 명령어와 셸 스크립트 등을 하나의 파일 Dockerfile
에 저장합니다.
Make an Simple Web Server Image
Dockerfile을 사용하기 위한 간단한 시나리오로 웹 서버 이미지를 생성하는 예를 설명해보겠습니다.
1 | # mkdir dockerfile && cd dockerfile |
이제 Dockerfile을 생성해보겠습니다. 다음과 같은 명령어로 이미지에 아파치 웹 서버를 설치한 뒤, 로컬에 있는 test.html 파일을 웹 서버로 접근할 수 있는 컨테이너의 디렉터리인 /var/www/html에 복사합니다.
1 | # vim Dockerfile |
Dockerfile의 명령어는 위에서 아래로 line by line으로 실행됩니다.
Command of Dockerfile
FROM : 생성할 이미지의 베이스 이미지를 의미합니다. Dockerfile을 작성할 때 반드시 한 번 이상 입력해야 하며, 이미지 이름의 포맷은 docker run 명령어에서 이미지 이름을 사용했을 때와 같습니다. 도커에 사용할 이미지가 없다면 자동으로 pull 합니다.
MAINTAINER : 이미지를 생성한 개발자의 정보를 나타냅니다. 일반적으로는 작성한 사람과 연락할 수 있는 이메일 드을 입력합니다. 단, 도커 1.13.0 버전 이후로 사용하지 않습니다. 대신 아래와 같은 LABEL로 대체 가능합니다.
1 | LABEL maintainer "{username} <username@email.com> |
LABEL : 이미지에 메타데이터를 추가합니다. 이 때 “key : value”형태로 저장되며, 여러개의 메타데이터가 저장될 수 있습니다. 추가된 메타데이터는 docker inspect 명령어로 이미지의 정보를 구해서 확인할 수 있습니다.
RUN : 이미지를 만들기 위해 컨테이너 내부에서 명령어를 실행합니다. 위 예제에서는 apt-get update와 apt-get install apache2 명령어를 실행하여 아파치 웹 서버를 설치한 이미지가 생성됩니다. 단, install을 진행하다보면 [Y/N]를 묻는 경우가 있는데 이때를 위해 -y 옵션을 통해 Yes로 입력되도록 설정을 해야합니다. 이미지를 빌드할 때 별도의 입력을 받아야하는 것이 있다면 이를 오류로 간주하고 빌드를 종료하게 됩니다.
ADD : 파일을 이미지에 추가합니다. 추가하는 파일은 Dockerfile이 위치한 디렉터리인 Context에서 가져옵니다. 위 예제에서는 현재 디렉토리의
test.html
파일을/var/www/html
디렉터리에 추가합니다.
WORKDIR : working directory 즉, 명령어를 실행할 디렉터리를 의미합니다. bash shell에서 cd를 입력하는 것과 같은 기능을 합니다. 위 예제에서는
/var/www/html
디렉터리로 이동한 후/bin/bash
shell을 통해 echo hello << test2.html을 입력한 것입니다. 또한. WORKDIR를 여러번 입력하면 cd를 여러번 입력한 것과 같습니다.
EXPOSE : 노출할 포트를 설정하는 명령어입니다. 하지만, 반드시 이 포트가 호스트의 포트와 바인딩되는 것은 아니고 단지 컨테이너의 80번 포트를 사용한다는 의미입니다. run docker -p option과 함께 사용됩니다. 이 명령어의 사용법은 뒤에서 다시 설명하겠습니다.
CMD : 컨테이너가 시작될 때마다 실행할 커맨드를 설정하며, Dockerfile에서 한번만 사용할 수 있습니다. 위 예제에서는 컨테이너를 생성하고 바로
apachectl -DFOREGROUND
라는 커맨드를 통해 컨테이너가 시작될 때 바로 아파치 웹 서버가 실행되는 것입니다. 이는docker run -d
option과 같습니다. 만약docker run -it
option을 사용한다면 포그라운드에서 동작하는 것만 확인할 수 있게됩니다. run 명령어의 커맨드와 같은 역할입니다. 만약docker run
커맨드 명령어를 입력하면 Dockerfile에서 사용한 CMD의 명령어는run
의 커맨드 명령어로 덮어쓰입니다.
Another Command
ENV: Dockerfile에서 사용될 환경변수를 지정합니다. 설정한 환경변수는
${ENV_NAME}
또는$ENV_NAME
의 형태로 사용할 수 있습니다. 이 환경변수는 Dockerfile뿐 아니라 이미지에도 저장됩니다. 다음 Dockerfile에서는 test라는 환경변수에 /home이라는 값을 설정했습니다.
1 | vi Dockerfile |
만약 이미지를 빌드할 때 -e 옵션을 통해 같은 이름의 환경변수를 사용한다면 기존의 값은 덮어 쓰여집니다.
1 | docker run -it --name env_test_override \ |
VOLUME : 빌드된 이미지로 컨테이너를 생성했을 때 호스트와 공유할 컨테이너의 내부의 디렉터리를 설정합니다. 즉, 컨테이너와 연동할 호스트의 디렉터리를 설정하는 것입니다. VOLUME [“/home/dir”, “home/dir2”]처럼 JSON 배열의 형식으로 여러개 사용하거나
VOLUME /home/dir /home/dir2
로도 사용할 수 있습니다.
ARG : build 명령어를 실행할 때 추가로 입력받아 Dockerfile 내에서 사용될 변수의 값을 설정합니다. 즉,
docker build --build-arg my_arg =/home -t myarg:0.0 ./
과 같이 입력해야만 오류가 나지 않습니다.
1 | vi Dockerfile |
1 | docker build --build-arg my_arg=/home -t myarg:0.0 ./ |
USER : USER로 컨테이너 내에서 사용될 사용자 계정의 이름을 설정하면 그 아래의 멸영어는 해당 사용자의 권한으로 실행됩니다. root권한이 필요가 없다면 USER를 사용하는 것을 권장합니다.
ONBUILD : 빌드된 이미지를 기반으로 하는 다른 이미지가 Dockerfile로 생성될 때 실행할 명령어를 추가합니다. ONBUILD, FROM, MAINTAINER를 제외한 RUN, ADD 등, 이미지가 빌드될 때 수행돼야 하는 각종 Dockerfile의 명령어를 나중에 빌드될 이미지를 위해 미리 저장해 놓는 것입니다. 단, 상속받은 자식이미지는 ONBUILD 속성은 상속되지 않습니다.
한가지 예시로 메이븐이미지파일은 다음과 같은 ONBUILD 옵션을 가지고 있는데 이 이미지를 사용하는 개발자는 프로젝트 폴더에 Dockerfile을 위치하고, 아래의 Dockerfile로부터 빌드된 이미지를 FROM 항목에 입력함으로써 메이븐을 쉽게 사용할 수 있습니다.
1 | FROM maven:3-jdk-8-alpine |
STOPSIGNAL : 컨테이너가 정지될 때 사용될 시스템콜의 종류를 지정합니다. default로는 SIGTERM입니다. 다음과 같이 사용할 수 있습니다.
1 | FROM ubuntu:14.04 |
HEALTHCHECK : 이미지로부터 생성된 컨테이너에서 동작하는 애플리케이션의 상태를 체크하도록 설정합니다. 즉, 컨테이너 내부에서 동작 중인 애플리케이션의 프로세스가 종료되지는 않았으나 동작하고 있지 않은 상태를 방지하기 위해 사용되는 옵션입니다.
SHELL : Dockerfile에서 기본적으로 사용하는 쉘은
/bin/sh -c
입니다. 윈도우에서는cmd /S /C
입니다. 따라서 RUN echo “hello, world’라는 명령어를 사용한다면 리눅스에서는/bin/sh -c echo "hello,world"
로 사용하고 윈도우에서는cmd /S /C echo "hello,world"
라고 사용합니다. 따라서 다음과 같이 쉘을 변경할 수 있습니다.
1 | FROM node |
COPY : 로컬 디렉터리에서 읽어들인 컨텍스트로부터 이미지에 파일을 복사하는 역할을 합니다. 형식은 ADD와 같습니다. 차이점은 ADD는 외부 URL 및 tar 파일에서도 파일을 추가할 수 있지만 COPY는 로컬의 파일만 추가할 수 있습니다. 단, ADD는 그다지 권장되지 않습니다. 그 이유는 정확히 어떤파일이 추가될지 알 수 없다는 것입니다.
ENTRYPOINT : CMD와 동일하게 컨테이너가 시작될 때 실행할 명령어를 설정하는 옵션입니다. 차이점은 CMD를 인자로 받아 사용할 수 있는 스크립트의 역할을 할 수 있다는 점에서 다릅니다. 즉, entrypoint가 없다면 CMD가 그냥 실행되지만
entrypoint가 있다면 CMD는 그저 entrypoint의 인자로 쓰여지게 되는 것입니다.
단, 실행할 스크립트 파일은 컨테이너 내부에 존재해야 합니다.
최종적으로 정리하자면 다음과 같은 순서로 이미지가 작동합니다.
- 어떤 설정 및 실행이 필요한지에 대해 스크립트로 정리
- ADD 또는 COPY로 스크립트를 이미지로 복사
- ENTRYPOINT를 이 스크립트로 설정
- 이미지를 빌드해 사용
- 스크립트에서 필요한 인자는 docker run 명령어에서 cmd로 entrypoint의 스크립트에 전달
Build
밑의 명령어로 Dockerfile을 빌드해보겠습니다.
1 | # docker build -t mybuild:0.0 ./ |
-t : 생성될 이미지의 이름을 설정하는 옵션입니다. 이를 사용하지 않을 경우 “docker run -it ubuntu:14.04”를 했을 경우 16진수 형태의 컨테이너가 생성된 것처럼 이미지도 똑같이 16진수 형태로 저장됩니다.
build 명령어 끝에는 Dockerfile이 저장된 경로를 입력합니다. 즉, docker build -t {name} {location of Dockerfile}
이 됩니다. 외부 URL로부터 Dockerfile의 내용을 가져와 빌드할 수도 있습니다.
그 결과로 다음과 같은 내용이 출력됩니다.
1 | [+] Building 84.4s (11/11) FINISHED |
이제 다음 명령어를 입력해 생성된 이미지로 컨테이너를 실행해보겠습니다.
1 | # docker run -d -P --name myserver mybuild:0.0 |
-P 옵션은 dockerfile에서 설정한 EXPOSE의 모든 포트를 호스트에 연결하도록 설정하는 것입니다. 즉, ‘컨테이너의 80번 포트를 사용한다’를 의미합니다. 개발자는 EXPOSE를 통해 실제로 사용될 때 어떤 포트가 사용돼야 하는지 명시할 수 있으며, 사용자는 어떤 포트가 사용됐는지 알 수 있게 됩니다.
docker port
명령어를 통해 다음과 같이 컨테이너와 호스트가 연결된 포트를 확인할 수 있습니다.
1 | csj7480@DESKTOP-03N08DE:~/dockerfile$ docker port myserver |
Precautions for Building with Dockerfile
좋은 습관(practice)
: Dockerfile을 사용할 때 좋은 습관이 있습니다. 예를들면 하나의 명령어를 사용할 때 "\"
를 사용하여 가독성을 높일 수 있도록 작성하거나 .dockerignore
파일을 사용하여 불필요한 파일을 빌드 컨텍스트에 적용시키지 않아야 합니다. 그리고 이미지 구조에 대해서도 알아야할 것이 있습니다.
1 | vi Dockerfile |
fallocate
은 가상으로 파일을 만들어 메모리를 할당하는 명령어 인데 위의 예시에서는 100MB를 할당한 후 /test/에 dummy파일을 만든후 RUN rm /test/dummy를 통해 파일을 다시 제거했습니다.
그렇다면 상식적으로 ubuntu 깡통파일에서 dummy를 추가했다 삭제했으므로 이미지파일은 깡통에서 100MB만 추가 되어야 하겠지만 사실은 그렇지 않습니다. 컨테이너를 이미지로 생성할 때 레이어를 쌓는 개념으로 생성하므로 단지 파일을 추가하는 레이어, 삭제하는 레이어만 추가된 것입니다. 따라서 용량이 증가하여 불필요한 크기의 이미지가 생기는 것입니다.
이를 방지하기 위해 fallocate과 rm을 &&
연산자를 통해 하나로 묶는다면 이러한 오류를 제거할 수 있습니다.
Reference
시작하세요! 도커/쿠버네티스
- 용찬호