docker&kubernets中级篇(十一)

Docker镜像构建操作

Docker提供了比较简单的方式来构建镜像或者更新现有的镜像——docker build和docker commit。不过原则上讲,用户并不能“无中生有”地创建一个镜像,无论是启动一个容器或者构建一个镜像,都是在其他镜像的基础上进行的,Docker有一系列镜像称为基础镜像(如基础Ubuntu镜像ubuntu、基础Fedora镜像fedora等),基础镜像便是镜像构建的起点。不同的是,docker commit是将容器提交为一个镜像,也就是从容器更新或者构建镜像;而docker build是在一个镜像的基础上构建镜像。

docker commit命令只提交容器镜像发生变更了的部分,即修改后的容器镜像与当前仓库中对应镜像之间的差异部分,这使得该操作实际需要提交的文件往往并不多。Docker daemon接收到对应的HTTP请求后,需要执行的步骤如下。
(1) 根据用户输入pause参数的设置确定是否暂停该Docker容器的运行。
(2) 将容器的可读写层导出打包,该读写层代表了当前运行容器的文件系统与当初启动该容器的镜像之间的差异。
(3) 在层存储(layerStore)中注册可读写层差异包。
(4) 更新镜像历史信息和rootfs,并据此在镜像存储(imageStore)中创建一个新的镜像,记录其元数据。
(5) 如果指定了repository信息,则给上述镜像添加tag信息。

  1. build构建镜像一般来说,用户主要使用Dockerfile和docker build命令来完成一个新镜像的构建。这条命令的格式如下:
docker build [option] PAHT|URL| -

其中PATH或URL所指向的文件称为context(上下文), context包含build Docker镜像过程中需要的Dockerfile以及其他的资源文件。下面介绍该命令的执行流程。
● Docker client端当Docker client接收到用户命令,首先解析命令行参数。根据第一个参数的不同,将分为以下4种情况分别处理。
情况1:第一个参数为“-”,即

#没有STDI中读入Dockerfile,没有context
sudo docker build - < dockerfile
#或者从压缩文件中读取
sudo docker build - < context.tar.gz

此时,通过输入命令行参数对dockerfile或者context进行配置

情况2:第一个参数为URL,且是git repository URL,如

docker build github.com/create/docker-firefox

则调用git clone --depth 1–recursive命令克隆该GitHub repository,该操作会在本地的一个临时目录中进行,命令成功之后该目录将作为context传给Docker daemon,该目录中的Dockerfile会被用来进行后续构建Docker镜像。
情况3:第一个参数为URL,且不是git repository URL,则从该URL下载context,并将其封装为一个io流–io.Reader,后面的处理与情况1相同,只是将STDIN换为了io.Reader。
情况4:其他情况,即context为本地文件或目录的情况。

#使用当前文件做context
sudo docker build -t vieux/apache:2.0

或者
如果目录中有.dockerignore文件,则将context中文件名满足其定义的规则的文件都从上传列表中排除,不打包传给Docker daemon。但唯一的例外是.dockerignore文件中若误写入了.dockerignore本身或者Dockerfile,将不会产生作用。如果用户定义了tag,则对其指定的repository和tag进行验证。完成了相关信息的设置之后,Docker client向Docker server发送POST/build的HTTP请求,包含了所需的context信息

● Docker server端
Docker server接收到相应的HTTP请求后,需要做的工作如下。
(1) 创建一个临时目录,并将context指定的文件系统解压到该目录下。
(2) 读取并解析Dockerfile。
(3) 根据解析出的Dockerfile遍历其中的所有指令,并分发到不同的模块去执行。Dockerfile每条指令的格式均为INSTRUCTION arguments, INSTRUCTION是一些特定的关键词,包括FROM、RUN、USER等,都会映射到不同的parser进行处理。
(4) parser为上述每一个指令创建一个对应的临时容器,在临时容器中执行当前指令,然后通过commit使用此容器生成一个镜像层。
(5) Dockerfile中所有的指令对应的层的集合,就是此次build后的结果。如果指定了tag参数,便给镜像打上对应的tag。最后一次commit生成的镜像ID就会作为最终的镜像ID返回。

Docker镜像的分发方法

Docker技术兴起的原动力之一,是在不同的机器上创造无差别的应用运行环境。因此,能够方便地实现“在某台机器上导出一个Docker容器并且在另外一台机器上导入”这一操作,就显得非常必要。docker export与docker import命令实现了这一功能。当然,由于Docker容器与镜像的天然联系性,容器迁移的操作也可以通过镜像分发的方式达成,这里可以用到的方法是docker push和docker pull,或者docker save和docker load命令进行镜像的分发,不同的是docker push通过线上Docker Hub的方式迁移,而docker save则是通过线下包分发的方式迁移。所以,我们不难看到同样是对容器进行持久化操作,直接对容器进行持久化和使用镜像进行持久化的区别在于以下两点。

