ENTRYPOINT 入口点

ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

  1. <ENTRYPOINT> "<CMD>"

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

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

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

  1. FROM centos:8
  2. CMD [ "curl", "-s", "http://myip.ipip.net" ]

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

  1. $ docker run myip
  2. 当前 IP183.17.224.220 来自于:中国 广东 深圳 电信

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

  1. $ docker run myip -i
  2. docker: Error response from daemon: OCI runtime create failed: container_linux.go:370: starting container process caused: exec: "-i": executable file not found in $PATH: unknown.
  3. ERRO[0000] error waiting for container: context canceled

我们可以看到可执行文件找不到的报错,这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在我们重新用 ENTRYPOINT 来实现这个镜像:

  1. FROM centos:8
  2. ENTRYPOINT ["curl","-s","http://myip.ipip.net"]

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

  1. $ docker run myip -i
  2. HTTP/1.1 200 OK
  3. Date: Thu, 18 Feb 2021 07:35:07 GMT
  4. Content-Type: text/plain; charset=utf-8
  5. Content-Length: 69
  6. Connection: keep-alive
  7. X-Via-JSL: 1d9bd9a,-
  8. Set-Cookie: __jsluid_h=571eb5e8c3529c4ec9f5389a02e2c2cd; max-age=31536000; path=/; HttpOnly
  9. X-Cache: bypas
  10. 当前 IP183.17.224.220 来自于:中国 广东 深圳 电信

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

启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。
比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。

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

这些准备工作是和容器 CMD 无关的,无论 CMD 做什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD>)作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的:

  1. FROM alpine:3.4
  2. ...
  3. RUN addgroup -S redis && adduser -S -G redis redis
  4. ...
  5. ENTRYPOINT ["docker-entrypoint.sh"]
  6. EXPOSE 6379
  7. CMD [ "redis-server" ]

可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINTdocker-entrypoint.sh 脚本。

  1. #!/bin/sh
  2. ...
  3. # allow the container to be started with `--user`
  4. if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
  5. find . \! -user redis -exec chown redis '{}' +
  6. exec gosu redis "$0" "$@"
  7. fi
  8. exec "$@"

该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。比如:

  1. $ docker run -it redis id
  2. uid=0(root) gid=0(root) groups=0(root)