ENTRYPOINT 的格式和 RUN 指令格式同样,分为 exec 格式和 shell 格式。html
ENTRYPOINT 的目的和 CMD 同样,都是在指定容器启动程序及参数。 mysql
ENTRYPOINT 在运行时也能够替代,不过比 CMD 要略显繁琐,须要经过docker run 的参数 --entrypoint 来指定。linux
当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变,再也不是直接的运行其命令,而是将 CMD 的内容做为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:nginx
<ENTRYPOINT> "<CMD>"
那么有了 CMD 后,为何还要有 ENTRYPOINT 呢?这种redis
<ENTRYPOINT> "<CMD>"
有什么好处么?让咱们来看几个场景。sql
场景一:让镜像变成像命令同样使用docker
假设咱们须要一个得知本身当前公网 IP 的镜像,那么能够先用 CMD 来实现:shell
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 来自:北京市 联通
嗯,这么看起来好像能够直接把镜像当作命令使用了,不过命令总有参数,若是我们但愿加参数呢?ubuntu
好比从上面的 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 pr ocess caused \"exec: \\\"-i\\\": executable file not found in $P ATH\"\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 ,从而达到了咱们预期的效果。
场景二:应用运行前的准备工做
启动容器就是启动主进程,但有些时候,启动主进程前,须要一些准备工做。
好比 mysql 类的数据库,可能须要一些数据库配置、初始化的工做,这些工做要在最终的 mysql 服务器运行以前解决。
此外,可能但愿避免使用 root 用户去启动服务,从而提升安全性,而在启动服务前还须要以 root 身份执行一些必要的准备工做,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可使用 root 身份执行,方便调试等。
这些准备工做是和容器 CMD 无关的,不管 CMD 为何,都须要事先进行一个预处理的工做。这种状况下,能够写一个脚本,而后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD> )做为命令,在脚本最后执行。
好比官方镜像 redis 中就是这么作的:
FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ]
能够看到其中为了 redis 服务建立了 redis 用户,并在最后指定了 ENTRYPOINT 为 docker-entrypoint.sh 脚本。
#!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then chown -R redis . exec su-exec redis "$0" "$@" fi exec "$@"
该脚本的内容就是根据 CMD 的内容来判断,若是是 redis-server 的话,则切
换到 redis 用户身份启动服务器,不然依旧使用 root 身份执行。好比:
$ docker run -it redis id uid=0(root) gid=0(root) groups=0(root)