美文网首页Java
每天5分钟玩转 Docker 容器技术之镜像

每天5分钟玩转 Docker 容器技术之镜像

作者: Java熬夜党 | 来源:发表于2022-07-31 09:40 被阅读0次

    镜像是 Docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。为什么我们要讨论镜像的内部结构?

    如果只是使用镜像,当然不需要了解,直接通过 docker 命令下载和运行就可以了。

    但如果我们想创建自己的镜像,或者想理解 Docker 为什么是轻量级的,就非常有必要学习这部分知识了。

    一、最小的镜像

    1、运行hello-world镜像

    hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。

    我们先通过 docker pull 从 Docker Hub 下载它。

    用 docker images 命令查看镜像的信息。

    其实我们更关心 hello-world 镜像包含哪些内容。

    其实我们更关心 hello-world 镜像包含哪些内容。

    2. hello-world镜像内容

    Dockerfile 是镜像的描述文件,定义了如何构建Docker镜像。Dockerfile的语法简洁且可读性强,后面我们会专门讨论如何编写Dockerfile。

    hello-world 的 Dockerfile 内容如下:

    只有短短三条指令。

    #1、此镜像是从白手起家,从 0 开始构建。FROM scratch#2、将文件“hello”复制到镜像的根目录。COPY hello /#3、容器启动时,执行 /helloCMD ["/hello"]
    

    镜像 hello-world 中就只有一个可执行文件 “hello”,其功能就是打印出 “Hello from Docker ......” 等信息。

    /hello 就是文件系统的全部内容,连最基本的 /bin,/usr, /lib, /dev 都没有。

    hello-world 虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据

    需要安装和配置软件。这样的镜像我们称作 base 镜像。我们下一节讨论 base 镜像。

    二、base 镜像

    1.base镜像含义

    base 镜像有两层含义:

    1、不依赖其他镜像,从 scratch 构建。2、其他镜像可以之为基础进行扩展。
    

    所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。

    2.base镜像内容

    我们以 CentOS 为例考察 base 镜像包含哪些内容。

    下载镜像:docker pull centos

    查看镜像信息

    镜像大小 231MB。等一下!一个 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 的内容:

    这里需要说明的是:

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

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

    Linux kernel 差别不大。所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。

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

    这里需要说明的是:

    容器只能使用 Host 的 kernel,并且不能修改。所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
    

    下一节我们讨论镜像的分层结构。

    三、镜像的分层结构

    Docker 支持通过扩展现有镜像,创建新的镜像。

    1、镜像分层示例

    实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:

    ① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
    ② 安装 emacs 编辑器。
    ③ 安装 apache2。
    ④ 容器启动时运行 bash。

    构建过程如下图所示:

    可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层

    2、镜像分层好处

    问什么 Docker 镜像要采用这种分层结构呢?

    最大的一个好处就是 - 共享资源。

    比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base

    镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。

    这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc

    是否也会被修改?

    答案是不会!

    修改会被限制在单个容器内

    这就是我们接下来要学习的容器 Copy-on-Write 特性。

    3、Copy-on-Write 特性

    可写的容器层

    当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。

    所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。

    只有容器层是可写的,容器层下面的所有镜像层都是只读的。

    下面我们深入讨论容器层的细节。

    镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会

    覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。

    添加文件

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

    读取文件

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

    修改文件

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

    删除文件

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

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

    这样就解释了我们前面提出的问题:*容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享

    理解了镜像的原理和结构,下一节我们学习如何构建镜像。

    四、构建镜像

    1、为何要构建镜像

    对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或

    其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。

    使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师

    知道如何更好的在容器中运行软件。

    当然,某些情况下我们也不得不自己构建镜像,比如:

    1\. 找不到现成的镜像,比如自己开发的应用程序。2\. 需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。
    

    所以本节我们将介绍构建镜像的方法。同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。

    2、构建镜像方法

    Docker 提供了两种构建镜像的方法:

    1\. docker commit 命令2\. Dockerfile 构建文件
    

    3、docker commit构建镜像

    docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:

    1\. 运行容器2\. 修改容器3\. 将容器保存为新的镜像
    

    举个例子:在 ubuntu base 镜像中安装 vi 并保存为新镜像。

    1)、第一步:运行容器

    -it 参数的作用是以交互模式进入容器,并打开终端。6e2d389d4576 是容器的内部 ID。

    2)、第二步:安装 vi

    确认 vi 没有安装。开始安装 apt-get install -y vim

    3)、第三步:保存新镜像

    在新窗口中查看当前运行的容器。

    新镜像命名为 ubuntu-with-vi。

    执行 docker commit 命令将容器保存为镜像。

    新镜像命名为 ubuntu-with-vi。

    查看新镜像的属性。

    从 size 上看到镜像因为安装了软件而变大了。从新镜像启动容器,验证 vi 已经可以使用。

    以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:

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

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

    原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们

    更加深入地理解构建过程和镜像的分层结构。

    下一节我们学习如何通过 Dockerfile 构建镜像。

    五、Dockerfile 构建镜像

    Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。

    1、Dockerfile 构建镜像

    1)创建Dockerfile文件

    touch Dockerfile
    

    2)用 Dockerfile 创建上节的 ubuntu-with-vi,其内容则为:

    3)构建镜像

    docker build -t ubuntu-with-vi-dockerfile . 
    

    ubuntu-with-vi-dockerfile是构建镜像所取的名字

    运行 docker build 命令,-t 将新镜像命名为 ubuntu-with-vi-dockerfile,命令末尾的 . 指明 build context 为当前目录。

    Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -f 参数指定 Dockerfile 的位置。

    4)镜像构建成功

    通过 docker images 查看镜像信息。

    可以看到新镜像已经构建成功,而且大小跟之前docker commit 构建的大小是一样大的。

    构建成功

    通过 docker images 查看镜像信息。

    可以看到新镜像已经构建成功,而且大小跟之前docker commit 构建的大小是一样大的。

    建的大小是一样大的。

    建的大小是一样大的。

    相关文章

      网友评论

        本文标题:每天5分钟玩转 Docker 容器技术之镜像

        本文链接:https://www.haomeiwen.com/subject/dxkkwrtx.html