镜像分层结构理论详解2

镜像分层结构理论

要学习去构建镜像,就要做到为什么镜像会如此的小

从最小的镜像开始

世界上最小的镜像是hello-world(1.84kb)

[root@localhost ~]# docker images
REPOSITORY     TAG        IMAGE ID          CREATED             SIZE
hello-world    latest     fce289e99eb9      15 months ago       1.84kB

尝试运行这个镜像会发生什么

[root@localhost ~]# docker run -it --rm hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

运行后输出的内容就是这么多,也就是这个镜像所占用的空间,它类似于一个说明的文档

通过以下命令可以看到这个镜像的结构

[root@localhost ~]# docker history hello-world:latest 
IMAGE          CREATED         CREATED BY                                      SIZE    COMMENT
fce289e99eb9   15 months ago   /bin/sh -c #(nop)  CMD ["/hello"]               0B                  
<missing>      15 months ago   /bin/sh -c #(nop)  COPY file:f77490f70ce51da2…  1.84kB 

在官网中可以看到关于这个镜像构成的Dockerfile文件,是这样的

FROM scratch  # 从0开始构建镜像
COPY hello /  # 将镜像hello复制到容器的根目录,这个hello中就是以上的输出内容
CMD ["/hello"]  # 然后执行容器根下的hello

base镜像

base 镜像有两层含义:

  1. 不依赖其他镜像,从 scratch 构建。

    如:centos。常见的操作系统镜像都是只要构建的

  2. 其他镜像可以与之为基础进行扩展。

    如:在centos镜像生成的实例中安装软件,再次构成镜像

Linux发行版本的操作系统(centos/debian/ubuntu)都是base镜像

为什么centos只有200多M

