1. Dockerfile
Dockerfile은 Docker 이미지를 생성하기 위해 사용자가 명령줄에서 호출할 수 있는 명령을 포함하는 텍스트 문서이다.
2. 구성
Docker 이미지는 일련의 레이어로 구성된다. Docker는 이미지를 빌드할 때 각 layer들을 쌓아 올린다. 이때 컨테이너 전용 이미지 레이어를 제외한 아래 계층의 베이스 이미지 레이어들은 읽기 전용(read-only)이므로 수정할 수 없다. 또한 이러한 베이스 이미지들은 컨테이너를 여러 개 생성할 때 공유되므로 디스크 용량을 효율적으로 관리한다.
아래를 살펴보자.
docker inspect b953ee2e003f | jq '.[].GraphDriver'
{
"Data": {
"LowerDir": "...diff:/var/lib/docker/overlay2/b07c43f6c5b4ed2fae02a3aa0abae5e08e65b2189a009fc45fa36ea89d14aa5f/diff:/var/lib/docker/overlay2/86df802792040f59a2b901697d0b5c35b517622cf764cf4febb3cef60cd10115/diff:/var/lib/docker/overlay2/09be7540f6f842dd59c953b04f331e194d209a0893a4d67dcd24db0e2c8e73df/diff:/var/lib/docker/overlay2/cc40e8720b47233b7acfe2cfa81dfed9477b836f254ae9d312dbf926cd0eee0c/diff:/var/lib/docker/overlay2/10984d59e1554e34220efc255da17c3ec6ff698ee0e564e491e8074690c64d31/diff:/var/lib/docker/overlay2/c2688d90053e8ae5f64d01feab067c78eb8f8711ce50735417dd224653c92eee/diff",
"MergedDir": "/var/lib/docker/overlay2/39f9c8ec4562ed8a17ca571dca8ea9ab6c751a22c93b8789139093cba886022b/merged",
"UpperDir": "/var/lib/docker/overlay2/39f9c8ec4562ed8a17ca571dca8ea9ab6c751a22c93b8789139093cba886022b/diff",
"WorkDir": "/var/lib/docker/overlay2/39f9c8ec4562ed8a17ca571dca8ea9ab6c751a22c93b8789139093cba886022b/work"
},
"
docker inspect b7d04ab02160 | jq '.[].GraphDriver'
{
"Data": {
"LowerDir": ".../diff:/var/lib/docker/overlay2/b07c43f6c5b4ed2fae02a3aa0abae5e08e65b2189a009fc45fa36ea89d14aa5f/diff:/var/lib/docker/overlay2/86df802792040f59a2b901697d0b5c35b517622cf764cf4febb3cef60cd10115/diff:/var/lib/docker/overlay2/09be7540f6f842dd59c953b04f331e194d209a0893a4d67dcd24db0e2c8e73df/diff:/var/lib/docker/overlay2/cc40e8720b47233b7acfe2cfa81dfed9477b836f254ae9d312dbf926cd0eee0c/diff:/var/lib/docker/overlay2/10984d59e1554e34220efc255da17c3ec6ff698ee0e564e491e8074690c64d31/diff:/var/lib/docker/overlay2/c2688d90053e8ae5f64d01feab067c78eb8f8711ce50735417dd224653c92eee/diff",
"MergedDir": "/var/lib/docker/overlay2/66525665b89604f1209b7a0fcc4da4c03a2952caf500612fe8a409c2e73b99a1/merged",
"UpperDir": "/var/lib/docker/overlay2/66525665b89604f1209b7a0fcc4da4c03a2952caf500612fe8a409c2e73b99a1/diff",
"WorkDir": "/var/lib/docker/overlay2/66525665b89604f1209b7a0fcc4da4c03a2952caf500612fe8a409c2e73b99a1/work"
},
"Name": "overlay2"
}
LowerDir은 베이스 이미지 레이어(읽기만 가능한)들이 포함되고 UpperDir이 컨테이너 레이어(쓰기가 가능한)가 포함된다 정도만 이해하면 된다. 앞의 부분은 임의로 잘라냈다. 살펴보면 해쉬값이 같은 것을 확인할 수 있다. 더 자세한 내용을 알고 싶다면 OverlayFS에 대한 이해가 필요하다(이건 추후에 다뤄보자.).
이미지 레이어에 대한 더 자세한 내용을 알고 싶다면 여길 참고하자 (만들면서 이해하는 도커(Docker) 이미지의 구조)
아래의 명령문은 해당 블로그에서 참고했다.
2.1 FROM
Dockerfile 최상단에 FROM 으로 시작해야 한다. 이는 base 이미지를 정의한다.
FROM <이미지>
# 또는
FROM <이미지>:<태그>
2.2 WORKDIR
WORKDIR은 쉘의 cd처럼 컨테이너 상에서 작업 디렉터리로 전환을 위해서 사용한다. WORKDIR로 작업 디렉터리를 전환하면 그 이후에 등장하는 RUN, CMD, ENTRYPOINT, COPY, ADD 명령먼은 해당 디렉터리를 기준으로 실행한다.
WORKDIR <이동할 경로>
2.3 RUN
RUN은 마치 쉘에서 커맨드를 실행하는 것처럼 이미지 빌드 과정에서 필요한 커멘드를 실행하기 위해서 사용된다. 보통은 이미지 안에 라이브러리 등 필요한 것들을 설치할 때 많이 사용한다.
RUN apt-get install <설치할 패키지 명>
2.4 ENTRYPOINT
ENTRYPOINT의 form은 다음 두 가지 형태를 지닌다.
- exec form
ENTRYPOINT ["executable", "param1", "param2"]
- shell form
ENTRYPOINT command param1 param2
ENTRYPOINT는 이미지를 컨테이너로 띄울 때 항상 실행되야 하는 커맨드를 지정할 때 사용한다. ENTRYPOINT는 Docker 이미지를 마치 하나의 실행파일처럼 사용할 때 유용하다. 왜냐하면 컨테이너가 뜰 때 ENTRYPOINT로 지정된 커맨드가 실행되고, 이 커맨드로 실행된 프로세스가 죽을 때, 컨테이너도 따라서 종료되기 때문이다.
2.4.1 Shell form
쉘 form은 CMD 또는 실행 명령 행 인수가 사용되는 것을 방지하지만 ENTRYPOINT가 신호를 전달하지 않는 "/bin/sh -c" 의 하위 명령으로 시작된다는 단점이 있다. 즉, 실행 파일은 컨테이너의 PID 1이 아니며 Unix 신호를 수신하지 않으므로 실행파일은 도커 중지 컨테이너에서 SIGTERM을 받지 못한다.
정리하자면 exec form은 /bin/sh -c <command> 형태로 실행한다.
2.4.2 Exec form
ENTRYPOINT의 exec form을 사용하여 안정적인 기본 명령 및 인수를 설정한 다음 CMD 형식 중 하나를 사용하여 변경 가능성이 높은 추가 기본값을 설정할 수 있다.
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
해당 Dockerfile로 빌드된 이미지를 컨테이너로 생성하면 다음과 같은 결과를 확인할 수 있다.
$ docker run -it --rm --name test top -H
top - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05
Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers
KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top
다음 Dockerfile은 ENTRYPOINT를 사용하여 foreground에서 apache를 실행하는 것을 보여준다. (즉 PID 1)
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
셸 폼과 달리 exec 폼은 커맨드 셸을 호출하지 않는데, 이는 정상적인 셸 처리가 일어나지 않는다는 것을 의미한다. 예를 들어, ENTRYPOINT [ "echo", "$ HOME"]는 $HOME에서 변수 대체를 수행하지 않는다. 쉘 처리를 원한다면 쉘 형식을 사용하거나 쉘을 직접 실행해라: ENTRYPOINT [ "sh", "-c", "echo $ HOME"].
exec form을 사용하고 shell을 직접 실행할 때, shell form의 경우와 마찬가지로 환경 변수 확장을 수행하는 것은 shell이지 docker가 아니다.
2.5 CMD
CMD의 form 은 다음 세 가지의 형태를 가진다.
- CMD ["executable", "param1", "param2"] (exec form, this is the preferred form)
- CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
- CMD command param1 param2 (shell form)
NOTE!
Dockerfile에는 CMD 명령이 하나만 있을 수 있다. CMD를 두 개 이상 나열하면 마지막 CMD만 적용된다.
CMD의 주요 목적은 실행 중인 컨테이너에 기본값을 제공하는 것이다. 이러한 기본값은 실행파일을 포함하거나 실행 파일을 생략할 수 있으며, 이 경우에는 ENTRYPOINT 명령도 지정해야한다.
셀 형식과 달리 exec 형식은 명령 쉘을 호출하지 않는다. 이것은 정상적인 쉘 처리가 일어나지 않는다는 것을 의미함. 예를 들어 CMD ["echo", "$HOME"]은 $HOME에서 변수 대체를 수행하지 않는다. 쉘을 처리하려면 쉘 형식을 사용하거나 직접 쉘을 실행해라.
CMD ["sh", "-c", "echo $HOME"]
CMD의 쉘 form을 사용하면 <command>가 /bin/sh -c에서 실행한다.
FROM ubuntu
CMD echo "This is a test." | wc -
쉘 없이 <command>를 실행하려면 명령을 JSON 배열로 표현하고 exec에 전체 경로를 제공해야 한다.
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
컨테이너에서 매번 동일한 실행 파일을 실행하려면 ENTRYPOINT를 CMD와 함께 사용하는 것을 고려해라.
CMD가 많은 경우, ENTRYPOINT와 함께 사용하게 되는데, ENTRYPOINT로는 커맨드를 지정하고, CMD로 디폴트 파라미터를 지정해 주면 매우 유연하게 이미지를 실행할 수 있다.
NOTE!
RUN과 CMD를 혼동하지 마라. RUN은 실제로 명령을 실행하고 결과를 커밋한다. CMD는 빌드 타임에 아무것도 실행하지 않지만 image에 대해 원하는 명령을 지정한다.
예를 들어, node 커맨드로 디폴트로는 index.js를 실행하되, docker run 커맨드에 인자가 있는 경우, 해당 인자를 실행하고 싶다면 다음과 같이 작성하면 된다.
ENTRYPOINT ["node"]
CMD ["index.js"]
그러면 다음과 같이 docker run 커맨드의 인자 유무에 따라 node 커맨드로 다른 파일이 실행된다.
# node index.js를 실행
docker run <image>
# node main.js 실행
docker run <image> main.js
부록
CMD와 ENTRYPOINT의 상호작용을 이해하자
CMD 및 ENTRYPOINT는 모두 컨테이너를 실행할 때 실행되는 명령을 정의한다. 그들의 협력을 설명하는 몇 가지 규칙이 있다.
- Dockerfile은 CMD 또는 ENTRYPOINT 명령 중 적어도 하나를 지정해야 한다.
- 컨테이너를 실행 파일로 사용할 때 ENTRYPOINT를 정의해야 한다.
- CMD는 ENTRYPOINT 명령에 대한 기본 인수를 정의하거나 컨테이너에서 ad-hoc 명령을 실행하는 방법으로 사용해야 한다.
- 대체 인수를 사용하여 컨테이너를 실행할 때 CMD가 무시된다.
아래 표는 다양한 ENTRYPOINT/CMD 조합에 대해 실행되는 명령이 나와 있다.
NOTE!
CMD가 기본 이미지에서 정의된 경우 ENITYPOINT를 설정하면 CMD가 빈 값으로 재설정됩니다. 이 시나리오에서는 값을 가지려면 현재 이미지에 CMD를 정의해야 합니다.
참조
https://docs.docker.com/engine/reference/builder/
https://docs.docker.com/storage/storagedriver/
https://www.44bits.io/ko/post/how-docker-image-work
https://woochan-autobiography.tistory.com/468
https://www.daleseo.com/dockerfile/
'Docker' 카테고리의 다른 글
Docker Compose - 1 (0) | 2023.02.23 |
---|---|
Docker 명령어 정리 - 2 (0) | 2023.01.26 |
Docker에 대해 알아보자 - 정의, 작동 방식, 사용 이유 (0) | 2023.01.22 |
[Docker] 1. WSL2 에 Docker 개발 환경 구축하기 (0) | 2022.11.28 |
[완벽한 IT 인프라 구축을 위한 Docker] 1장 시스템과 인프라 기초지식 - 1 (0) | 2022.11.23 |