云原生制品那些事:容器镜像

Posted 亨利笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云原生制品那些事:容器镜像相关的知识,希望对你有一定的参考价值。

题图摄于北京颐和园


上篇文章和大家说到 的影响,原因是 Docker 发明的镜像格式极具革命性,无可替代。不管 Kubernetes 那边风吹浪打,Docker 我自巍然不动。从本篇开始和大家说说镜像那些事,共分四次连载,从《Harbor权威指南》一书节选的纯技术干货,敬请关注、转发和收藏。


第一篇:容器镜像的结构

第二篇:OCI 镜像规范

第三篇:OCI 制品

第四篇:Registry 的作用原理


《Harbor权威指南》目前当当网低于半价优惠中,点击下图直接购买。

相关活动:


容器镜像的结构


容器有不可改变性(immutability)和可移植性(portability)。容器把应用的可执行文件、依赖文件及操作系统文件等打包成镜像,使应用的运行环境固定下来不再变化;同时,镜像可在其他环境下重现同样的运行环境。这些特性给运维和应用的发布带来极大的便利,这要归功于封装应用的镜像。


1.1  镜像的发展


2013 年,Docker推出容器管理工具,同时发布了封装应用的镜像。这是 Docker与之前各种方案的重大区别,也是 Docker 得以胜出和迅速流传的主要原因。可以说,镜像体现了 Docker 容器的核心价值。



2016年,Docker 制定了镜像规范v2,并在 Docker 1.10 中实现了这个规范。镜像规范v2分为 Schema 1和 Schema 2。Schema 1主要兼容使用 v规范的 Docker 客户端,如 Docker 1.9 及之前的客户端。Schema主要实现了两个功能:支持多体系架构的镜像和可通过内容寻址的镜像,其中最大的改进就是根据内容的SHA256 摘要生成 ID,只要内容相同,ID 就是一样的,可区分相同的层文件(即可内容寻址)。从2017年2月起,镜像规范v1不再被 Registry 支持,用户需要把已有的v1镜像转化为v2镜像才能推送到Registry中。


OCI 在 2017 年 7 月发布了 OCI 镜像规范1.0。因为 Docker v2 的镜像规范已经成为事实上的标准,OCI 镜像规范实质上是以 Docker 镜像规范v2为基础制定的,因此二者在绝大多数情况下是兼容或相似的。


1.2  Docker 镜像的结构



Docker容器镜像通常由 “docker build” 命令依照 Dockerfile 构建的,Dockerfile 描述了镜像包含的所有内容和配置信息(如启动命令等)。下面是一个简单的Dockerfile 例子:


FROM ubuntu:20.04

RUN apt update && apt install -y python

RUN apt install -y python-numpy

ADD myApp.py /opt/


在这个例子中,容器镜像的基础镜像是操作系统 Ubuntu 20.04,然后安装Python 软件包,再安装 Python 库 NumPy,最后增加应用程序 myApp。在镜像构建完成之后会有4个层文件,如下图所示。

云原生制品那些事(1):容器镜像

图中的镜像层在容器创建时作为只读文件系统加载到容器中,此外,容器运行时会为每个容器实例都创建一个可读写层,叠加在文件系统的最上层,用于应用读写文件。容器的不可改变性就是通过镜像的镜像层(只读)实现的。另外,无论镜像在哪种环境下启动,始终有相同的镜像层,从而实现了应用的可移植性。


(1)方便基础层和依赖软件层的共享(如包含操作系统文件、软件包等),不同的镜像可以共享基础层或软件层,在同一台机器上存放公共层的镜像时只需保存一份层文件,可以大大减少文件存储空间。


(2)在构建镜像时,已构建过的层会被保存在缓存中,再次构建时如果下面的层不变,则可以通过构建缓存来缩短构建时间。


(3)因为很多时候同一个应用的镜像更新时变化的只是最上层(应用层),所以分层可以减少同种镜像的分发时间。


(4)分层可以更加方便地跟踪镜像的变化,因为每一层都是和构建命令关联的,所以可以更好地管理镜像的变化历史。


Docker容器的文件系统分层机制主要靠联合文件系统(UnionFS)来实现。联合文件系统保证了文件的堆叠特性,即上层通过增加文件来修改依赖层文件,在保证镜像的只读特性时还能实现容器文件的读写特性。