[root@localhost ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED             SIZE
centos        latest    5e35e350aded   4 months ago        203MB

Linux 操作系统由内核空间和用户空间组成。

内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉

bootfs所有的容器都要和物理机通用,物理机的内核如下

[root@localhost ~]# uname -r
3.10.0-1062.el7.x86_64

下载ubuntu在centos主机运行看是否可以通用内核,通过查看发现,Ubuntu容器中的内核版本和物理机的内核是一样的,没有差别的一致

[root@localhost ~]# docker run -it --rm --name test ubuntu
root@9b1552dece57:/# uname -r
3.10.0-1062.el7.x86_64

用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。

对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。

centos镜像小就是因为,使用的物理机的系统也是centos,他们的相同点很多,大部分都会通过软链接的形式来使用物理机的内容

通过以下可以看到,即使是容器中的根目录,也有很多软链接

[root@localhost ~]# docker run -it --rm --name test centos
[root@438cee62ecb0 /]# ll
total 12
-rw-r--r--.   1 root root 12123 Oct  1 01:16 anaconda-post.log
lrwxrwxrwx.   1 root root     7 Oct  1 01:15 bin -> usr/bin
drwxr-xr-x.   5 root root   360 Mar 26 15:06 dev
drwxr-xr-x.   1 root root    66 Mar 26 15:06 etc
drwxr-xr-x.   2 root root     6 Apr 11  2018 home
lrwxrwxrwx.   1 root root     7 Oct  1 01:15 lib -> usr/lib
lrwxrwxrwx.   1 root root     9 Oct  1 01:15 lib64 -> usr/lib64
drwxr-xr-x.   2 root root     6 Apr 11  2018 media
drwxr-xr-x.   2 root root     6 Apr 11  2018 mnt
drwxr-xr-x.   2 root root     6 Apr 11  2018 opt
dr-xr-xr-x. 253 root root     0 Mar 26 15:06 proc
dr-xr-x---.   2 root root   114 Oct  1 01:16 root
drwxr-xr-x.  11 root root   148 Oct  1 01:16 run
lrwxrwxrwx.   1 root root     8 Oct  1 01:15 sbin -> usr/sbin
drwxr-xr-x.   2 root root     6 Apr 11  2018 srv
dr-xr-xr-x.  13 root root     0 Mar 24 20:50 sys
drwxrwxrwt.   7 root root   132 Oct  1 01:16 tmp
drwxr-xr-x.  13 root root   155 Oct  1 01:15 usr
drwxr-xr-x.  18 root root   238 Oct  1 01:15 var

base镜像是提供最小的linunx发行版的镜像

[root@localhost ~]# docker pull alpine
[root@localhost ~]# docker images
REPOSITORY      TAG        IMAGE ID         CREATED        SIZE
alpine          latest     a187dde48cd2     2 days ago     5.6MB

很多软件都是基于alpine构成的,可以让镜像变得更小,centos的200M对于容器中的镜像来说,已经是很大的了,现在来看下centos镜像的Dockerfile文件是怎么来构成镜像的。

FROM scratch
ADD centos-7-x86_64-docker.tar.xz /

LABEL org.label-schema.schema-version="1.0"     org.label-schema.name="CentOS Base Image"     org.label-schema.vendor="CentOS"     org.label-schema.license="GPLv2"     org.label-schema.build-date="20191001"

CMD ["/bin/bash"]

第二行 ADD指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev, /proc, /bin 等目录

不同 Linux 发行版的区别主要就是 rootfs。

比如 Ubuntu使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。

所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统的rootfs,所以会在rootfs层多出一个busybox

BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel

这里需要说明的是:

  1. base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不同的。
    例如 ubuntu使用 4.x.x 的 kernel,如果 Docker Host 是centos7(比如我们的实验环境),那么在ubuntu容器中使用的实际是是 Host 3.x.x 的 kernel。
    ① Host kernel 为 3.10
    ② 启动并进入 ubuntu容器
    ③ 验证容器是ubuntu
    ④ 容器的 kernel 版本与 Host 一致
  2. 容器只能使用 Host 的 kernel,并且不能修改。
    所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。

Dockerfile

[root@localhost ~]# vim Dockerfile
# 以下就是镜像分层的样子,一行为一层
FROM centos  # 使用centos镜像为基础
RUN yum install httpd -y  
RUN yum install net-tools -y
RUN yum install elinks -y
CMD ["/bin/bash"]

以上文件中的每一行就是一个层,新镜像是从 base 镜像 之上叠加生成的,每执行一个 yum 就在现有的基础上增加一层。

采用分层结构的最大好处就是 共享资源

有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需要在磁盘上保存一份 base 镜像;同时内存中也只需要加载一份 base 镜像,就可以为所有的容器服务了,而且镜像的每一层都可以被共享。

多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,其他容器的相同文件是不会被修改的,修改会被限制在单个容器内。

下载两个镜像开看一下是怎么进行共享的

下载nginx镜像

[root@localhost ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
68ced04f60ab: Pull complete 
28252775b295: Pull complete 
a616aa3b0bf2: Pull complete 

下载httpd镜像

[root@localhost ~]# docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
68ced04f60ab: Already exists  # 已经存在
35d35f1e0dc9: Pull complete 
8a918bf0ae55: Pull complete 
d7b9f2dbc195: Pull complete 
d56c468bde81: Pull complete 

仔细观察下载的内容中,有两个id是一样的68ced04f60ab,在下载httpd的时候,会报出已经存在,因为在下载nginx时已经下载过了,这是因为这两个程序都是基于debian操作系统制作的镜像,这里就将这个id的镜像共享使用了

可写的容器层

当容器启动时,一个新的可写层会被加载到镜像的顶部,这一层叫做 “容器层” ; 容器层之下的都叫 “镜像层”;

镜像分层结构理论详解2

 

 

所有对容器的增删改查,都只会发生在容器层中;只有容器层是可写的,容器层下的所有的镜像都是只读的

镜像分层结构理论详解2

 

 

读:容器在进行读取文件时,要去读取镜像层中的文件,从上到下,即使文件有冲突也不会影响,只要在上层找到文件就不会再去找了。

改:容器进行修改一个文件,先去镜像层中找到先读到容器中,再进行修改,修改后的文件不会被保存到镜像层,只会存在容器层中。

删:容器进行删除一个文件时,还是去镜像中找到这个文件后,去操作,虽然删除了,就是不让容器去看这个文件了,但是容器中的该文件还是存在的。

增:容器新增加了镜像层中没有的数据时,也不会去写入镜像层,但是如果容器已关闭,这个数据就会丢失。我们可以考虑对容器新增数据进行持久化到本地物理机(卷容器(data volume))

Docker 并不建议用户通过这种方式构建镜像。原因如下:

  1. 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vi,还得重复前面的所有步骤。
  2. 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。

既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?

原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。

深入讨论容器层的细节

镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /test,上层的 /test 会覆盖下层的 /test,也就是说用户只能访问到上层中的文件 /test。在容器层中,用户看到的是一个叠加之后的文件系统。

1.添加文件

在容器中创建文件时,新文件被添加到容器层中。

2.读取文件

在容器中读取某个文件时,Docker 会 从上往下 依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层, 然后打开并读入内存。

3.修改文件

在容器中修改已存在的文件时,Docker 会 从上往下 依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。

4.删除文件

在容器中删除文件时,Docker 也是 从上往下 依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。

只有需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。所以容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享

镜像分层结构理论详解2

上一篇:镜像分层结构理论详解1


下一篇:正则表达式(Regular Expression)字符表达