CMD容器启动命令ENTRYPOINT⼊⼝点——Dockerfile
Docker 不是虚拟机,容器中的应⽤都应该以前台执⾏,⽽不是像虚拟机、物理机⾥⾯那样,⽤systemd去启动后台服务,容器内没有后台服务的概念。
对于容器⽽⾔,其启动程序就是容器应⽤进程,容器就是为了主进程⽽存在的,主进程退出,容器就失去了存在的意义,从⽽退出,其它辅助进程不是它需要关⼼的东西。
CMD指令的格式和RUN相似,也是两种格式:
shell格式:CMD <;命令>
exec格式:CMD ["可执⾏⽂件", "参数1", "参数2"...]
参数列表格式:CMD ["参数1", "参数2"...]。在指定了ENTRYPOINT指令后,⽤CMD指定具体的参数。
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运⾏的程序及参数。CMD指令就是⽤于指定默认的容器主进程的启动命
令的。curl是什么命令
在运⾏时可以指定新的命令来替代镜像设置中的这个默认命令,
⽐如,ubuntu镜像默认的CMD是/bin/bash,如果我们直接docker run -it ubuntu的话,会直接进⼊bash。
我们也可以在运⾏时指定运⾏别的命令,如docker run -it ubuntu cat /etc/os-release。这就是⽤cat /etc/os-release命令替换了默认的/bin/bash命令了,输出了系统版本信息。
在指令格式上,⼀般推荐使⽤exec格式,这类格式在解析时会被解析为 JSON 数组,因此⼀定要使⽤双引号",⽽不要使⽤单引号。
如果使⽤shell格式的话,实际的命令会被包装为sh -c的参数的形式进⾏执⾏。⽐如:
CMD echo $HOME
在实际执⾏中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
这就是为什么我们可以使⽤环境变量的原因,因为这些环境变量会被 shell 进⾏解析处理。
提到CMD就不得不提容器中应⽤在前台执⾏和后台执⾏的问题。这是初学者常出现的⼀个混淆。
Docker 不是虚拟机,容器中的应⽤都应该以前台执⾏,⽽不是像虚拟机、物理机⾥⾯那样,⽤systemd去启动后台服务,容器内没有后台服务的概念。
⼀些初学者将CMD写为:
CMD service nginx start
然后发现容器执⾏后就⽴即退出了。甚⾄在容器内去使⽤systemctl命令结果却发现根本执⾏不了。
这就是因为没有搞明⽩前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的⾓度去理解容器。
对于容器⽽⾔,其启动程序就是容器应⽤进程,容器就是为了主进程⽽存在的,主进程退出,容器就失去了存在的意义,从⽽退出,其它辅助进程不是它需要关⼼的东西。
⽽使⽤service nginx start命令,则是希望 upstart 来以后台守护进程形式启动nginx服务。⽽刚才说了CMD service nginx start会被理解为CMD [ "sh", "-c", "service nginx start"],因此主进程实际上是sh。那么当service nginx start命令结束后,sh也就结束了,sh作为主进程退出了,⾃然就会令容器退出。
正确的做法是直接执⾏nginx可执⾏⽂件,并且要求以前台形式运⾏。⽐如:
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT的格式和RUN指令格式⼀样,分为exec格式和shell格式。
ENTRYPOINT的⽬的和CMD⼀样,都是在指定容器启动程序及参数。
ENTRYPOINT在运⾏时也可以替代,不过⽐CMD要略显繁琐,需要通过docker run的参数--entrypoint来指定。
当指定了ENTRYPOINT后,CMD的含义就发⽣了改变,不再是直接的运⾏其命令,⽽是将CMD的内容作为参数传给ENTRYPOINT指令,换句话说实际执⾏时,将变
为:
<ENTRYPOINT> "<CMD>"
那么有了CMD后,为什么还要有ENTRYPOINT呢?这种<ENTRYPOINT> "<CMD>"有什么好处么?让我们来看⼏个场景。
场景⼀:让镜像变成像命令⼀样使⽤
假设我们需要⼀个得知⾃⼰当前公⽹ IP 的镜像,那么可以先⽤CMD来实现:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "ip" ]
假如我们使⽤docker build -t myip .来构建镜像的话,如果我们需要查询当前公⽹ IP,只需要执⾏:
假如我们使⽤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: :247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
那么如果我们希望加⼊-i这参数,我们就必须重新完整的输⼊这个命令:
$ docker run myip curl -s ip -i
显然不是很好的解决⽅案,⽽使⽤ENTRYPOINT就可以解决这个问题。现在我们重新⽤ENTRYPOINT来实现这个镜像:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "ip" ]
再来尝试直接使⽤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)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。