小哥之哥 小哥之哥
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小哥之哥

运维扫地僧
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Kubertenes

  • Prometheus

  • Docker

    • Docker实战

    • Docker杂谈

      • Namespace和Cgroups
      • Docker网络
      • Docker 存储引擎
    • 数据库

    • 运维利器

    • 运维
    • Docker
    • Docker杂谈
    tchua
    2023-02-20
    目录

    Docker 存储引擎

    # 一、前言


    本文主要探究docker存储引擎使用,并会对镜像及容器在宿主机底层文件系统中的存储进行细说。默认情况下,当我们安装好docker服务器之后,会在主机创建/var/lib/docker目录,该目录下保存容器相关信息: 网络、容器数据、卷、构建文件等。

    • Docker存储引擎

    image-20230217174417958

    • Docker根目录结构

    image-20230217173351990

    # 二、Docker 镜像的存储结构


    注意

    docker镜像采用分层设计,每一层都成为"layer",这些layer被放在了/var/lib/docker/<storage-driver>/下,这里的storage-driver可以有很多种如:AUFS、OverlayFS、VFS、Brtfs等。本文镜像存储位置 : /var/lib/docker/overlay2

    # 2.1 镜像存储内容

    我们以nginx镜像为例,对镜像进行探究


    # 可以看到该镜像共有6层
    [root@localhost overlay2]# docker pull nginx
    Using default tag: latest
    latest: Pulling from library/nginx
    bb263680fed1: Pull complete 
    258f176fd226: Pull complete 
    a0bc35e70773: Pull complete 
    077b9569ff86: Pull complete 
    3082a16f3b61: Pull complete 
    7e9b29976cce: Pull complete 
    Digest: sha256:6650513efd1d27c1f8a5351cbd33edf85cc7e0d9d0fcb4ffb23d8fa89b601ba8
    Status: Downloaded newer image for nginx:latest
    docker.io/library/nginx:latest
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    对于一个镜像,pull到本地之后,镜像的每一层都会存储在/var/lib/docker/overlay2/目录下,文件名为随机生成,其实就是下面将要介绍的cache-id


    [root@localhost ~]# ll /var/lib/docker/overlay2/
    total 0
    drwx--x---. 4 root root     72 Feb 17 17:53 17574149b77113b94a939436f238cb76f8a3e1f9f2ca54e74049191991c89672
    drwx--x---. 4 root root     72 Feb 17 17:53 74b2cdf01468ffa270f73c6e1678f8b263f61c0e64f0dad349ca503a9bc91de4
    drwx--x---. 4 root root     55 Feb 17 17:53 ab1bd2cb0dadb413e726246c0d87737db3c68cfd563ff221512decbd9e102632
    drwx--x---. 4 root root     72 Feb 17 17:53 b3ae534bd7e47e38cdc36ea977e292d5a85a6dd243941b4beb94b9477e6e558d
    brw-------. 1 root root 253, 0 Feb 14 10:18 backingFsBlockDev
    drwx--x---. 4 root root     72 Feb 17 17:53 d54a73b229b6f509b26aee4bad3f39a8ea88cf96237bbafdc00f9911ea0265e2
    drwx--x---. 3 root root     47 Feb 17 17:53 fbd6c79cba6c92bab616888278f0aca3b910b9f5b5ef18b8f11c7f90ea40e84f
    drwx------. 2 root root    210 Feb 17 18:03 l
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    image-20230217180806408

    • l : 所有层的软连接,短链接使用短名称,避免mount时候参数达到页面大小限制。
    • 该目录下的目录是镜像的每一层镜像信息,而不是某一个镜像的。

    探究镜像每一层


    说明

    • 处于底层的镜像目录包含了一个diff和一个link文件,diff目录存放了当前层的镜像内容,而link文件则是与之对应的短名称
    • 其它层的镜像还多了work目录和lower文件,lower文件用于记录父层的短名称,work目录用于联合挂载指定的工作目录。

    image-20230217182113718

    这些目录和镜像的关系是怎么组织在的一起呢?答案是通过元数据关联。元数据分为image元数据和layer元数据。

    # 2.2 image元数据

     #  Docker image信息存储目录
    [root@localhost ~]# tree /var/lib/docker/image/overlay2/ -L 1
    /var/lib/docker/image/overlay2/
    ├── distribution		# Image的所有Layer diffid和digest映射关系
    ├── imagedb				# 本地所有Image的元数据信息
    ├── layerdb				# 本地所有Image的所有Layer的索引结构
    └── repositories.json	# 本地所有repo的所有版本Image的信息
    
    1
    2
    3
    4
    5
    6
    7

    说明

    ​ 镜像元数据存储在了/var/lib/docker/image/overlay2/imagedb/content/sha256/,名称是以镜像ID命名的文件,镜像ID可通过docker images查看,这些文件以json的形式保存了该镜像的rootfs信息、镜像创建时间、构建历史信息、所用容器、包括启动的Entrypoint和CMD等等,通过格式化查看文件(vim :%!python -m json.tool),可以看到最下面diff_ids字段,对应的的是一个镜像层,其排列也是有顺序的,从上到下依次表示镜像层的最低层到最顶层。

    ​ 其实镜像ID就是通过该image的元数据配置文件信息通过sha256的结果命名的,例如:

    [root@localhost ~]# sha256sum /var/lib/docker/image/overlay2/imagedb/content/sha256/3f8a00f137a0d2c8a2163a09901e28e2471999fde4efc2f9570b91f1c30acf94
    3f8a00f137a0d2c8a2163a09901e28e2471999fde4efc2f9570b91f1c30acf94  /var/lib/docker/image/overlay2/imagedb/content/sha256/3f8a00f137a0d2c8a2163a09901e28e2471999fde4efc2f9570b91f1c30acf94
    [root@localhost ~]# docker images
    REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
    nginx        latest    3f8a00f137a0   10 days ago   142MB
    
    
    1
    2
    3
    4
    5
    6
    # 2.3 Image Layer元数据

    说明

    layer 对应镜像层的概念,在 docker 1.10 版本以前,镜像通过一个 graph 结构管理,每一个镜像层都拥有元数据,记录了该层的构建信息以及父镜像层 ID,而最上面的镜像层会多记录一些信息作为整个镜像的元数据。graph 则根据镜像 ID(即最上层的镜像层 ID) 和每个镜像层记录的父镜像层 ID 维护了一个树状的镜像层结构。   在 docker 1.10 版本后,镜像元数据管理巨大的改变之一就是简化了镜像层的元数据,镜像层只包含一个具体的镜像层文件包。用户在 docker 宿主机上下载了某个镜像层之后,docker 会在宿主机上基于镜像层文件包和 image 元数据构建本地的 layer 元数据,包括 diff、parent、size 等。而当 docker 将在宿主机上产生的新的镜像层上传到 registry 时,与新镜像层相关的宿主机上的元数据也不会与镜像层一块打包上传。   Docker 中定义了 Layer只读层和 RWLayer读写层 两种接口,分别用来定义只读层和可读写层的一些操作,又定义了 roLayer 和 mountedLayer,分别实现了上述两种接口。其中,roLayer 用于描述不可改变的镜像层,mountedLayer 用于描述可读写的容器层。具体来说,roLayer 存储的内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、父镜像层 parent、storage_driver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。这些元数据被保存在/var/lib/docker/image/<storage_driver>/layerdb/sha256/<chainID>/文件夹下。

    # Docker image 层级管理信息目录结构
    [root@localhost ~]# tree /var/lib/docker/image/overlay2/layerdb/
    /var/lib/docker/image/overlay2/layerdb/
    ├── mounts
    ├── sha256
    └── tmp
    # 元数据 每个chainID目录下都会有三个文件 cache-id、diff、zize
    [root@localhost ~]# ls -l /var/lib/docker/image/overlay2/layerdb/sha256/
    total 0
    drwx------. 2 root root 85 Feb 17 17:53 1486739bc51436dd10d2bc1d45e130771c73d3aee35e49971905aa767d195342
    drwx------. 2 root root 85 Feb 17 17:53 452008e5f3c114989bfc978a2829cf061f0868463f3553b4e20c964a41eda749
    drwx------. 2 root root 71 Feb 17 17:53 4695cdfb426a05673a100e69d2fe9810d9ab2b3dd88ead97c6a3627246d83815
    drwx------. 2 root root 85 Feb 17 17:53 ccfe545858415bccd69b8edff4da7344d782985f22ad4398bdaa7358d3388d15
    drwx------. 2 root root 85 Feb 17 17:53 cf7515030d4de4fb66994e0d9fccbaf19fcfbf46f7dad8cf895051750b840128
    drwx------. 2 root root 85 Feb 17 17:53 e34f63c02e162795cc8a2b43d1a3ff0ccd6d3456ce12aebb74452e252d1ecb8a
    # 
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    • cache-id:

      docker随机生成的uuid,内容是保存镜像层的目录索引,也就是/var/lib/docker/overlay2/中的目录,这就是为什么通过chainID能找到对应的layer目录。以chainID为1486739bc51436dd10d2bc1d45e130771c73d3aee35e49971905aa767d195342对应的cache-id文件内容为d54a73b229b6f509b26aee4bad3f39a8ea88cf96237bbafdc00f9911ea0265e2 ,也就保存在/var/lib/docker/overlay2/d54a73b229b6f509b26aee4bad3f39a8ea88cf96237bbafdc00f9911ea0265e2

      image-20230220115636933

    注意

    cache-id是宿主机创建或者拉取Imag的时候随机生成的,同一个Image被Push然后Pull后,该值是会变的。

    • diff:

    保存了镜像元数据中的diff_id(与元数据中的diff_ids中的uuid对应),该值是在Image创建后就固定不变的。

    通过/var/lib/docker/image/overlay2/imagedb/content/sha256/3f8a00f137a0d2c8a2163a09901e28e2471999fde4efc2f9570b91f1c30acf94文件,vim :%!python -m json.tool查看。

    image-20230220135419809

    • ChainID:

    关于ChainID计算,这里分为两种情况:

    • 如果该Layer是最底层Layer,那么该层:ChainID=DiffID。

    image-20230220142255100

    • 否则ChianID=SHA256(parent Layer ChainID + " " + DiffID)

    image-20230220142812569

    总结

    ImageID是通过Image的元数据配置信息文件经过SHA26得到的Hash值;image元数据,文件名以镜像ID命名,文件内容中roofs.diff_ids中的内容与Image Layer的元数据目录下diff文件内容一一对应,用于生成ChainID;Image Layer的元数据,文件以ChainID命名,目录下cache-id内容与宿主机/var/lib/docker/overlay2目录一一对应,通过该cache-id找到本层映射到宿主机上的存储目录。
    
    1

    image-20230220160937224

    # 三、Docker 容器的存储结构


    上面我们针对一个镜像pull或者本地构建后,各层文件存储相关的探究,接下来我们对Docker基于镜像运行后,容器的文件存储结构进行探究。

    # 3.1 运行镜像
    [root@localhost ~]# docker run -d --name nginx nginx:latest
    23fd9b4987a85160d44ca7ee4dc9d2e89139ad52723f68bba15c68cc7e0ffb7f
    
    1
    2
    # 3.2 查看目录

    当我们运行上面Nginx镜像之后,可以再次查看layerdb/mounts目录下会多出以ContainerID为目录的layer元数据管理层目录。

    # 容器层元数据目录
    [root@localhost ~]# ls -l  /var/lib/docker/image/overlay2/layerdb/mounts/23fd9b4987a85160d44ca7ee4dc9d2e89139ad52723f68bba15c68cc7e0ffb7f/
    total 12
    -rw-r--r--. 1 root root 69 Feb 20 16:37 init-id
    -rw-r--r--. 1 root root 64 Feb 20 16:37 mount-id
    -rw-r--r--. 1 root root 71 Feb 20 16:37 parent
    # 容器层数据存储目录
    [root@localhost ~]# ls /var/lib/docker/overlay2/bdbd8a7cb2ab6cf250c22d7abf8d57508cbc7f6f66e842f27a5b30fa63afc78e -l
    total 8
    drwxr-xr-x. 7 root root  62 Feb 20 16:37 diff
    -rw-r--r--. 1 root root  26 Feb 20 16:37 link
    -rw-r--r--. 1 root root 202 Feb 20 16:37 lower
    drwx------. 2 root root   6 Feb 20 16:37 merged
    drwx------. 3 root root  18 Feb 20 16:37 work
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    image-20230220170616287

    • diff: 目录为容器的读写层,容器内修改的文件都会在 diff 中出现。

    • merged:目录为分层文件联合挂载后的结果,也是容器内的工作目录。

    • init-id: 是由mountID+"-init"拼接而成,同样会索引到Container的init Layer的内容;该init Layer本身也是mountedLayer,最终Container的读写层是基于该init Layer+Image roLayer堆叠而成的。

    • mount-id: 随机生成,记录该容器层的cache-id,用来索引Container的读写层Layer内容存储目录。

    • parent: 指向Image最顶层的roLayer的ChainID。

    • init-id、mount-id,其实就是索引overlay2目录下该Layer存储目录中的两个文件。

    # 3.3 容器层读写文件

    这里我们运行容器之后,在容器中创建一个文件,然后观察容器层存储变化。

    [root@localhost ~]# docker exec -it nginx /bin/bash
    root@23fd9b4987a8:/# echo 123 >/tmp/1.txt
    root@23fd9b4987a8:/# cat /tmp/1.txt 
    123
    
    1
    2
    3
    4

    image-20230220171819518

    # 3.4 总结

    通过以上分析,对于一个完整的容器,我们可以分为如下的read-only,init 和readable/writeable三种:

    • read-only: 指的就是容器的rootfs镜像层。
    • init : Docker 项目单独生成的一个内部层,专门用来存放 /etc/hosts、/etc/resolv.conf等信息,作用是专门存放/etc/hosts、/etc/resolv.conf等信息,需要这一层的原因是当容器启动时候,这些本该属于image层的文件或目录,比如hostname,用户需要修改,但是image层又不允许修改,所以启动时候通过单独挂载一层init层,通过修改init层中的文件达到修改这些文件目的。而这些修改往往只读当前容器生效,而在docker commit提交为镜像时候,并不会将init层提交。该层文件存放的目录为/var/lib/docker/overlay2/<init_id>/diff 。
    • readable/writeable: 容器层,在没有写入文件之前,这个目录是空的。而一旦在容器里做了写操作,你修改产生的内容就会以增量的方式出现在这个层中。

    # 四、Docker容器分层文件系统


    上面我们了解了镜像及容器的存储结果,我们知道,对于一台宿主机往往不仅运行一个容器,随着运行的容器越来越多,/var/lib/docker/overlay2目录下的层也就会越多,如果我们想要定位运行容器的占用宿主机磁盘情况,那么就需要我们找到对应的关系。

    # 4.1 查看目前容器运行

    目前运行artalk、rabbitmq两个容器

    image-20230809093939376

    # 4.2 容器存储目录

    可以看到仅仅是运行2个容器,就有几十个层目录

    image-20230809094107944

    # 4.3 查找关联容器

    我们随便以一个层id,查找关联的容器855b9e0cac98ce02b8bce8908126186a73c596bcd2f6ddc2031e795b13adfbb0

    docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.Name}}, {{.GraphDriver.Data}}' | grep "855b9e0cac98ce02b8bce8908126186a73c596bcd2f6ddc2031e795b13adfbb0"
    
    1

    image-20230809094451263

    说明

    docker inspect --format命令可以指定格式获取Docker容器的详细信息:

    • .State.Pid :提取了容器的进程标识符(PID)。
    • .Name:获取容器名称,Docker 容器的名称可能包括斜杠(/),例如 /rabbitmq。
    • .GraphDriver.Data: 获取容器使用的存储驱动的相关数据,对于该参数,我们还可以更加详细的存储数据:
      • .GraphDriver.Data.WorkDir:这是容器内部进程的默认工作目录,它决定了容器内部进程在启动时所在的目录位置。
      • .GraphDriver.Data.LowerDir: 这是底层的只读文件系统层,通常包含基础操作系统的文件和目录。多个容器可以共享相同的 lowerdir。
      • .GraphDriver.Data.UpperDir:这是容器的可写文件系统层,包含了容器内部的变化。每个容器都会有自己的唯一的 upperdir。
      • .GraphDriver.Data.MergedDir: 这是 OverlayFS 的联合视图,它将 UpperDir 和 LowerDir 层叠加在一起,创建了一个统一的文件系统视图,供容器内的进程使用。

    有时候我们在/var/lib/docker/overlay2 目录下找到了子目录,但实际容器已经不存在,这可能是因为:

    • 容器已经被删除: 如果你删除了一个容器,Docker 可能会在一段时间后清理该容器的存储层(overlay2 子目录)。这是为了释放磁盘空间并维护系统的整洁性。所以,在子目录存在但容器不存在的情况下,很可能是该容器已被删除。

    • 容器没有正常停止: 如果一个容器没有正常停止(比如通过 docker stop 命令),而是强制终止或崩溃,Docker 可能会保留其存储层,以便在需要时进行调查和分析。这可能会导致在 /var/lib/docker/overlay2 目录下看到未运行的容器的子目录。

    • Docker 存储层问题: 有时,Docker 存储层的问题可能导致容器的子目录留下。这可能是由于未处理的 Docker 异常或存储层的不一致性。

    • 文件系统损坏: 在某些情况下,文件系统损坏可能会导致残留的容器子目录。这通常是一个不太常见的情况,但也需要考虑。

    # 4.4 使用场景

    我们通常会使用此方法,查找占用宿主机磁盘较大的容器:

    # 统计存储目录大小
    cd /var/lib/docker/overlay2
    du -h --max-depth=1
    # 查找关联容器
    docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.Name}}, {{.GraphDriver.Data.WorkDir}}' | grep "${上一步统计的子目录文件夹名称}"
    
    1
    2
    3
    4
    5
    编辑 (opens new window)
    上次更新: 2023/08/09, 10:00:51
    Docker网络
    MySQL 索引详解

    ← Docker网络 MySQL 索引详解→

    最近更新
    01
    cert-manager自动签发Lets Encrypt
    09-05
    02
    Docker构建多架构镜像
    08-02
    03
    Prometheus数据迁移至VMstorage
    08-01
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 |豫ICP备2021026650号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式