【云原生|Docker】07-Entrypoin与Cmd的区别
文章目录
前言
这一章节我们将再次复习CMD和ENTRYPOINT的用法,将详细介绍CMD和ENTRYPOINT的使用区别,同时也通过实用场景来介绍CMD与ENTRYPOINT的结合使用。
Entrypoint
Entrypoint概述
entrypoint(入口)才是真正用于定义容器启动以后的执行体的,ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint来指定。
当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
Entrypoint使用
格式:
方法1:ENTRYPOINT ["executable","param1","param2"]
方法2:ENTRYPOINT command param1 param2 (shell中执行)
示例:
ENTRYPOINT ["ls", "/usr/local"]
CMD ["/usr/local/tomcat"]
-
当镜像中只有Entrypoint的时候,Entrypoint会被当作容器启动后的指令执行,在docker ps -a中看到的cmmd参数则为镜像中Entrypoint。 示例:
FROM centos:7 LABEL name=xiaohaizhou maile=xiaohaizhou2018@163.com RUN yum install openssh-server -y ADD ./keys/ssh_host_ecdsa_key /etc/ssh/ ADD ./keys/ssh_host_ed25519_key /etc/ssh/ ADD ./keys/ssh_host_rsa_key /etc/ssh/ RUN chown root:root /etc/ssh/ssh_host_* && chmod 0600 /etc/ssh/ssh_host_* \ && useradd -s /bin/bash -m -d /home/xhz xhz && \ echo 'xhz123'|passwd xhz --stdin EXPOSE 22 80 ENTRYPOINT [ "/usr/sbin/sshd","-D" ]
- 当镜像中有Entrypoint和CMD的时候,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给 ENTRYPOINT 指令。示例:
FROM centos:7
LABEL name=xiaohaizhou maile=xiaohaizhou2018@163.com
RUN yum install openssh-server -y
ADD ./keys/ssh_host_ecdsa_key /etc/ssh/
ADD ./keys/ssh_host_ed25519_key /etc/ssh/
ADD ./keys/ssh_host_rsa_key /etc/ssh/
RUN chown root:root /etc/ssh/ssh_host_* && chmod 0600 /etc/ssh/ssh_host_* \
&& useradd -s /bin/bash -m -d /home/xhz xhz && \
echo 'xhz123'|passwd xhz --stdin
EXPOSE 22 80
ENTRYPOINT [ "/usr/sbin/sshd" ]
CMD [ "-C" ]
- 当镜像中有Entrypoint和CMD的时候(或者通过run直接传参),传入的参数会覆盖cmd,同时该参数会是entrypoint的参数,以上述dockerfie为例:
总结
- 镜像中ENTRYPOINT只能有一个,如果存在多个,只有最后一条会被执行。
- 镜像中只有Entrypoint的时候,Entrypoint会被当作容器启动后的指令执行。
- 镜像中有Entrypoint和CMD的时候,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给 ENTRYPOINT 指令。
CMD
CMD概述
Docker不是虚拟机,事实上容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令。重点是**“默认”**,那就意味着,如果docker run没有指定任何可执行的命令,或者在镜像中也没有entrypoint,那么就会使用CMD指定的默认命令执行,比如:
比如ubuntu镜像默认的CMD是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。
CMD的使用
格式:
方法1:CMD ["executable","param1","param2"] #使用exec执行,推荐的方式
方法2:CMD command param1 param2 #在/bin/sh中执行,提供给需要交互的应用
方法3:CMD ["param1","param2"] #提供给ENTRYPOINT的默认参数
示例:
CMD ["/usr/sbin/sshd","-D"]
方法1:
命令在没有任何shell终端环境下,如果我们执行shell,必须把shell加入到括号的参数中,
FROM centos:7
CMD ["/bin/bash","-c","echo $HOME"]
需要注意的是,采用括号的形式,那么第一个参数必须是命令的全路径才行。
方法2
如果使用/bin/sh方式执行的话(cmd默认是在/bin/sh -c下执行),实际的命令会被包装为 sh -c 的参数的形式进行执行。比如
FROM centos:7
CMD echo $HOME
在实际执行中,会变更为:
CMD [ "sh", "-c", "echo $HOME" ]
方法3
当镜像中存在entrypoint的时候,镜像中的CMD会被当作提供给ENTRYPOINT的默认参数,比如:
FROM centos:7
ENTRYPOINT ["/usr/bin/sshd"]
CMD ["-D"]
在实际执行中,会变更为:
"/usr/bin/sshd -D"
总结
- 当镜像总ENTRYPOINT和CMD同时存在的时候,此时镜像中的CMD会被当作提供给ENTRYPOINT的默认参数;
- 当经常总只有cmd的时候,CMD有方法1和方法2两种书写格式,需要注意的是,采用括号的形式(方法1),那么第一个参数必须是命令的全路径才行。
- 镜像中CMD只能有一个,如果存在多个,只有最后一条会被执行。
- 当执行docker run 后面接参数的时候,该参数会替换镜像中默认的CMD
CMD和Entrypoint组合使用
建议:
组合使用ENTRYPOINT和CMD命令式, 确保你一定用的是Exec表示法. 如果用其中一个用的是Shell表示法, 或者一个是Shell表示法, 另一个是Exec表示法, 你永远得不到你预期的效果。
场景一:让镜像变成像命令一样使用
假设我们需要一个ssh的命令,来进行远程客户机,我们通过cmd来实现,
FROM centos:7
LABEL name=xiaohaizhou maile=xiaohaizhou2018@163.com
RUN yum install openssh-clients -y
EXPOSE 22
# 192.168.194.128为本机ip
CMD [ "/usr/bin/ssh", "192.168.194.128" ]
我们使用 docker build -tssh_client . 来构建镜像的话,如果我们需要ssh本地,只需要执行:
[root@clinet ubuntu]# docker run -it ip:v1.2
这么看起来好像可以直接把镜像当做命令使用了,但是我们希望不要写死远程ip,而是可以根据自己传入的ip进行远程连接。那我们就可以这样去写:
FROM centos:7
LABEL name=xiaohaizhou maile=xiaohaizhou2018@163.com
RUN yum install openssh-clients -y
EXPOSE 22
ENTRYPOINT [ "/usr/bin/ssh" ]
CMD [ "127.0.0.1" ]
这样就是实现,然而这个实验并不完善且没有实际意义。通过这个场景我们再次证实了当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,这里的127.0.0.1就是默认参数,192.168.194.128/129就是我们手动传入的参数。
场景2:应用运行前的准备工作
启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。
比如 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脚本:
# 该脚本的内容就是根据CMD的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行
#!/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 "$@"