Dockerfile实例

一、先看最简单的例子,定制nginx镜像,打印出 <h1>Hello, Docker!</h1>

 Dockerfile文件:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

 在该Dockerfile目录下运行

docker build -t nginx:v1 .

 则将生成一个 nginx:v1 的新镜像,运行该镜像

docker run -p 80:80 nginx:v1

 在浏览器直接访问地址 localhost,可以看到nginx的主页已被修改

 二、RUN 命令关键解析

Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为,新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。

FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

如上dockerfile所示,创建了 7 层镜像,这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。

正确的写法:

FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' 
&& apt-get update 
&& apt-get install -y $buildDeps 
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" 
&& mkdir -p /usr/src/redis 
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 
&& make -C /usr/src/redis 
&& make -C /usr/src/redis install 
&& rm -rf /var/lib/apt/lists/* 
&& rm redis.tar.gz 
&& rm -r /usr/src/redis 
&& apt-get purge -y --auto-remove $buildDeps

三、CMD 与 ENTRYPOINT对比

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

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

FROM ubuntu:16.04
RUN apt-get update 
&& apt-get install -y curl 
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]

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

docker run myip

可以打印出当前的ip地址

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

$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: con
tainer_linux.go:247: starting container process caused "exec: \"-i\": executable
file not found in $PATH"
".

可以看到可执行文件找不到的报错, executable file not found 。之前我们说过,跟在镜像名后面的是 command ,运行时会替换 CMD 的默认值。因此这里的 -i 替换了远了的CMD ,而不是添加在原来的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令:

$ docker run myip curl -s http://ip.cn -i

这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在我们重新用ENTRYPOINT 来实现这个镜像:

FROM ubuntu:16.04
RUN apt-get update 
&& apt-get install -y curl 
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

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

成功!这是因为当存在 ENTRYPOINT 后, CMD 的内容将会作为参数传给ENTRYPOINT ,而这里 -i 就是新的 CMD ,因此会作为参数传给 curl ,从而达到了我们预期的效果

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

启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。
比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的mysql 服务器运行之前解决。
此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等。
这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD> )作为命令,在脚本最后执行。

四、HEALTHCHECK 健康检查

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12引入的新指令

HEALTHCHECK 支持下列选项:
--interval=<时长> :两次健康检查的间隔,默认为 30 秒;
--timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被
视为失败,默认 30 秒;
--retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3
次。

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s 
CMD curl -fs http://localhost/ || exit 1

这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。

构建镜像,并且启动容器

$ docker build -t myweb:v1 .

$ docker run -d --name web -p 80:80 myweb:v1

当运行该镜像后,可以通过 docker ps 看到最初的状态为 (health: starting) 

在等待几秒钟后,再次 docker ps ,就会看到健康状态变化为了 (healthy) 

如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy) 。
为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr )都会被存储于健康状态里,可以用 docker inspect 来查看。

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</titl
e>
<style>
 body {
  35em;
 margin: 0 auto;
 font-f
amily: Tahoma, Verdana, Arial, sans-serif;
 }
</style>
</head>
<body>
<h1>Welc
ome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully inst
alled and
working. Further configuration is required.</p>

<p>For online documentat
ion and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Co
mmercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>


<p><em>Thank you for using nginx.</em></p>
</body>
</html>
",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}

五、COPY与ADD区别

在 Docker 官方的最佳实践文档中要求,尽可能的使用 COPY ,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用ADD 的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是, ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY 指令,仅在需要自动解压缩的场合使用 ADD 。

原文地址:https://www.cnblogs.com/UniqueColor/p/11133843.html