Docker镜像分层原理-联合文件系统(UnionFS)

文章目录

  • 前言
  • Docker镜像分层原理
    • Docker简述
    • 文件联合系统(UnionFS)
    • Docker使用UnionFS
      • 思考
      • 写时拷贝(Copy-on-Write)
      • Docker 镜像原理

前言

本文仅介绍Docker镜像分层原理-UnionFS
完整版Docker教程请移步:Docker 完整版教程笔记

Docker镜像分层原理

Docker简述

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。

文件联合系统(UnionFS)

UnionFS (Union File System)
2004年由纽约州立大学开发,它可以把多个目录内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFs可以把只读和可读写文件系统合并在一起,具有写时复制功能,允许只读文件系统的修改可以保存到可写文件系统当中。

  • UnionFs: UnionFS 时一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。
  • 特性: 一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

Docker使用UnionFS

Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像。可以制作各种具体的应用镜像。
分层的优点:

  • 分层最大的一个优点是共享资源;
  • 多个镜像都从相同的base镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像即可;
  • 同时内存中也只需要加载一份base镜像,就可以为所有容器服务,而且镜像的每一层都可以被共享。

思考

当我们浏览Docker hub时,能发现大多数镜像都不是从头开始制作,而是从一些base镜像基础上创建,比如centos基础镜像,而新镜像就是从基础镜像上一层层叠加新的逻辑构成的。这种分层设计,一个优点就是资源共享。

  1. 想象这样一个场景,一台宿主机上运行了100个基于centos base镜像的容器,难道每个容器里都有一份重复的debian拷贝呢?

这显然不合理!借助Linux的UnionFS,宿主机只需要在磁盘上保存一份base镜像,内存中也只需要加载一份,就能被基于这个镜像的所有容器共享。

  1. 当某个容器修改了基础镜像的内容,比如 /bin文件夹下的文件,这时其他容器的/bin文件夹是否会发生变化呢?

根据容器镜像的 写时拷贝(Copy-on-Write) 技术,某个容器对基础镜像的修改会被限制在单个容器内。

写时拷贝(Copy-on-Write)

容器镜像由多个镜像层组成,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /text,上层的 /text 会覆盖下层的 /text,也就是说用户只能访问到上层中的文件 /text,这就是COW技术。

Docker 镜像原理

Docker镜像分层原理-联合文件系统(UnionFS)_第1张图片Docker镜像分层原理-联合文件系统(UnionFS)_第2张图片

  • docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统为UnionFS

  • bootfs(boot file system) 主要包含bootloader 和 kernel,bootloader主要引导加载kernel,linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层时bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此使内存的使用权已由bootfs转交给内核,此使系统也会卸载bootfs。

  • rootfs(root file system) ,在bootfs之上。包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,CentOS等等

  • 将中间只读的 rootfs 的集合称为 Docker 镜像,Docker 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。UnionFS 使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

Docker镜像分层原理-联合文件系统(UnionFS)_第3张图片

  • 当用docker run启动这个容器时,实际上在镜像的顶部添加了一个新的可写层,这个可写层也叫容器层

Docker镜像分层原理-联合文件系统(UnionFS)_第4张图片

  • 容器启动后,其内的应用所有对容器的改动,文件的增删改操作都只会发生在容器层中,对容器层下面的所有只读镜像层没有影响。

Docker 镜像都是只读的,当容器启动时,一个新的写层加载到镜像的顶部,这一层就是我们通常说的容器层,容器之下的都叫镜像层

  • layer
