Docker - 深入理解Dockerfile中的 RUN, CMD 和 ENTRYPOINT

RUN

docker file 中的 RUN 命令相对来教容易理解

  1. RUN 指令用于在构建镜像时执行命令,这些命令会在 Docker 镜像的构建过程中执行。常用于安装软件包、设置环境变量、创建目录等。
  2. RUN 指令会在镜像构建中创建新的镜像层,每个 RUN 指令都会创建一个新的镜像层。
  3. RUN 指令执行的命令会在构建阶段生效,而不会在容器运行时执行。

关键是
RUN 可以有多条
RUN 只会在构建阶段执行, 在容器启动时不会执行

提供1个例子:
我们准备1个docker file
test_cmd1

FROM busybox

RUN echo "hello run"
RUN echo "hello run2"

CMD ["echo", "hi"]

当build 时, RUN两条命令都被执行了, 但是CMD 的没有执行

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$docker build -f test_cmd1 -t test_cmd1:latest .
 create mode 100644 dockerfile/test_cmd1
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            Install the buildx component to build images with BuildKit:
            https://docs.docker.com/go/buildx/

Sending build context to Docker daemon  8.192kB
Step 1/4 : FROM busybox
 ---> 65ad0d468eb1
Step 2/4 : RUN echo "hello run"
 ---> Running in 276d14886a99
hello run
Removing intermediate container 276d14886a99
 ---> 1323139f939a
Step 3/4 : RUN echo "hello run2"
 ---> Running in 483674db3603
hello run2
Removing intermediate container 483674db3603
 ---> dbdc9963c5dc
Step 4/4 : CMD ["echo", "hi"]
 ---> Running in 3db1c9c199d9
Removing intermediate container 3db1c9c199d9
 ---> 9ff4d1d0ea33
Successfully built 9ff4d1d0ea33
Successfully tagged test_cmd1:latest

但是启动容器时, 只有CMD 那句被执行

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd1:latest
hi
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ 

RUN 命令顺便带过了, 不是本文的重点





CMD

  1. CMD 指令用于指定容器启动时要运行的默认命令。它可以被 Dockerfile 中的 ENTRYPOINT 指令覆盖。
  2. 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 指令会生效。
  3. CMD 指令的内容会在容器启动时执行,可以通过 docker run 命令传递参数来覆盖默认命令。

我们先讲下CMD的写法

原则上, CMD 后面的内容应该是1个数组
CMD [“executeable”, “arg1”, “arg2”…]

其中第1个参数必须是1个在容器内可以执行的命令(定义在$PATH 中的 可执行文件)
但是CMD 有多个写法

我们用各种例子来体现



例子1

# ok
# command "echo hhih" 
CMD ["echo", "hhih"]

这种写法是可以的
正常输出:

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd3:latest
hhih

可以用docker ps -a --no-trunc 命令来查看容器被启动时真正执行的命令

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker ps -a --no-trunc | head -n 2
CONTAINER ID                                                       IMAGE                                                                     COMMAND                                                CREATED          STATUS                      PORTS     NAMES
7f4be762c74eaaa281010eab2b40a1cd39a638ff688e63d371d0166cfdd23a21   test_cmd3:latest                                                          "echo hhih"                                            18 minutes ago   Exited (0) 18 minutes ago             upbeat_kirch

可见执行的命令就是 “echo hhih"

CMD [“echo”, “hhih”] 这种写法就是符合了第1个参数是可以执行命令, 第2个参数是args 的原则.



例子2

# error unable to start container process: exec: "echo hhih", and pts hundled in host server
# command "echo hhih"
CMD ["echo hhih"] 

这种写法会启动时会出错
因为CMD 第1个参数是 “echo hhih" 并不是1个可执行文件命令



例子3

# ok
# command  "/bin/sh -c 'echo hhih'" 
CMD echo hhih

这种写法是可以的, 实际上 后面的字符串按照空格被自动分割成多个item, 第1个item是echo , 第2个是hhih
但是这种写法与例子1还是有点区别, 就是当容器被启动时, 执行的命令是
/bin/sh -c ‘echo hhih’



例子4

# first CMD will be ignored
CMD echo hhih
CMD echo hhihb

这种写法break了CMD 只能有单个的原则, 但是构建镜像时不会出错, 只是容器启动时只会执行最后一条CMD 命令, 而忽略之前的



例子5

# error /bin/sh: echo hhih: not found
# command "/bin/sh -c '\"echo hhih\"'" 
CMD "echo hhih" 

这种写法也是错误示范, 这里的echo hhih 被双引号扩着, 则会被CMD 认为是1个 元素item, 违反了第一个item 必须是可执行文件命令的原则



例子6

