最終更新日 2020年9月15日
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 したりして挙動を体得していただければと思います。