# docker image inspect 镜像名
# 查看镜像分层方式
[root@VM-16-4-centos ~]# docker image inspect python
[
    {
        "Id": "sha256:0f95b1e38607bbf15b19ad0d111f2316e92eb047a35370eac71973c636acb9d2",
        "RepoTags": [
            "python:latest"
        ],
        "RepoDigests": [
            "python@sha256:eeed7cac682f9274d183f8a7533ee1360a26acb3616aa712b2be7896f80d8c5f"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-06-23T10:44:58.474893642Z",
        "Container": "fceecb8656d753a144b3968d1c6c9b8f5453ff307d0688242f642743d9680beb",
        "ContainerConfig": {
            "Hostname": "fceecb8656d7",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "LANG=C.UTF-8",
                "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
                "PYTHON_VERSION=3.10.5",
                "PYTHON_PIP_VERSION=22.0.4",
                "PYTHON_SETUPTOOLS_VERSION=58.1.0",
                "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/6ce3639da143c5d79b44f94b04080abf2531fd6e/public/get-pip.py",
                "PYTHON_GET_PIP_SHA256=ba3ab8267d91fd41c58dbce08f76db99f747f716d85ce1865813842bb035524d"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"python3\"]"
            ],
            "Image": "sha256:f968cc011b4d45dae5d5973d55488b86535bf317efb8f81481889503b79d1f8a",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.12",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "LANG=C.UTF-8",
                "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
                "PYTHON_VERSION=3.10.5",
                "PYTHON_PIP_VERSION=22.0.4",
                "PYTHON_SETUPTOOLS_VERSION=58.1.0",
                "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/6ce3639da143c5d79b44f94b04080abf2531fd6e/public/get-pip.py",
                "PYTHON_GET_PIP_SHA256=ba3ab8267d91fd41c58dbce08f76db99f747f716d85ce1865813842bb035524d"
            ],
            "Cmd": [
                "python3"
            ],
            "Image": "sha256:f968cc011b4d45dae5d5973d55488b86535bf317efb8f81481889503b79d1f8a",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 919687727,
        "VirtualSize": 919687727,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/f4cd5061845ac5514f5aaa387dd58b63ea37c14d4383e6f01360c5ab1a6357b5/diff:/var/lib/docker/overlay2/a2eeebe65fe6050b40368dfaaf6115998e46544636faf3c820c686d7fb1bde16/diff:/var/lib/docker/overlay2/4866d5cf1beb0816ba148e1700d92ea0c42cf1f7ba1ffd80e53113f95dcc2c8e/diff:/var/lib/docker/overlay2/bc1115c2e1d53bfd674718bdd08ef6a2c82c73667067fed2e05c18f2131e1c3d/diff:/var/lib/docker/overlay2/616914d1a8c57dd7386b550dbcd1ba501d11c09380afffa39fd227dcf3e4123d/diff:/var/lib/docker/overlay2/34ed671ac7b1d2cf8e30cf0f3cf85da97c34b643e217325c8110dea59c36c730/diff:/var/lib/docker/overlay2/b4e5edd486054fdcfbc558c6c2b64ccdd27e62776e6330828beab3674acfdcd6/diff:/var/lib/docker/overlay2/a09d8bfe4e8ff39f2b4b6589d36dc5bb97d3440aa2afc8d86e671a584f460eaa/diff",
                "MergedDir": "/var/lib/docker/overlay2/a34dc8c6fd5f524c445da51fff81a50b3e15cf53c1db69aadca32c732b88209b/merged",
                "UpperDir": "/var/lib/docker/overlay2/a34dc8c6fd5f524c445da51fff81a50b3e15cf53c1db69aadca32c732b88209b/diff",
                "WorkDir": "/var/lib/docker/overlay2/a34dc8c6fd5f524c445da51fff81a50b3e15cf53c1db69aadca32c732b88209b/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:97d5fec864d84417c057008f153140193d2cc924b545b0c6fec10ae891fb26f9",
                "sha256:6840c8ff46bd2c0ab4086d311c3e4903639b586c84d4f4ad28d156a2cc749e5f",
                "sha256:66183893ba248fa10375cfec3e598d7df626b44c7740156e935ce2fcd5589aa7",
                "sha256:5afd661c6106dfe99ed40c2dd18b8f6ffc5d978fa3302037b513c7a6e366609f",
                "sha256:33a247b4fc529be9d43b5e70cc7d1beadf12e58c6e74967a4ade33e5e58f936d",
                "sha256:ca5c6d5c3d01e1e0463415cd97adc7da3d3d5bb09f9ed01ec9d27cf6eeacb928",
                "sha256:a8db90eb5ce025d596d828c84dec139ef539fb2bfb67c47e046fff306cb8b79b",
                "sha256:9d17ba627a77ee6184993444d132f0dc9b7527db91af69f5fd9493dc29006957",
                "sha256:a84ac09f498fca42148b003e0a51971916a5ef676e676c9522910f1e406e12bb"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]



总结:

  1. 平时我们安装进虚拟机的CentOS都是好几个G,而docker为什么只需要200多M?
  • 这是因为对于精简的 OS,rootfs 可以很小,只需要包合最基本的命令,工具和程序库就可以了,因为底层直接用宿主机的kernel,自己只需要提供 rootfs 就可以了。
  • 由此可见对于不同的Linux发行版, bootfs 基本是一致的,rootfs会有差別,因此不同的发行版可以公用 bootfs。

你可能感兴趣的:(笔记,Docker,docker,容器,运维)