Docker镜像的存储机制

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:

近几年 Docker 风靡技术圈,不少从业人员都或多或少使用过,也了解如何通过 Dockerfile 构建镜像,从远程镜像仓库拉取自己所需镜像,推送构建好的镜像至远程仓库,根据镜像运行容器等。这个过程十分简单,只需执行 docker build、docker pull、docker push、docker run 等操作即可。但大家是否想过镜像在本地到底是如何存储的?容器又是如何根据镜像启动的?推送镜像至远程镜像仓库时,服务器又是如何存储的呢?下面我们就来简单聊一聊。

Docker 镜像本地存储机制及容器启动原理

Docker 镜像不是一个单一的文件,而是有多层构成。我们可通过 docker images 获取本地的镜像列表及对应的元信息, 接着可通过docker history <imageId> 查看某个镜像各层内容及对应大小,每层对应着 Dockerfile 中的一条指令。Docker 镜像默认存储在 /var/lib/docker/<storage-driver>中,可通过 DOCKER_OPTS 或者 docker daemon 运行时指定 --graph= 或 -g 指定。

Docker 使用存储驱动来管理镜像每层内容及可读写的容器层,存储驱动有 DeviceMapper、AUFS、Overlay、Overlay2、Btrfs、ZFS 等,不同的存储驱动实现方式有差异,镜像组织形式可能也稍有不同,但都采用栈式存储,并采用 Copy-on-Write(CoW) 策略。且存储驱动采用热插拔架构,可动态调整。那么,存储驱动那么多,该如何选择合适的呢?大致可从以下几方面考虑:

  • 若内核支持多种存储驱动,且没有显式配置,Docker 会根据它内部设置的优先级来选择。优先级为 AUFS > Btrfs/ZFS > Overlay2 > Overlay > DeviceMapper。若使用 DeviceMapper 的话,在生产环境,一定要选择 direct-lvm, loopback-lvm 性能非常差。
  • 选择会受限于 Docker 版本、操作系统、系统版本等。例如,AUFS 只能用于 Ubuntu 或 Debian 系统,Btrfs 只能用于 SLES (SUSE Linux Enterprise Server, 仅 Docker EE 支持)。
  • 有些存储驱动依赖于后端的文件系统。例如,Btrfs 只能运行于后端文件系统 Btrfs 上。
  • 不同的存储驱动在不同的应用场景下性能不同。例如,AUFS、Overlay、Overlay2 操作在文件级别,内存使用相对更高效,但大文件读写时,容器层会变得很大;DeviceMapper、Btrfs、ZFS 操作在块级别,适合工作在写负载高的场景;容器层数多,且写小文件频繁时,Overlay 效率比 Overlay2 更高;Btrfs、ZFS 更耗内存。

Docker 容器其实是在镜像的最上层加了一层读写层,通常也称为容器层。在运行中的容器里做的所有改动,如写新文件、修改已有文件、删除文件等操作其实都写到了容器层。容器层删除了,最上层的读写层跟着也删除了,改动自然也丢失了。若要持久化这些改动,须通过 docker commit <containerId> [repository[:tag]] 将当前容器保存成为一个新镜像。若想将数据持久化,或是多个容器间共享数据,需将数据存储在 Docker volume 中,并将 volume 挂载到相应容器中。

存储驱动决定了镜像及容器在文件系统中的存储方式及组织形式,下面分别对常见的 AUFS、Overlay 作一简单介绍。

AUFS

AUFS 简介

AUFS 是 Debian (Stretch 之前的版本,Stretch默认采用 Overlay2) 或 Ubuntu 系统上 Docker 的默认存储驱动,也是 Docker 所有存储驱动中最为成熟的。具有启动快,内存、存储使用高效等特点。如果使用的 Linux 内核版本为 4.0 或更高,且使用的是 Docker CE,可考虑使用Overlay2 (比 AUFS 性能更佳)。

配置 AUFS 存储驱动

① 验证内核是否支持 AUFS

 
 
  1. $ grep aufs /proc/filesystems  
  2. nodev aufs 

② 若内核支持,可在 docker 启动时通过指定参数 --storage-driver=aufs 选择 AUFS

AUFS 存储驱动工作原理

采用 AUFS 存储驱动时,有关镜像和容器的所有层信息都存储在 /var/lib/docker/aufs/ 目录下,下面有三个子目录:

  • /diff:每个目录中存储着每层镜像包含的真实内容
  • /layers:存储有关镜像层组织的元信息,文件内容存储着该镜像的组建镜像列表
  • /mnt:挂载点信息存储,当创建容器后,mnt 目录下会多出容器对应的层及该容器的 init 层。目录名称与容器 ID 不一致。实际的读写层存储在 /var/lib/docker/aufs/diff,直到容器删除,此读写层才会被清除掉。

采用 AUFS 后容器如何读写文件?

