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 Layer
和 Bootstrap 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 体现出来的效果越明显。
添加评论