searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

深入理解容器image

2024-08-19 09:30:22
8
0

前言

我们学习docker技术也许都是从执行命令docker pull ubuntu然后docker run -it ubuntu开始的,经过一段时间使用会对docker image有了基本认识,也大概了解了imagecontainer之间的关系,docker image是一个层次结构,通过union filesystem技术将若干个只读layer合并成一个文件系统,并在最上层叠加一个可读写layercontainer提供统一的根文件系统视图,通过copy on writewhite out技术,提供container内文件新增,删除和修改功能。随着学习深入还是有几个问题待解:首先,image是静态只读的,而container是动态读写的,启动container时,比如简单的一条命令docker run -it ubuntu,并没有其他参数,docker daemon是如何获取相应image的位置,又是如何利用层次结构信息组装出一个可读写的rootfs提供给container使用;其次,随着docker技术的普及,docker image是将来应用交付,测试和部署的唯一媒介。开发人员除了完成源代码开发和构建应用,也要学会如何制作image,不能只会使用现成的类似ubuntu这样的imageimage是使用docker build命令依据Dockfile文件生成的,学会从零开始构建一个简单但是可运行的image将非常有助于理解构建原理,对未来构建任何复杂的image是很好的起点。

 

准备工作

安装docker 版本1.11.0,环境配置(通过docker info获取) 

注意这里docker使用的存储驱动是aufsaufsdocker官方最早支持的存储驱动,稳定可用于生产环境。如果你的docker使用的存储驱动不是aufs,下文结果有所不同。

 

深入理解Docker image

ubuntu:latest为例,首先docker pull ubuntu,然后docker history ubuntu看它的层次结构。可以看到ubuntu:latest4image,这里上层依赖下层,最下层是base image。如果感觉不够直观,可以用dockviz(这个是第三方工具,可从github下载)看

每层image有个sha256hash id(图示中只截取前面几位),创建时间,创建命令,image大小。创建命令,其实对应生成该层的Dockerfile命令,我们看到还有image的大小为0B,说明该image应该是空的。

现在开始分析docker内部到底如何管理image的层次结构。从docker info知道dockerrootdir/var/lib/docker,它把所有的image layers存储在/var/lib/docker/aufs目录下,aufs3个子目录,分别是difflayersmnt

diff目录存储所有image layer内容,除1个目录没有内容外,其他都有若干个子目录

layers目录存储所有image layer的层次信息

从命令显示结果可以看到,每个image layer对应一个文件,文件内容是下层layer的名称。

mnt目录存储容器的每个image layer的对应挂载点的内容,因为现在还没创建任何容器,所以目录里面暂时都是空的。

其实,aufs目录只是存储image layers的内容,所有image layers的元数据都存储在另一个重要的目录/var/lib/docker/graph中,正是这个目录提供了全部layers的层次结构信息 

每个layer对应一个目录,每个目录下有2个文件(暂时不理_tmp目录),jsonlayersizelayersize文件内容是一个数字,表示该layer的大小。json文件有很多信息,为了输出更有可读性,用pythonjson模块来显示,命令是python -m json.tool json,在一堆冗长的输出找到2个信息:idparent id是本image layer的名称,而parent正是下层image layer的名称,通过这个关系依次查看其他几个image layer,可以得到整个ubuntu:latest4image layers的层次结构。

执行docker run -it ubuntu命令时docker daemon如何按照上面的层次信息组装出一个可读写的rootfs?熟悉Git的话都会想到肯定有一个地方,保存着一个指针,指向整个层次结构的,在/var/lib/docker目录有一个文件就有这个作用 。看到ubuntu:latest指向正是顶层image layer所在目录。

执行docker run -it ubuntu,可以看到容器中rootfs已经组装起来了

此时在/var/lib/docker/aufs的几个子目录,也发生了一些变化。diff中新增了2个名字几乎一样的目录,其中一个目录还有2个子目录

layers中新增了2个文件,文件名称与新增目录是对应的,这里的8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20-init8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20的下层

可以猜测新增的目录跟容器的可读写layer有关,试着验证下。在容器中新建一个文件testfile。果然在8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20目录中出现testfile文件,内容也是一致的。

新增还相对容易理解,在容器中删除文件呢?比如删除/var/log/dpkg.log,这个文件是位于8aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98image里面的,在容器中删除文件,如图148aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98image里文件依然存在,如图15。同时在8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20/var/log多了一个文件.wh.dpkg.log,大小是0。这就是传说中的whiteout技术,效果就是删除文件不必修改下层image,只要在顶层增加一个.wh文件,对应被删除的文件

最后看下如何修改文件,还是以倒霉的dpkg.log为例(谁叫你是日志文件呢,改就改了)。修改文件。在8aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98image里文件依然没有变化,如图188296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20/var/log多了一个文件dpkg.log从文件大小可以看出这个dpkg.log是完整的修改后的文件,可不是增量的修改内容。这其实就是copy on write技术,修改后的文件覆盖原文件,而且不必修改下层image

