ㅍㅍㅋㄷ

Dockerfile Entrypoint 와 CMD의 올바른 사용 방법 본문

IT/Docker

Dockerfile Entrypoint 와 CMD의 올바른 사용 방법

클쏭 2019. 6. 12. 21:56

 

 

 

 

 

ENTRYPOINT 와 CMD 는 무엇인가

 ENTRYPOINT 와 CMD는 해당 컨테이너가 수행하게 될 실행 명령을 정의하는 선언문이다.

 즉, 컨테이너가 무슨 일을 하는지 결정하는 최종 단계를 정의하는 명령이라고 생각하면 된다. 그렇기 때문에 Dockerfile 의 가장 마지막 부분 쯤에 Entrypoint 또는 CMD 를 선언하게 된다. 

 

 그렇다면, ENTRYPOINT 와 CMD는 어떤 차이가 있고 어떻게 사용해야 좋을까.

 

 

ENTRYPOINT 와 CMD 는 무엇이 다른가?

  ENTRYPOINT 와 CMD 의 가장 큰 차이점은 바로 컨테이너 시작시 실행 명령에 대한 Default 지정 여부이다.

 

  만약 ENTRYPOINT 를 사용하여 컨테이너 수행 명령을 정의한 경우,

  해당 컨테이너가 수행될 때 반드시 ENTRYPOINT 에서 지정한 명령을 수행되도록 지정 된다.

 

 하지만, CMD를 사용하여 수행 명령을 경우에는,

 컨테이너를 실행할때 인자값을 주게 되면 Dockerfile 에 지정된 CMD 값을 대신 하여 지정한 인자값으로 변경하여 실행되게 된다.

 

 말이 어려운데 실제로는 심플한 동작 방식이다.

 아래 Dockerfile 예제를 보자.

# Dockerfile

FROM ubuntu
CMD ["/bin/df", "-h"]

  위에서 정의된 Dockerfile 은 CMD 를 사용하여 df -h 명령을 한번 수행하고 종료되는 이미지를 만드는 것이다.

  먼저 테스트를 위해 위 Dockerfile 을 사용해 jhsong/df 라는 이름을 가진 이미지를 빌드해보자. 

❯ docker build -t jhsong/df .

Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu
 ---> 94e814e2efa8
Step 2/2 : CMD ["/bin/df", "-h"]
 ---> Running in c5f57fca1068
Removing intermediate container c5f57fca1068
 ---> 80eeec0ef7c0
Successfully built 80eeec0ef7c0
Successfully tagged jhsong/df:latest

 

  빌드된 jhsong/df 이미지를 컨테이너로 동작시켜 보면, Dockerfile 에서 정의된 대로 df -h 명령을 실행하고 종료되게 된다. 

❯ docker run --name jhsong-df jhsong/df

Filesystem      Size  Used Avail Use% Mounted on
overlay          59G  5.6G   50G  11% /
tmpfs            64M     0   64M   0% /dev
tmpfs          1000M     0 1000M   0% /sys/fs/cgroup
/dev/sda1        59G  5.6G   50G  11% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs          1000M     0 1000M   0% /proc/acpi
tmpfs          1000M     0 1000M   0% /sys/firmware

 

  이번에는, 컨테이너 실행시 추가 인자 값을 줘서 컨테이너가 수행할 명령을 바꿔보자. 

  docker run 으로 컨테이너 실행시 마지막에 ps 명령을 추가 인자를 주고 실행해 보면 아래와 같은 결과를 볼 수 있다.

❯ docker run --name jhsong-df jhsong/df ps -aef

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:19 ?        00:00:00 ps -aef

 

 즉, CMD 로 지정한 내용 대신 컨테이너 실행시 받은 인자로 대체하여 실행됨을 볼 수 있다.

 docker inspect 명령을 통해 컨테이너 설정 내용을 자세히 보자.

❯ docker inspect jhsong-df
....
            "Cmd": [
                "ps",
                "-aef"
            ],
...

 

위와 같이 컨테이너 설정에 Cmd 값이 인자 값으로 대체된 것이 확인 가능하다.

 

이번에는 ENTRYPOINT 를 사용하여 컨테이너 이미지를 만들어보자.

내용은 이전에 정의했던 것과 같으며, 단지 CMD를 ENTRYPOINT 로 대신한 것 뿐이다.

# Dockerfile

FROM ubuntu
ENTRYPOINT ["/bin/df", "-h"]

 

이번엔 jhsong/df:entry 라는 태그를 추가하여 이미지를 빌드 하고,

❯ docker build -t jhsong/df:entry .

Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu
 ---> 94e814e2efa8
Step 2/2 : ENTRYPOINT ["/bin/df", "-h"]
 ---> Running in 61f6f8ad4f61
Removing intermediate container 61f6f8ad4f61
 ---> cc23a8719b6e
Successfully built cc23a8719b6e
Successfully tagged jhsong/df:entry

 

빌드된 jhsong/df:entry 이미지로 컨테이너를 실행해 본다.

❯ docker run --name jhsong-df jhsong/df:entry

Filesystem      Size  Used Avail Use% Mounted on
overlay          59G  5.6G   50G  11% /
tmpfs            64M     0   64M   0% /dev
tmpfs          1000M     0 1000M   0% /sys/fs/cgroup
/dev/sda1        59G  5.6G   50G  11% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs          1000M     0 1000M   0% /proc/acpi
tmpfs          1000M     0 1000M   0% /sys/firmware

 

실행된 결과는 이전 CMD 와는 다른게 없다.

