ENTRYPOINTは「必ず実行」、CMDは「(デフォルトの)引数」

Docker (Moby) logo画像

CMD と ENTRYPOINT の違い

先日の勉強会で Dockerfile における「CMD」と「ENTRYPOINT」の使い分けについて質問がありました。結論からしますとタイトル通り、ENTRYPOINTは「必ず実行」、CMDは「(デフォルトの)引数」なのですが、初学者にとっては分かりづらいところ。デモを交えながら、ブログでも改めて説明します。

(違いについては、既にいろいろなトコロでも言及されていますが、初学者向けにまとめました。私自身、初めて両者に触れた時は、全く理解できなかった!という思いもあります)

コンテナ実行時の挙動と「CMD」命令

まず前提として、次のコマンドを実行したら、なぜ bash が起動するか分かりますか。コンテナ実行後にプロセスを確認しますと、/bin/bash がコンテナ内で PID 1 として動いています。

$ docker container run -it centos
[root@ab8f37c4434e /]# ps ax
  PID TTY      STAT   TIME COMMAND
    1 pts/0    Ss     0:00 /bin/bash
   14 pts/0    R+     0:00 ps ax

bash が起動する理由は、 CentOS 用の Docker 公式イメージで /bin/bash を実行する命令があるからです。具体的には、イメージをビルドする Dockerfile では、以下のような「CMD」命令があります。

CMD ["/bin/bash"]

「CMD」命令とは、ドキュメントによる定義では「The main purpose of a CMD is to provide defaults for an executing container.」(CMD の主な目的は、コンテナ実行時のデフォルトを指定するため)と記述があります。つまり、「centos:latest」イメージを実行する時、コンテナに対して何もオプションを指定しなければ、自動的に実行するコマンドを CMD 命令で指定しています。今回の場合は、「/bin/bash」がコンテナの名前空間内で PID1 のプロセスとして起動します。

つまり、以下のコマンドを実行しても、いずれも同じ結果になります。

# docker container run -it centos
# docker container run -it centos /bin/bash
# docker container run -it centos bash

一方で「CMD」命令は、Docker コンテナ実行時に「引数」の指定があれば、その引数の実行が優先されます。例えば「/bin/sh」を引数として指定しましょう。

# docker run -it centos /bin/sh
sh-4.4# ps ax
  PID TTY      STAT   TIME COMMAND
    1 pts/0    Ss     0:00 /bin/sh
    6 pts/0    R+     0:00 ps ax

ご覧のように、コンテナ実行時に引数のオプションがあれば、CMD 命令よりも実行時の引数が優先されます。

例えば、centos:latest イメージを使って ping も実行できます。

# docker run -it centos ping -c 3 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=1.63 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=1.50 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=1.54 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 5ms
rtt min/avg/max/mdev = 1.504/1.557/1.631/0.070 ms

ここでは、実行時にCMD 命令は上書きされて「ping -c 3 1.1.1.1」(1.1.1.1 に対して3回 ping )を実行しています。

ちなみに、次のような Dockerfile を使えば、コンテナ実行時にデフォルトで ping を実行するイメージを作れます。

FROM centos:latest
CMD ["ping","-c","3","1.1.1.1"]

Dockerfile を作成後、「myping:1」という名前のイメージを作成しましょう。

# docker image build -t myping:1 .

あとは、コンテナを実行すると、引数を指定が無くても自動的に ping (CMD命令で書いてある通り、3回の ping)を実行します。

# docker container run myping:1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=1.77 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=1.60 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=1.74 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 6ms
rtt min/avg/max/mdev = 1.601/1.702/1.769/0.080 ms

また、コンテナ実行時に「/bin/bash」を指定しますと、もちろん実行時のオプションが優先されます。

# docker container run -it myping:1 /bin/bash
[root@4a684780a7b3 /]#

ENTRYPOINT 命令が入った時の CMD 命令

「ENTRYPOINT」命令は「CMD」命令と似ているように見えますが、役割が違います。ENTRYPOINT(”入り口”の意味)は、コンテナの実行時にデフォルトで実行するコマンドと引数(オプション)です。

例えば、必ず「ping」コマンドを実行する Docker イメージを作ってみましょう。次のような Dockerfile を書きます。

FROM centos:latest
ENTRYPOINT ["ping","-c","3","1.1.1.1"]

それから「myping:2」という名前で Docker イメージを作成します。

# docker image build -t myping:2 .

このイメージを使ってコンテナを実行しますと、特にオプションを付けなくても必ず ping を実行します。

# docker container run -t myping:2
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=1.66 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=1.68 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=1.66 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 6ms
rtt min/avg/max/mdev = 1.657/1.665/1.681/0.035 ms

このように ping を実行するイメージが出来ましたが、ping 回数だけでなく ping 先も固定されていて、非常に使い勝手が悪い Docker イメージになってしまいました。これを改善するのが「CMD」命令です。

再び、このような Dockerfile を用意します。

FROM centos:latest
ENTRYPOINT ["ping","-c","3"]
CMD ["1.1.1.1"]

そして、「myping:3」として Docker イメージを作成します。

# docker image build -t myping:3 .

あとは実行します。

# docker container run myping:3
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=1.53 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=1.53 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=1.76 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 6ms
rtt min/avg/max/mdev = 1.526/1.605/1.759/0.113 ms

ご覧の通り、「ping -c 3 1.1.1.1」が実行されました。「ENTRYPOINT」命令で「ping -c 3」が指定されている場合、CMD命令の「1.1.1.1」は ENTRYPOINT で指定したコマンドに帯する引数として機能します。

さらに「CMD」命令は先ほど見たように、Docker コンテナ実行時に上書き可能です。試しに「8.8.8.8」をコンテナの引数に指定して実行します。

# docker container run myping:3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=54 time=1.39 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=54 time=1.36 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=54 time=1.40 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 5ms
rtt min/avg/max/mdev = 1.364/1.385/1.400/0.034 ms

ここでは「ENTRYPOINT」命令の「ping -c 3」は固定されたまま、CMD 命令は「8.8.8.8」のコンテナ実行時の引数によって上書きされ、結果として「ping -c 3 8.8.8.8」が実行されています。

このように、コンテナで必ず実行したいコマンドや引数を「ENTRYPOINT」命令に、デフォルトの引数や推奨パラメータを「CMD」命令に書いて使い分けられます。

画面上で見ているだけでは理解しづらいと思いますので、ぜひ、Dockerfile を書いたり build したりして挙動を体得していただければと思います。

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です