相关推荐recommended
Dockerfile里的ENTRYPOINT和CMD
作者:mmseoamin日期:2024-01-19

文章目录

  • 环境
  • 总结
  • 讲解
    • (一)不指定ENTRYPOINT和CMD
    • (二)CMD
    • (三)ENTRYPOINT
    • (四)ENTRYPOINT和CMD的组合
    • 参考

      环境

      • RHEL 9.3
      • Docker Community 24.0.7

        总结

        如果懒得看详细介绍,可以直接看总结:

        • ENTRYPOINT 和 CMD 都可以单独使用,指定启动容器时所运行的命令以及参数。
        • 更常见的用法是把 ENTRYPOINT 和 CMD 组合使用:
          • ENTRYPOINT 指定启动容器时所运行的命令和不变的参数。在启动容器时可以显式覆盖,但一般不这么做。
          • CMD 指定运行参数。在启动容器时可以显式覆盖。
          • ENTRYPOINT 和 CMD 都强烈推荐使用“exec形式”。

            例如:

            ENTRYPOINT ["ping", "-c", "20"]
            CMD ["localhost"]
            

            讲解

            ENTRYPOINT 和 CMD 指定启动容器时所运行的命令以及参数。二者都可以单独使用,也可以把二者组合使用。接下来通过示例来看一下二者的用法。

            (一)不指定ENTRYPOINT和CMD

            创建 Dockerfile 文件如下:

            FROM ubuntu:trusty
            

            构建:

            docker build -t kaidemo0 .
            

            启动容器:

            docker run kaidemo0
            

            结果什么也没有发生:既没有报错,也没有任何输出。

            通过 docker ps -a 查看容器:

            ➜  ~ docker ps -a
            CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS                      PORTS                      NAMES
            f3aeb967fdd2   kaidemo0                 "/bin/bash"              49 seconds ago   Exited (0) 48 seconds ago                              affectionate_noyce
            

            可以看到,在没有指定 ENTRYPOINT 和 CMD 时,实际运行的命令是 /bin/bash 。

            要想与容器交互,可以加上 -it 选项:

            ➜  ~ docker run -it kaidemo0
            root@1389cac2f57e:/# ls
            bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
            root@1389cac2f57e:/# ps -ef
            UID          PID    PPID  C STIME TTY          TIME CMD
            root           1       0  0 09:07 pts/0    00:00:00 /bin/bash
            root          18       1  0 09:07 pts/0    00:00:00 ps -ef
            root@1389cac2f57e:/# exit
            exit
            

            注:

            • -i :interactive,交互式的,接收用户输入
            • -t :tty,分配一个伪终端

              也可在 docker run 时指定运行的命令,比如:

              ➜  ~ docker run kaidemo0 ping localhost
              PING localhost (127.0.0.1) 56(84) bytes of data.
              64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.069 ms
              64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.032 ms
              64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.024 ms
              

              在另一个命令行窗口,查看容器:

              ➜  test1 docker ps
              CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
              ab8ed71eae08   kaidemo0         "ping localhost"         3 seconds ago   Up 3 seconds                              interesting_dijkstra
              

              最后,在第一个窗口,按下“Ctrl + C”停止ping命令,同时退出容器。

              总结:若不指定ENTRYPOINT和CMD,启动容器时,默认运行的命令是 /bin/bash 。要显式加上 -it 选项才能进入容器做事。也可以在启动容器时显式指定运行的命令。

              (二)CMD

              创建 Dockerfile 文件如下:

              FROM ubuntu:trusty
              CMD ping localhost
              

              构建:

              docker build -t kaidemo1 .
              

              启动容器:

              docker run kaidemo1
              

              由于 CMD 指令指定了 ping localhost 命令,容器启动时会自动运行该命令,如下:

              ➜  ~ docker run kaidemo1      
              PING localhost (127.0.0.1) 56(84) bytes of data.
              64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.145 ms
              64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.199 ms
              64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.065 ms
              ^C64 bytes from localhost (127.0.0.1): icmp_seq=4 ttl=64 time=0.084 ms
              64 bytes from localhost (127.0.0.1): icmp_seq=5 ttl=64 time=0.030 ms
              ......
              

              注意:按下 “Ctrl + C” 无法停止ping命令,原因稍后解释。

              在另一个命令行窗口,查看docker容器:

              ➜  ~ docker ps
              CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
              25e553984c8a   kaidemo1         "/bin/sh -c 'ping lo…"   7 seconds ago   Up 7 seconds                              sleepy_lalande
              

              同样,尝试停止容器也无效:

              ➜  ~ docker stop 25e553984c8a
              ^C
              

              docker stop 命令hang住了,只能按“Ctrl + C”中止。

              最后,运行 docker rm -f 强制删除容器:

              ➜  ~ docker rm -f 25e553984c8a
              25e553984c8a
              

              原因解释:通过刚才的 docker ps 可以看到,实际运行的命令是 /bin/sh -c 'ping localhost' 。

              • docker run kaidemo0 ping localhost 时,在容器里查看进程:
                ➜  docker docker exec  ps -ef       
                UID          PID    PPID  C STIME TTY          TIME CMD
                root           1       0  0 02:08 ?        00:00:00 ping localhost
                root           7       0  0 02:09 ?        00:00:00 ps -ef
                
                • docker run kaidemo1 时,在容器里查看进程:
                  ➜  docker docker exec  ps -ef
                  UID          PID    PPID  C STIME TTY          TIME CMD
                  root           1       0  0 02:12 ?        00:00:00 /bin/sh -c ping localhost
                  root           7       1  0 02:12 ?        00:00:00 ping localhost
                  root           8       0  0 02:12 ?        00:00:00 ps -ef
                  
                  • docker run -it kaidemo1 时,在容器里查看进程:
                    ➜  docker docker exec  ps -ef
                    UID          PID    PPID  C STIME TTY          TIME CMD
                    root           1       0  0 02:16 pts/0    00:00:00 /bin/sh -c ping localhost
                    root           8       1  0 02:16 pts/0    00:00:00 ping localhost
                    root           9       0  0 02:16 ?        00:00:00 ps -ef
                    

                    注意PID为1的进程,分别为:

                    • ping localhost
                    • /bin/sh -c ping localhost (没有TTY)
                    • /bin/sh -c ping localhost (有TTY)

                      当我们在容器外部发送POSIX信号(比如“Ctrl + C”)到容器里,对于 docker run kaidemo1 , /bin/sh 命令不会转发消息给实际运行的ping命令,所以无法停止ping。

                      之所以出现这样的问题,是因为我们在Dockerfile里使用了“shell形式”,即:

                      CMD ping localhost
                      

                      Docker会把该命令作为shell的子命令(即 /bin/sh -c xxxxx ),这就带来了问题。

                      为了避免这个问题,可以使用“exec形式”。

                      创建 Dockerfile 文件如下:

                      FROM ubuntu:trusty
                      CMD ["ping", "localhost"]
                      

                      构建:

                      docker build -t kaidemo2 .
                      

                      启动容器:

                      docker run kaidemo2
                      

                      在另一个命令行窗口,查看容器:

                      ➜  docker docker ps
                      CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
                      a6e67096ca3a   kaidemo2         "ping localhost"         6 seconds ago   Up 4 seconds                              stupefied_euler
                      

                      查看容器里的进程:

                      ➜  ~ docker exec  ps -ef
                      UID          PID    PPID  C STIME TTY          TIME CMD
                      root           1       0  0 02:33 ?        00:00:00 ping localhost
                      root           7       0  0 02:33 ?        00:00:00 ps -ef
                      

                      可见,应尽量使用“exec形式”,以避免子shell的问题。

                      可以在启动容器时,显式指定要运行的命令,覆盖 CMD 的命令。例如:

                      ➜  ~ docker run kaidemo2 ls -l 
                      total 8
                      drwxr-xr-x.   2 root root 4096 Dec 17  2019 bin
                      drwxr-xr-x.   2 root root    6 Apr 10  2014 boot
                      drwxr-xr-x.   5 root root  340 Jan  6 02:39 dev
                      ......
                      

                      总结:假设没有指定 ENTRYPOINT 指令,则 CMD 指令可以指定启动启动时要运行的命令。强烈推荐使用“exec形式”,以避免子shell的问题。可以在启动容器时,显式指定要运行的命令,覆盖 CMD 指定的命令。

                      (三)ENTRYPOINT

                      创建 Dockerfile 文件如下:

                      FROM ubuntu:trusty
                      ENTRYPOINT ping localhost
                      

                      构建:

                      docker build -t kaidemo3 .
                      

                      启动容器,可以发现, ENTRYPOINT 和 CMD 的表现几乎一模一样。

                      因此,应尽量使用“exec形式”:

                      ENTRYPOINT ["ping", "localhost"]
                      

                      另外,在启动容器时,覆盖 ENTRYPOINT 的方法和 CMD 不同,例如:

                      ➜  ~ docker run kaidemo4 ls       
                      ping: unknown host ls
                      

                      可见,运行的还是ping命令,只不过参数变成了 ls 。这是因为覆盖的是 CMD 指令,而不是 ENTRYPOINT 指令。关于二者的组合,稍后会有介绍。

                      要想覆盖 ENTRYPOINT 指令,可以使用 --entrypoint 选项:

                      ➜  ~ docker run --entrypoint ls kaidemo4 
                      bin
                      boot
                      dev
                      ......
                      

                      但是,不推荐使用这种做法,原因稍后会有介绍。

                      总结: ENTRYPOINT 的表现几乎和 CMD 完全一致。假设没有指定 CMD 指令,则 ENTRYPOINT 指令可以指定启动启动时要运行的命令。强烈推荐使用“exec形式”,以避免子shell的问题。可以在启动容器时,通过 --entrypoint 选项显式指定要运行的命令,覆盖 ENTRYPOINT 指定的命令,但一般不这么做。

                      (四)ENTRYPOINT和CMD的组合

                      前面说了这么多, ENTRYPOINT 和 CMD 貌似也没什么本质的区别,那为什么Dockerfile里要有两个相似的指令呢?

                      实际上,二者的设计理念不一样,典型的用法是把它们组合起来使用:

                      • ENTRYPOINT :指定默认的启动程序
                      • CMD :指定默认的运行参数

                        创建 Dockerfile 文件如下:

                        FROM ubuntu:trusty
                        ENTRYPOINT ["ping", "-c", "20"]
                        CMD ["localhost"]
                        

                        注: -c 是 ping 命令的选项,c表示count, -c 20 就是ping 20次。

                        构建:

                        docker build -t kaidemo5 .
                        

                        启动容器:

                        docker run kaidemo5
                        

                        在另一个命令行窗口,查看容器:

                        ➜  ~ docker ps                      
                        CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
                        135c6466f888   kaidemo5         "ping -c 20 localhost"   3 seconds ago   Up 2 seconds                              upbeat_vaughan
                        

                        查看容器里的进程:

                        ➜  ~ docker exec  ps -ef
                        UID          PID    PPID  C STIME TTY          TIME CMD
                        root           1       0  0 03:16 ?        00:00:00 ping -c 20 localhost
                        root           7       0  0 03:17 ?        00:00:00 ps -ef
                        

                        可见,实际运行的命令,是把 ENTRYPOINT 的内容和 CMD 的内容组合起来了

                        • ENTRYPOINT : ["ping", "-c", "20"]
                        • CMD : ["localhost"]

                          最终命令是: ping -c 20 localhost

                          如果我们想ping另外一台主机,只需在 docker run 时覆盖 CMD 的值:

                          docker run kaidemo5 127.0.0.1
                          

                          在另一个命令行窗口,查看容器:

                          ➜  ~ docker ps
                          CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
                          b438242b4e84   kaidemo5         "ping -c 20 127.0.0.1"   3 seconds ago   Up 2 seconds                              intelligent_torvalds
                          

                          查看容器里的进程:

                          ➜  ~ docker exec  ps -ef
                          UID          PID    PPID  C STIME TTY          TIME CMD
                          root           1       0  0 03:25 ?        00:00:00 ping -c 20 127.0.0.1
                          root           8       0  0 03:25 ?        00:00:00 ps -ef
                          

                          可见,通过覆盖 CMD 的值,就可以改变运行的参数。

                          在上面的例子里, -c 20 是写死在 ENTRYPOINT 里的,如果想要用户可以配置ping的次数,则应放在 CMD 里,以便用户在 docker run 时覆盖。

                          创建 Dockerfile 文件如下:

                          FROM ubuntu:trusty
                          ENTRYPOINT ["ping"]
                          CMD ["-c", "20", "localhost"]
                          

                          构建:

                          docker build -t kaidemo6 .
                          

                          启动容器:

                          docker run kaidemo6
                          

                          效果和 docker run kaidemo5 是一样的,运行的都是 ping -c 20 localhost 。

                          启动容器时替换 CMD 参数:

                          docker run kaidemo6 -c 30 127.0.0.1
                          

                          在另一个命令行窗口查看容器:

                          ➜  ~ docker ps                      
                          CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
                          7885692cb508   kaidemo6         "ping -c 30 127.0.0.1"   4 seconds ago   Up 3 seconds                              wizardly_shtern
                          

                          查看容器里的进程:

                          ➜  ~ docker exec  ps -ef
                          UID          PID    PPID  C STIME TTY          TIME CMD
                          root           1       0  0 03:38 ?        00:00:00 ping -c 30 127.0.0.1
                          root           7       0  0 03:38 ?        00:00:00 ps -ef
                          

                          可见,实际运行的是 ping -c 30 127.0.0.1 。

                          另外要注意的是,二者一定都要使用“exec形式”。如果使用“shell形式”,则会转为 /bin/sh xxx ,二者组合后,会造成混乱。

                          CMD localhostCMD [“localhost”]
                          ENTRYPOINT ping -c 10/bin/sh -c ‘ping -c 10’ /bin/sh -c localhost/bin/sh -c ‘ping -c 3’ localhost
                          ENTRYPOINT [“ping”,“-c”,“10”]ping -c 10 /bin/sh -c localhostping -c 3 localhost

                          可见,只有 二者都是“exec形式”时,才能组合出期望的结果。

                          总结: ENTRYPOINT 和 CMD 组合使用:把运行命令和不变的参数放到 ENTRYPOINT 里,把可变的参数放到 CMD 里,以便在 docker run 时替换。二者都要使用“exec形式”。可用 --entrypoint 覆盖命令,但一般不这么做,因为 ENTRYPOINT 代表的是容器用途,一般不会改变,可变的是运行参数。

                          参考

                          • https://spacelift.io/blog/docker-entrypoint-vs-cmd (里面有些内容和我实际测试结果不同,可能是Docker版本不同?)
                          • https://zhuanlan.zhihu.com/p/30555962 (里面有些内容和我实际测试结果不同,可能是Docker版本不同?)
                          • https://docs.docker.com/engine/reference/builder