所以,我们不难看到同样是对容器进行持久化操作,直接对容器进行持久化和使用镜像进行持久化的区别在于以下两点。
❏ 两者应用的对象有所不同,docker export用于持久化容器,而docker push和docker save用于持久化镜像。
❏ 将容器导出后再导入(exported-imported)后的容器会丢失所有的历史,而保存后再加载(saved-loaded)的镜像则没有丢失历史和层,这意味着后者可以通过docker tag命令实现历史层回滚,而前者不行。更具体一些,我们可以从实现的角度来看一下pull、push、export以及save。1. pull镜像
Docker的server端收到用户发起的pull请求后,需要做的主要工作如下。
(1) 根据用户命令行参数解析出其希望拉取的repository信息,这里repository可能为tag格式,也可能为digest格式。
(2) 将repository信息解析为ReposotryInfo并验证其是否合法。
(3) 根据待拉取的repository是否为official版本以及用户没有配置Docker Mirrors获取endpoint列表,并遍历endpoint,向该endpoint指定的registry发起会话。endpoint偏好顺序为API版本v2>v1,协议https>http。
(4) 如果待拉取的repository为official版本,或者endpoint的API版本为v2, Docker便不再尝试对v1 endpoint发起会话,直接向v2 registry拉取镜像。
(5) 如果向v2 registry拉取镜像失败,则尝试从v1 registry拉取。

下面仅以向v2 registry拉取镜像的过程为例总结一次拉取过程。
(1) 获取v2 registry的endpoint。
(2) 由endpoint和待拉取镜像名创建HTTP会话、获取拉取指定镜像的认证信息并验证API版本。
(3) 如果tag值为空,即没有指定标签,则获取v2 registry中repository的tag list,然后对于tag list中的每一个标签,都执行一次pullV2Tag方法。该方法的功能分成两大部分,一是验证用户请求;二是当且仅当某一层不在本地时进行拉取这一层文件到本地。

注意 以上描述的是Docker server端对于tag为空的处理流程。需要说明的是,Docker client端在pull镜像时如果用户没有指定tag,则client会默认使用latest作为tag,即Docker server端会收到latest这个tag,所以并不会执行以上描述的过程。但如果用户在client端没有指定tag,而是指定了下载同一个repository所有tag镜像的flag,即-a,那么传给server的tag仍然保持空,这时候才会执行以上描述的过程。

2 push镜像
当用户制作了自己的镜像后,希望将它上传至仓库,此时可以通过docker push命令完成该操作。而在Docker server接收到用户的push请求后的关键步骤如下。
(1) 解析出repository信息。
(2) 获取所有非Docker Mirrors的endpoint列表,并验证repository在本地是否存在。遍历endpoint,然后发起同registry的会话。如果确认会话对方API版本是v2,则不再对v1 endpoint发起会话。
(3) 如果endpoint对应版本为v2 registry,则验证被推registry的访问权限,创建V2Pusher,调用pushV2 Repository方法。这个方法会判断用户输入的repository名字是否含有tag,如果含有,则在本地repository中获取对应镜像的ID,调用pushV2Tag方法;如果不含有tag,则会在本地repository中查询对应所有同名repository,对其中每一个获取镜像ID,执行pushV2Tag方法。

3 docker export命令导出容器
Docker server接收到相应的HTTP请求后,会通过daemon实例调用ContainerExport方法来进行具体的操作,这个过程的主要步骤如下。
(1) 根据命令行参数(容器名称)找到待导出的容器。
(2) 对该容器调用containerExport()函数导出容器中的所有数据,包括:
❏ 挂载待导出容器的文件系统;
❏ 打包该容器basefs(即graphdriver上的挂载点)下的所有文件。以aufs为例,basefs对应的是aufs/mnt下对应容器ID的目录;
❏ 返回打包文档的结果并卸载该容器的文件系统。
(3) 将导出的数据回写到HTTP请求应答中。

4 docker save命令保存镜像
Docker client发来的请求由getImagesGet Handler进行处理,该Handler调用ExportImage函数进行具体的处理。ExportImage会根据imageStore、layerStore、referenceStore构建一个imageExporter,调用其save函数导出所有镜像。save函数负责查询到所有被要求export的镜像ID(如果用户没有指定镜像标签,会指定默认标签latest),并生成对应的镜像描述结构体。然后生成一个saveSession并调用其save函数来处理所有镜像的导出工作。
save函数会创建一个临时文件夹用于保存镜像json文件。然后循环遍历所有待导出的镜像,对每一个镜像执行saveImage函数来导出该镜像。另外,为了与老版本repository兼容,还会将被导出的repository的名称、标签及ID信息以JSON格式写入到名为repositories的文件中。而新版本中被导出的镜像配置文件名、repository的名称、标签以及镜像层描述信息则是写入到名为manifest.json的文件中。最后执行文件压缩并写入到输出流。saveImage函数首先根据镜像ID在imageStore中获取image结构体。其次是一个for循环,遍历该镜像RootFS中所有layer,对各个依赖layer进行export工作,即从顶层layer、其父layer及至base layer。循环内的具体工作如下。
(1) 为每个被要求导出的镜像创建一个文件夹,以其镜像ID命名。
(2) 在该文件夹下创建VERSION文件,写入“1.0”。
(3) 在该文件夹下创建json文件,在该文件中写入镜像的元数据信息,包括镜像ID、父镜像ID以及对应的Docker容器ID等。
(4) 在该文件夹下创建layer.tar文件,压缩镜像的filesystem。该过程的核心函数为TarLayer,对存储镜像的diff路径中的文件进行打包。
(5) 对该layer的父layer执行下一次循环。为了兼容V1版本镜像格式,上述循环保持不变,随后为该镜像生成一份名为$digest_id.json的配置文件,并将配置文件的创建修改时间重置为镜像的创建修改时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yitian_hm

您的支持是我最大鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值