读文件

  • 容器进行读文件操作有以下三种场景:
  • 容器层不存在: 要读取的文件在容器层中不存在,存储驱动会从镜像层逐层向下找,多个镜像层中若存在同名文件,上层的有效。
  • 文件只存在容器层:读取容器层文件

容器层与镜像层同时存在:读取容器层文件

修改文件或目录

容器中进行文件的修改同样存在三种场景:

  • 第一次写文件:若待修改的文件在某个镜像层中,AUFS 会先执行 copy_up 操作将文件从只读的镜像层拷贝到可读写的容器层,然后进行修改。在文件非常大的情况下效率比较低下。
  • 删除文件:删除文件时,若文件在镜像层,其实是在容器层创建一个特殊的 writeout 文件,容器层访问不到,并没有实际删掉。

目录重命名:目前 AUFS 还不支持目录重命名。

OverlayFS

OverlayFS 简介

OverlayFS 是一种类似 AUFS 的现代联合文件系统,但实现更简单,性能更优。OverlayFS 严格说来是 Linux 内核的一种文件系统,对应的 Docker 存储驱动为 Overlay 或者 Overlay2,Overlay2 需 Linux 内核 4.0 及以上,Overlay 需内核 3.18 及以上。且目前仅 Docker 社区版支持。条件许可的话,尽量使用 Overlay2,与 Overlay 相比,它的 inode 利用率更高。

容器如何使用 Overlay/Overlay2 读写文件

读文件

读文件存在以下三种场景:

  • 文件不存在容器层:若容器要读的文件不在容器层,会继续从底层的镜像层找
  • 文件仅在容器层:若容器要读的文件在容器层,直接读取,不用在底层的镜像层查找
  • 文件同时在容器层和镜像层:若容器要读的文件在容器层和镜像层中都存在,则从容器层读取

修改文件或目录

写文件存在以下三种场景:

  • 首次写文件:若要写的文件位于镜像层中,则执行 copy_up 将文件从镜像层拷贝至容器层,然后进行修改,并在容器层保存一份新的。若文件较大,效率较低。OverlayFS 工作在文件级别而不是块级别,这意味着即使对文件稍作修改且文件很大,也须将整个文件拷贝至容器层进行修改。但需注意的是,copy_up 操作仅发生在首次,后续对同一文件进行修改,操作容器层文件即可
  • 删除文件或目录:容器中删除文件或目录时,其实是在容器中创建了一个 writeout 文件,并没有真的删除文件,只是使其对用户不可见
  • 目录重命名:仅当源路径与目标路径都在容器层时,调用 rename(2) 函数才成功,否则返回 EXDEV

远程镜像仓库如何存储镜像?

不少人可能经常使用 Docker,那么有没有思考过镜像推送至远程镜像仓库,是如何保存的呢?Docker 客户端是如何与远程镜像仓库交互的呢?

