作者:网易云基础服务姜政冬。
近几年 Docker 风靡技术圈,不少从业人员都或多或少使用过,也了解如何通过 Dockerfile 构建镜像,从远程镜像仓库拉取自己所需镜像,推送构建好的镜像至远程仓库,根据镜像运行容器等。这个过程十分简单,只需执行 docker build、docker pull、docker push、docker run 等操作即可。但大家是否想过镜像在本地到底是如何存储的?容器又是如何根据镜像启动的?推送镜像至远程镜像仓库时,服务器又是如何存储的呢?下面我们就来简单聊一聊。
Docker 镜像不是一个单一的文件,而是有多层构成。我们可通过 docker images 获取本地的镜像列表及对应的元信息, 接着可通过docker history 查看某个镜像各层内容及对应大小,每层对应着 Dockerfile 中的一条指令。Docker 镜像默认存储在 /var/lib/docker/中,可通过 DOCKER_OPTS 或者 docker daemon 运行时指定 –graph= 或 -g 指定。
Docker 使用存储驱动来管理镜像每层内容及可读写的容器层,存储驱动有 devicemapper、aufs、overlay、overlay2、btrfs、zfs 等,不同的存储驱动实现方式有差异,镜像组织形式可能也稍有不同,但都采用栈式存储,并采用 Copy-on-Write(CoW) 策略。且存储驱动采用热插拔架构,可动态调整。那么,存储驱动那么多,该如何选择合适的呢?大致可从以下几方面考虑:
Docker 容器其实是在镜像的最上层加了一层读写层,通常也称为容器层。在运行中的容器里做的所有改动,如写新文件、修改已有文件、删除文件等操作其实都写到了容器层。容器层删除了,最上层的读写层跟着也删除了,改动自然也丢失了。若要持久化这些改动,须通过 docker commit [repository[:tag]] 将当前容器保存成为一个新镜像。若想将数据持久化,或是多个容器间共享数据,需将数据存储在 Docker volume 中,并将 volume 挂载到相应容器中。
存储驱动决定了镜像及容器在文件系统中的存储方式及组织形式,下面分别对常见的 aufs、overlay 作一简单介绍。
AUFS 是 Debian (Stretch 之前的版本,Stretch默认采用 overlay2) 或 Ubuntu 系统上 Docker 的默认存储驱动,也是 Docker 所有存储驱动中最为成熟的。具有启动快,内存、存储使用高效等特点。如果使用的 Linux 内核版本为 4.0 或更高,且使用的是 Docker CE,可考虑使用overlay2 (比 AUFS 性能更佳)。
① 验证内核是否支持 AUFS
$ grep aufs /proc/filesystems
nodev aufs
② 若内核支持,可在 docker 启动时通过指定参数 –storage-driver=aufs 选择 AUFS
采用 AUFS 存储驱动时,有关镜像和容器的所有层信息都存储在 /var/lib/docker/aufs/ 目录下,下面有三个子目录:
读文件
容器进行读文件操作有以下三种场景:
修改文件或目录
容器中进行文件的修改同样存在三种场景:
OverlayFS 是一种类似 AUFS 的现代联合文件系统,但实现更简单,性能更优。OverlayFS 严格说来是 Linux 内核的一种文件系统,对应的 Docker 存储驱动为 overlay 或者 overlay2,overlay2 需 Linux 内核 4.0 及以上,overlay 需内核 3.18 及以上。且目前仅 Docker 社区版支持。条件许可的话,尽量使用 overlay2,与 overlay 相比,它的 inode 利用率更高。
容器如何使用 overlay/overlay2 读写文件
读文件
读文件存在以下三种场景:
修改文件或目录
写文件存在以下三种场景:
不少人可能经常使用 docker,那么有没有思考过镜像推送至远程镜像仓库,是如何保存的呢?Docker 客户端是如何与远程镜像仓库交互的呢?
我们平时本地安装的 docker 其实包含两部分:docker client 与 docker engine,docker client 与 docker engine 间通过 API 进行通信。Docker engine 提供的 API 大致有认证、容器、镜像、网络、卷、swarm 等,具体调用形式请参考:Docker Engine API。
Docker engine 与 registry (即:远程镜像仓库)的通信也有一套完整的 API,大致包含 pull、push 镜像所涉及的认证、授权、镜像存储等相关流程,具体请参考:Registry API。目前常用 registry 版本为 v2,registry v2 拥有断点续传、并发拉取镜像多层等特点。能并发拉取多层是因为镜像的元信息与镜像层数据分开存储,当 pull 一个镜像时,先进行认证获取到 token 并授权通过,然后获取镜像的 manifest 文件,进行 signature 校验。校验完成后,依据 manifest 里的层信息并发拉取各层。其中 manifest 包含的信息有:仓库名称、tag、镜像层 digest 等, 更多,请参考:manifest 格式文档。
各层拉下来后,也会先在本地进行校验,校验算法采用 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 地址、存储驱动信息、回调通知等。