Dockerfile作为基础框架即代码的重要载体,随着容器化的不断推进,起到的作用愈加强大,Docker镜像在构建过程中,关于层数与大小的控制方面如何进行控制,在这篇文章中结合具体示例进行整理和总结。
说明1:基础镜像并非越小越好,满足需求和扩展性以及开发者使用的熟悉程度才是更重要的因素。
如果只是基础镜像越小越好的话,所有从from scratch生成的以busybox等小型系统为基础的镜像将会成为统一的选择。Alpine将镜像的大小控制在5M左右,但是也并非是所有的镜像都在统一使用Alpine镜像,使用centos和ubuntu的镜像仍然很多。Alpine镜像的重要基础之一的musl libc本身也给使用带来了限制性。
说明2: 镜像功能的组合和对镜像大小的需求之间的平衡
容器功能尽可能单一化与简单化,原本跑在虚拟机或者物理机上,现在几乎不做任何改变在容器中启动起来的容器化方式对系统改造影响确实会少一些,但是问题也会出现,比如可能会出现希望在一个容器中同时启动多个进程,这样的功能需求对镜像的大小和构建的复杂度都造成直接的影响,表面的现象可能是Docker的功能是否支持这样的特性需求,较深层次的问题往往是传统方式的架构在向着容器化和微服务演变的过程中的方式是否考虑到当下容器相关技术的特点和支撑能力。
我们知道镜像是分层的,如下图的示例所示,使用镜像所启动的容器之中,每一次的操作都会形成一个新的可写的层。
而在Dockerfile中,每一行的命令都会生成一个新层,比如我们以Alpine镜像作为基础镜像,在其中将时区设定从缺省的UTC改成CST,则需要如下三步即可
确认Alpine基础镜像的分层,可以看到有2层
[root@host132 ~]# docker run --rm -it alpine cat /etc/alpine-release
3.10.2
[root@host132 ~]# docker history alpine
IMAGE CREATED CREATED BY SIZE COMMENT
961769676411 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
3 weeks ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB
[root@host132 ~]#
在这个基础之上,如果使用如下Dockerfile
[root@host132 dockerfile]# cat Dockerfile
FROM alpine:3.10.2
RUN apk add tzdata
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
[root@host132 dockerfile]#
使用docker build进行构建,生成名为alpinemorelayers的镜像,构建的过程中已经能够看出镜像与各行命令的关系,即每行命令生成一个新的层。
[root@host132 dockerfile]# docker build -t alpinemorelayers .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM alpine:3.10.2
---> 961769676411
Step 2/4 : RUN apk add tzdata
---> Running in 324b5a1eb8d1
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2019b-r0)
Executing busybox-1.30.1-r2.trigger
OK: 9 MiB in 15 packages
Removing intermediate container 324b5a1eb8d1
---> 7c411dba6a51
Step 3/4 : RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
---> Running in 356cf9219b1c
Removing intermediate container 356cf9219b1c
---> 168dec202480
Step 4/4 : RUN echo "Asia/Shanghai" > /etc/timezone
---> Running in 140e1588aafc
Removing intermediate container 140e1588aafc
---> f0c65f318006
Successfully built f0c65f318006
Successfully tagged alpinemorelayers:latest
[root@host132 dockerfile]#
再次使用docker history命令来确认一下刚刚生成的镜像的分层状况:
[root@host132 dockerfile]# docker history alpinemorelayers
IMAGE CREATED CREATED BY SIZE COMMENT
f0c65f318006 17 seconds ago /bin/sh -c echo "Asia/Shanghai" > /etc/timez… 14B
168dec202480 18 seconds ago /bin/sh -c cp /usr/share/zoneinfo/Asia/Shang… 533B
7c411dba6a51 18 seconds ago /bin/sh -c apk add tzdata 2.76MB
961769676411 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
3 weeks ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB
[root@host132 dockerfile]#
可以看到上述方式重新生成了3个Layer,而如果希望减少层数,则只需要将使用连接符将三条命令连接起来就可以了,在Easypack的很多镜像的Dockerfile你可以看到尽可能的情况下每个Dockerfile中只有一个DOCKER RUN,比如上述Dockerfile可以简单地修改为:
[root@host132 dockerfile]# cat Dockerfile
FROM alpine:3.10.2
RUN apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
[root@host132 dockerfile]#
使用docker build进行构建,生成名为alpinelesslayers的镜像,构建的过程中已经能够看出只生成了一个新的层。
[root@host132 dockerfile]# docker build -t alpinelesslayers .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine:3.10.2
---> 961769676411
Step 2/2 : RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
---> Running in bdbc874c8c20
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2019b-r0)
Executing busybox-1.30.1-r2.trigger
OK: 9 MiB in 15 packages
Removing intermediate container bdbc874c8c20
---> 01ed1ebdaf3d
Successfully built 01ed1ebdaf3d
Successfully tagged alpinelesslayers:latest
[root@host132 dockerfile]#
使用docker history也可以清楚地看出,整体的分层只有三层:
[root@host132 dockerfile]# docker history alpinelesslayers
IMAGE CREATED CREATED BY SIZE COMMENT
01ed1ebdaf3d 2 minutes ago /bin/sh -c apk add tzdata && cp /usr/sha… 2.76MB
961769676411 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
3 weeks ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB
[root@host132 dockerfile]#
这样通过最简单的方式控制了分层的数量,将新增的层从3降为了1层。
说明:
不用的文件往往分为多种,在Dockerfile中往往会产生各种不用的临时文件,比如
不用的文件示例1: 使用COPY拷贝的tar文件,解压之后的原先的tar文件一直没有未被使用的情况,可以予以删除
不用文件示例2: 在安装或者设定的过程中产生的临时性的中间文件,不是用的时候也应该予以删除
不用文件示例3: 可将安装的组件进行定制化的精简或者删除
不用文件示例4: 缓存文件根据系统的不同进行清空,也可以有效地降低缓存的大小
层数和镜像的大小并没有直接的关联,比如仍以在上一个环节中进行示例的镜像文件进行说明
[root@host132 dockerfile]# docker images |egrep 'alpinelesslayers|alpinemorelayers|3.10.2'
alpinelesslayers latest 01ed1ebdaf3d 11 minutes ago 8.34MB
alpinemorelayers latest f0c65f318006 28 minutes ago 8.34MB
alpine 3.10.2 961769676411 3 weeks ago 5.58MB
[root@host132 dockerfile]#
可以看到,所生成的名为alpinemorelayers的镜像新增了3个层,而alpinelesslayers新增了一个层,但是大小都是8.34MB,接下来将会与具体的示例进一步介绍如何减小文件的大小。
以Alpine下的安装为例,在使用apk进行安装或者更新的时候不可避免,其他操作系统也往往都是类似,如何将这些不用的文件进行删除,这就是在减少容器尺寸中所需要注意的。比如可以在安装的添加no-cache即可不必产生临时性的缓存文件,让我们来看一下修改后的Dockerfile
[root@host132 dockerfile]# cat Dockerfile
FROM alpine:3.10.2
RUN apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
[root@host132 dockerfile]#
使用docker build构建生成新的镜像
[root@host132 dockerfile]# docker build -t alpinewithoutcache .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine:3.10.2
---> 961769676411
Step 2/2 : RUN apk --no-cache add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
---> Running in 9c9cbc8bc03d
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2019b-r0)
Executing busybox-1.30.1-r2.trigger
OK: 9 MiB in 15 packages
Removing intermediate container 9c9cbc8bc03d
---> 3349462ff9b6
Successfully built 3349462ff9b6
Successfully tagged alpinewithoutcache:latest
[root@host132 dockerfile]#
然后使用docker images命令确认此镜像的大小,可以看到从8.34MB降低为6.92MB
[root@host132 dockerfile]# docker images alpinewithoutcache
REPOSITORY TAG IMAGE ID CREATED SIZE
alpinewithoutcache latest 3349462ff9b6 About a minute ago 6.92MB
[root@host132 dockerfile]#
在Alpine中通过安装tzdata,然后通过设定相应的内容,使得镜像的本地时区为CST,tzdata本身就类似时区信息的相关的数据库,设定时只需要用到CST的内容,实际上安装之后将其内容拷贝出来进行设定,然后删除tzdata即可。所以了解到这个程度之后即可对对Dockerfile进行如下修改:
[root@host132 dockerfile]# cat Dockerfile
FROM alpine:3.10.2
RUN apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata \
&& rm /var/cache/apk/*
[root@host132 dockerfile]#
然后使用此Dockerfile进行镜像的构建
[root@host132 dockerfile]# docker build -t alpineconcise .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine:3.10.2
---> 961769676411
Step 2/2 : RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone && apk del tzdata && rm /var/cache/apk/*
---> Running in 8563e2723923
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2019b-r0)
Executing busybox-1.30.1-r2.trigger
OK: 9 MiB in 15 packages
(1/1) Purging tzdata (2019b-r0)
Executing busybox-1.30.1-r2.trigger
OK: 6 MiB in 14 packages
Removing intermediate container 8563e2723923
---> 09c08d393e73
Successfully built 09c08d393e73
Successfully tagged alpineconcise:latest
[root@host132 dockerfile]#
而使用docker image也可以看到通过这两个步骤,显著的降低了镜像尺寸的大小。
[root@host132 dockerfile]# docker images |egrep 'alpineconcise|3.10.2'
alpineconcise latest 09c08d393e73 30 minutes ago 5.6MB
alpine 3.10.2 961769676411 3 weeks ago 5.58MB
[root@host132 dockerfile]#
可以看到镜像最终只是从5.58MB上升到了5.6MB的程度。
Docker镜像的构建实践中,在对于层数和大小的控制上来说并不是一个比谁层数最少和尺寸最小的竞赛,将传统的基础架构方面的准备和设定也作为和源码一个级别进行管理和质量的控制才是Dockerfile实践中的最根本的内容,兼容性、易读性与性能等都是需要综合考虑的内容,而层数和大小也只是Dockerfile实践中的一个部分,需要我们花费更多的精力在这些细节上进行进一步地实践才能真正推动容器化更好地落地实施。