type
status
date
slug
summary
tags
category
icon
password

1、hello-world——最小的镜像

hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。我们先通过 docker pull 从 Docker Hub 下载它
然后使用 docker run 运行,打印如下:
查看 hello-world 的 Dockerfile,如下所示
官方的镜像一般会在 Docker Hub 的介绍页贴上源码地址,一般是 Github 地址。跳转到源码地址,搜索 Dockerfile 就可以找到镜像的原始 Dockerfile。例如 hello-world 对应的 Dockerfile 地址是
Dockerfile
docker-library
只有短短三条指令。
  1. FROM scratch 镜像是从白手起家,从0开始构建。
  1. COPY hello/ 将文件“hello”复制到镜像的根目录。
  1. CMD["/hello"] 容器启动时,执行 /hello。
镜像 hello-world 中就只有一个可执行文件“hello”,其功能就是打印出“Hello from Docker ......”等信息。/hello 就是文件系统的全部内容,连最基本的 /bin、/usr、/lib、 /dev都没有。
hello-world虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件。这样的镜像我们称作base镜像。

2、base镜像

base 镜像有两层含义:
  1. 不依赖其他镜像,从 scratch 构建;
  1. 其他镜像可以以之为基础进行扩展。
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu、Debian、CentOS 等
base 镜像是怎么制作的呢?首先我们需要了解,Linux 操作系统由内核空间和用户空间组成。内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。用户空间的文件系统是 rootfs,包含我们熟悉的 /dev、/proc、/bin等目录。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了
notion image
不同 Linux 发行版的区别主要就是 rootfs。比如 Ubuntu 14.04使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
查看 CentOS 的 Dockerfile,如下所示:
第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev、/proc、/bin 等目录。
我们下载好 centos-7-docker.tar.xz 压缩包(下载地址 ),也可以通过上面的 Dockerfile 文件自定义 base 镜像。
这里需要说明的是:
  1. base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不同的。
  1. 容器只能使用 Host 的 kernel,并且不能修改。所有容器都共用 host 的kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。

3、镜像的分层结构

Docker 支持通过扩展现有镜像,创建新的镜像。Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。例如我们使用 alpine 基础镜像来创建一个新的镜像。Dockerfile 内容如下:
执行下面命令,基于该 Dockerfile 构建新的镜像 myalpine:2.0。
分别查看 alpine 和 myalpine:2.0 镜像的构建历史。我们可以看到
  • alpine 和 myalpine:2.0 都使用相同的层 5758b97d8301c84a204,大小为 7.8MB。
  • myalpine:2.0 里面的pk add curlapk add htop 两个执行命令分别生成新的层,大小分别为 5.32MB 和 3.06MB。
  • missing 表示无法获取IMAGE ID,通常从 Docker Hub 下载的镜像会有这个问题。
为什么 Docker 镜像要采用这种分层结构呢?最大的一个好处就是:共享资源。比如:有多个镜像都从相同的 base 镜像构建而来,那么Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了,而且镜像的每一层都可以被共享。
这时候有一个问题如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc下的文件,这时其他容器的 /etc是否也会被修改?
答案是不会。容器运行时,是以镜像为基础层,在其上增加了 init 层和可读写层。所以容器的 rootfs 分为三层:
notion image
  • 可读写层(rw):是容器的 rootfs 的最上面一层,用于保存对 rootfs 的增删查改。在没有写入文件之前,这个目录是空的。
    • 如果执行了增查改操作,改变产生的内容就会以增量的方式出现在这个层中。
    • 如果执行了删除操作,会在可读写层创建一个 whiteout 文件,把只读层里的文件遮挡起来。比如,你要删除只读层里一个名叫 foo 的文件,那么这个删除操作实际上是在可读写层创建了一个名叫 .wh.foo 的文件。这样,当这两个层被联合挂载之后,foo 文件就会被 .wh.foo 文件遮挡起来,只读层里的内容则不会有任何变化。
  • Init层(ro+wh):专门用来存放 /etc/hosts、/etc/resolv.conf 这些系统文件。我们修改系统文件后只对当前的容器有效,docker commit 不会提交 Init 层的内容,只会提交可读写层。
  • 只读层(ro+wh):只读层是 Init 层下面的层,它们的挂载方式都是只读的,这些层以增量的方式叠加。
所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
Docker系列:Docker容器的底层技术Nginx的限流算法
mcbilla
mcbilla
一个普通的干饭人🍚
Announcement
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎来到飙戈的博客🎉
-- 感谢您的支持 ---
👏欢迎学习交流👏