博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

docker entrypoint

Posted on 2018-03-13 18:14  bw_0927  阅读(299)  评论(0)    收藏  举报

https://yeasy.gitbooks.io/docker_practice/image/dockerfile/entrypoint.html

https://aws.amazon.com/cn/blogs/china/demystifying-entrypoint-cmd-docker/

Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。

 

Dockerfile 是应包含一条 ENTRYPOINT 说明、一条 CMD 说明,还是两者?

一般原则

ENTRYPOINT + CMD = 默认容器命令参数

 

需要注意的是,在 Dockerfile 中,ENTRYPOINT 和 CMD 始终将转换为阵列 — 即使您声明它们为字符串。(但为避免歧义,我始终建议将它们声明为阵列。)

假设我们声明一个用于启动 Web 服务器的 CMD 如下:

CMD /usr/bin/httpd -DFOREGROUND

Docker 会自动将 CMD 转换为阵列,如下所示:

["/bin/sh", "-c", "/usr/bin/httpd -DFOREGROUND"]

这同样适用于 ENTRYPOINT 参数。

因此,当我们声明 ENTRYPOINT 和 CMD 时,ENTRYPOINT 将是一个列表,这两个参数将组合成为一个默认的参数列表 — 即使我们声明 CMD 为字符串。

请看下面的示例。如果我们声明如下:

ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD "/bin/service -d"

默认的参数列表将为 ["/bin/chamber", "exec", "production", "--", "/bin/sh", "-c", "/bin/service -d"]

注意:ENTRYPOINT 和 CMD 不能同时为字符串值。 它们可以都是阵列值,或者 ENTRYPOINT 为一个阵列值而 CMD 为一个字符串值;但如果 ENTRYPOINT 为一个字符串值,则 CMD 将被忽略

这是将参数字符串转换为阵列后无法避免的不幸后果。这也是我始终建议尽可能指定阵列的原因之一。

CMD 仅为默认值

定 Dockerfile 中的 CMD 仅会创建一个默认值:如果我们将无选项的参数提交到 docker run,则它们将被 CMD 的值覆盖。

为方便演示,假设我们拥有如下的 Dockerfile,并从它创建了一个叫做 myservice 的映像

ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD ["/bin/service", "-d"]

如果我们调用 docker run myservice,则将创建包含下列参数的容器:

["/bin/chamber", "exec", "production", "--", "/bin/service", "-d"]

如果我们改为调用 docker run myservice /bin/debug,则将创建包含下列参数的容器:

["/bin/chamber", "exec", "production", "--", "/bin/debug"]

请注意 CMD 将被完全替换 — 不能在其开头或结尾处添加任何字符。

ENTRYPOINT 也可覆盖

我可以轻松覆盖在 Dockerfile 中声明的 ENTRYPOINT。为此我们将指定 docker run 的 --entrypoint 选项参数

如前面一样,假设我们拥有如下的 Dockerfile,并从它创建了一个叫做 myservice 的映像:

ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD ["/bin/service", "-d"]

然后让我们通过运行如下命令来修改 ENTRYPOINT

docker run --entrypoint /bin/logwrap myservice   #entrypoint image cmd

根据我们的一般原则,将会构建如下参数列表:

["/bin/logwrap", "/bin/service", "-d"]

同时覆盖 ENTRYPOINT 和 CMD

我们能否同时覆盖 ENTRYPOINT 和 CMD? 当然可以:

docker run --entrypoint /bin/logwrap myservice /bin/service -e

对应的参数列表如下 — 到这时应该不会发生任何意外了:

["/bin/logwrap", "/bin/service", "-e"]

我们何时应该使用 ENTRYPOINT? CMD 呢?

假设我们为某个项目构建自己的 Dockerfile。我们已经了解了 ENTRYPOINT 和 CMD 如何结合起来构建容器的默认参数列表的原理。但现在我们需要知道如何选择:何时建议使用 ENTRYPOINT,何时又建议使用 CMD

您所做的选择基本上属于一种艺术,它严重依赖您的使用案例。但我的经验是,ENTRYPOINT 几乎适合我遇到的所有案例。可以考虑下列使用案例:

封套

一些映像包含所谓的“封套”,将原有的程序进行装饰或者进行其他准备,以便于在容器化环境中应用。例如,假设您的服务设计为从文件读取配置,而非从环境变量读取。则在这种情况下,您可以包含一个将利用环境变量生成配置文件的封套脚本,然后在最后调用 exec /path/to/app 以启动应用程序。

声明指向封套的 ENTRYPOINT 就是确保封套始终运行的一个极佳方法,而不论将何参数发送到 docker run

单用途映像

如果您的映像仅用于执行一个操作( 例如运行 Web 服务器),则使用 ENTRYPOINT 来指定服务器二进制代码和任何强制参数的路径。一个典型示例是 nginx 映像,它的唯一用途是运行 nginx Web 服务器。这使它本身拥有一个天然的命令行调用:docker run nginx。然后您可以顺理成章地在命令行后添加程序命令,例如 docker run nginx -c /test.conf,就好比您在运行无 Docker 的 nginx。

