7.4 容器镜像

容器镜像简单理解就是个 Gzip 压缩的特殊文件系统,内部包含了容器运行时所需的程序、库、资源、配置等。作为容器运行时文件系统视图基础,容器镜像衍生出了构建、存储、分发到运行时的整个镜像生命周期各种生态。早期的镜像格式是由 Docker 设计,现在镜像格式基本都遵循了 OCI 标准。

7.4.1 容器镜像的组成

镜像主要是由镜像层和运行时配置两大部分组成。镜像层和运行时配置各自有一个唯一 Hash,这些 Hash 会被写进一个叫 Manifest 的 JSON 文件里,在 Pull 镜像时实际就是先拉取 Manifest 文件,然后再根据 Hash 去 Registry 拉取对应的镜像层/容器运行时配置。

7.4.2 镜像标准规范

OCIv1 镜像主要包括以下几块内容:

  • Image Manifest:提供了镜像的配置和文件系统层定位信息,可以看作是镜像的目录,文件格式为 json 。
  • Image Layer Filesystem Changeset:序列化之后的文件系统和文件系统变更,它们可按顺序一层层应用为一个容器的 rootfs,因此通常也被称为一个 layer(与下文提到的镜像层同义),文件格式可以是 tar ,gzip 等存档或压缩格式。
  • Image Configuration:包含了镜像在运行时所使用的执行参数以及有序的 rootfs 变更信息,文件类型为 json。

我们把一个 Redis 解压之后,查看其目录结构,可以看到 有 oci-layout、index.json 以及 blobs 下 layer 配置信息等。

 $ tree redis
.
├── blobs
│   └── sha256
│       ├── 08769906aa59a6b44a9adc62fc0e0679e3448eaab13317401034f390435c14bf
│       ├── 376e1ba47d221972f7eb9dd659c50d8e42bcfd0e58382d755794a23a8b80976a
│       ├── 37e84c7a626f560a60b27167c9fa9e6c983d3edf548d84419ab018191dc37ae1
│       ...
├── index.json
└── oci-layout

 

其中,oci-layout 是 OCI 镜像的布局文件,主要说明它所遵循的镜像规范标准。

 $ cat oci-layout | jq
{
  "imageLayoutVersion": "1.0.0"
}

 

此处可以看到,该镜像遵循的标准为 OCI 1.0.0 布局规范。index.json 描述了 OCI 镜像的实际配置和其中 Layer 的信息,对其中 blobs 第一个 layer 进行解压查看,会发现是这一个 rootfs 目录,这也印证了我们前面所说镜像的本质。

 $ tar xzvf f03b40093957615593f2ed142961afb6b540507e0b47e3f7626ba5e02efbbbf1 -C test

$ cd test && ls 
bin	dev	home	lib64	mnt	proc	run	srv	tmp	var
boot	etc	lib	media	opt	root	sbin	sys	usr

 

7.4.3 镜像构建

我们先前面讲述过,容器镜像实际上就是利用 UnionFs 实现的一个特殊文件系统。那么容器镜像的构建就是基于底层 rootfs (基础镜像)定制上层配置、文件、依赖等信息。我们把每一层修改、操作命令都写入一个脚本,用这个脚本来构建、定制镜像,这个脚本就是 Dockerfile。

有了 Dockerfile 之后, 就可以制定自己的镜像规则,在 Dockerfile 上添加或者修改指令, 就可生成镜像产物。

docker 镜像构建步骤如下:

  • 编写 Dockerfile 文件
  • docker build 命令构建镜像
  • docker run 按照镜像运行容器实例。如下图所示:

1. Dockerfile 常用指令

通过 Dockerfile 构建镜像时,Docker 安装顺序读取 Dockerfile 内的指令,并解析出所有的指令。这些指令被分成多个层,每个层都对应着一个镜像层。

下表列举了常用的 Dockerfile 指令。

指令 用途
FROM 指定构建镜像的基础镜像
MAINTAINER 镜像的维护信息
RUN 构建镜像时运行的指令
COPY 复制文件或目录到镜像内(只能在构建镜像的主机上读取资源)
ADD 支持从远程服务器读取资源,复制到镜像内,同时支持自动解压 tar, zip 等压缩文件
ENV 环境变量设置
USER 指定运行 RUN、CMD COPY 等指令的用户
EXPOSE 容器运行的端口
WORKDIR 指定运行 RUN、CMD、COPY 指令的工作目录
VOLUME 设置挂载卷
CMD 启动后运行的指令

2. 镜像构建

熟悉常用的 Dockerfile 指令之后,我们可以开始尝试通过 Dockerfile 构建一个 Nginx 镜像。

 #第1阶段
FROM skillfir/alpine:gcc AS builder01
RUN wget https://nginx.org/download/nginx-1.24.0.tar.gz -O nginx.tar.gz && \
tar -zxf nginx.tar.gz && \
rm -f nginx.tar.gz && \
cd /usr/src/nginx-1.24.0 && \
 ./configure --prefix=/app/nginx --sbin-path=/app/nginx/sbin/nginx && \
  make && make install
  