我们平时本地安装的 Docker 其实包含两部分:docker client 与 docker engine,docker client 与 docker engine 间通过 API 进行通信。Docker engine 提供的 API 大致有认证、容器、镜像、网络、卷、swarm 等,具体调用形式请参考:Docker Engine API(https://docs.docker.com/engine/api/v1.27/#)。

Docker engine 与 registry (即:远程镜像仓库)的通信也有一套完整的 API,大致包含 pull、push 镜像所涉及的认证、授权、镜像存储等相关流程,具体请参考:Registry API(https://github.com/docker/distribution/blob/master/docs/spec/api.md)。目前常用 Registry 版本为 v2,Registry v2 拥有断点续传、并发拉取镜像多层等特点。能并发拉取多层是因为镜像的元信息与镜像层数据分开存储,当 pull 一个镜像时,先进行认证获取到 token 并授权通过,然后获取镜像的 manifest 文件,进行 signature 校验。校验完成后,依据 manifest 里的层信息并发拉取各层。其中 manifest 包含的信息有:仓库名称、tag、镜像层 digest 等, 更多,请参考:manifest 格式文档(https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md)。

各层拉下来后,也会先在本地进行校验,校验算法采用 sha256。Push 过程则先将镜像各层并发推至 Registry,推送完成后,再将镜像的 manifest 推至 Registry。Registry 其实并不负责具体的存储工作,具体存储介质根据使用方来定,Registry 只是提供一套标准的存储驱动接口,具体存储驱动实现由使用方实现。

目前官方 Registry 默认提供的存储驱动包括:微软 Azure、Google gcs、Amazon s3、OpenStack swift、阿里云 OSS、本地存储等。若需要使用自己的对象存储服务,则需要自行实现 registry 存储驱动。网易云目前将镜像存储在自己的对象存储服务 nos 上,故专门针对 nos 实现了一套存储驱动,另外认证服务也对接了网易云认证服务,并结合自身业务实现了一套认证、授权逻辑,并有效地限制了仓库配额。

Registry 干的事情其实很简单,大致可分为:① 读配置 ;② 注册 handler ;③ 监听。本质上 Registry 是个 HTTP 服务,启动后,监听在配置文件设定的某端口上。当 http 请求过来后,便会触发之前注册过的 Handler。Handler 包含 manifest、tag、blob、blob-upload、blob-upload-chunk、catalog 等六类,具体请可参考 Registry 源码: /registry/handlers/app.go:92。配置文件包含监听端口、auth 地址、存储驱动信息、回调通知等。 


原文发布时间为:2017-10-12

本文作者:姜政冬

本文来自云栖社区合作伙伴“51CTO”,了解相关信息可以关注。

相关实践学习
通过容器镜像仓库与容器服务快速部署spring-hello应用
本教程主要讲述如何将本地Java代码程序上传并在云端以容器化的构建、传输和运行。
Kubernetes极速入门
Kubernetes(K8S)是Google在2014年发布的一个开源项目,用于自动化容器化应用程序的部署、扩展和管理。Kubernetes通常结合docker容器工作,并且整合多个运行着docker容器的主机集群。 本课程从Kubernetes的简介、功能、架构,集群的概念、工具及部署等各个方面进行了详细的讲解及展示,通过对本课程的学习,可以对Kubernetes有一个较为全面的认识,并初步掌握Kubernetes相关的安装部署及使用技巧。本课程由黑马程序员提供。 &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
12天前
|
Docker 容器
将本地的应用程序打包成Docker镜像
将本地的应用程序打包成Docker镜像
|
12天前
|
数据库 Docker 容器
Docker在现代软件开发中扮演着重要角色,通过Dockerfile自动化构建Docker镜像,实现高效、可重复的构建过程。
Docker在现代软件开发中扮演着重要角色,通过Dockerfile自动化构建Docker镜像,实现高效、可重复的构建过程。Dockerfile定义了构建镜像所需的所有指令,包括基础镜像选择、软件安装、文件复制等,极大提高了开发和部署的灵活性与一致性。掌握Dockerfile的编写,对于提升软件开发效率和环境管理具有重要意义。
30 9
|
1月前
|
缓存 Linux 网络安全
docker的镜像无法下载如何解决?
【10月更文挑战第31天】docker的镜像无法下载如何解决?
1323 29
|
12天前
|
存储 缓存 运维
Docker镜像采用分层存储,每层代表镜像的一部分,如基础组件或应用依赖,多层叠加构成完整镜像
Docker镜像采用分层存储,每层代表镜像的一部分,如基础组件或应用依赖,多层叠加构成完整镜像。此机制减少存储占用,提高构建和传输效率。Docker还通过缓存机制提升构建和运行效率,减少重复工作。文章深入解析了Docker镜像分层存储与缓存机制,包括具体实现、管理优化及实际应用案例,帮助读者全面理解其优势与挑战。
32 4
|
29天前
|
存储 关系型数据库 Linux
【赵渝强老师】什么是Docker的镜像
Docker镜像是一个只读模板,包含应用程序及其运行所需的依赖环境。镜像采用分层文件系统,每次修改都会以读写层形式添加到原只读模板上。内核bootfs用于加载Linux内核,根镜像相当于操作系统,上方为应用层。镜像在物理存储上是一系列文件的集合,默认存储路径为“/var/lib/docker”。
|
1月前
|
存储 监控 Linux
docker构建镜像详解!!!
本文回顾了Docker的基本命令和管理技巧,包括容器和镜像的增删改查操作,容器的生命周期管理,以及如何通过端口映射和数据卷实现容器与宿主机之间的网络通信和数据持久化。文章还详细介绍了如何使用Docker部署一个简单的Web应用,并通过数据卷映射实现配置文件和日志的管理。最后,文章总结了如何制作自定义镜像,包括Nginx、Python3和CentOS镜像,以及如何制作私有云盘镜像。
129 2
|
1月前
|
关系型数据库 MySQL Docker
docker环境下mysql镜像启动后权限更改问题的解决
在Docker环境下运行MySQL容器时,权限问题是一个常见的困扰。通过正确设置目录和文件的权限,可以确保MySQL容器顺利启动并正常运行。本文提供了多种解决方案,包括在主机上设置正确的权限、使用Dockerfile和Docker Compose进行配置、在容器启动后手动更改权限以及使用 `init`脚本自动更改权限。根据实际情况选择合适的方法,可以有效解决MySQL容器启动后的权限问题。希望本文对您在Docker环境下运行MySQL容器有所帮助。
120 1
|
1月前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。
|
1月前
|
存储 缓存 Java
Java应用瘦身记:Docker镜像从674MB优化至58MB的实践指南
【10月更文挑战第22天】 在容器化时代,Docker镜像的大小直接影响到应用的部署速度和运行效率。一个轻量级的Docker镜像可以减少存储成本、加快启动时间,并提高资源利用率。本文将分享如何将一个Java基础Docker镜像从674MB缩减到58MB的实践经验。
73 1
|
29天前
|
缓存 JavaScript 安全
深入理解Docker镜像构建过程
深入理解Docker镜像构建过程
65 0