多模式映像

支持多种“模式”的映像在 docker run <image> 上使用第一个参数来指定映射到模式的谓语(例如 shellmigrate 或 debug),也十分常见。对于此类使用案例,我建议 ENTRYPOINT 的设置应指向一个将解释动作参数并根据其值执行相关操作的脚本,例如:

ENTRYPOINT ["/bin/parse_container_args"]

这些参数将在调用时通过 ARGV[1..n] 或 $1$2 等发送到入口点。

小结

Docker 拥有极其强大与灵活的映像构建功能,但在确定如何构建容器的默认运行时参数方面可能极具挑战。但愿本文可以帮助您更加清楚参数-汇编机制,以及如何在您的环境中发挥它们的最佳作用。

 

 


 

#docker run [OPTIONS] IMAGE [COMMAND] [ARG...]   #是CMD,而不是entrypoint。除非显示指定--entrypoint

 

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> "<CMD>" 有什么好处么?让我们来看几个场景。

 

场景一:让镜像变成像命令一样使用

假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD 来实现:

FROM ubuntu:16.04
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]

假如我们使用 docker build -t myip . 来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:

$ docker run myip
当前 IP:61.148.226.66 来自:北京市 联通

嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 CMD 中可以看到实质的命令是 curl,那么如果我们希望显示 HTTP 头信息,就需要加上 -i 参数。那么我们可以直接加 -i 参数给 docker run myip 么?

$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".

我们可以看到可执行文件找不到的报错,executable file not found。之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值

因此这里的 -i 替换了原来的 CMD,而不是添加在原来的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。

那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令:

$ docker run myip curl -s http://ip.cn -i

这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在我们重新用 ENTRYPOINT 来实现这个镜像:

FROM ubuntu:16.04
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

这次我们再来尝试直接使用 docker run myip -i

$ docker run myip
当前 IP:61.148.226.66 来自:北京市 联通

$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive

当前 IP:61.148.226.66 来自:北京市 联通

可以看到,这次成功了。这是因为当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果。

 

例子:
#Dockerfile_entrypoint
FROM centos:centos6
RUN yum install curl
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

#生成镜像
docker build -t dockerfile:entrypoint -f Dockerfile_entrypoint .

#测试
[root@10.20.170.131 test]# docker run dockerfile:entrypoint
当前 IP:183.61.167.131 来自:广东省东莞市 电信
[root@10.20.170.131 test]# docker run dockerfile:entrypoint -i
HTTP/1.1 200 OK
Date: Tue, 13 Mar 2018 10:09:00 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: __cfduid=dae038b394c996d3c4d0c3a3a228511241520935740; expires=Wed, 13-Mar-19 10:09:00 GMT; path=/; domain=.ip.cn; HttpOnly
Server: cloudflare
CF-RAY: 3fadb3d8404b935a-SJC

当前 IP:183.61.167.131 来自:广东省东莞市 电信

  

场景二:应用运行前的准备工作

启动容器就是启动主进程但有些时候,启动主进程前,需要一些准备工作。

比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。

此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等。

这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作

这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,CMD的参数作为这个脚本的参数,在脚本最后执行。

#Dockerfile_entrypoint2
FROM centos:centos6
COPY ./test.sh /data/
ENTRYPOINT [ "/data/test.sh" ]
CMD [ "default_param1" ]

#test.sh
#!/bin/sh
echo $1
echo $@

#生成镜像
docker build -t dockerfile:entrypoint2 -f Dockerfile_entrypoint2 .

#测试
[root@ test]# docker run dockerfile:entrypoint2 
param1
param1

  

 上面的test.sh只能进行简单的处理,并不能处理命令行的docker run附带的命令,例如

docker run dockerfile:entrypoint2 /bin/bash
docker run -it dockerfile:entrypoint2 /bin/bash   #我的本意是打算用test.sh执行一些预处理, 然后进行执行/bin/bash进入容器的的bash,但上面的test.sh不行。

需要改成下面这样:
#test.sh
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'default_param1']; then
   #do some work
elif [ "$1" = 'other_param1']; then
 #do some other work
else
   exec "$@" #关键点
fi

  

这样的话,命令docker run -it dockerfile:entrypoint2 /bin/bash在执行完test.sh的工作后,会进入容器的bash
docker run -it dockerfile:entrypoint2  #不带参数,使用默认参数default_param, 执行#do some work
 
docker run -it dockerfile:entrypoint2 default_param  #带参数,参数是默认参数,执行#do some work

docker run -it dockerfile:entrypoint2 other_param  #带参数,参数是other_param,执行#do other some work

docker run -it dockerfile:entrypoint2 /bin/bash#带参数,参数是/bin/bash,进入容器的bash



是做额外处理,还是分类处理,自己在test.sh中控制 
#test.sh
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'some_cmd']; then
   #do some work
fi
exec "$@" #关键点,始终执行
如果命令行docker run 传递的命令是some_cmd, 做一些预处理,然后执行exec $@

 

test.sh里面进行的软件安装不会影响image的大小,只有在dockerfile里进行软件安装才会影响image的大小