하지만, docker inspect 로 자세히 살펴보면 약간의 다른점을 볼 수 있다.

❯ docker inspect jhsong-df
...
            "Cmd": null,
...

            "Entrypoint": [
                "/bin/df",
                "-h"
            ],
...

Entrypoint 항목에 실행된 명령 정보가 있고, CMD는 null 로 비워져 있는것을 볼 수 있다.

 

이제, 위에서 했던 작업과 같이 docker run 으로 수행시 인자를 추가로 넣어 컨테이너를 실행해 보면 

ENTRYPOINT 와 CMD의 확실한 차이를 볼 수 있다.

❯ docker run --name jhsong-df jhsong/df:entry ps -aef

/bin/df: invalid option -- 'e'
Try '/bin/df --help' for more information.

위와 같이 에러를 출력하며 원하는 동작이 실행되지 않았음을 볼 수 있다.

왜 그런지는 docker inspect 를 통해 살펴보면 알 수 있을 것이다.

 ❯ docker inspect jhsong-df
 ...
            "Cmd": [
                "ps",
                "-aef"
            ],
 ...
            "Image": "jhsong/df:entry",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/bin/df",
                "-h"
            ],
...

 

즉, 컨테이너 실행시 /bin/df 명령은 유지하고, 추가 인자를 CMD로 받아 처리한 것을 볼 수 있다.

결국, 컨테이너 시작시 아래와 같은 명령어를 수행한 것과 같은 것이며, 이는 적절한 명령이 아니었으므로 에러로 끝난 것임을 알 수 있다.

> df -h ps -aef     # ENTRYPOINT : df -h  / CMD : ps -aef

df: invalid option -- 'e'
Try 'df --help' for more information.

 

 

ENTRYPOINT 와 CMD의 올바른 사용 방법

 

  그렇다면 ENTRYPOINT와 CMD 는 어떻게 사용하는게 좋을까.

 

  첫째로는, 컨테이너가 수행될 때 변경되지 않을 실행 명령은 CMD 보다는 ENTRYPOINT 로 정의하는게 좋다.

  컨테이너를 만들때 아마 대부분은 해당 컨테이너가 실행될 목적이 분명할 것이다.

  웹서버(nginx)가 될 수도 있고, App서버(node)가 될 수도 있으며 DB(mysql) 가 될 수도 있다.

  즉, 이미지를 만들때는 이러한 실행 목적이 분명하므로 nginx / node / mysql 같은 메인 프로세스가 될 명령의 경우는 ENTRYPOINT 로 정의하는게 명확할 것이다.

 

  두번째로는, 메인 명령어가 실행시 default option 인자 값은 CMD로 정의해 주는게 좋다.

  CMD 는 ENTRYPOINT 와 함께 사용시 추가 인자 값으로 활용 된다. 

  그러므로,  메인 프로세스에 대한 default 옵션값을 CMD 로 정의해주면 좋을 것이다. 

  

  추가로 ENTRYPOINT 와 CMD는 리스트 포맷 ( ["args1", "args2",...] )으로 정의해 주는게 좋다. 

  보통 ENTRYPOINT 와 CMD 를 작성할때는 대부분 List 형태로 작성하지만, 

  아래와 같이 일반적인 shell 형태로도 작성 가능하다.

# Dockerfile

FROM ubuntu
Add loop.sh /usr/local/bin/loop.sh
ENTRYPOINT /usr/local/bin/loop.sh 1    # Shell format

 

 하지만, 이런 방식으로 이미지를 빌드 한 후 컨테이너를 실행하게 되면

 아래와 같이 컨테이너 실행시 몇가지 다른 방식으로 프로세스를 구동되게 됨을 알수 있다.

 

 아래는 loop 를 돌며 ps 명령을 통해 컨테이너의 프로세스를 확인하는 쉘 스크립트이다.

#!/bin/bash

INTERVAL=$1

while true;
do
  ps x;
  sleep $INTERVAL;
done

 이걸 사용하여 컨테이너가 구동시 실제 컨테이너 내부의 프로세스를 확인해 보자.

 ❯ docker run --name jhsong-loop jhsong/loop
 
 PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh -c /usr/local/bin/loop.sh 1
    6 ?        S      0:00 /bin/bash /usr/local/bin/loop.sh 1
   43 ?        R      0:00 ps x
   ....

 컨테이너 내부 프로세스를 보니, 

 먼저 /bin/sh -c 명령으로 loop.sh 실행 명령을 string 으로 읽은 후에,

 loop.sh 을 /bin/bash 명령으로 실행하는 것을 볼 수 있다.

 

 그렇다면 동일한 내용을 ENTRYPOINT를 List 포맷으로 실행하면 어떨까.

# Dockerfile

FROM ubuntu
Add loop.sh /usr/local/bin/loop.sh
ENTRYPOINT ["/usr/local/bin/loop.sh"]    # exec form
CMD ["1"]

이미지를 다시 빌드 후 docker run 으로 실행해 보면 아래와 같은 결과를 볼 수 있다.

❯ docker run --name jhsong-loop2 jhsong/loop:2
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/bash /usr/local/bin/loop.sh 1
    7 ?        R      0:00 ps x
 ...

아까와는 다르게 /bin/sh -c 를 거치지 않고 바로 script 를 실행한 것을 볼 수 있다.

별다른 차이는 아니지만, sh 를 거쳐 실행하는 것보다는 바로 script 를 실행하는 것이 clear 할 것이다.

그래서 Docker 공식 Documentation 을 보면, List 형태로 작성하는 것을 추천하고 있다.

 

 

[Reference]

Comments