Docker原理剖析--文件系统

3,524 阅读10分钟

Overlay2

OverlayFS将单个Linux主机上的两个目录合并成一个目录。这些目录被称为层,统一过程被称为联合挂载 OverlayFS底层目录称为lowerdir, 高层目录称为upperdir,合并统一视图称为merged

                   ├───────────────────────────────────│
  Container Mount  │ FILE 1 │ FILE 2 │ FILE 3 │ FILE 4 │       "merged"
                   ├────↑────────↑────────↑───────↑────│
  Container layer  │    ↑   │ FILE 2 │    ↑   │ FILE 4 │       "upperdir"
                   ├────↑─────────────────↑────────────│
     Image layer   │ FILE 1 | FILE 2 | FILE 3 |        │       "lowerdir"
                   ├───────────────────────────────────│

存储流程

  • 最下层是lower层,也是只读/镜像层
  • upper是容器的读写层,采用了CoW(写时复制)机制,只有对文件进行修改才会将文件拷贝到upper层,之后所有的修改操作都会对upper层的副本进行修改
  • upper并列还有workdir层,它的作用是充当一个中间层的作用,每当对upper层里面的副本进行修改时,会先当到workdir,然后再从workdir移动upper
  • 最上层是mergedir,是一个统一图层,从mergedir可以看到lower,upper,workdir中所有数据的整合,整个容器展现出来的就是mergedir层.

image layerOverlayFS

每一个Docker image都是由一系列read-only layer组成的.Image layer的内容都存储在Docker hosts filesystem/var/lib/docker/overlay2下面

如果在没有拉取任何镜像的前提下,可以发现没有存储任何内容的

root@DESKTOP-UMENNVI:# ls -htral /var/lib/docker/overlay2
总用量 12K
drwx--x--x 15 root root 4.0K 3月   2 17:50 ..
drwx------  2 root root 4.0K 3月   2 17:50 l        ## 这里面都是软连接文件目录的简写标识,这个主要是为了避免mount时候页大小的限制
drwx------  3 root root 4.0K 3月   2 17:50 .

接下来拉取ubuntu:18.04镜像

## 拉取 ubuntu:18.04 镜像
root@DESKTOP-UMENNVI:~# docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
35c102085707: Downloading 
251f5509d51d: Downloading 
8e829fe70a46: Downloading 
6001e1789921: Downloading 
18.04: Pulling from library/ubuntu
423ae2b273f4: Pull complete 
de83a2304fa1: Pull complete 
f9a83bce3af0: Pull complete 
b6b53be908de: Pull complete 
Digest: sha256:04d48df82c938587820d7b6006f5071dbbffceb7ca01d2814f81857c631d44df
Status: Downloaded newer image for ubuntu:18.04
docker.io/library/ubuntu:18.04

## 查看 /var/lib/docker/overlay2
root@DESKTOP-UMENNVI:/var/lib/docker/overlay2# tree . -L 3
.
├── 06c3268634199360b720ea7e484497424245bce0ac59ba02843d862eb2d3e975
│   ├── committed
│   ├── diff
│   │   └── var
│   ├── link
│   ├── lower
│   └── work
├── 141562f2c1d9915cdadf58d5d8dbfdbe664ac5645dba4bfbf4665167acef03c3
│   ├── committed
│   ├── diff
│   │   ├── etc
│   │   ├── sbin
│   │   ├── usr
│   │   └── var
│   ├── link
│   ├── lower
│   └── work
├── 1c6bdda320809e237e381ea7bf1936d837f4cb96cfa4fe5ddf2a0878c37d6cf7
│   ├── committed
│   ├── diff
│   │   ├── bin
│   │   ├── boot
│   │   ├── dev
│   │   ├── etc
│   │   ├── home
│   │   ├── lib
│   │   ├── lib64
│   │   ├── media
│   │   ├── mnt
│   │   ├── opt
│   │   ├── proc
│   │   ├── root
│   │   ├── run
│   │   ├── sbin
│   │   ├── srv
│   │   ├── sys
│   │   ├── tmp
│   │   ├── usr
│   │   └── var
│   └── link
├── 758c83af3abbe440d44aa9f8e1fb6feb93a7ea51d5deda031a6cece579f51c04
│   ├── diff
│   │   └── run
│   ├── link
│   ├── lower
│   └── work
└── l
    ├── 5VTK2ORIHBK33URDA6QHOTNXFW -> ../758c83af3abbe440d44aa9f8e1fb6feb93a7ea51d5deda031a6cece579f51c04/diff
    ├── CJEDSUSQJVOA4SKGDYKCO6D2BA -> ../1c6bdda320809e237e381ea7bf1936d837f4cb96cfa4fe5ddf2a0878c37d6cf7/diff
    ├── GRZIGIYAS45PEF36RWVHBM4LGT -> ../06c3268634199360b720ea7e484497424245bce0ac59ba02843d862eb2d3e975/diff
    └── U2YACKS2WP5XTR7QY4YSPU6AN4 -> ../141562f2c1d9915cdadf58d5d8dbfdbe664ac5645dba4bfbf4665167acef03c3/diff

