Dockerfile 指令 功能 三 (ENTRYPOINT)
ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式 和 shell 格式
ENTRYPOINT 的目的和 CMD 一样,都是在指定的容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
场景一:让镜像变成像命令一样使用
传参的用途
使用一个公网IP的镜像来得知自己当前的公网IP,那么可以先用 CMD来实现:
FROM ubuntu:18.04
RUN apt-get update
&& apt-get install -y curl
&& rm -rf /var/lib/apt/lists/*
CMD ["curl","-s","http://myip.ipip.net"]
使用docker build 构建镜像
[root@ip-10-1-0-142 ipip]# docker build -t myip:v1 .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:18.04
18.04: Pulling from library/ubuntu
feac53061382: Pull complete
Digest: sha256:7bd7a9ca99f868bf69c4b6212f64f2af8e243f97ba13abb3e641e03a7ceb59e8
Status: Downloaded newer image for ubuntu:18.04
---> 39a8cfeef173
Step 2/3 : RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
---> Running in 35433ebb43d4
Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
Get:2 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
Get:3 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
.
.
.
Running hooks in /etc/ca-certificates/update.d...
done.
Removing intermediate container 35433ebb43d4
---> 4d4e2a6ea83b
Step 3/3 : CMD [ "curl", "-s", "http://myip.ipip.net" ]
---> Running in 30a3a5f2f303
Removing intermediate container 30a3a5f2f303
---> 2c191ad0c4c5
Successfully built 2c191ad0c4c5
Successfully tagged myip:v1
运行myip
[root@ip-10-1-0-142 ipip]# docker run myip
当前 IP:52.215.213.34 来自于:爱尔兰 都柏林郡 都柏林 amazon.com
嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 CMD 中可以看到实质的命令是 curl,那么如果我们希望显示 HTTP 头信息,就需要加上 -i 参数。那么我们可以直接加 -i 参数给 docker run myip 么?
[root@ip-10-1-0-142 ipip]# docker run myip -i
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "-i": executable file not found in $PATH: unknown.
ERRO[0000] error waiting for container: context canceled
我们可以看到可执行文件找不到的报错,executable file not found。之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。因此这里的 -i 替换了原来的 CMD,而不是添加在原来的 curl -s http://myip.ipip.net 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令:
[root@ip-10-1-0-142 ipip]# docker run myip curl -s http://myip.ipip.net -i
HTTP/1.1 200 OK
Date: Mon, 02 Aug 2021 11:00:28 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 84
Connection: keep-alive
CF-Cache-Status: DYNAMIC
Report-To: {"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v3?s=RUCd8%2BCdlxsaLUf44f7INb92ZCnaIYiD4X2GYbLNyXJTiKg%2BOsAfEp3bPXOnfJrfRFnNDfyAP9p058HJ06wjUpA44ezop7eQEpLUBq37DiBP4GXI2YMaAt%2FkO%2Fz7Xx0%3D"}],"group":"cf-nel","max_age":604800}
NEL: {"report_to":"cf-nel","max_age":604800}
Server: cloudflare
CF-RAY: 6786cd23feed1e89-AMS当前 IP:52.215.213.34 来自于:爱尔兰 都柏林郡 都柏林 amazon.com
我们尝试使用 ENTRYPOINT 解决这个问题,使用 ENTRYPOINT 更新 Dockerfile 文件
FROM ubuntu:18.04
RUN apt-get update
&& apt-get install -y curl
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
直接使用 docker run myipe 查看输出结果,
[root@ip-10-1-0-142 ipip]# docker run myipe
当前 IP:52.215.213.34 来自于:爱尔兰 都柏林郡 都柏林 amazon.com
这次我们再来尝试直接使用 docker run myip -i:
[root@ip-10-1-0-142 ipip]# docker run myipe -i
HTTP/1.1 200 OK
Date: Mon, 02 Aug 2021 11:04:59 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 84
Connection: keep-alive
CF-Cache-Status: DYNAMIC
Report-To: {"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v3?s=K2An7ZP3qbdYTYdDYzojNsNZSi2jbEPs8N%2BQ7pwalGsr6kYLF9RE2ZSU2l%2FekmmBabeN9h%2FqBSwXZ2SuW%2F9RPbSx76YnYUtOAkSFGeC1CXEu6GyUWuRM7sB4Z%2BCyG4I%3D"}],"group":"cf-nel","max_age":604800}
NEL: {"report_to":"cf-nel","max_age":604800}
Server: cloudflare
CF-RAY: 6786d416eaab53ec-LHR当前 IP:52.215.213.34 来自于:爱尔兰 都柏林郡 都柏林 amazon.com
运行 docker run myipe -i 执行成功。是因为当 ENTRYPOINT 后,CMD 的内容被作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到想要的结果,可以查看到HTTP 头的信息。
场景二 应用运行前的准备工作
启动容器就是启动主进程,有些时候需要在启动主进程前做一些准备工作。
比如mysql的数据库,需要做数据库的配置、初始化等,这些动作需要在启动mysql 服务之前准备好。
另外,避免使用root用户去启动服务,从提高安全性来讲,在启动前需要使用root做一些准备工作,然后在使用普通服务用户去启动服务。
这些准备工作和容器 CMD 无关,无论 CMD 为什么指令,都需要预先做一个预处理的工作。
使用脚本,然后利用 ENTRYPOINT 传参执行,脚本会接到参数作为命令,在脚本最后执行。
官方镜像 redis 是这么做的:
[root@ip-10-1-0-142 redis]# tree
.
├── docker-entrypoint.sh
└── redis-server0 directories, 2 files
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 [ "(id -u)" = '0' ]; then
find . ! -user redis -exec chown redis '{}' +
exec gosu redis "@"
fiexec "$@"
该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server ,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。比如,
$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)