#第2阶段
FROM skillfir/alpine:glibc
RUN apk update && apk upgrade && apk add pcre openssl-dev pcre-dev zlib-dev 

COPY --from=builder01 /app/nginx /app/nginx
WORKDIR /app/nginx
EXPOSE 80
CMD ["./sbin/nginx","-g","daemon off;"]

 

制作镜像

 docker build -t alpine:nginx .

 

查看镜像产物

 $ docker images 
REPOSITORY                TAG             IMAGE ID       CREATED          SIZE
alpine                    nginx           ca338a969cf7   17 seconds ago   23.4MB

 

测试镜像

 docker run --rm --name nginx -p 80:80 alpine:nginx

 

构建镜像最有挑战性之一的就是使用镜像尽可能小,小的镜像不论在大规模集群部署、故障转移、存储成本方面都有巨大的优势,以下是一些镜像构建的小技巧:
* 选用精简的基础镜像
* 使用多阶段构建
* COPY ADD 和 RUN 命令都会增加镜像层数,所以构建镜像时可以通过合并 RUN 指令减少叠加层,同时 RUN 命令最后可以通过一些工具的清理命令如yum clean conda clean --all来清理缓存,以此来减小 RUN 层的大小
* 在高层 layer 删除某文件时,该文件依然低层 layer 可见
* 尽量使用 COPY 命令而非 ADD 命令,可以在 RUN 命令中使用 wget curl 等命令替代 ADD
* 改动不频繁的 layer 尽量往前在 Dockerfile 的前面

7.4.3 镜像加速器 Nydus

随着时间推移,在大规模集群的应用实践上,目前镜像存在着一些问题。这些问题主要集中在:

  • 内容冗余:不同层之间相同信息在传输和存储时都是冗余内容,在不读取内容的时候无法判断到这些冗余的存在。
  • 无法并行:单一层是一个整体,对同一个层既无法并行传输,也不能并行提取。
  • 无法进行小块数据的校验 只有完整的层下载完成之后,才能对整个层的数据做完整性校验。

上述这些问题核心是是镜像的基本单位是 layer。但实际上,容器的运行整个镜像并不会被充分利用,一般镜像只有 6% 的内容会被实际用到。为了解决上面这些问题,我们就考虑实现一种新型的镜像结构,对镜像存储利用率、启动速度等更进一步优化,而这些就是 Nydus 要做的工作。

1. Nydus 介绍及特点

Nydus 是阿里云发起的基于延迟加载原理的镜像加速项目,配合 Dragonfly 做 P2P 加速,能够极大缩短镜像下载时间、提升效率,从而让用户能够更安全快捷地启动容器应用。

Nydus 提供了容器镜像按需加载的能力,在生产环境支撑了每日百万级别的加速镜像容器创建,在启动性能,镜像空间优化,端到端数据一致性,内核态支持等方面相比 OCIv1 有巨大优势。Nydus 符合 OCI 标准,与 containerd、CRI-O、Kata-containers 等流行的运行时有良好的兼容性。

Nydus 镜像格式并没有对 OCI 镜像格式在架构上进行修改,主要优化了其中的 Layer 数据层的数据结构。Nydus 将原本统一存放在 Layer 层的文件数据和元数据 (文件系统的目录结构、文件元数据等) 分开,分别存放在 Blob LayerBootstrap Layer 中。并对 Blob Layer 中存放的文件数据进行分块,以便于延迟加载 (在需要访问某个文件时,只需要拉取对应的 Chunk 即可,不需要拉取整个 Blob Layer) 。

同时,这些分块信息,包括每个 Chunk 在 Blob Layer 的位置信息等也被存放在 Bootstrap Layer 这个元数据层中。这样,容器启动时,仅需拉取 Bootstrap Layer 层,当容器具体访问到某个文件时,再根据 Bootstrap Layer 中的元信息拉取对应 Blob Layer 中的对应的 Chunk 即可。

2. Nydus 优势总结

总结,使用 Nydus 的优势如下:

  • 容器镜像按需下载,用户不再需要下载完整镜像就能启动容器。
  • 块级别的镜像数据去重,最大限度为用户节省存储资源。
  • 镜像只有最终可用的数据,不需要保存和下载过期数据。
  • 端到端的数据一致性校验,为用户提供更好的数据保护。
  • 兼容 OCI 分发标准和 artifacts 标准,开箱可用。
  • 支持不同的镜像存储后端,镜像数据不只可以存放在镜像仓库,还可以放到 NAS 或者类似 S3 对象存储中。
  • 与 Dragonfly 良好集成。

用户部署了 Nydus 镜像服务后,由于使用了按需加载镜像数据的特性,容器的启动时间明显缩短。在官网的测试数据中,Nydus 能够把常见镜像的启动时间,从数分钟缩短到数秒钟。理论上来说,容器镜像越大,Nydus 体现出来的效果越明显。


  • 无标签
写评论...