### ENTRYPOINT 入口点
`ENTRYPOINT` 的格式和 `RUN` 指令格式一样,分为 `exec` 格式和 `shell` 格式。
`ENTRYPOINT` 的目的和 `CMD` 一样,都是在指定容器启动程序及参数。`ENTRYPOINT` 在运行时也可以替代,不过比 `CMD` 要略显繁琐,需要通过 `docker run` 的参数 `--entrypoint` 来指定。
当指定了 `ENTRYPOINT` 后,`CMD` 的含义就发生了改变,不再是直接的运行其命令,而是将 `CMD` 的内容作为参数传给 `ENTRYPOINT` 指令,换句话说实际执行时,将变为:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun"><</span><span class="pln">ENTRYPOINT</span><span class="pun">></span><span class="pln"> </span><span class="str">"<CMD>"</span></code></li></ol>
```
那么有了 `CMD` 后,为什么还要有 `ENTRYPOINT` 呢?这种 `<ENTRYPOINT> "<CMD>"` 有什么好处么?让我们来看几个场景。
#### 场景一:让镜像变成像命令一样使用
假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 `CMD` 来实现:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">FROM ubuntu</span><span class="pun">:</span><span class="lit">16.04</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update \</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln"> </span><span class="pun">&&</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y curl \</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln"> </span><span class="pun">&&</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="com">/*</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="com">CMD [ "curl", "-s", "http://ip.cn" ]</span></code></li>
</ol>
```
假如我们使用 `docker build -t myip .` 来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">$ docker run myip</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun">当前</span><span class="pln"> IP</span><span class="pun">:</span><span class="lit">61.148</span><span class="pun">.</span><span class="lit">226.66</span><span class="pln"> </span><span class="pun">来自:北京市</span><span class="pln"> </span><span class="pun">联通</span></code></li>
</ol>
```
嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 `CMD` 中可以看到实质的命令是 `curl`,那么如果我们希望显示 HTTP 头信息,就需要加上 `-i` 参数。那么我们可以直接加 `-i` 参数给 `docker run myip` 么?
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">$ docker run myip </span><span class="pun">-</span><span class="pln">i</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">docker</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Error</span><span class="pln"> response from daemon</span><span class="pun">:</span><span class="pln"> invalid header field value </span><span class="str">"oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n"</span><span class="pun">.</span></code></li>
</ol>
```
我们可以看到可执行文件找不到的报错,`executable file not found`。之前我们说过,跟在镜像名后面的是 `command`,运行时会替换 `CMD` 的默认值。因此这里的 `-i` 替换了原来的 `CMD`,而不是添加在原来的 `curl -s http://ip.cn` 后面。而 `-i` 根本不是命令,所以自然找不到。
那么如果我们希望加入 `-i` 这参数,我们就必须重新完整的输入这个命令:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">$ docker run myip curl </span><span class="pun">-</span><span class="pln">s http</span><span class="pun">://</span><span class="pln">ip</span><span class="pun">.</span><span class="pln">cn </span><span class="pun">-</span><span class="pln">i</span></code></li></ol>
```
这显然不是很好的解决方案,而使用 `ENTRYPOINT` 就可以解决这个问题。现在我们重新用 `ENTRYPOINT` 来实现这个镜像:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">FROM ubuntu</span><span class="pun">:</span><span class="lit">16.04</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">RUN apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update \</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln"> </span><span class="pun">&&</span><span class="pln"> apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install </span><span class="pun">-</span><span class="pln">y curl \</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln"> </span><span class="pun">&&</span><span class="pln"> rm </span><span class="pun">-</span><span class="pln">rf </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">lists</span><span class="com">/*</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="com">ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]</span></code></li>
</ol>
```
这次我们再来尝试直接使用 `docker run myip -i`:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">$ docker run myip</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun">当前</span><span class="pln"> IP</span><span class="pun">:</span><span class="lit">61.148</span><span class="pun">.</span><span class="lit">226.66</span><span class="pln"> </span><span class="pun">来自:北京市</span><span class="pln"> </span><span class="pun">联通</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">$ docker run myip </span><span class="pun">-</span><span class="pln">i</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> OK</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Server</span><span class="pun">:</span><span class="pln"> nginx</span><span class="pun">/</span><span class="lit">1.8</span><span class="pun">.</span><span class="lit">0</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Tue</span><span class="pun">,</span><span class="pln"> </span><span class="lit">22</span><span class="pln"> </span><span class="typ">Nov</span><span class="pln"> </span><span class="lit">2016</span><span class="pln"> </span><span class="lit">05</span><span class="pun">:</span><span class="lit">12</span><span class="pun">:</span><span class="lit">40</span><span class="pln"> GMT</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> text</span><span class="pun">/</span><span class="pln">html</span><span class="pun">;</span><span class="pln"> charset</span><span class="pun">=</span><span class="pln">UTF</span><span class="pun">-</span><span class="lit">8</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Vary</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Accept</span><span class="pun">-</span><span class="typ">Encoding</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">X</span><span class="pun">-</span><span class="typ">Powered</span><span class="pun">-</span><span class="typ">By</span><span class="pun">:</span><span class="pln"> PHP</span><span class="pun">/</span><span class="lit">5.6</span><span class="pun">.</span><span class="lit">24</span><span class="pun">-</span><span class="lit">1</span><span class="pun">~</span><span class="pln">dotdeb</span><span class="pun">+</span><span class="lit">7.1</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">X</span><span class="pun">-</span><span class="typ">Cache</span><span class="pun">:</span><span class="pln"> MISS from cache</span><span class="pun">-</span><span class="lit">2</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">X</span><span class="pun">-</span><span class="typ">Cache</span><span class="pun">-</span><span class="typ">Lookup</span><span class="pun">:</span><span class="pln"> MISS from cache</span><span class="pun">-</span><span class="lit">2</span><span class="pun">:</span><span class="lit">80</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">X</span><span class="pun">-</span><span class="typ">Cache</span><span class="pun">:</span><span class="pln"> MISS from proxy</span><span class="pun">-</span><span class="lit">2_6</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Transfer</span><span class="pun">-</span><span class="typ">Encoding</span><span class="pun">:</span><span class="pln"> chunked</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Via</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1.1</span><span class="pln"> cache</span><span class="pun">-</span><span class="lit">2</span><span class="pun">:</span><span class="lit">80</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1.1</span><span class="pln"> proxy</span><span class="pun">-</span><span class="lit">2_6</span><span class="pun">:</span><span class="lit">8006</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="typ">Connection</span><span class="pun">:</span><span class="pln"> keep</span><span class="pun">-</span><span class="pln">alive</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun">当前</span><span class="pln"> IP</span><span class="pun">:</span><span class="lit">61.148</span><span class="pun">.</span><span class="lit">226.66</span><span class="pln"> </span><span class="pun">来自:北京市</span><span class="pln"> </span><span class="pun">联通</span></code></li>
</ol>
```
可以看到,这次成功了。这是因为当存在 `ENTRYPOINT` 后,`CMD` 的内容将会作为参数传给 `ENTRYPOINT`,而这里 `-i` 就是新的 `CMD`,因此会作为参数传给 `curl`,从而达到了我们预期的效果。
#### 场景二:应用运行前的准备工作
启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。
比如 `mysql` 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。
此外,可能希望避免使用 `root` 用户去启动服务,从而提高安全性,而在启动服务前还需要以 `root` 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 `root` 身份执行,方便调试等。
这些准备工作是和容器 `CMD` 无关的,无论 `CMD` 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 `ENTRYPOINT` 中去执行,而这个脚本会将接到的参数(也就是 `<CMD>`)作为命令,在脚本最后执行。比如官方镜像 `redis` 中就是这么做的:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">FROM alpine</span><span class="pun">:</span><span class="lit">3.4</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun">...</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">RUN addgroup </span><span class="pun">-</span><span class="pln">S redis </span><span class="pun">&&</span><span class="pln"> adduser </span><span class="pun">-</span><span class="pln">S </span><span class="pun">-</span><span class="pln">G redis redis</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun">...</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">ENTRYPOINT </span><span class="pun">[</span><span class="str">"docker-entrypoint.sh"</span><span class="pun">]</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">EXPOSE </span><span class="lit">6379</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">CMD </span><span class="pun">[</span><span class="pln"> </span><span class="str">"redis-server"</span><span class="pln"> </span><span class="pun">]</span></code></li>
</ol>
```
可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 `ENTRYPOINT` 为 `docker-entrypoint.sh` 脚本。
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="com">#!/bin/sh</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pun">...</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="com"># allow the container to be started with `--user`</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="kwd">if</span><span class="pln"> </span><span class="pun">[</span><span class="pln"> </span><span class="str">"$1"</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'redis-server'</span><span class="pln"> </span><span class="pun">-</span><span class="pln">a </span><span class="str">"$(id -u)"</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'0'</span><span class="pln"> </span><span class="pun">];</span><span class="pln"> </span><span class="kwd">then</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln"> chown </span><span class="pun">-</span><span class="pln">R redis </span><span class="pun">.</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln"> exec su</span><span class="pun">-</span><span class="pln">exec redis </span><span class="str">"$0"</span><span class="pln"> </span><span class="str">"$@"</span></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="kwd">fi</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"></code></li>
<li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">exec </span><span class="str">"$@"</span></code></li>
</ol>
```
该脚本的内容就是根据 `CMD` 的内容来判断,如果是 `redis-server` 的话,则切换到 `redis` 用户身份启动服务器,否则依旧使用 `root` 身份执行。比如:
```
<pre class="prettyprint"><ol class="linenums"><li class="l"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">$ docker run </span><span class="pun">-</span><span class="pln">it redis id</span></code></li>
<li class="l1"><code class="pcalibre10 pcalibre11 pcalibre9"><span class="pln">uid</span><span class="pun">=</span><span class="lit">0</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span><span class="pln"> gid</span><span class="pun">=</span><span class="lit">0</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span><span class="pln"> groups</span><span class="pun">=</span><span class="lit">0</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span></code></li>
</ol>
```
- 致谢
- 目录
- 01. 前言
- 02. 修订记录
- 03. 如何贡献
- 04. Docker 简介
- 什么是 Docker
- 为什么要用 Docker
- 05. 基本概念
- 镜像
- 容器
- 仓库
- 06. 安装 Docker
- Ubuntu
- Debian
- CentOS
- Raspberry Pi
- macOS
- Windows PC
- 镜像加速器
- 07.使用镜像
- 获取镜像
- 列出镜像
- 删除本地镜像
- 利用 commit 理解镜像构成
- 使用 Dockerfile 定制镜像
- Dockerfile 指令详解
- COPY 复制文件
- ADD 更高级的复制文件
- CMD 容器启动命令
- ENTRYPOINT 入口点
- ENV 设置环境变量
- ARG 构建参数
- VOLUME 定义匿名卷
- EXPOSE 暴露端口
- WORKDIR 指定工作目录
- USER 指定当前用户
- HEALTHCHECK 健康检查
- ONBUILD 为他人作嫁衣裳
- 参考文档
- Dockerfile 多阶段构建
- 其它制作镜像的方式
- 实现原理
- 08. 操作容器
- 启动
- 守护态运行
- 终止
- 进入容器
- 导出和导入
- 删除
- 09. 访问仓库
- Docker Hub
- 私有仓库
- 私有仓库高级配置
- 10. 数据管理
- 数据卷
- 监听主机目录
- 11. 使用网络
- 外部访问容器
- 容器互联
- 配置 DNS
- 12. 高级网络配置
- 快速配置指南
- 容器访问控制
- 配置 docker0 网桥
- 自定义网桥
- 工具和示例
- 编辑网络配置文件
- 实例:创建一个点到点连接
- 映射容器端口到宿主主机的实现
- 13. Docker 三剑客之 Compose 项目
- 简介
- 安装与卸载
- 使用
- 命令说明
- Compose 模板文件
- 实战 Django
- 实战 Rails
- 实战 WordPress
- 14. Docker 三剑客之 Machine 项目
- 安装
- 使用
- 15. Docker 三剑客之 Docker Swarm
- 16. Swarm mode
- 基本概念
- 创建 Swarm 集群
- 部署服务
- 使用 compose 文件
- 管理敏感数据
- 管理配置信息
- 17. 安全
- 内核命名空间
- 控制组
- 服务端防护
- 内核能力机制
- 其它安全特性
- 总结
- 18. 底层实现
- 基本架构
- 命名空间
- 控制组
- 联合文件系统
- 容器格式
- 网络
- 19. Etcd 项目
- 简介
- 安装
- 集群
- 使用 etcdctl
- 使用 etcdctl v2
- 20. CoreOS 项目
- 简介
- 工具
- 快速搭建 CoreOS 集群
- 21. Kubernetes 项目
- 简介
- 快速上手
- 基本概念
- kubectl 使用
- 架构设计
- 22. Mesos - 优秀的集群资源调度平台
- Mesos 简介
- 安装与使用
- 原理与架构
- Mesos 配置项解析
- 日志与监控
- 常见应用框架
- 本章小结
- 23. 容器与云计算
- 简介
- 亚马逊云
- 腾讯云
- 阿里云
- 小结
- 24. 实战案例-操作系统
- Busybox
- Alpine
- Debian Ubuntu
- CentOS Fedora
- 本章小结
- 25. 实战案例-CI-CD
- Drone
- 26. Docker 开源项目
- LinuxKit
- 27. 附录
- 附录一:常见问题总结
- 附录二:热门镜像介绍
- Ubuntu
- CentOS
- MySQL
- MongoDB
- Redis
- Nginx
- WordPress
- Node.js
- 附录三:Docker 命令查询
- 附录四:Dockerfile 最佳实践
- 附录五:资源链接
- 附录六:Docker 中文资源