Docker 镜像结构详解
一、镜像与容器关系
基本概念
- Docker镜像:支撑Docker容器运行的静态文件系统,如centos:7镜像提供基本的CentOS 7发行版环境(不包含Linux内核)
- Docker容器:动态的运行实例,包含进程、内存、CPU等资源
核心关系
- 容器实质上是一个或多个运行进程,父进程为Docker守护进程
- 容器隔离的关键:每个容器拥有独立的文件系统,由Docker镜像提供
- 镜像包含应用程序运行所需的所有内容:
- 代码/二进制文件
- 运行时环境
- 依赖项
- 文件系统对象
静态到动态的转换
- 转换依据:镜像的json配置文件
- 转换执行者:Docker守护进程
- 转换过程 :
- 解析镜像json文件
- 配置运行环境
- 运行指定进程
- 完成容器创建
容器运行后,镜像主要作用是为容器进程提供文件系统资源。

二、镜像的分层结构
Base镜像
两层含义:
- 不依赖其他镜像,从scratch构建
- 作为基础镜像进行扩展
特点:
- 通常是各种Linux发行版镜像
- 不同发行版区别主要在rootfs
- 容器使用Docker host的kernel且不能修改

分层架构原理
Docker里的镜像绝大部分都是在别的镜像的基础上去进行创建的,也就是使用镜像的分层结构。实际上,Docker Hub中99%的镜像都是通过在base镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,具体操作如下

实践示例 :vim Dockerfile
dockerfile
FROM centos:7
RUN mkdir /galaxy
RUN touch /galaxy/cy
CMD ["/bin/bash"]
构建命令:
bash
docker build -t centos:cy .
层级关系
- 容器层:最顶部的可写层
- 镜像层:容器层之下的所有只读层
- 文件访问:上层文件覆盖下层同名文件,用户看到的是叠加后的文件系统

