Docker的镜像
一.Docker镜像的概念
镜像是Docker(镜像,容器,仓库)三大核心概念之一。镜像本质上是一个只读文件,它包含了文件系统、源码、库文件、依赖、工具等运行应用程序所必须的文件。
镜像是由文件系统叠加而成,最底层是一个bootfs引导文件系统。在引导文件系统之上,是root文件系统的rootfs。Rootfs可以是一种或多种操作系统(CentOS或Ubuntn)。
Docker镜像是用于构建Docker容器的一个静态模板文件,通过这个模板文件可以实例化出多个容器。
二.联合文件系统
联合文件系统(Union File System)是一种轻量级的高性能分层文件系统,它能够将多个文件目录挂载到同一个虚拟文件系统下。这个被挂载的虚拟文件系统就是一个联合挂载点。联合挂载实际上是将多个文件系统层叠在一起,形成一个逻辑上统一的文件系统。联合挂载可以一次同时加载多个文件系统,但是在外面看起来好像只是一个文件系统。它会将各层文件系统叠加到一起,这样最终的文件系统会包含所有底层的文件和目录。
联合文件系统是实现Docker镜像的技术基础。Docker镜像文件是一种分层结构,每一层构建在其他层之上,每一层文件系统就是一层layer。Docker的每一层layer都是只读的。这些层是联合文件系统的一部分,它们可以被组合在一起以形成一个完整的镜像。当对镜像进行修改时,实际上是在创建一个新的层,这个新层包含了所做的修改,而底层则保持不变。
三.写时复制
我们知道Docker镜像文件由多个只读层叠加而成(联合文件系统)。当Docker创建一个容器时,它会在加载镜像的所有只读层后构建出一个镜像栈,在镜像栈的顶部添加一个读写层。
如果在容器中需要修改一个已经存在的文件,那么这个文件将会从这个读写层下面的只读层复制到该读写层后,才能进行修改。而文件的只读版本会依然存在,只是已经被复制到读写层中该文件的副本所隐藏,这就是"写时复制(copy-on-write, COW)"的机制。
Docker的写时复制是一种优化机制,可以节省磁盘和内存空间。
四.Docker镜像命令说明
1.拉取镜像
docker pull <镜像名>[:<标签>]命令可以从远程镜像仓库拉取指定版本的镜像。如果不指定标签,则默认是拉取latest标签的镜像。
示例:
我们以busybox镜像"Linux系统的瑞士军刀"为例,从网易的镜像仓库中进行镜像的拉取操作。
bash
docker pull hub.c.163.com/library/busybox:1.27-uclibc
2.查看镜像
(1).查看本地镜像列表
docker images [OPTIONS]命令可以查看本地存储的所有Docker镜像。该命令会列出镜像的仓库名、标签、镜像ID、创建时间和文件大小的信息。
参数选项:
- -a,--all=true|false:列出所有镜像文件,默认为否;
- --digests=true|false:列出镜像的数字摘要值,默认为否;
- -f,--filter=[]:对镜像进行过滤,列出满足条件的镜像。
- --format="TEMPLATE":控制输出格式,如.ID代表ID信息.Repository代表仓库信息等;
- --no-trunc=true|false:对输出结果中太长的部分是否进行截断,如镜像的ID信息,默认为是;
- -q,--quiet=true|false:仅输出ID信息,默认为否。
示例:
bash
docker images
(2).查看镜像详细信息
docker inspect <镜像ID>命令可以查看指定镜像的详细信息。详细信息是以JSON的形式返回。信息中包含了镜像的创建时间、适应架构、各层的数字摘要、配置、层、环境变量等。
示例:
bash
docker inspect d20ae45477cb
(3).查看镜像历史
docker history [OPTIONS] <镜像ID> | <镜像名>[:<标签>]命令可以查看指定镜像的历史记录,该命令可以列出镜像每一层的镜像ID、创建时间、创建方式和文件大小等信息。
参数选项:
- -H,--human:大小和日期采用人容易读的格式展现;
- --no-trunc:显示全部信息,不要隔断;
- -q,--quiet:只显示镜像 id 信息。
示例:
bash
docker history d20ae45477cb
3.添加镜像标签
docker tag <镜像名>[:<原标签>] <镜像名>[:<新标签>]命令可以给指定的镜像添加新的标签。这个命令通常用于为镜像创建别名或版本标记。
示例:
bash
docker tag hub.c.163.com/library/busybox:1.27-uclibc mybusybox:latest
从上图中我们可以看出hub.c.163.com/library/busybox:1.27-uclibc和mybusybox:latest镜像的ID是一样的。说明它们实际上是指向了同一个镜像文件,只是别名不同而已。docker tag命令起到了类似链接的作用。
4.删除和清理镜像
(1).使用标签删除镜像
docker rmi [OPTIONS] <镜像名> [:<标签>]是通过标签来删除指定的镜像。
参数选项:
- -f, -force:强制删除镜像;
- -no-prune:不要清理未带标签的父镜像。
示例:
bash
docker rmi hub.c.163.com/library/busybox:1.27-uclibc
(2).使用镜像ID删除镜像
docker rmi [OPTIONS] <镜像ID>命令是通过镜像ID来删除指定的镜像。
参数选项:
- -f, -force:强制删除镜像;
- -no-prune:不要清理未带标签的父镜像。
示例:
bash
docker rmi d20ae45477cb
docker rmi命令(不管是根据标签或者ID)来删除一个镜像时,如果这个镜像有被容器使用,则无法直接删除这个镜像。如果这个容器是退出的状态,我们可以使用 -f参数来强制删除一个存在容器依赖的镜像。不过并不推荐使用这种方式,最好还是先删除镜像依赖的容器,然后再来删除镜像。
当同一个镜像拥有多个标签的时候,docker rmi命令只是删除了该镜像多个标签中的指定标签而已,并不会删除镜像文件。但当镜像只剩下一个标签的时候,此时使用docker rmi命令就会彻底删除镜像。
(3).清理镜像
docker image prune [OPTIONS]命令是用来进行清理系统中未被任何容器使用的悬空镜像(dangling images)和使用Docker一段时间后,系统中遗留的一些临时镜像文件。
参数选项:
- -a, -all:删除所有无用镜像,不光是临时镜像;
- -filter filter:只清理符合给定过滤器的镜像;
- -f,-force:强制删除镜像,而不进行提示确认。
示例:
bash
docker image prune
5.创建镜像
(1).基于Dockerfile创建
docker build [OPTIONS] <镜像名>[:<标签>] <Dockerfile所在路径>命令是根据Dockerfile来构建新的Docker镜像。基于Dockerfile创建是最常见的方式。Dockerfile是一个文本文件,其中包含了构建镜像所需的一系列指令和参数。
参数选项:
- --build-arg=[]:设置镜像创建时的变量。
- -f:指定要使用的 Dockerfile 路径,如果在当前路径,则默认使用.来表示。
- --label=[]:设置镜像使用的元数据。
- --no-cache:创建镜像的过程不使用缓存。
- --pull:尝试去更新镜像的新版本。
- --quiet,-q:安静模式,成功后只输出镜像 ID。
- --tag, -t:镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
- --network:默认 default。在构建期间设置 RUN 指令的网络模式。
(2).基于容器创建
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]命令是将容器的当前状态保存为一个新的Docker镜像。
参数选项:
- -a,--author="":作者信息;
- -c,--change=[]:提交的时候执行Dockerfile指令,包括CMD|ENTRYPOINT|ENV|EXPOSE|LABEL|ONBUILD|USER|VOLUME|WORKDIR等;
- -m,--message="":提交消息;
- -p,--pause=true:提交时暂停容器运行。
示例:
该命令是基于容器来创建一个镜像,所以我们首先使用已拉取的busybox镜像来启动一个名称为"container_busybox"的容器。
bash
docker run -it --name container_busybox hub.c.163.com/library/busybox:1.27-uclibc
bash
docker ps
使用docker ps命令可以发现一个名称为"container_busybox"的容器已经创建完成。我们使用docker commit命令在这个容器的基础上再创建一个名称为"image_busybox"的镜像。
bash
docker commit container_busybox image_busybox
6.导出镜像
docker save -o <文件名> <镜像名>[:<标签>]命令是将指定的镜像导出为本地文件。
示例:
我们使用docker save命令把image_busybox:latest的镜像导出成本地的一个名称为"image_busybox"的tar包。
bash
docker save -o image_busybox.tar image_busybox:latest
7.导入镜像
docker load -i <文件名>命令是将导出到本地的镜像文件导入到本地镜像库。导入镜像及其相关的元数据信息与被导出的镜像的信息是完全一致的。
示例:
bash
docker load -i image_busybox.tar
五.制作Docker镜像
本文中介绍的Docker镜像制作,主要是基于Dockerfile文件来构建自定义镜像文件。基于Dockerfile创建来构建镜像是最常见的方式。
1.Dockerfile是什么
Dockerfile是一个用于自动化构建Docker镜像的纯文本文件,它是基于DSL(Domain Specific Language)语法指令来构建一个Docker镜像。Dockerfile相当于Docker镜像的构建蓝图,它包含了用户定义的镜像制作的完整操作流程。用户使用它可以快速创建出自定义镜像。
Dockerfile文本中包含了从一个基础镜像开始,到最终形成所需定制镜像的所有指令。文中的每一条指令都对应着构建镜像的一层,这些构建的镜像层被堆叠在一起以构建成最终的镜像。Dockerfile指导了Docker如何构建一个自定义的镜像。
2.Dockerfile的基本结构
Dockerfile的内容是由一条一条的命令语句组成,从总体而言Dockerfile主体内容主要由四大部分组成:基础镜像信息、维护者信息、镜像操作指令、容器启动时执行指令。
- 基础镜像信息:使用FROM指令指定构建镜像时依赖的基础镜像。
- 维护者信息:使用LABEL指令设置镜像的维护者信息。
- 镜像操作指令:使用包括RUN、COPY、EXPOSE、ENV、ARG等指令,在基础镜像之上执行镜像构建过程中的命令和操作。
- 使用CMD或ENTRYPOINT指定容器启动时执行的命令。
3.Dockerfile示例
本文中的Dockerfile的示例主要是介绍在拉取ubuntu的基础镜像后,然后在这个操作系统中安装jdk8。Dockerfile脚本如下:
bash
#1.基础镜像信息
#指定拉取的基础镜像,基础镜像为网易镜像库的ubuntu,版本为16.04-tools
FROM hub.c.163.com/public/ubuntu:16.04-tools
#2.维护者信息
#设置镜像的维护者信息
LABEL maintainer="xxx@example.com"
#3.镜像的操作指令
#使用RUN命令安装openjdk8
RUN apt-get update && \
apt-get install -y openjdk-8-jdk && \
rm -rf /var/lib/apt/lists/*
#定义工作目录
WORKDIR /data
#设置JAVA_HOME的环境变量
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
#4.指定容器启动时执行的指令
#定义容器启动时默认执行的指令
CMD ["bash"]
从以上示例脚本中可以看出,Dockerfile支持以#开头的注释行。整个Dockerfile文件的主体部分主要是由4大部分组成。
(1).基础镜像信息:首先是使用FROM指令指明所基于的镜像名称。本例中的基础镜像是网易镜像仓库的ubuntu,版本为16.04-tools。
(2).维护者信息:使用LABEL指令来说明维护者信息。
(3).镜像的操作指令:本例中是使用RUN指令来安装openjdk8,WORKDIR命令来指定工作目录,ENV指令来设置jdk的环境变量。每运行一条指令,镜像就添加新的一层并提交。
(4). 指定容器启动时执行的指令: 本例中是使用CMD指令,在容器启动的时候立即在容器内打开一个shell终端。
4.Dockerfile的常用指令
(1).ARG
ARG命令的作用是定义创建镜像过程中使用的变量。当使用docker build命令构建镜像时,可以通过 --build-arg <参数名>=<值> 的方式来为声明的变量赋值。ARG命令中最好不要定义敏感信息的变量,因为docker history命令是可以看到ARG定义的变量值的。
Docker中内置了一些镜像创建的变量,用户无须声明可以直接使用。它们如下:
格式:
ARG <name>[=<default value>] [<name>[=<default value>]...]
示例:
ARG param=value
(2).ENV
ENV命令的作用是设置环境变量,在镜像生成过程中会被后续RUN指令使用,在镜像启动的容器中也会存在。
格式:
ENV <key>=<value> [<key>=<value>...]
示例:
bash
ENV JAVA_HOME /opt/jdk/jdk1.8
ENV PATH $PATH:$JAVA_HOME/bin
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib
注意:
ARG命令和ENV命令都是用于定义变量,那它们之间有什么区别?
ARG用于设置构建镜像时的参数,其值在构建镜像时传递给Docker引擎,而不会保存在生成的镜像中。ENV用于设置环境变量,其值会在容器运行时被使用,并保存在生成的镜像中。所以ARG和ENV是在作用时机和生命周期有所不同。
(3).FROM
FROM命令的作用是指定创建镜像需要拉取的基础镜像。一个有效的Dockerfile文件一般都是从FROM指令开始的,但ARG是唯一可以位于FROM之前的指令。在同一个Dockerfile文件中可以有多个FROM来创建多个镜像。通过FROM指令添加AS name,可以选择为新生成阶段指定名称。该名称可以在后续的FROM和COPY--FROM=<name>指令中使用,以引用在此阶段中构建的镜像。
格式:
FROM [--platform=<platform>] <image> [AS <name>] | FROM [--platform=<platform>] <image>[:<tag>] [AS <name>] | FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
示例:
bash
FROM hub.c.163.com/public/centos:7.2-tools
(4).ADD
ADD命令的作用是将宿主机中的文件添加到镜像中。
**格式:**ADD [OPTIONS] <src> ... <dest> | ADD [OPTIONS] ["<src>", ... "<dest>"]
参数选项:
- --keep-git-dir:当<src>是HTTP或SSH Git仓库的地址时,将Git仓库的内容添加到镜像中时是否保留.git目录。默认为否,表示不否保留.git目录。
- --checksum:验证远程资源的校验和,支持哈希算法。
- --chown:为添加到镜像中的文件指定的用户或组。只适用于Linux。
- --chmod:为添加到镜像中的文件指定的访问权限。只适用于Linux。
- --link:使用--link时,您的源文件会被复制到一个空的目标目录中。该目录将变成一个链接到您之前状态之上的层。
- --exclude:指定需要排除的文件。
格式:
ADD [OPTIONS] <src> ... <dest> | ADD [OPTIONS] ["<src>", ... "<dest>"]
示例:
bash
ADD jdk-8u212-linux-x64.tar.gz /opt/jdk/jdk-8u212-linux-x64.tar.gz
(5).COPY
COPY命令的作用是将宿主机中的文件复制到镜像中。
格式:
COPY [OPTIONS] <src> ... <dest> | COPY [OPTIONS] ["<src>", ... "<dest>"]
参数选项:
- --from:可以从映像、构建阶段或命名上下文中复制文件。
- --chown:为添加到镜像中的文件指定的用户或组。只适用于Linux。
- --chmod:为添加到镜像中的文件指定的访问权限。只适用于Linux。
- --link:使用--link时,您的源文件会被复制到一个空的目标目录中。该目录将变成一个链接到您之前状态之上的层。
- --parents:在镜像中保留复制文件的父目录。
- --exclude:指定需要排除的文件。
示例:
bash
COPY jdk-8u212-linux-x64.tar.gz /opt/jdk/jdk-8u212-linux-x64.tar.gz
注意:
COPY命令和ADD命令的功能十分类似。COPY命令主要用于将宿主机中的文件复制到镜像中。ADD命令除了执行与COPY相同的基本文件复制功能外,还可以通过URL中将远程的文件复制到镜像中。如果需要添加的源文件是压缩包的形式,ADD命令会自动解压和提取这些文件到目标路径。
相较于ADD命令COPY更为简单和明确,如果只是添加本地宿主机中的文件到镜像中,推荐使用COPY命令。
(6).ENTRYPOINT
ENTRYPOINT是镜像的默认入口命令,在容器启动时就会执行这个命令。一个Dockerfile中只能有一个ENTRYPOINT,当指定多个ENTRYPOINT时,只有最后的一个起作用。ENTRYPOINT命令有exec和shell两种形式
格式:
ENTRYPOINT ["executable", "param1", "param2"] //exec形式
ENTRYPOINT command param1 param2 //shell形式
示例:
bash
ENTRYPOINT ["/bin/echo", "Hello"]
(7).CMD
CMD是容器启动时默认会执行的命令。每个Dockerfile文件只能有一条CMD命令。如果列出多个CMD,则只有最后一个生效。如果用户启动容器时候手动指定了运行的命令(作为run命令的参数),则会覆盖掉CMD指定的命令。
格式:
CMD ["executable","param1","param2"] //exec形式
CMD ["param1","param2"] //exec形式
CMD command param1 param2 //shell形式
示例:
bash
CMD ["./bin/startup.sh"]
注意:
ENTRYPOINT和CMD命令都是在容器启动时要执行的命令。如果我们使用带有命令参数docker run来启动容器的话,CMD命令就会被覆盖,但是ENTRYPOINT是不会被覆盖,ENTRYPOINT会始终被执行。
(8).EXPOSE
EXPOSE命令是指定Docker容器在运行时监听的网络端口。端口可以是TCP也可以是UDP,如果不指定协议,默认为TCP。EXPOSE指令实际上并没有发布端口,它只用于文档目的,告诉其他开发者和用户容器需要暴露哪些端口。
格式:
EXPOSE <port> [<port>/<protocol>...]
示例:
bash
EXPOSE 80/tcp
EXPOSE 80/udp
(9).HEALTHCHECK
HEALTHCHECK命令是用于定义如何检查容器内运行的服务是否健康。通过按照一定频率来运行命令,Docker可以自动监测容器的运行状况并返回。我们可以根据返回结果来判断容器是否健康。
格式:
HEALTHCHECK [OPTIONS] CMD command
参数选项:
- --interval:检查之间的时间间隔,单位为秒(默认是30秒)。
- --timeout:单次检查的超时时间,单位为秒(默认是30秒)。
- --start-period:设置需要容器启动初始化的时间段,在这个时间段内如果检查失败, 则不会记录失败次数。如果在启动时间内成功执行了健康检查,则容器将被视为已经启动, 如果在启动时间内再次出现检查失败,则会记录失败次数。单位为秒(默认是0秒)。
- --retries:失败的重试次数,(默认是3次)。
示例:
bash
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost/ || exit 1
(10).LABEL
LABEL命令是为生成的镜像添加元数据标签信息。LABEL命令是以label="value"的形式出现。每个标签就是一个键值对。一个镜像可以有多个标签。标签可以用来辅助过滤出特定镜像。
格式:
LABEL <key>=<value> [<key>=<value>...]
示例:
bash
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
(11).RUN
RUN会运行指定的命令,在当前镜像的顶层再创建一个新层。新加的层的镜像将用于Dockerfile中的后续步骤。
格式:
RUN [OPTIONS] <command> ... // shell形式
RUN [OPTIONS] [ "<command>", ... ] //exec形式
参数选项:
- --mount:允许挂载可以访问的文件系统。
- --network:控制命令在哪个网络环境中运行。
- --security:设置运行命令的安全模式。--security=insecure是在不安全模式下运行命令,默认值是sandbox。
示例:
bash
RUN tar -zxvf jdk-8u212-linux-x64.tar.gz
(12).USER
USER命令设置运行镜像时要使用的用户名(或 UID)以及可选的用户组(或 GID)。在Dockerfile中RUN,CMD和ENTRYPOINT指令都会以指定的用户来执行。USER命令确保容器内的应用程序以特定的用户来运行,这通常是出于安全和权限管理的考虑。默认情况下,容器以root用户身份运行,这可能会带来安全风险。
格式:
USER <user>[:<group>]
USER <UID>[:<GID>]
示例:
bash
USER username
(13).VOLUME
VOLUME命令创建一个数据卷挂载点,可以从本地主机或其他容器挂载数据卷,数据卷是用来存储可以需要保持的数据。数据卷可以在多个容器间共享和重用。我们对数据卷的修改是立即生效的,并且对数据卷的修改不会对更新新镜像产生影响。
格式:
VOLUME ["/data"]
示例:
bash
VOLUME /myvol
(14).WORKDIR
WORKDIR命令是为Dockerfile中紧随其后的RUN、CMD、ENTRYPOINT、COPY和ADD命令设置工作目录。如果设置的工作目录不存在,就会被创建。在Dockerfile中可以多次使用WORKDIR指令。如果后续命令的参数是相对路径,它会基于之前的命令来指定路径。
格式:
WORKDIR /path/to/workdir
示例:
bash
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为/a/b/ c。
(15).STOPSIGNAL
STOPSIGNAL命令设置将发送到容器以退出容器系统的信号值。信号值的格式可以是SIG<name>的信号名称,例如SIGKILL。也可以是Linux内核系统调用表中的合法数字,例如9。
格式:
STOPSIGNAL signal
示例:
bash
STOPSIGNAL SIGINT
5.创建镜像
在编写完成Dockerfile后,我们就可以通过docker build命令来创建镜像。docker build命令通过读取Dockerfile中定义的指令来逐步构建镜像,并将最终结果保存到本地镜像库中。
格式:
docker build [OPTIONS] PATH|URL|-
参数选项:
- --add-host:添加自定义主机名到IP的映射(格式: host:ip)。
- --annotation:为镜像添加注解。
- --build-arg:设置镜像创建时的变量。
- --cache-from:使用指定镜像作为缓存源。
- --cache-to:导出镜像作为目标缓存。
- --cgroup-parent:继承上层cgroup。
- -f,--file:指定要使用的Dockerfile的文件路径。默认为当前构建上下文根目录下的Dockerfile文件。
- --iidfile:将镜像ID写入文件。
- --label:设置镜像的元数据。
- --network:在构建过程中为RUN指令设置网络模式。
- --no-cache:创建镜像时不适用缓存。
- -o,--output:设置镜像构建结果的导出操作。
- --platform:设置镜像构建的平台类型。
- --pull:总是尝试拉取所有相关的镜像。
- --quiet,-q:不打印镜像创建过程中的日志信息,构建成功后只输出镜像ID。
- --shm-size:设置使用RUN指令时为构建容器分配的共享内存的大小。
- --ssh:暴露构建SSH agent的套接字或密钥。
- -t,--tag:为构建的镜像指定名称和标签。
- --targe:设定要创建的目标构建阶段。
- --ulimit:指定ulimit的配置。
PATH:Dockerfile文件的目录路径或 .(表示当前目录)。
URL:指向Dockerfile文件的远程url路径(如Git仓库)。
-:从标准输入读取Dockerfile文件。
我们可以在第3小结的Dockerfile示例的基础上,使用docker build命令来创建一个镜像名为ubuntu_openjdk8版本为latest的镜像。
bash
docker build -t ubuntu_openjdk8:latest .
最后可以使用docker images命令来查看上步已完成制作的镜像。
最后可以使用docker images命令来查看上步已完成制作的镜像。