base镜像(通常为各种linux发行版的docker镜像):
(1)不依赖其他镜像,从scratch构建
(2)其他镜像可以以之为基础创建
镜像的分层结构
新镜像是从base镜像层一层一层叠加生成的。没安装一个软件,就现有镜像的基础层面上面增加一层
好处:共享资源。比如有多个镜像都是从相同的base镜像构建而来,那么docker host只需要在磁盘上保存一份base镜像;同时,内存中也只需要增加一份base镜像,就可以为所有容器服务了,而且镜像的每一层都可以被共享
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,用户只能访问上层容器中的文件。在容器层中,用户看到的是一个叠加之后的文件系统
(1)添加文件。在容器中创建文件时,新文件被添加到容器层中
(2)读取文件。在容器中读取某个文件时,docker会从上往下依次搜索各镜像层中查找此文件,一旦找到,打开并读入内存
(3)修改文件。在容器中修改已存在的文件时,docker会从上往下依次在各镜像层中查找此文件,一旦查找到,立即将其复制到容器层,然后修改之
(4)删除文件。在容器中删除文件时,docker也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作
注:只有方需要修改时才复制一份数据。这种特性被称做copy-on-write。可见。容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改
构建镜像
(1)docker commit
步骤:1.运行容器
2.修改容器
3.将容器保存为新镜像(docker commit 旧镜像名 新镜像名)
(2)docker file
步骤:1.编写dockerfile文件
2.运行docker build命令(docker build -t 新镜像名 .(命令末尾的.指明build-context为当前目录。docker默认会从build context中查找dockerfile文件,我们也可以通过-f参数指定dockerfile的位置))
3.开始构建过程,首先docker将docker context中的所有文件发送给docker daemon。build context为镜像构建提供所需要的文件或目录。dockerfile中的ADD、COPY等命令可以将build context中的文件添加到镜像
4.按dockerfile文件内容进行执行指令
5.镜像构建成功
查看镜像分层结构:docker history 镜像名
注:missing表示无法获取image id,通常从docker hub下载的镜像会有这个问题
镜像的缓存特性
docker会缓存已有的镜像层,构建镜像时,如果某镜像层已经存在,就直接使用,无需创建,如果不希望在构建镜像时使用缓存,可以在docker build命令中加上--no-cache参数。dockerfile中每一个指令都会创建一个镜像层,上层依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。除了构建时使用缓存,docker在下载镜像时也会使用
调试dockerfile
在执行dockerfile时,如果因为某种原因执行到某个指令失败了,也能够得到前一个指令成功执行构建出的容器,可以运行新的镜像,手动运行那条失败的指令,从而定位失败的原因
dockerfile常用指令
FROM:指定base镜像
MAINTAINER:设置镜像的作者,可以是任意字符串
COPY:将文件从build context复制到镜像。COPY支持两种形式:COPY src dest与COPY [“scr”,”dest”]
注意:src只能指定build context中的文件或目录
ADD:与COPY类似,从build context复制文件到镜像。不同的是,如果src是归档类文件(tar、zip、tgz、xz等),文件会被解压到dest
ENV:设置环境变量,环境变量可被后面的指令使用。例如:ENV MY_VERSION 1.3 RUN apt-get install -y mypackage=$MY_VERSION
EXPOSE:指定容器中的进程会监听某个端口 ,docker可以将端口暴露出来。会在容器网络部分详细讨论
VOLUME:将文件或目录申明为volume。会在容器存储部分详细讨论
WORKDIR:为后面的RUN、CMD、ENTRYPOINT、ADD或COPY指令设置镜像中当前的工作目录
RUN:在容器中运行指定的命令
CMD:在容器启动时运行指定的命令。dockerfile可以有多个CMD命令,但只有最后一个生效。CMD可以被docker run之后的参数替换
ENTRYPOINT:设置容器运行时运行的命令。dockerfile中可以有多个ENTRYPOINT指令,但只有最后一个生效。CMD或docker run之后的参数会被当做参数传递给ENTRYPOINT
RUN vs CMD vs ENTRYPOINT
RUN、CMD、ENTRYPOINT这三个dockerfile指令看上去很类似,很容易混淆。本节将通过实践详细讨论它们的区别
简单的说:
- RUN:执行命令并创建新的镜像层,RUN经常用于安装软件包
- CMD:设置容器启动后默认执行的命令及参数,但CMD能够被docker run后面跟的命令行参数替换
- ENTRYPOINT:配置容器启动时运行的命令
shell和exec格式
可以用两种方法指定RUN、CMD和ENTRYPOINT要运行的命令:shell格式和exec格式,二者在使用上有细微的差别。
shell格式:<instruction> <command>
例如:
RUN yum install python3
CMD echo “hello world”
ENTRYPOINT echo “hello world”
当执行指令时,shell格式底层会调用/bin/sh -c [command]。例如下面片段:
ENV name cloud ENTRYPOINT echo “hello,$name”
执行docker run时,将输出hello,cloud
exec格式:<instruction> [“executable”,”paraml”,”param2”]
例如:
RUN [“apt-get”,”install”,”python3”]
CMD [“/bin/echo”,”hello world”]
ENTRYPOINT [“/bin/echo”,”hello world”]
当执行指令时,会直接调用command,不会被shell解析。例如下面片段:
ENV name cloud [“/bin/echo”,”hello,$name”]
运行容器将输出hello,$name
注意:环境变量name没有被替换,如果希望使用环境变量,做如下修改
ENV name cloud ENTRYPOINT [“/bin/sh”,”-c”,”echo hello,$name”]
运行容器将输出hello,cloud
CMD和ENTRYPOINT推荐使用exec格式,因为指令可读性更强,更容易理解。RUN则两种格式都可以
RUN
RUN指令通常用于安装应用和软件包,RUN在当前镜像的顶部执行命令,并创建新的镜像层。dcokerfile中常常包含多个RUN指令。
RUN有两种格式:
(1)shell格式:RUN
(2)exec格式:RUN [“executable”,”param1”,”param2”]
下面是使用RUN安装多个包的例子:
RUN apt-get u[date && apt-get install -y subversion
注意:apt-get update和apt-get install被放在一个RUN指令中执行,这样能够保证每次安装的是最新的包。如果apt-get install在单独的RUN中执行,则会使用apt-get update创建镜像层,而这一层可能是很久以前的缓存
CMD
CMD指令允许用户指定容器的默认执行的命令,此命令会在容器启动且docker run没有指定其他命令时运行
如果docker run指定了其他命令,CMD指定的默认命令将被忽略,如果dockerfile中有多个CMD指令,只有最后一个有效
CMD有三种格式:
(1)exec格式:CMD [“executable”,”param1”,”param2”]这是CMD推荐格式
(2)CMD [“param1”,”param2”]为ENTRYPOINT提供额外的参数,此时ENTRYPOINT必须使用exec格式
(3)shell格式:CMD command param1 param2
第一种和第三种已经介绍过了,第二种要与exec格式的ENTRYPOINT指令配合使用,其用途是为ENTRYPOINT设置默认的参数。
下面看看CMD是如何工作的。dockerfile片段如下:
CMD echo “hello world”
运行容器docker run -it [image]将输出:hello world,但在后面加上一个命令,比如docker run -it [image] /bin/bash,CMD会被忽略掉,命令bash将被执行
ENTRYPOINT
ENTRYPOINT指令可以让容器以应用程序或者服务的形式运行,ENTRYPOINT看上去与CMD很像,它们都可以指定要执行的命令及参数。不同之处在于ENTRYPOINT不会被忽略,一定会被执行,即使docker run时指定了其他命令
ENTRYPOINT有两种格式:
- exec格式:ENTRYPOINT [“executable”,”param1”,”param2”]这是ENTRYPOINT的推荐格式
- shell格式:ENTRYPOINT command param1 param2
在为ENTRYPOINT选择格式时必须要小心,因为这两种格式的效果差别很大
- exec格式
ENTRYPOINT的exec格式用于设置要执行的命令及参数,同时可以通过CMD提供额外的参数,ENTRYPOINT中的参数始终会被使用,而CMD的额外参数可以在启动容器时动态替换
比如下面的dockerfile片段:
ENTRYPOINT [“/bin/echo”,”hello”] CMD [“world”]
当容器通过docker run -it [image]启动时,输出为:hello world
而如果通过docker run -it [image] cloud时,输出为:hello cloud
2.shell格式
ENTRYPOINT的shell格式会忽略任何CMD给docker run提供的参数
分发镜像
为镜像命名
镜像名一般是两部分组成的:[image name]=[repository]:[tag],如果没有指定tag,默认会使用latest,tag常用于描述镜像的版本信息,可以是任意字符串
注:latest其实没有什么特殊含义。当没有指定镜像tag时,docker会使用默认值latest,仅此而已。docker hub上很多仓库将latest作为最新稳定版本的别称,但这只是一种约定,而不是强制规定,所以最好还是避免使用这个
命令:docker tag 旧镜像名 新镜像名
使用公共registry
docker hub是docker公司维护的公共registry。用户可以将自己的镜像保存在docker hub免费的repository中,如果不希望别人访问自己的镜像,也可以购买私有repository
(1)首先在docker hub上注册一个账号
(2)在docker host上登录(docker login -u 用户名)
(3)修改镜像的repoistory,使之与docker hub账号对应。docker hub为了区分不同的同名镜像,镜像的registry中要包含用户名,完整的格式为:[username]/xxx:tag。通过docker tag命令重命名镜像
(4)docker push 镜像名:将镜像上传到docker hub
搭建本地registry
(1)启动registry容器(使用的是registry:2镜像)
-d:后台启动容器
-p:将容器的5000端口映射到host的5000端口。5000是registry服务端口
-v:将容器/var/lib/registry目录映射到host的/myregistry,用于存放镜像数据
通过docker tag重命名镜像,使之与registry匹配
在镜像前面加上了registry的主机名称和端口。前面已经讨论了镜像名称由repository和tag两部分组成。而repository的完整格式为:[registry-host]:[port]/[username]/xxx,只有docker hub上的镜像可以省略registry-host:[port]
(2)通过docker push上传镜像,此时,就可以使用docker pull进行下载镜像。