Docker (三) Docker镜像

镜像是什么

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

所有的应用,直接打包docker镜像,就可以直接跑起来!

如何得到镜像:

  • 从远程仓库下载
  • 朋友拷贝
  • 自己制作一个镜像DockerFile

Docker镜像加载原理

UnionFS(联合文件系统)

我们pull镜像的时候看到的一层一层的就是这个

UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite serveral directories into a single virtual filesystem)。Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性:一次同时加载多个文件系统,但是从外表看来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

镜像加载原理

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等等。

镜像加载原理.png

平时我们安装虚拟机的centOS都是好几个G,为什么docker这里才200M?

对于一个精简的OS,rootfs可以很小,只需要包含最基础的命令,工具和程序库就可以了,因为底层直接同host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的Linux发行版,bootfs基本是一指的,rootfs会有差别,不同的发行版本可以共用bootfs。

分层理解

镜像的 分层

我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层一层的在下载!

docker下载镜像时.png

为什么docker镜像会采用这种分层的结构呢?

最大的好处我觉得应该是资源的共享了,比如有多个镜像都从相同的base镜像构建而来,那么宿主机只需要在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。

可以通过docker image inspect redis查看镜像的分层

juning@chengjiajundeMacBook-Pro ~ % docker image inspect redis
[
    {
        "Id": "sha256:987b78fc9e38b2e1e42254002787e33b7e7d8b46995138f1d7783376d5accef4",
        "RepoTags": [
            "redis:latest"
        ],
        "RepoDigests": [
            "redis@sha256:89051d5ec46a89d4a708467af38eaaf4029450c4b1b9835ffd413cf70625b22e"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2020-05-18T18:28:51.663424193Z",
        "Container": "cfc523113233ffeb28fae725808fd4ea2036d1abe0ba062e11949bd6e0168539",
        "ContainerConfig": {
            "Hostname": "cfc523113233",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "6379/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "GOSU_VERSION=1.12",
                "REDIS_VERSION=6.0.3",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.3.tar.gz",
                "REDIS_DOWNLOAD_SHA=bca46dce81fe92f7b2de4cf8ae41fbc4b94fbd5674def7f12c87e7f9165cbb3a"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"redis-server\"]"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:07b0c1bae7d68640cc6925cb76ed505bfc13a4eca022cdbb96f6cb387de471df",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "/data",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "18.09.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "6379/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "GOSU_VERSION=1.12",
                "REDIS_VERSION=6.0.3",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.3.tar.gz",
                "REDIS_DOWNLOAD_SHA=bca46dce81fe92f7b2de4cf8ae41fbc4b94fbd5674def7f12c87e7f9165cbb3a"
            ],
            "Cmd": [
                "redis-server"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:07b0c1bae7d68640cc6925cb76ed505bfc13a4eca022cdbb96f6cb387de471df",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "/data",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 104120620,
        "VirtualSize": 104120620,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/129f7a36cde7b30fd1417214c0f98c736c7c2dbf1a861ebb0641bde6d3feb426/diff:/var/lib/docker/overlay2/081991d2656a1790412ec1fcd22afe0fc48a612c08a7ca6256d05fd13f4c0cfd/diff:/var/lib/docker/overlay2/4b99d2c381d3acd6c6ea0e87437f6f1b8dc94e5a68a5212b0bff82e50336f43b/diff:/var/lib/docker/overlay2/936e27a417e30003a8cd038b64983b1f99971e7c0c1778e604fca1f6ab85507a/diff:/var/lib/docker/overlay2/b92d56cd2b04d1a34fc0ea65322bbc5ce79839c9a22a79d7133fc1f70e53c12d/diff",
                "MergedDir": "/var/lib/docker/overlay2/3dc61ba9436540df70ec34b443437367203a7ea3d117fe71238f84e264bf1e0c/merged",
                "UpperDir": "/var/lib/docker/overlay2/3dc61ba9436540df70ec34b443437367203a7ea3d117fe71238f84e264bf1e0c/diff",
                "WorkDir": "/var/lib/docker/overlay2/3dc61ba9436540df70ec34b443437367203a7ea3d117fe71238f84e264bf1e0c/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:ffc9b21953f4cd7956cdf532a5db04ff0a2daa7475ad796f1bad58cfbaf77a07",
                "sha256:d4e681f320297add0ede0554524eb9106d8c3eb3a43e6e99d79db6f76f020248",
                "sha256:59bd5a888296b623ae5a9efc8f18285c8ac1a8662e5d3775a0d2d736c66ba825",
                "sha256:abef44452659f23ec349153a796bb160cefa667cb8d6d16d064fa9a0ab7f1dbb",
                "sha256:2f8fcc565367faa65192da55ce7c3ae8d73b92d69b5c0b0dbd5bd01215a11ae0",
                "sha256:5e107edf3216c060540ca8d2703a4c6fa836a6a9589033da9364bea8e0ea5a2a"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

所有的docker镜像都是起始于一个基础镜像,当进项修改或者增加新内容的时候,就会在当前镜像层之上创建一个新的镜像层。

举个简单的例子,假如基于Ubuntu创建一个新的镜像,这就是新镜像的第一层(add MySQL),如果在该镜像中添加NGINX包,那么会在之前的基础上再新建一层。

镜像的系统结构.png

在添加额外镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要,下图中举了一个例子,每个镜像包含三层,而这个大镜像包含了来自两个镜像的六个文件(你以为我在第二层,实际上我在第六层)。

镜像层级1.png

上图中的镜像层级跟之前图中的略有区别,主要目的是便于展示文件。

下图中展示了一个稍微复杂一点的三层镜像,在外部看来整个镜像只有六个文件,这是因为上层的文件7是文件5的一个更新版本。(你以为我是文件5其实我是文件7)

镜像层级2.png

这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件,这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。

docker通过存储引擎(新版本采用快照机制)的方法来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。

Linux上可用的存储引擎有AUFS、Overlay2、Device Mapper、Btrfs以及ZFS,每种存储引擎都基于Linux中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。

docker在Windows上仅支持Windowsfile一种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW(写时复制)

下图展示了与系统显示相同的三层镜像,所有镜像层堆叠合并,对外提供统一的视图。

镜像层级3.png

特点

docker镜像都是只读的,当容器启动的时候,一个新的可写层被加载到镜像的顶部!

这一层就是我们通常说的容器层,容器层之下的都是镜像层,而镜像层我们是不可以修改它的,因为它是只读层。

镜像层级4.png

commit镜像

docker commit 提交容器成为一个新的副本
# 命令和Git原理类似
docker commit -m="提交的描述信息" -a="作者" 容器ID  目标镜像名:[TAG]

实战测试

# 启动一个官方的Tomcat
docker run -it -p 8080:8080 tomcat:9.0
# 官方版的Tomcat的webapps文件夹下默认没有任何内容
# 自己将webapps.dist文件夹下的内容拷贝到webapps
docker commit -a="juning" -m="将webapps.dist下的文件拷贝到webapps文件夹下" f8d310faa2af tomcat_juning:1.0
# 将我们修改过的容器通过commit提交为一个新的镜像,我们以后直接使用即可
juning@chengjiajundeMacBook-Pro ~ % docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
tomcat_juning       1.0                 23b063af873c        53 seconds ago      652MB
redis               latest              987b78fc9e38        6 days ago          104MB
tomcat              9.0                 1b6b1fe7261e        9 days ago          647MB
centos              latest              470671670cac        4 months ago        237MB
上一篇 下一篇