# ok
# command "/bin/sh -c '\"echo\" \"hhih\"'" 
CMD "echo" "hhih" 

这种写法是ok的类似 例子3



小结

CMD 的写法大概是两种
一种是 用中扩号括住, 显式数组写法
CMD [“executeable”, “args1”, “args2”…]
这种写法容器启动时 正常执行 executeable args1 args2 …

另1种写法是 不用中扩号
直接 CMD executeable args1 args2 …
这种写法容器启动时, 会被按照 sh -c " executeable args1 args2 …"

注意, 其实这两种写法还有变种, 例如显式利用sh -c
CMD [“sh”, “-c”, “echo hhic”]
CMD [“sh”, “-c”, “echo hhic, $0”, "hhic2]
CMD sh
-c
-|
echo hhic
等等, 但是都是基于上面提到的两大类扩展的, 这种写法也适合于 Dockerfile, cloudbuild yaml, k8s yaml中命令参数的编写



CMD 定义的命令可以在容器启动时被覆盖

这个特性很重要

例子:
定义1个 docker file

FROM busybox
CMD echo hhih

构建镜像

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ git pull && docker build -f test_cmd3 -t test_cmd3:latest .
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 8 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (8/8), 659 bytes | 131.00 KiB/s, done.
From e.coding.net:nvd11/bq/sql_backup
   483f2de..438d518  master     -> origin/master
Updating 483f2de..438d518
Fast-forward
 dockerfile/test_cmd3 | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            Install the buildx component to build images with BuildKit:
            https://docs.docker.com/go/buildx/

Sending build context to Docker daemon  8.704kB
Step 1/2 : FROM busybox
 ---> 65ad0d468eb1
Step 2/2 : CMD echo hhih
 ---> Using cache
 ---> 4c73e1a69ab2
Successfully built 4c73e1a69ab2
Successfully tagged test_cmd3:latest

正常执行时会输出
hhih

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd3:latest
hhih
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$

但是我们可以在docker run 命令后面加上命令覆盖掉CMD 定义的命令

gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd3:latest echo hello
hello
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ 

这个特性相当重要, 可以让我们的容器部署更加灵活





ENTRYPOINT

  1. ENTRYPOINT 指令用于配置容器启动时要运行的命令,它定义了容器的主要执行命令。
  2. 如果 Dockerfile 中有多个 ENTRYPOINT 指令,只有最后一个 ENTRYPOINT 指令会生效。
  3. ENTRYPOINT 指令的内容不会被覆盖,但可以通过 docker run 命令的 --entrypoint 选项来覆盖。

ENTRYPOINT 的用法是CMD 基本一样, 大部分场景下能互相替换

例如:
test_cmd4

FROM busybox

# error /bin/sh: echo hello: not found
# command /bin/sh -c '\"echo hello\"'
# ENTRYPOINT "echo hello"

# error /bin/sh: echo 'hello': not found
# command "/bin/sh -c '\"echo 'hello'\"'"
# ENTRYPOINT "echo 'hello'"

# error  runc create failed: unable to start container process: exec: "echo hello"
# command "echo hello"
# ENTRYPOINT ["echo hello"]

# ok
# command "echo hello"
# ENTRYPOINT ["echo", "hello"]

# ok
# command "/bin/sh -c 'echo hello'"
# could not be append or override
# ENTRYPOINT echo hello

# ok
# command "/bin/sh -c 'echo hello;'"
# could not be append or override
ENTRYPOINT echo hello; 

那么既然CMD 能用, 为何需要ENTRYPOINT
是因为 ENTRYPOINT 定义的命令在容器启动时一定会执行, 无法被覆盖

在某些场景下, 为了安全等原因, 不想容器启动命令被覆盖, 则应该考虑用ENTRYPOINT 来构建镜像

但是, ENTRYPOINT 仍然可以配合CMD 命令来令到命令的某些部分 or 参数可以被覆盖

例如下面3中写法都是ok的

# ok
# command "/bin/sh -c 'echo hello; $0'"
# $0 could be override
ENTRYPOINT echo hello; $0


# ok
# command "sh -c 'echo hello; $0'"
# $0 could be override
# equal to "ENTRYPOINT echo hello; $0"
ENTRYPOINT [ "sh", "-c", "echo hello; $0"]


# ok 
# command "/bin/sh -c 'sh -c \"echo 'hello'; $0\"'"
# $0 could be override
ENTRYPOINT sh -c "echo 'hello'; $0"
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd4:latest
hello
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd4:latest "echo hi"
hello
hi

注意这里两点

  1. docker run 提供的参数必须用“括住”, 除非你在ENTRYPOINT 定义了两个参数 $0 和 $1
  2. 即使部分被覆盖, 但是 ENTRYPOINT 定义的部分仍然会执行
  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值