1.3  Docker镜像的仓库存储结构


Docker 容器镜像的存储分为本地存储和镜像仓库(Registry)存储。其中,本地存储指镜像下载到本地后是如何在本地文件系统中存储的;镜像仓库存储指镜像以什么方式存储在远端的镜像仓库中。



在说明镜像的存储格式之前,先介绍拉取同一个 Docker 镜像时可使用的两种不同命令格式。如下所示,latest 是镜像的 Tag,“sha256:46d659…a3ee9a”是镜像的摘要,在支持 Docker 镜像规范 v2 Schema 2 的镜像仓库中,二者都标识同一个镜像:

$ docker pull debian:latest

$ docker pull debian:@sha256:46d659005ca1151087efa997f1039ae45a7bf7a2cbbe2d17d3dcbda632a3ee9a


在镜像仓库上存储容器镜像的简化结构如下图所示,主要由三部分组成:清单文件(manifest)、镜像配置(configuration)和层文件(layers)。上面命令中的镜像摘要就是依据镜像清单文件内容计算 SHA256 哈希值而来的,在镜像清单文件中存放了配置文件的摘要和层文件的摘要,这些摘要都是通过具体的文件内容计算而来的,所以镜像存储也叫作内容寻址。


这样做的好处是,除了可以唯一标识不同的文件,还可以在传输过程中通过摘要做文件校验。在文件下载完成后,计算所下载文件的摘要值,然后与下载时的摘要标识进行对比,如果二者一致,即可判断下载的文件是正确的。需要指出的是,由于文件在镜像仓库端是以压缩形式存放的,所以摘要值也是基于压缩文件计算而来的。



Tag 在镜像仓库中可与镜像清单或者镜像索引关联,多个 Tag 可以对应同一个镜像清单或镜像索引,由镜像仓库维护着它们的映射关系,可参考上图(图中未包含镜像索引)。当客户端拉取镜像时,既可用 Tag,也可用镜像摘要获取同样的镜像。


1.4  Docker镜像的本地存储结构


Docker客户端从镜像仓库拉取一个镜像并存储到本地文件系统的过程大约如下。

(1)向镜像仓库请求镜像的清单文件。

(2)获取镜像ID,查看镜像ID是否在本地存在。

(3)若不存在,则下载配置文件 config,在 config 文件中含有每个层文件未压缩的文件摘要DIFF_ID。

(4)检查层文件是否在本地存在,若不存在,则从镜像仓库中拉取每一层的压缩文件。

(5)拉取时,使用镜像清单中压缩层文件的摘要作为内容寻址下载。

(6)下载完一层的文件后,解压并按照摘要校验。

(7)当所有层文件都拉取完毕时,镜像就下载完成了。

下载镜像后,在本地查看镜像 debian:latest 的信息,结果如下:

$ docker images debian:latest

REPOSITORY        TAG          IMAGE ID          CREATED          SIZE

debian            latest      1b686a95ddbf     2 weeks ago        114MB



使用配置文件的摘要作为本地镜像的标识,主要是因为本地镜像存放的文件都是非压缩的文件,而镜像仓库存放的是压缩文件,因此层文件在本地和镜像仓库中有不同的摘要值。因为压缩文件的内容会受到压缩算法等因素的影响,所以同样内容的层无法保证压缩后摘要的唯一性,而镜像清单文件包含压缩层文件的摘要(参考上文示例),因此通过镜像清单文件的摘要(即镜像摘要)无法确定镜像的唯一性。配置文件则不同,其中包含的层信息是未压缩的摘要值,因此相同镜像的各层内容必然相同,配置文件的摘要值是唯一确定的。


(未完待续,欢迎点“再看”或转发、分享、收藏)

《Harbor权威指南》目前当当网低于半价优惠中,点击上图直接购买。


以上是关于云原生制品那些事:容器镜像的主要内容,如果未能解决你的问题,请参考以下文章

[云原生那些事]k8s实践入门

面向云原生环境的安全体系

云原生架构师指南

[云原生]Docker

云原生之Docker实战使用Dockerfile构建docker镜像

云原生安全:Trivy + Harbor实现镜像漏洞的简单高效扫描