Dockerfile使用介绍

Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile来快速创建自定义的镜像。

1.Dockerfile概念

我们使用Dockerfile定义镜像,依赖镜像来运行容器,因此Dockerfile是镜像和容器的关键。首先通过一张图来了解Docker镜像、容器和Dockerfile三者之间的关系。

img

通过上图可以看出使用Dockerfile定义镜像,运行镜像然后启动容器。

Dockerfile镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是Dockerfile

Dockerfile是一个文本文件,其内包含了一条条指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。有了Dockerfile,当我们需要定制自己额外的需求时,只需要在Dockerfile上添加或者修改指令,重新生成image即可,省去了敲命令的麻烦。

2.Dockerfile的基本结构

Dockerfile由一行行命令语句组成,并支持以#开头的注释行。Dockerfile文件格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
##  Dockerfile文件格式
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# 1、第一行必须指定 基础镜像信息
FROM ubuntu
# 2、维护者信息
MAINTAINER docker_user docker_user@email.com
# 3、镜像操作指令
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf

# 4、容器启动执行指令
CMD /usr/sbin/nginx

Dockerfile分为四部分:1.基础镜像信息、2.维护者信息、3.镜像操作指令、4.容器启动执行指令。一开始必须要指明所基于镜像名称,接下来一般会说明维护者信息;后面则是镜像操作指令,例如RUN指令。每执行一条RUN指令,镜像添加新的一层,并提交;最后是CMD指令,来指明运行容器时的操作指令。

3.构建镜像

docker build命令会根据Dockerfile文件以及上下文构建新Docker镜像。构建上下文是指Dockerfile所在的本地路径或一个URL(Git仓库地址)。构建上下文环境会被递归处理,所以构建所指定的路径还包含了子目录,而URL还包括了其中指定的子模块。

将当前目录做为构建上下文时,可以像下面这样使用docker build命令构建镜像:

1
2
3
docker build .
Sending build context to Docker daemon 6.51 MB
...

说明:构建会在Docker后台守护进程(daemon)中执行,而不是CLI中。构建前,构建进程会将全部内容(递归)发送到守护进程。大多情况下,应该将一个空目录作为构建上下文环境,并将Dockerfile文件放在该目录下。

在构建上下文中使用的Dockerfile文件,是一个构建指令文件。为了提高构建性能,可以通过.dockerignore文件排除上下文目录下不需要的文件和目录。

在 Docker 构建镜像的第一步,docker CLI会先在上下文目录中寻找.dockerignore文件,根据.dockerignore 文件排除上下文目录中的部分文件和目录,然后把剩下的文件和目录传递给 Docker 服务。

Dockerfile 一般位于构建上下文的根目录下,也可以通过-f指定该文件的位置:

1
docker build -f /path/to/a/Dockerfile .

构建时,还可以通过-t参数指定构建成镜像的仓库、标签。

3.缓存

Docker 守护进程会一条一条的执行Dockerfile中的指令,而且会在每一步提交并生成一个新镜像,最后会输出最终镜像的ID。生成完成后,Docker 守护进程会自动清理你发送的上下文。Dockerfile文件中的每条指令会被独立执行,并会创建一个新镜像,RUN cd /tmp等命令不会对下条指令产生影响。 Docker 会重用已生成的中间镜像,以加速docker build的构建速度。以下是一个使用了缓存镜像的执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
---> Using cache
---> 2a1c91448f5f
Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/
---> Using cache
---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
---> Using cache
---> 7ea8aef582cc
Successfully built 7ea8aef582cc

构建缓存仅会使用本地父生成链上的镜像,如果不想使用本地缓存的镜像,也可以通过--cache-from指定缓存。指定后将不再使用本地生成的镜像链,而是从镜像仓库中下载。

3.1寻找缓存的逻辑