构建最小image

从上节我们知道运行容器的必要条件就是要提供一个rootfs,之前使用的ubuntu就是一个rootfs,是从docker hubpull下载的。理论上只要具备组成rootfs的要素,就可以自己构建出一个image,使用它运行容器。构建docker image是用dockerfile完成的。我们来试着构建一个世界上最小的image

先写一个最简单的程序,hello,功能就是输出hello,docker!

编译,gcc -o hello hello.c得到hello。然后写一个Dockerfile

Dockerfile的语法规则简单解释下,FROM命令后面的参数是base image,一切Dockerfile都是从这句开始的。在现成的image上做二次开发,比如ubuntu就写FROM ubuntu,这里我们是从零开始,所以是FROM scratchscratch是空的base imageCOPY就是复制本地文件到image中,类似cp命令。CMD是容器启动时执行的命令。

有了Dockerfile,就可以开始构建。命令是docker build -t hello .-t的参数是image的名称,成功完成

可以看到新的image已经生成,大小约8.5kB

有了image,那就启动容器试试,结果报错了,没有输出期待的hello,docker! 

想到hello会依赖一些系统库文件,而scratch image里连/lib目录都没有 

看下hello到底依赖哪些系统库 

其中/lib/x86_64-linux-gnu/libc.so.6/lib64/ld-linux-x86-64.so.2是符号链接,分别指向/lib/x86_64-linux-gnu/libc-2.19.so/lib/x86_64-linux-gnu/ld-2.19.so修改Dockerfile,把它们全部放进去,修改后的Dockerfile

顺便说下这里分几次COPY而不是一次完成,是为了更好利用dockerbuild cache机制。再次build

新构建的image已经生成,大小接近4MB,当然大部分空间是系统库文件占用了。再次启动容器,输出正常

这样就制作出了一个不能再精简的image,其实以后要移植一些大型的应用,比如bashmysqltomcat等,道理都是一样的,只要把应用的可执行文件和所有依赖库复制进来就可实现,还有一些技巧,确保image的构建时间较短,层次结构合理,这对大型应用的image构建有重要意义。

0条评论
0 / 1000
季****华
1文章数
0粉丝数
季****华
1 文章 | 0 粉丝
季****华
1文章数
0粉丝数
季****华
1 文章 | 0 粉丝
原创

深入理解容器image

2024-08-19 09:30:22
8
0

前言

我们学习docker技术也许都是从执行命令docker pull ubuntu然后docker run -it ubuntu开始的,经过一段时间使用会对docker image有了基本认识,也大概了解了imagecontainer之间的关系,docker image是一个层次结构,通过union filesystem技术将若干个只读layer合并成一个文件系统,并在最上层叠加一个可读写layercontainer提供统一的根文件系统视图,通过copy on writewhite out技术,提供container内文件新增,删除和修改功能。随着学习深入还是有几个问题待解:首先,image是静态只读的,而container是动态读写的,启动container时,比如简单的一条命令docker run -it ubuntu,并没有其他参数,docker daemon是如何获取相应image的位置,又是如何利用层次结构信息组装出一个可读写的rootfs提供给container使用;其次,随着docker技术的普及,docker image是将来应用交付,测试和部署的唯一媒介。开发人员除了完成源代码开发和构建应用,也要学会如何制作image,不能只会使用现成的类似ubuntu这样的imageimage是使用docker build命令依据Dockfile文件生成的,学会从零开始构建一个简单但是可运行的image将非常有助于理解构建原理,对未来构建任何复杂的image是很好的起点。

 

准备工作

安装docker 版本1.11.0,环境配置(通过docker info获取) 

注意这里docker使用的存储驱动是aufsaufsdocker官方最早支持的存储驱动,稳定可用于生产环境。如果你的docker使用的存储驱动不是aufs,下文结果有所不同。

 

深入理解Docker image

ubuntu:latest为例,首先docker pull ubuntu,然后docker history ubuntu看它的层次结构。可以看到ubuntu:latest4image,这里上层依赖下层,最下层是base image。如果感觉不够直观,可以用dockviz(这个是第三方工具,可从github下载)看

每层image有个sha256hash id(图示中只截取前面几位),创建时间,创建命令,image大小。创建命令,其实对应生成该层的Dockerfile命令,我们看到还有image的大小为0B,说明该image应该是空的。

现在开始分析docker内部到底如何管理image的层次结构。从docker info知道dockerrootdir/var/lib/docker,它把所有的image layers存储在/var/lib/docker/aufs目录下,aufs3个子目录,分别是difflayersmnt

diff目录存储所有image layer内容,除1个目录没有内容外,其他都有若干个子目录

layers目录存储所有image layer的层次信息

从命令显示结果可以看到,每个image layer对应一个文件,文件内容是下层layer的名称。

