天天5分钟玩转Docker容器技术(一)docker
上一节咱们介绍了最小的 Docker 镜像,本节讨论 base 镜像。apache
base 镜像有两层含义:ubuntu
因此,能称做 base 镜像的一般都是各类 Linux 发行版的 Docker 镜像,好比 Ubuntu, Debian, CentOS 等。vim
咱们以 CentOS 为例考察 base 镜像包含哪些内容。centos
下载镜像:docker pull centos安全
查看镜像信息:bash
镜像大小不到 200MB。ssh
等一下!
一个 CentOS 才 200MB ?
平时咱们安装一个 CentOS 至少都有几个 GB,怎么可能才 200MB !编辑器
相信这是几乎全部 Docker 初学者都会有的疑问,包括我本身。下面咱们来解释这个问题。
Linux 操做系统由内核空间和用户空间组成。以下图所示:
内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,以后 bootfs 会被卸载掉。
用户空间的文件系统是 rootfs,包含咱们熟悉的 /dev, /proc, /bin 等目录。
对于 base 镜像来讲,底层直接用 Host 的 kernel,本身只须要提供 rootfs 就好了。
而对于一个精简的 OS,rootfs 能够很小,只须要包括最基本的命令、工具和程序库就能够了。相比其余 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
咱们平时安装的 CentOS 除了 rootfs 还会选装不少软件、服务、图形桌面等,须要好几个 GB 就不足为奇了。
下面是 CentOS 镜像的 Dockerfile 的内容:
第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制做镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev, /proc, /bin 等目录。
注:可在 Docker Hub 的镜像描述页面中查看 Dockerfile 。
不一样 Linux 发行版的区别主要就是 rootfs。
好比 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差异不大。
因此 Docker 能够同时支持多种 Linux 镜像,模拟出多种操做系统环境。
上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。
这里须要说明的是:
1. base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不一样的。
例如 CentOS 7 使用 3.x.x 的 kernel,若是 Docker Host 是 Ubuntu 16.04(好比咱们的实验环境),那么在 CentOS 容器中使用的实际是是 Host 4.x.x 的 kernel。
① Host kernel 为 4.4.0-31
② 启动并进入 CentOS 容器
③ 验证容器是 CentOS 7
④ 容器的 kernel 版本与 Host 一致
2. 容器只能使用 Host 的 kernel,而且不能修改。
全部容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。若是容器对 kernel 版本有要求(好比应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
Docker 支持经过扩展示有镜像,建立新的镜像。
实际上,Docker Hub 中 99% 的镜像都是经过在 base 镜像中安装和配置须要的软件构建出来的。好比咱们如今构建一个新的镜像,Dockerfile 以下:
① 新镜像再也不是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。
构建过程以下图所示:
能够看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增长一层。
问什么 Docker 镜像要采用这种分层结构呢?
最大的一个好处就是 - 共享资源。
好比:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就能够为全部容器服务了。并且镜像的每一层均可以被共享,咱们将在后面更深刻地讨论这个特性。
这时可能就有人会问了:若是多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,好比 /etc 下的文件,这时其余容器的 /etc 是否也会被修改?
答案是不会!
修改会被限制在单个容器内。
这就是咱们接下来要学习的容器 Copy-on-Write 特性。
当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层一般被称做“容器层”,“容器层”之下的都叫“镜像层”。
全部对容器的改动 - 不管添加、删除、仍是修改文件都只会发生在容器层中。
只有容器层是可写的,容器层下面的全部镜像层都是只读的。
下面咱们深刻讨论容器层的细节。
镜像层数量可能会不少,全部镜像层会联合在一块儿组成一个统一的文件系统。若是不一样层中有一个相同路径的文件,好比 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加以后的文件系统。
只有当须要修改时才复制一份数据,这种特性被称做 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像自己进行任何修改。
这样就解释了咱们前面提出的问题:容器层记录对镜像的修改,全部镜像层都是只读的,不会被容器修改,因此镜像能够被多个容器共享。
对于 Docker 用户来讲,最好的状况是不须要本身建立镜像。几乎全部经常使用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或其余人和组织建立的镜像,咱们只须要稍做配置就能够直接使用。
使用现成镜像的好处除了省去本身作镜像的工做量外,更重要的是能够利用前人的经验。特别是使用那些官方镜像,由于 Docker 的工程师知道如何更好的在容器中运行软件。
固然,某些状况下咱们也不得不本身构建镜像,好比:
因此本节咱们将介绍构建镜像的方法。同时分析构建的过程也可以加深咱们对前面镜像分层结构的理解。
Docker 提供了两种构建镜像的方法:
docker commit 命令是建立新镜像最直观的方法,其过程包含三个步骤:
举个例子:在 ubuntu base 镜像中安装 vi 并保存为新镜像。
-it
参数的做用是以交互模式进入容器,并打开终端。412b30588f4a
是容器的内部 ID。
确认 vi 没有安装。
安装 vi。
silly_goldberg
是 Docker 为咱们的容器随机分配的名字。
执行 docker commit 命令将容器保存为镜像。
新镜像命名为 ubuntu-with-vi
。
查看新镜像的属性。
从 size 上看到镜像由于安装了软件而变大了。
重新镜像启动容器,验证 vi 已经可使用。
以上演示了如何用 docker commit 建立新镜像。然而,Docker 并不建议用户经过这种方式构建镜像。缘由以下:
既然 docker commit 不是推荐的方法,咱们干吗还要花时间学习呢?
缘由是:即使是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 可以帮助咱们更加深刻地理解构建过程和镜像的分层结构。
Dockerfile 是一个文本文件,记录了镜像构建的全部步骤。
用 Dockerfile 建立上节的 ubuntu-with-vi,其内容则为:
下面咱们运行 docker build 命令构建镜像并详细分析每一个细节。
① 当前目录为 /root。
② Dockerfile 准备就绪。
③ 运行 docker build 命令,-t
将新镜像命名为 ubuntu-with-vi-dockerfile
,命令末尾的 .
指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,咱们也能够经过 -f
参数指定 Dockerfile 的位置。
④ 从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的全部文件发送给 Docker daemon。build context 为镜像构建提供所须要的文件或目录。
Dockerfile 中的 ADD、COPY 等命令能够将 build context 中的文件添加到镜像。此例中,build context 为当前目录 /root
,该目录下的全部文件和子目录都会被发送给 Docker daemon。
因此,使用 build context 就得当心了,不要将多余文件放到 build context,特别不要把 /
、/usr
做为 build context,不然构建过程会至关缓慢甚至失败。
⑤ Step 1:执行 FROM
,将 ubuntu 做为 base 镜像。
ubuntu 镜像 ID 为 f753707788c5。
⑥ Step 2:执行 RUN
,安装 vim,具体步骤为 ⑦、⑧、⑨。
⑦ 启动 ID 为 9f4d4166f7e3 的临时容器,在容器中经过 apt-get 安装 vim。
⑧ 安装成功后,将容器保存为镜像,其 ID 为 35ca89798937。
这一步底层使用的是相似 docker commit 的命令。
⑨ 删除临时容器 9f4d4166f7e3。
⑩ 镜像构建成功。
经过 docker images 查看镜像信息。
镜像 ID 为 35ca89798937,与构建时的输出一致。
在上面的构建过程当中,咱们要特别注意指令 RUN 的执行过程 ⑦、⑧、⑨。Docker 会在启动的临时容器中执行操做,并经过 commit 保存为新的镜像。
ubuntu-with-vi-dockerfile 是经过在 base 镜像的顶部添加一个新的镜像层而获得的。
这个新镜像层的内容由 RUN apt-get update && apt-get install -y vim
生成。这一点咱们能够经过 docker history
命令验证。
docker history
会显示镜像的构建历史,也就是 Dockerfile 的执行过程。
ubuntu-with-vi-dockerfile 与 ubuntu 镜像相比,确实只是多了顶部的一层 35ca89798937,由 apt-get 命令建立,大小为 97.07MB。docker history 也向咱们展现了镜像的分层结构,每一层由上至下排列。
注: 表示没法获取 IMAGE ID,一般从 Docker Hub 下载的镜像会有这个问题。
做者:cloudman6