Docker镜像构建介绍
Docker镜像可以通过Docker hub或者阿里云等仓库中获取,这些镜像是由官方或者社区人员提供的,对于Docker用户来说并不能满足我们的需求,但是从无开始构建镜像成本大。常用的数据库、中间件、应用软件等都有现成的Docker官方镜像或社区创建的镜像,我们只需要稍作配置就可以直接使用。
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为Docker的工程师知道如何更好的在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像,比如找不到现成的镜像,比如自己开发的应用程序,需要在镜像中加入特定的功能。
三、Dockerfile详解
镜像构建方法
- docker commit命令(不推荐)
- 基于本地模板导入
- Dockerfile构建(推荐)
docker commit命令可以基于容器创建镜像,创建过程大致分为三步,先创建容器,在容器中安装我们所需要的内容,再使用docker commit将容器打包为镜像即可。
bash
# 1、先基于centos7运行容器,容器名为cy,并使用-it生成终端进入容器。
[root@docker ~] docker run --name cy -it centos:7 /bin/bash
# 2、在容器中安装vim-common。
[root@2dc0f7d06aa8 /] yum -y install vim-common
# 3、退出容器后,使用docker commit将v1容器打包为镜像,新镜像名为centos:v1
[root@docker ~] docker commit cy centoscy:7
[root@docker ~] docker images | grep centos
[root@docker ~] docker run --name cy2 -it centoscy:7
Dockerfile优势
- 可重复性强
- 构建过程透明
- 便于审计
- 自动化程度高
Dockerfile结构
四个主要部分:
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动执行指令
示例:
dockerfile
[root@docker ~] ls
Dockerfile
[root@docker ~] vim Dockerfile
# 1、第一行必须指定,基础镜像信息
FROM centos:7
# 2、维护者信息
MAINTAINER chenyu@example.com
# 3、镜像操作指令
RUN yum install -y httpd EXPOSE 80
# 4、容器启动执行指令
CMD ["/bin/bash"]
构建命令:
bash
docker build -t httpd:cycy /root/
镜像构建过程
把构建容器所需要的指令都存放在Dockerfile文件中,这个文件的名字是固定的,不能够更改,再使用docker build命令构建容器,使用-t定义新的镜像名,如果构建镜像的Dockerfile文件不在当前目录下可以使用-f指定 Dockerfile文件路径,示例如下:
bash
[root@docker ~] docker build -t httpd:cycy /root/
通过以上镜像的构建过程可以看出,Dockerfile文件内的指令会逐一运行,构建过程如下
- 下载centos7镜像
- 添加镜像构建者信息
- 基于centos7镜像启动容器,安装httpd软件,安装完毕后将容器打包为镜像
- 基于上一步生成的镜像启动容器,将80端口打开,打开后将容器打包为镜像
- 基于上一步生成的镜像启动容器,添加容器启动后需要执行的指令,再打包为镜像
ini
[root@docker ~] docker history httpd:cycy
IMAGE CREATED CREATED BY SIZE COMMENT
b44f420b6834 10 minutes ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
65c05b81e846 10 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B
e7e8c35f9b15 10 minutes ago /bin/sh -c yum -y install httpd 203MB
a9c67ae8f88e 13 minutes ago /bin/sh -c #(nop) MAINTAINER chenyu@example... 0B
eeb6ee3f44bd 9 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 9 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc... 0B
<missing> 9 months ago /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4... 204MB
镜像缓存机制
- Docker缓存构建过程中的每一层临时镜像
- 构建新镜像时复用缓存层,加速构建
- 禁用缓存:
docker build --no-cache
使用DockerFile文件构建完镜像以后,Docker会把构建过程中的每一层临时镜像进行缓存。在构建新镜像时,可以直接使用之前缓存的镜像层,这样能加速镜像的构建。镜像缓存示例如下
bash
[root@docker ~] vim Dockerfile
# 1、第一行必须指定,基础镜像信息
FROM centos:7
# 2、维护者信息
MAINTAINER chenyu@example.com
# 3、镜像操作指令
RUN mkdir /galaxy
[root@docker ~] docker build -t cycentos:7 .
四、Dockerfile指令详解
1. FROM
FROM
指令必须为Dockerfile文件开篇的第一个非注释行,用于指定构建镜像所使用的基础镜像,后续的指令 运行都要依靠此基础镜像所提供的的环境。实际使用中,如果没有指定仓库,docker build会先从本机查找是否有此基 础镜像,如果没有会默认去Docker Hub Registry上拉取,再找不到就会报错,格式如下
Digest
:镜像的哈希码,防止镜像被冒名顶替
dockerfile
FROM <Repository>[:<Tag>]
FROM <Repository>@<Digest>
- 必须为第一个非注释行
- 指定基础镜像
2. MAINTAINER/LABEL
MAINTAINER
指令用于让Dockerfile的作者提供个人的信息,Dockerfile并不限制MAINTAINER指令的位置, 但是建议放在FROM指令之后,在较新的Docker版本中,已经被LABEL替代,格式如下
LABEL
指令用于让用户为镜像指定各种元数据(键值对的格式),格式如下
dockerfile
MAINTAINER "cy@example.com"
LABEL <key>=<value> <key>=<value>
- 作者信息
- 镜像元数据
3. COPY/ADD
COPY
指令用于复制宿主机上的文件到目标镜像中,格式如下
dockerfile
COPY <src>... <dest>
COPY ["<src>",... "<dest>"]
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]
:要复制的源文件或者目录,支持通配符
:目标路径,即正创建的镜像的文件系统路径,建议使用绝对路径,否则,COPY指令会以 WORKDIR为其起始路径。如果路径中如果包含空白字符,建议使用第二种格式用引号引起来,否则会被 当成两个文件
- 复制文件到镜像
- ADD支持tar文件和URL
4. WORKDIR
WORKDIR
指令用于指定工作目录,可以指多个,每个WORKDIR只影响他下面的指令,直到遇见 下一个WORKDIR为止。WORKDIR也可以调用由ENV指令定义的变量。,格式如下
dockerfile
WORKDIR 相对路径或者绝对路径
- 指定工作目录
5. VOLUME
VOLUME
指令用于在镜像中创建一个挂载点目录。Volume有两种类型:绑定挂载卷和docker管 理的卷。在Dockerfile中只支持Docker管理的卷,也就是说只能指定容器内的路径,不能指定宿主机的路 径,格式如下
dockerfile
VOLUME <mountpoint>
VOLUME ["<mountpoint>"]
- 创建挂载点目录
6. EXPOSE
EXPOSE
指令用于指定容器中待暴露的端口。比如容器提供的是一个https服务且需要对外提供访 问,那就需要指定待暴露443端口,然后在使用此镜像启动容器时搭配-P的参数才能将待暴露的状态转换 为真正暴露的状态,转换的同时443也会转换成一个随机端口,跟-p :443一个意思。EXPOSE指令可以一 次指定多个端口,例如:EXPOSE 11111/udp 11112/tcp,格式如下
dockerfile
EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
- 指定待暴露端口
7. ENV
ENV
指令用于为镜像定义所需的环境变量,并可被ENV指令后面的其它指令所调用。调用格式为variable_name或者 {variable_name},使用docker run启动容器的时候加上-e的参数为variable_name赋值,可以覆盖Dockerfile中ENV指令指 定的此variable_name的值。但是不会影响到Dockerfile中已经引用过此变量的文件名,格式如下
dockerfile
ENV <key> <value>
ENV <key>=<value>...
- 设置环境变量
8. RUN
RUN
指令运行于docker build过程中运行的程序,可以是任何命令。RUN指令后所执行的命令必须在FROM指令后的 基础镜像中存在才行,格式如下
dockerfile
RUN <command>
RUN ["executable", "param1", "param2"]
通常是一个shell命令,系统默认会把后面的命令作为shell的子进程来运行,以"/bin/sh -c"来运行它。 第二种格式的参数是一个JSON格式的数组,其中"executable"为要运行的命令,后面的"paramN"为传递给命令的选项或参 数
- 构建过程中执行的命令
9. CMD
CMD
指令用于用户指定启动容器的默认要运行的程序,也就是PID为1的进程命令,且其运行结束后容器也会终止。如果 不指定,默认是bash。CMD指令指定的默认程序会被docker run命令行指定的参数所覆盖。Dockerfile中可以存在多个CMD 指令,但仅最后一个生效。因为一个Docker容器只能运行一个PID为1的进程。类似于RUN指令,也可以运行任意命令或程 序,但是两者的运行时间点不同。RUN指令运行在docker build的过程中,而CMD指令运行在基于新镜像启动容器时,格式 如下
dockerfile
CMD command param1 param2
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
- 容器启动默认运行程序
- 仅最后一个生效
10. ENTRYPOINT
ENTRYPOINT
指令类似CMD指令的功能,用于为容器指定默认运行程序。Dockerfile中可以存在多个ENTRYPOINT指 令,但仅最后一个生效,与CMD区别在于,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且这 些命令行参数会被当做参数传递给ENTRYPOINT指令指定的程序,格式如下
dockerfile
ENTRYPOINT command param1 param2
ENTRYPOINT ["executable", "param1", "param2"]
- 指定默认运行程序
- 不会被docker run参数覆盖
11. USER
USER
用于指定docker build过程中任何RUN、CMD等指令的用户名或者UID。默认情况下容器的运行用户为root, 格式如下
dockerfile
USER <user>[:<group>]
USER <UID>[:<GID>]
- 指定运行用户
五、核心要点总结
- 镜像的来源多种多样,可以通过
Docker Hub社区
获取,或者通过国内的公有镜像仓库中获取。 Docker镜像是Docker容器运行的基础
,没有Docker镜像,就不可能有Docker容器,这也是Docker的设计原则之一。- Docker可以同时支持多种Linux镜像,模拟出多种操作系统环境。
容器只能使用Docker host的kernel,并且不能修改
。 - base镜像有两层含义:
不依赖其他镜像,从scratch构建
;以此为基础镜像,进行扩展。 - 构建镜像的方式有三种:
使用docker commit构建
、基于本地模板导入
和使用Dockerfile构建构建
。 docker commit命令可以基于容器创建镜像
,创建过程大致分为三步,先创建容器,在容器中安装我们所需要的内容,再 使用docker commit将容器打包为镜像即可。- 用户可以使用
docker import
命令直接从一个操作系统模板文件导入一个镜像。 - 构建镜像的方式有三种:使用
docker commit构建
、基于本地模板导入
和使用Dockerfile构建
构建。 - Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是 描述该层应当如何构建。
- 在Dockerfile语法中,RUN、CMD都是运行命令的指令,
RUN
是在构建镜像时运行的命令,CMD
是容器启动时运行的命 令。