3 minute read

Overview

Dockerfile의 지시어(Instruction)는 문장 서두에서 문장의 동작을 정의합니다.

지시어는 명령어(Command)와 함께 사용하는데, 두 가지 형식이 있습니다.

<지시어> <커맨드> "파라미터 1" "파라미터 2"
<지시어> ["실행 가능한 것", "파라미터1", "파라미터 2"]

이 글에서 다룰 CMDENTRYPOINT도 지시어이며, 도커 이미지가 실행된 뒤에 실행할 명령어를 지정할 때 사용합니다. (execute)

CMD와 ENTRYPOINT의 유사점

단독으로 썼을 때 동작은 같습니다.

FROM ubuntu:22.04 as base

CMD ["echo", "hello"]     

# 실행되는 명령어는 echo hello가 됩니다.
# 출력 결과는 hello cmd 입니다.
FROM ubuntu:22.04 as base

ENTRYPOINT ["echo", "hello"]

# 실행되는 명령어는 똑같이 echo hello가 됩니다.
# 출력 결과는 hello entrypoint 입니다.

따로 쓸땐 둘 다 명령어를 실행하지만, 같이 쓰면 쓰면 동작이 달라집니다.

서로 상호 작용이 있으며, 이는 둘의 존재 목적이 다르기 때문입니다.

FROM ubuntu:22.04 as base

ENTRYPOINT ["echo", "hello", "entrypoint\n"]

CMD ["echo", "hello", "cmd"]         

# 실행되는 명령어는 'echo hello entrypoint\n echo hello cmd'와 유사합니다. (같진 않습니다)
#
# 출력 결과는 
# hello entrypoint
# echo hello cmd
# 입니다.

도커 컨테이너는 실행할 때 프로세스를 실행하고, 프로세스가 종료되면 함께 컨테이너를 종료하는데, 이를 executable라고 표현합니다. 이는 명령어, 쉘 스크립트, 바이너리 등을 의미합니다.

ENTRYPOINTexecutable을 지정하는 지시어입니다. (exec form)

An ENTRYPOINT allows you to configure a container that will run as an executable.

CMD는 executable로 실행할 명령어(command)를 지정하는 지시어입니다. 단 ENTRYPOINT가 없으면 executable 정의를 대신하며, 있으면 executable의 파라미터로 전달합니다.

The CMD instruction sets the command to be executed when running a container from an image.

만약 hello 라는 문자열을 출력하는 이미지를 빌드하려면 다음과 같아져야 합니다.

FROM ubuntu:22.04 as base

# executable은 터미널인 bash나 sh가 되고 단일 명령어를 실행하는 -c 옵션을 붙여줍니다.
ENTRYPOINT ["/bin/sh", "-c"]    

# executable인 쉘에 전달할 커맨드는 문자열이 되어야 합니다.
CMD ["echo hello" ] 

# 실제 명령어는 /bin/sh -c "echo hello" 가 됩니다.

존재 목적은 서로 다르지만, 기능적으로는 유사하기 때문에 아래 내용들도 출력 결과는 같습니다.

FROM ubuntu:22.04 as base

CMD ["echo", "hello"]     

# 실행되는 명령어는 echo hello가 됩니다.
# 출력 결과는 hello cmd 입니다.
FROM ubuntu:22.04 as base

ENTRYPOINT ["echo", "hello"]

# 실행되는 명령어는 똑같이 echo hello가 됩니다.
# 출력 결과는 hello entrypoint 입니다.

따라서 ENTRYPOINTCMD는 존재의의는 다르지만 동작이 중첩됩니다.

오히려 Dockerfile에서 두 지시어를 구분하기보다, 동작할 때 executablecommand의 차이점을 아는 것이 중요합니다.

executable과 command

도커 컨테이너는 executable을 시작 프로세스로 실행하면서 시작하며, executable을 종료하면서 컨테이너를 종료합니다. executable은 PID가 1인 init process가 됩니다.

도커 컨테이너에 보내는 시그널은 init process에 전달됩니다. 따라서 init process의 자식 프로세스나 다른 프로세스에는 전달되지 않을 수 있습니다.

예를 들어

Dockerfile에 1초에 한번 문자열 출력을 20번 하는 hello.sh를 생성하는 base 스테이지가 있습니다. SIGINT 이벤트를 등록해뒀기 때문에 외부 인터럽트를 수신할 수 있습니다.

FROM ubuntu:22.04 as base


# 1초에 한번 hello 출력을 20번 반복하는 스크립트를 생성
RUN echo '#!/bin/bash\n\
        trap '\''echo "SIGINT received"; exit 1'\'' INT\n\
        for i in $(seq 1 20);\
                do\n\
                        echo "hello $@"\n\
                sleep 1\n\
        done' > /hello.sh

# 실행 권한 부여
RUN chmod +x /hello.sh

그리고 hello.sh를 실행시키는 스테이지 case1이 있습니다.

FROM base as case1
# CMD에서 shell form으로  /hello.sh를 실행한 경우

CMD /hello.sh cmd

CMD가 shell form이므로 /bin/sh -c "/hello.sh"가 되어 executable/bin/sh , command/hello.sh 가 됩니다.

이 스테이지는 실행 중 인터럽트를 시도하면 init process인 /bin/sh로 시그널을 보내지만, /hello.sh가 실행 중이기 때문에 종료되지 않습니다.

bure@burepool:~/test/docker_cmd_and_entrypoint$ docker build --target case1 -t test4 -f ./init_check_init_process.Dockerfile .  && docker run --rm test4
> hello cmd
> hello cmd
> ...
> ^C hello cmd         # 인터럽트 시도
> hello cmd
> hello cmd
> ...
bure@burepool:~/test/docker_cmd_and_entrypoint$

하지만 exec form으로 실행하는 스테이지는 인터럽트가 가능합니다.

init process가 /hello.sh 이므로 SIGINT 시그널이 전달됩니다.

FROM base as case2

# Case 2-------------------------------
# CMD를 exec form 으로 작성한 경우

CMD [ "/hello.sh", "cmd with exec"]

# Output 2
> hello cmd with exec
> hello cmd with exec
> hello cmd with exec
> ...
> hello cmd with exec
> ^CSIGINT received
bure@burepool:~/test/docker_cmd_and_entrypoint$

Leave a comment