Docker 镜像不是一个单一的文件, 而是有多层构成. 我们可通过 docker images 获取本地的镜像列表及对应的元信息, 接着可通过 docker history 查看某个镜像各层内容及对应大小, 每层对应着 Dockerfile 中的一条指令.
近几年 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
AUFS 简介
AUFS 是 Debian (Stretch 之前的版本, Stretch 默认采用 overlay2) 或 Ubuntu 系统上 Docker 的默认存储驱动, 也是 Docker 所有存储驱动中最为成熟的. 具有启动快, 内存, 存储使用高效等特点. 如果使用的 Linux 内核版本为 4.0 或更高, 且使用的是 Docker CE, 可考虑使用 overlay2 (比 AUFS 性能更佳).
配置 AUFS 存储驱动
验证内核是否支持 AUFS
- $ grep aufs /proc/filesystems
- nodev aufs
若内核支持, 可在 docker 启动时通过指定参数 -storage-driver=aufs 选择 AUFS
AUFS 存储驱动工作原理
采用 AUFS 存储驱动时, 有关镜像和容器的所有层信息都存储在 / var/lib/docker/aufs/ 目录下, 下面有三个子目录:
采用 AUFS 后容器如何读写文件?
读文件
容器进行读文件操作有以下三种场景:
1. 容器层不存在: 要读取的文件在容器层中不存在, 存储驱动会从镜像层逐层向下找, 多个镜像层中若存在同名文件, 上层的有效.
2. 文件只存在容器层: 读取容器层文件
3. 容器层与镜像层同时存在: 读取容器层文件
修改文件或目录
容器中进行文件的修改同样存在三种场景:
第一次写文件: 若待修改的文件在某个镜像层中, 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 读写文件
读文件
读文件存在以下三种场景:
1. 文件不存在容器层: 若容器要读的文件不在容器层, 会继续从底层的镜像层找
2. 文件仅在容器层: 若容器要读的文件在容器层, 直接读取, 不用在底层的镜像层查找
3. 文件同时在容器层和镜像层: 若容器要读的文件在容器层和镜像层中都存在, 则从容器层读取
修改文件或目录
写文件存在以下三种场景:
1. 首次写文件: 若要写的文件位于镜像层中, 则执行 copy_up 将文件从镜像层拷贝至容器层, 然后进行修改, 并在容器层保存一份新的. 若文件较大, 效率较低. OverlayFS 工作在文件级别而不是块级别, 这意味着即使对文件稍作修改且文件很大, 也须将整个文件拷贝至容器层进行修改. 但需注意的是, copy_up 操作仅发生在首次, 后续对同一文件进行修改, 操作容器层文件即可
2. 删除文件或目录: 容器中删除文件或目录时, 其实是在容器中创建了一个 writeout 文件, 并没有真的删除文件, 只是使其对用户不可见
3. 目录重命名: 仅当源路径与目标路径都在容器层时, 调用 rename(2) 函数才成功, 否则返回 EXDEV
远程镜像仓库如何存储镜像?
不少人可能经常使用 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 地址, 存储驱动信息, 回调通知等.
来源: http://stor.51cto.com/art/201806/575386.htm