41 directories, 10 files
  1. 可以看到Ubuntu:18.04 镜像一共有4layer,每层的diff即是文件系统在统一挂载时的挂载点

  2. l文件夹下都是链接各个layer的软连接

  3. 查看1c6bdda320809e237e381ea7bf1936d837f4cb96cfa4fe5ddf2a0878c37d6cf7这个目录,可以看到已经基本系统的雏形了

  4. lower文件描述了层序的组织关系(前一个layer依靠后一个layer),比如查看

root@DESKTOP-UMENNVI:/var/lib/docker/overlay2# cat 758c83af3abbe440d44aa9f8e1fb6feb93a7ea51d5deda031a6cece579f51c04/lower

l/U2YACKS2WP5XTR7QY4YSPU6AN4:l/GRZIGIYAS45PEF36RWVHBM4LGT:l/CJEDSUSQJVOA4SKGDYKCO6D2BA

查看容器运行起来后的变化

接着以Ubuntu:18.04为基础镜像,创建一个名为test-ubuntu的镜像,这个镜像只是在/tmp文件夹中添加了Hello World文件,可以用Dockerfile来实现

FROM ubuntu:18.04

RUN echo "Hello World" > /tmp/newfile

执行构建镜像

## 进入到构建镜像文件夹内
root@DESKTOP-UMENNVI:DockerLayer# docker build -t test-ubuntu .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu:18.04
 ---> 72300a873c2c
Step 2/2 : RUN echo "Hello World" > /tmp/newfile
 ---> Running in 0644707c9a4c
Removing intermediate container 0644707c9a4c
 ---> 8fe0d75009f9
Successfully built 8fe0d75009f9
Successfully tagged test-ubuntu:latest

然后执行 docker images 查看现有的镜像,可以清楚查看到test-ubuntu使用了哪些image layer

root@DESKTOP-UMENNVI:# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test-ubuntu         latest              8fe0d75009f9        27 minutes ago      64.2MB
ubuntu              18.04               72300a873c2c        9 days ago          64.2MB

## 查看镜像构建layer层
root@DESKTOP-UMENNVI:# docker history test-ubuntu
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
8fe0d75009f9        27 minutes ago      /bin/sh -c echo "Hello World" > /tmp/newfile    12B                 
72300a873c2c        9 days ago          /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           9 days ago          /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>           9 days ago          /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B                
<missing>           9 days ago          /bin/sh -c [ -z "$(apt-get indextargets)" ]     987kB               
<missing>           9 days ago          /bin/sh -c #(nop) ADD file:91a750fb184711fde…   63.2MB  

从输出中可以看到8fe0d75009f9 image layer 位于最上层,只有12B大小,由以下命令创建

/bin/sh -c echo "Hello World" > /tmp/newfile