mnt目录存储容器的每个image layer的对应挂载点的内容,因为现在还没创建任何容器,所以目录里面暂时都是空的。

其实,aufs目录只是存储image layers的内容,所有image layers的元数据都存储在另一个重要的目录/var/lib/docker/graph中,正是这个目录提供了全部layers的层次结构信息 

每个layer对应一个目录,每个目录下有2个文件(暂时不理_tmp目录),jsonlayersizelayersize文件内容是一个数字,表示该layer的大小。json文件有很多信息,为了输出更有可读性,用pythonjson模块来显示,命令是python -m json.tool json,在一堆冗长的输出找到2个信息:idparent id是本image layer的名称,而parent正是下层image layer的名称,通过这个关系依次查看其他几个image layer,可以得到整个ubuntu:latest4image layers的层次结构。

执行docker run -it ubuntu命令时docker daemon如何按照上面的层次信息组装出一个可读写的rootfs?熟悉Git的话都会想到肯定有一个地方,保存着一个指针,指向整个层次结构的,在/var/lib/docker目录有一个文件就有这个作用 。看到ubuntu:latest指向正是顶层image layer所在目录。

执行docker run -it ubuntu,可以看到容器中rootfs已经组装起来了

此时在/var/lib/docker/aufs的几个子目录,也发生了一些变化。diff中新增了2个名字几乎一样的目录,其中一个目录还有2个子目录

layers中新增了2个文件,文件名称与新增目录是对应的,这里的8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20-init8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20的下层

可以猜测新增的目录跟容器的可读写layer有关,试着验证下。在容器中新建一个文件testfile。果然在8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20目录中出现testfile文件,内容也是一致的。

新增还相对容易理解,在容器中删除文件呢?比如删除/var/log/dpkg.log,这个文件是位于8aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98image里面的,在容器中删除文件,如图148aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98image里文件依然存在,如图15。同时在8296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20/var/log多了一个文件.wh.dpkg.log,大小是0。这就是传说中的whiteout技术,效果就是删除文件不必修改下层image,只要在顶层增加一个.wh文件,对应被删除的文件

最后看下如何修改文件,还是以倒霉的dpkg.log为例(谁叫你是日志文件呢,改就改了)。修改文件。在8aa2fc7185e20bacda32d815eaae32cbc1c0457dc160ed5b3995ab79a8c7fd98image里文件依然没有变化,如图188296fab70eb4c7853701b28b6c5951918bf745e6c1befc5606cbfaed2156de20/var/log多了一个文件dpkg.log从文件大小可以看出这个dpkg.log是完整的修改后的文件,可不是增量的修改内容。这其实就是copy on write技术,修改后的文件覆盖原文件,而且不必修改下层image

构建最小image

从上节我们知道运行容器的必要条件就是要提供一个rootfs,之前使用的ubuntu就是一个rootfs,是从docker hubpull下载的。理论上只要具备组成rootfs的要素,就可以自己构建出一个image,使用它运行容器。构建docker image是用dockerfile完成的。我们来试着构建一个世界上最小的image

先写一个最简单的程序,hello,功能就是输出hello,docker!

编译,gcc -o hello hello.c得到hello。然后写一个Dockerfile

Dockerfile的语法规则简单解释下,FROM命令后面的参数是base image,一切Dockerfile都是从这句开始的。在现成的image上做二次开发,比如ubuntu就写FROM ubuntu,这里我们是从零开始,所以是FROM scratchscratch是空的base imageCOPY就是复制本地文件到image中,类似cp命令。CMD是容器启动时执行的命令。

有了Dockerfile,就可以开始构建。命令是docker build -t hello .-t的参数是image的名称,成功完成

可以看到新的image已经生成,大小约8.5kB

有了image,那就启动容器试试,结果报错了,没有输出期待的hello,docker! 

想到hello会依赖一些系统库文件,而scratch image里连/lib目录都没有 

看下hello到底依赖哪些系统库 

其中/lib/x86_64-linux-gnu/libc.so.6/lib64/ld-linux-x86-64.so.2是符号链接,分别指向/lib/x86_64-linux-gnu/libc-2.19.so/lib/x86_64-linux-gnu/ld-2.19.so修改Dockerfile,把它们全部放进去,修改后的Dockerfile

顺便说下这里分几次COPY而不是一次完成,是为了更好利用dockerbuild cache机制。再次build

新构建的image已经生成,大小接近4MB,当然大部分空间是系统库文件占用了。再次启动容器,输出正常

这样就制作出了一个不能再精简的image,其实以后要移植一些大型的应用,比如bashmysqltomcat等,道理都是一样的,只要把应用的可执行文件和所有依赖库复制进来就可实现,还有一些技巧,确保image的构建时间较短,层次结构合理,这对大型应用的image构建有重要意义。

文章来自个人专栏
季祥华
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
1
0