Docker 寻找缓存的逻辑其实就是树型结构根据Dockerfile指令遍历子节点的过程。下图可以说明这个逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     FROM base_image:version           Dockerfile:
+----------+ FROM base_image:version
|base image| RUN cmd1 --> use cache because we found base image
+-----X----+ RUN cmd11 --> use cache because we found cmd1
/ \
/ \
RUN cmd1 RUN cmd2 Dockerfile:
+------+ +------+ FROM base_image:version
|image1| |image2| RUN cmd2 --> use cache because we found base image
+---X--+ +------+ RUN cmd21 --> not use cache because there's no child node
/ \ running cmd21, so we build a new image here
/ \
RUN cmd11 RUN cmd12
+-------+ +-------+
|image11| |image12|
+-------+ +-------+

大部分指令可以根据上述逻辑去寻找缓存,除了ADD和COPY。这两个指令会复制文件内容到镜像内,除了指令相同以外,Docker 还会检查每个文件内容校验(不包括最后修改时间和最后访问时间),如果校验不一致,则不会使用缓存。

除了这两个命令,Docker 并不会去检查容器内的文件内容,比如 RUN apt-get -y update,每次执行时文件可能都不一样,但是 Docker 认为命令一致,会继续使用缓存。这样一来,以后构建时都不会再重新运行apt-get -y update

如果 Docker 没有找到当前指令的缓存,则会构建一个新的镜像,并且之后的所有指令都不会再去寻找缓存。

4.利用Dockerfile构建镜像的简单示例

####4.1编辑Dockerfile文件

接下来用一个简单示例来感受一下Dockerfile是如何用来构建镜像启动容器。我们以定制Nginx镜像为例,在一个空白目录中,建立一个文本文件,并命名为Dockerfile

1
2
3
mkdir mynginx
cd mynginx
vim Dockerfile

构建一个Dockerfile文件内容为:

1
2
3
FROM nginx
# 前提是使用yum安装nginx,或者指定nginx中的index.html的位置
RUN echo '<h1>Hello,Docker!</h1>' > /usr/share/nginx/html/index.html

这个Dockerfile很简单,一共就两行涉及到了两条指令:FROMRUNFROM表示获取指定基础镜像,RUN执行命令,在执行的过程中重写了nginx的默认页面信息,并将信息替换为:Hello,Docker!

4.2构建镜像

Dockerfile文件所在的目录执行(或者通过-f选项来指定其路径):

1
2
# 命令最后有一个.表示当前目录
docker build -t nginx:v1 .

image.png

4.3查看镜像

构建完成之后,使用docker image命令查看所有镜像,如果存在REPOSITORYnginxTAGv1的信息,就表示构建成功。

image.png

4.4运行镜像

接下来使用docker run命令来启动容器:

1
docker run  --name docker_nginx_v1   -d -p 80:80 nginx:v1

image.png

这条命令会用nginx镜像启动一个容器,命名docker_nginx_v1,并且映射了 80 端口,这样我们可以用浏览器去访问这个 nginx 服务器:xx.xx.xx.xx,页面返回信息:

image.png

这样一个简单使用Dockerfile构建镜像,运行容器的实例就完成了。

4.5修改容器内容

容器启动后,需要对容器内的文件进行进一步的完善,可以使用docker exec -it xx bash命令再次进行修改,以上面的示例为基础,修改nginx启动页面内容:

1
2
3
4
docker exec -it docker_nginx_v1   bash
root@3729b97e8226:/# echo '<h1>Hello, Docker Michaeljian!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit

image.png

以交互式终端方式进入docker_nginx_v1容器,并执行了bash命令,也就是获得一个可操作的 Shell。然后,我们用<h1>Hello, Docker Michaeljian!</h1>覆盖了 /usr/share/nginx/html/index.html 的内容。再次刷新浏览器,会发现内容被改变。

image.png

修改了容器的文件,也就是改动了容器的存储层,可以通过docker diff命令看到具体的改动。

image.png

参考资料

纯洁的微笑

Docker官网文档

文章目录
  1. 1. 1.Dockerfile概念
  2. 2. 2.Dockerfile的基本结构
  3. 3. 3.构建镜像
  4. 4. 3.缓存
    1. 4.1. 3.1寻找缓存的逻辑
  5. 5. 4.利用Dockerfile构建镜像的简单示例
    1. 5.1. 4.2构建镜像
    2. 5.2. 4.3查看镜像
    3. 5.3. 4.4运行镜像
    4. 5.4. 4.5修改容器内容
  6. 6. 参考资料