所以我们构建的镜像test-ubuntu只占用了12B的磁盘空间, 而最下面的四层image layer则是构成ubuntu:18.04镜像的4个image layer. <missing>标记的layer,是因为在Docker v1.10版本 之前,每次都会创建一个新的图层作为commit操作的结果,Docker也会创建一个相应的图像,该图像由随机生成的256位UUID,通常称为图像ID(在用户界面中显示为12位十六进制字符串或长64位十六进制字符串) Docker将图层内容存储在目录中,并且可以给每个图层添加镜像名称(比如mysql:5.1.1.1),但很多图层其实是缓存(中间层)并不算是最终的镜像 所以从Docker v1.10开始,一个镜像可以包含多个图层,并且显示父级镜像的sha256值,其他图层则用<missing>代替

------------------------------

接下来查看test-ubuntu存储信息

root@DESKTOP-UMENNVI:/var/lib/docker/overlay2# docker inspect test-ubuntu
[
    {
        "Id": "sha256:8fe0d75009f9b670a0de0a42e47e54e924af8d053b6e06e3ec8f29879f320e9e",
        "RepoTags": [
            "test-ubuntu:latest"
        ],
        "RepoDigests": [],
        "Parent": "sha256:72300a873c2ca11c70d0c8642177ce76ff69ae04d61a5813ef58d40ff66e3e7c",
        "Comment": "",
        "Created": "2020-03-02T14:23:40.7871895Z",
        "Container": "0644707c9a4c2377e848d9766caea05a97b29499df7c89ac67a6b170694895ef",
        "ContainerConfig": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "echo \"Hello World\" > /tmp/newfile"
            ],
            "Image": "sha256:72300a873c2ca11c70d0c8642177ce76ff69ae04d61a5813ef58d40ff66e3e7c",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "DockerVersion": "19.03.4",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/bash"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:72300a873c2ca11c70d0c8642177ce76ff69ae04d61a5813ef58d40ff66e3e7c",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 64206129,
        "VirtualSize": 64206129,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/758c83af3abbe440d44aa9f8e1fb6feb93a7ea51d5deda031a6cece579f51c04/diff:/var/lib/docker/overlay2/141562f2c1d9915cdadf58d5d8dbfdbe664ac5645dba4bfbf4665167acef03c3/diff:/var/lib/docker/overlay2/06c3268634199360b720ea7e484497424245bce0ac59ba02843d862eb2d3e975/diff:/var/lib/docker/overlay2/1c6bdda320809e237e381ea7bf1936d837f4cb96cfa4fe5ddf2a0878c37d6cf7/diff",
                "MergedDir": "/var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6/merged",
                "UpperDir": "/var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6/diff",
                "WorkDir": "/var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:cc4590d6a7187ce8879dd8ea931ffaa18bc52a1c1df702c9d538b2f0c927709d",
                "sha256:8c98131d2d1d1c8339d268265005394de6cafe887020dcca9ba9d9d07a56280c",
                "sha256:03c9b9f537a4ae66d7ae7a4361e7f36e6755380107eadff3fbc11cd604c6c9b9",
                "sha256:1852b2300972ff9f68fd3d34f1f112df3e35757d91dcd40cc8b379bbf2be62d5",
                "sha256:8904c7689676b958102eff5a32550ed1c31348a2e99fab5233b5d5d11f4db187"
            ]
        },
        "Metadata": {
            "LastTagTime": "2020-03-02T22:23:40.8005173+08:00"
        }
    }
]

GraphDriver.Data可以看出我们新创建的图层在/var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6

## 进入到 /var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6 内

oot@DESKTOP-UMENNVI:~# cd /var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6

## 查看目录
root@DESKTOP-UMENNVI:/var/lib/docker/overlay2/1c9b3562edc8d89cd05813a4d2793e1de2694dc7f4f2f7e18684acf72f3726e6# tree .
.
├── diff
│   └── tmp
│       └── newfile
├── link
├── lower
└── work

3 directories, 3 files

diff/ 层下面多了一个tmp/newfile文件,这样我们在创建容器的时候,会根据LowerDir -> UpperDir/WorkDir -> MergedDir层最终在MergedDir层展示出来