镜像的缓存特性
Docker会缓存已有的镜像层,构建新镜像时,如果镜像层已经存在,就直接使用,无需重新构建
举例说明
在前面构建的test/ubuntu镜像中添加新的内容,往里面复制一个文件
root@ubuntu:~# ls ① Dockerfile testfile root@ubuntu:~# root@ubuntu:~# docker build -t test/ubuntu:v2 . Sending build context to Docker daemon 32.77 kB Step 1 : FROM ubuntu ---> f753707788c5 Step 2 : RUN apt-get update && apt-get install -y vim ---> Using cache ② ---> 35ca89798937 Step 3 : COPY testfile / ③ ---> 8d02784a78f4 Removing intermediate container bf2b4040f4e9 Successfully built 8d02784a78f4
对上面的步骤进行相应的说明:先确保testfile文件存在。在Dockerfile文件执行过程中,运行RUN指令时,由于之前已经运行过相同的RUN指令,这次直接使用缓存中的镜像层。最后直接执行COPY指令,其过程是启动临时容器,复制testfile文件,提交新的镜像层,再删除生成的临时容器。全部过程执行完毕,就得到了所需的在test/ubuntu镜像上直接添加一层新的镜像的test/ubuntu:v2
如果构建镜像时不想使用缓存,可以在docker build命令中添加--no-cache参数
Dockerfile中每一个指令都会创建一个镜像层,上层依赖下层。无论什么时候,只要某一层发生变化,其上面的所有层的缓存都会失效。也就是说,如果我们修改Dockerfile指令的顺序,或者修改、添加指令,都会使缓存失效
比如这里修改前面的Dockerfile文件,交换一下指令的执行顺序
从逻辑上说,这对整个镜像的构建没有什么影响,但对于docker镜像的缓存来说,这里已经改变了下层镜像的结构,而上层镜像依赖于下层镜像,当下层镜像发生变化时,原来的镜像缓存则不再生效。这里的执行过程:先执行FROM指令,在本地找到基础镜像并运行一个容器,然后在该容器中执行COPY指令,该指令执行完毕后删除生成的临时容器,最后再执行RUN指令。如果是前面的顺序,执行RUN指令时会直接用test/ubuntu镜像的缓存,但这里先是执行了COPY指令,导致下层镜像发生变化,所以这里得重新执行RUN指令,其过程与test/ubuntu的过程一样
调试Dockerfile
先后顾Dockerfile构建镜像的过程
1. 从基础镜像运行一个容器
2. 执行一条指令,对容器进行修改
3. 执行类似docker commit的操作,生成一个新的镜像层
4. Docker再基于刚提交的镜像运行一个新的容器
5. 重复2-4步,直到Dockerfile中的所有指令执行完毕
从这个过程可以看出,如果Dockerfile由于某种原因执行到某个指令失败了,我们也能够得到前一个指令成功执行构建出的镜像,这对调试很有帮助,可以运行最新的镜像来定位指令失败的原因
下面来看一个调试的例子,Dockerfile文件的内容如下
执行docker build
Dockerfile运行到第三部时失败,我们可以利用第二步得到的镜像进行调试,方法是docker run -it启动镜像的一个容器
手工执行RUN命令很容易定位失败的原因,此处是因为busybox镜像中没有bash。这个例子很好的展示了Dockerfile的调试方法
Dockerfile常用的指令
FROM:指定基础镜像
MAINTAINER:说明镜像作者信息,可以是任意字符串。它不是Dockerfile的必要内容,但为了便于镜像的维护,建议写上去
COPY:将文件从build context复制到镜像
ADD:与COPY类似,从build context复制文件到镜像。不同的是,如果src是归档文件,文件会被自动解压到dest
ENV:设置环境变量,环境变量可被后面的指令使用
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、CMD、ENTRYPOINT的区别
1. RUN执行命令并创建新的镜像层,RUN经常用于安装软件包
2. CMD设置容器启动后默认执行的命令及其参数,但CMD能够被docker run后面跟的命令行参数替换
3. ENTRYPOINT配置容器启动时运行的命令
RUN
下面是使用RUN安装多个软件包的例子
RUN apt-get update && apt-get install -y wireshark vim wget mercurial subversion
注意:apt-get update和apt-get install被放在一个RUN指令中执行,这样能够保证每次安装的是最新的包。如果apt-get install在单独的RUN中执行,则会使用apt-get update创建的镜像层,而这一层可能是很久以前的缓存了
CMD
此命令在容器启动且docker run没有指定其他命令时运行
1. 如果docker run指定了其他命令,CMD指定的默认命令会被忽略
2. 如果Dockerfile中有多个CMD指令,只有最后一个有效
下面看看CMD是如何工作的,Dockerfile片段如下
CMD echo “Hello world”
运行容器docker run -it [ image ] 将输出
Hello world
但当后面加一个命令,比如docker run -it [ image ] /bin/bash,CMD会被忽略掉,命令bash将被执行
root@10a32dc7d3d3:/#
ENTRYPOINT
ENTRYPOINT指令可让容器以应用程序或者服务的形式运行
ENTRYPOINT看上去和CMD很像,它们都可以指定要执行的命令及其参数,不同的地方在于ENTRYPOINT不会被忽略,一定会被执行,即使运行docker run时指定了其他命令
使用公共Registry
下面介绍如何使用Docker Hub存放镜像
1. 首先得在Docker Hub上注册一个账号(当我们在hub.docker.com上注册账号时发现不能注册,可以在用谷歌浏览器登录,并在上面安装谷歌访问助手,安装完成后并可以注册。另一种方法是直接翻墙)
2. 在Docker Hub上登录
这里用的是我自己的账号,用户名为chenjin2018,输入密码即可登录
3. 修改镜像的repository使之与Docker Hub账号匹配
Docker Hub为了区分不同用户名的同名镜像,镜像的registry中要包含用户名,完整个格式为:[username]/xxx:tag
我们通过docker tag命令重命名镜像
4. 通过docker push将镜像上传到Docker Hub
5. 登录hub.docker.com,在Public Repository中就可以看到上传的镜像
如果要删除镜像,只能在Docker Hub界面上操作
6. 其他用户只需用docker pull就可以下载并使用这个镜像了
搭建本地的Registry
1. 启动registry容器
我们使用的镜像是registry:2
-d是以守护方式启动容器
-p将容器的5000端口映射到Host的5000端口。5000是registry服务端口
-v将容器/var/lib/registry目录映射到Host的/myregistry,用于存放镜像数据
2. 通过docker tag重命名镜像,使之与registry匹配
我们在镜像前面加了运行registry的主机名称和端口
repositroy的完整格式:[registry-host]:[port]/[username]/xxx
只有Docker Hub上的镜像可以省略[registry-host]:[port]
3. 通过docker push上传镜像
4. 现在已经可以通过docker pull从本地registry下载镜像了
Docker镜像小结
下面是镜像的常用操作子命令
images 显示镜像列表
history 显示镜像构建历史
commit 从容器创建新镜像
build 从Dockerfile中构建新镜像
tag 给镜像打tag
pull 从registry下载镜像
push 将镜像上传到registry
rmi 删除Docker host中的镜像
search 搜索Docker Hub中的镜像