前言
有一定旅行经验的朋友都知道,即使在出发前制定了详细的出行计划,也会在路途中因为各式各样的状况而不得不重新修改计划。这个状况在我们编写Dockerfile时一样存在。花了30分钟编写的Dockerfile在构建镜像的时候也会出现各式各样的状况。那么编写Dockerfile时有哪些可以优化的点呢?
在了解可优化点之前,先来了解下容器的分层文件系统。
分层文件系统
分层文件系统是容器技术中的重要概念,当通过Dockerfile构建镜像时,每一个可执行指令会被容器引擎一一执行,并形成临时的容器。最终叠加在一起成为新的镜像。这里有个名词,叫可执行指令,最常用的就是RUN指令,因为RUN指令会在构建时会形成新的镜像层。其它创建文件层的指令还有ADD和COPY。
对于以下的Dockerfile,可以通过 docker history 来查看分层文件系统的结构。各位需要注意的是docker history的输出结果和Dockerfile中指令的先后顺序。
#escape=` FROM microsoft/windowsservercore:1803 SHELL [ "powershell","-command" ] RUN $ErrorActionPreference = 'Stop'; ` $ProgressPreference = 'SilentlyContinue'; ` $null = New-Item -Path c:\apps -Type Directory RUN Invoke-WebRequest -Uri https://download.sysinternals.com/files/SysinternalsSuite.zip ` -UseBasicParsing -OutFile c:\apps\SysinternalsSuite.zip ` -Proxy http://192.168.0.124:1080; ` Expand-Archive -Path C:\apps\SysinternalsSuite.zip -DestinationPath C:\apps\sysinternals\ -Force; ` Remove-Item -Path c:\apps\SysinternalsSuite.zip ENTRYPOINT [ "powershell" ]
$ docker history greggu/sysinternals:20170516 IMAGE CREATED CREATED BY SIZE COMMENT 64b20b828374 2 weeks ago powershell -command #(nop) ENTRYPOINT ["pow… 41kB 5b6b75a8ed6c 2 weeks ago powershell -command Invoke-WebRequest -Uri h… 94.2MB 8cdde7fb6229 2 weeks ago powershell -command $ErrorActionPreference =… 39MB 214857b207fb 2 weeks ago powershell -command #(nop) SHELL [powershel… 41kB ad6116672030 6 weeks ago Install update 10.0.14393.2189 2.79GB <missing> 17 months ago Apply image 10.0.14393.0 7.68GB
优化Dockerfile
对相关指令采取分组合并
首先针对以下Dockerfile构建镜像,请在构建过程中观察输出,也可以使用docker history查看。
FROM microsoft/windowsservercore:1803 SHELL [ "powershell","-command" ] RUN $ErrorActionPreference = 'Stop' RUN $ProgressPreference = 'SilentlyContinue' RUN $null = New-Item -Path c:\apps -Type Directory
$ docker history greggu/test:0.0.1 IMAGE CREATED CREATED BY SIZE COMMENT 158b934a3b80 About a minute ago powershell -command $null = New-Item -Path c… 32.7MB e3b45ff79cc9 2 minutes ago powershell -command $ProgressPreference = 'S… 32.7MB a61529eb0225 2 minutes ago powershell -command $ErrorActionPreference =… 39.1MB 214857b207fb 2 weeks ago powershell -command #(nop) SHELL [powershel… 41kB ad6116672030 6 weeks ago Install update 10.0.14393.2189 2.79GB <missing> 17 months ago Apply image 10.0.14393.0 7.68GB
可以看到每一句RUN指令都新生成了一层镜像层,导致镜像大小变大。优化的方法就是使用;和`(换行符)将多条PowerShell语句合并成一条执行。也就是在本文最开始使用的例子。
移除不再需要的文件
还是本文最开始使用的例子,在这个例子中,首先从网络下载SysinternalsSuite.zip压缩包,随后执行了解压操作。最后执行Remove-Item操作把压缩包删除。而这个删除操作减小了镜像体积。这点大家很容易理解,不再需要的文件删除即可。当然这里还有一个小点,为了让Dockerfile能在任意位置被使用,一般推荐通过网络下载所依赖的文件。所以Invoke-WebRequest的常用参数也是需要掌握的。
合理使用分层文件系统
在之前的例子中,各位可以看到每一行RUN指令都生成了新的一层镜像,从而增加最终镜像的大小。那么是不是意味这把所有指令都写在一行内就完成了最佳优化实践呢?这个理解是不对的。为了在构建镜像时使用到Docker的缓存功能,需要对构建指令进行分组。请各位准备以下两个Dockerfile并执行构建操作,并特别留意构建第二个镜像时第3步的输出。
# escape=` FROM microsoft/windowsservercore:1803 SHELL [ "powershell","-command" ] RUN $ErrorActionPreference = 'Stop'; ` $ProgressPreference = 'SilentlyContinue'; ` $null = New-Item -Path c:\demos -Type Directory RUN $null = New-Item -Path c:\demos\demo01 -Type Directory
# escape=` FROM microsoft/windowsservercore:1803 SHELL [ "powershell","-command" ] RUN $ErrorActionPreference = 'Stop'; ` $ProgressPreference = 'SilentlyContinue'; ` $null = New-Item -Path c:\demos -Type Directory RUN $null = New-Item -Path c:\demos\demo02 -Type Directory
因为使用了两个Dockerfile,需要在docker build时指定具体的Dockerfile,具体构建命令可以放在一个批处理文件中
docker build -f Dockerfile.1 --rm --tag greggu/demo01:0.0.1 . docker build -f Dockerfile.2 --rm --tag greggu/demo02:0.0.1 .
构建日志
PS C:\Users\greggu\repos\dow-playground\windows\demo02> .\build.ps1 Sending build context to Docker daemon 4.096kB Step 1/4 : FROM microsoft/windowsservercore:1803 ---> 7e2287b03e2e Step 2/4 : SHELL [ "powershell","-command" ] ---> Running in cb5a6e633a04 Removing intermediate container cb5a6e633a04 ---> 8fc2f8b81b5b Step 3/4 : RUN $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; $null = New-Item -Path c:\demos -Type Directory ---> Running in dce5d9150f26 Removing intermediate container dce5d9150f26 ---> 922a8037b585 Step 4/4 : RUN $null = New-Item -Path c:\demos\demo01 -Type Directory ---> Running in 69d0d0277786 Removing intermediate container 69d0d0277786 ---> 57bde82f4a6f Successfully built 57bde82f4a6f Successfully tagged greggu/demo01:0.0.1 Sending build context to Docker daemon 4.096kB Step 1/4 : FROM microsoft/windowsservercore:1803 ---> 7e2287b03e2e Step 2/4 : SHELL [ "powershell","-command" ] ---> Using cache ---> 8fc2f8b81b5b Step 3/4 : RUN $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; $null = New-Item -Path c:\demos -Type Directory ---> Using cache ---> 922a8037b585 Step 4/4 : RUN $null = New-Item -Path c:\demos\demo02 -Type Directory ---> Running in 295a54558ab5 Removing intermediate container 295a54558ab5 ---> 386e4f6cd28d Successfully built 386e4f6cd28d Successfully tagged greggu/demo02:0.0.1
对指令进行排序
由于Docker在处理Dockerfile时是自顶向下处理的,每行指令将和缓存的文件层进行对比。如果缓存中没有所需的文件层,Docker将按照Dockerfile的指令进行构建。为了合理利用缓存从而加快镜像构建速度,一般推荐将在多个镜像中共享的文件层的构建指令放在Dockerfile前部,而改变的部分放在Dockerfile尾部,具体的Dockerfile可以参考上一个优化建议。
Dockerfile编写风格
在编写Dockerfile时,一般遵循以下两点。第一点,所有Docker指令在Dockerfile中需要大写,从而和具体的操作命令区分开来。第二点,对于过长的指令请合理使用换行符,从而增加Dockerfile的可阅读性。
总结
本文总结了在构建Windows容器时,如何对Dockerfile进行优化,提高构建效率,在各位编写多个Dockerfile之后,相信会增加对本文的理解。