三天肝了两本书,先整一份 1.5w 字 + 20 张图的高级 Docker 入门来学习一下


0. 前言

大家好,我是多选参数的程序锅,一个正在捣鼓操作系统、学数据结构和算法以及 Java 的失业人员。

这是我肝了 3 天,参考了两本书和一些博客之后,整理的一份关于 Docker 的高级入门。为啥说是高级入门呢?因为它比入门要深,但是比深入又要浅。

很早之前,我就接触过 Docker,但是没怎么用,也似懂非懂。偶尔看看一些推文,发现我还是不太懂 Docker 到底是个啥?镜像又是什么玩意?可能看的推文都不怎么较为深入的去讲镜像和容器是啥,都只是停留在镜像和容器整体上。对于我来说,不了解一下镜像和容器的细节内容,我觉得我都懂不了。而最大的 motivation 是我最近看了一篇论文,是关于镜像裁剪的,为了更好地看懂论文,所以决定还是把 Docker 好好学习一下。也正好把之前看 Linux 内核学到的 namespace 和 cgroups 知识给揉进去。

主要参考的两本书是《深入浅出 Docker》和《第一本 Docker》。个人感觉,前者主要偏讲原理,讲 Docker 架构、镜像、容器的时候都很细节,也有一些 docker 命令的使用,但是讲原理比较多;而后者则偏向操作,讲实战类,教你怎么用 docker 的,所以命令的使用比较多。这两本书(电子版的),我都有,需要的话,可以关注公众号回复 ”docker学习“ 即可。

本文的图很多都是截图或者网上原图。为什么不重新画呢?其实我也想重新画,但是我比较懒,我觉得图能将自己要阐述的点解释清楚,或者说和自己整理过后的文字结合的不错,我觉得这个图就没必要重新画了,人家的画已经很好看了,也很清晰了,你将它重新画,其实也是差不多,可能就是换个样式而已,核心的东西还是没有变。但是,在有些地方,我觉得别人的图跟我阐述的内容不符合,或者不能很好地阐述我想表达的东西,又或者这个地方需要一个图,那么我会画一个。

1. Docker 是什么

简单来说,Docker 它可以将程序及其应用环境打包在一起,当程序执行的时候使用的是打包的应用环境,而不是运行系统的环境,所以这也是什么在看到 Docker 的地方总能拿集装箱来比喻,这里你就可以将 Docker 理解成一个“集装箱”,这个“集装箱”里面包含了程序执行和打包的应用环境,那么“集装箱”里面的程序在运行时使用的是“集装”内的应用环境,但是程序在运行时还是靠主机的内核来执行的。

稍微具体点,Docker 包含了镜像和容器技术。镜像是静态的,你也可以将镜像理解成一个包含了 OS 文件系统和应用的对象(不包含内核),也就是“集装箱”的内容。容器是动态的,是镜像的运行时态:从镜像中启动一个程序,这个程序使用的应用环境是镜像提供的,而运行的程序和镜像组合在一起就算是容器了。给你的感觉就是运行起来之后还是在一个“集装箱”内。

因此,容器和镜像这两者的使用实现了一次打包,到处使用,免去了对运行环境的依赖,简化了应用的部署过程。

2. Docker 基本架构

Docker 的核心组件有:

  • Docker 客户端和服务器

  • Docker 容器

  • Docker 镜像和 Registry(镜像仓库服务)

整个 Docker 的基本架构如图所示(图来自菜鸟教程)

Docker 是一个客户-服务器(C/S)架构的程序。Docker 客户端只需要向 Docker 服务器或守护进程发出请求,服务器或守护进程将完成所有工作并返回结果。Docker 提供了一个命令行工具和一整套 RESTful API。你可以在同一台宿主机上运行 Docker 守护进程和客户端,也可以从本地的 Docker 客户端连接到运行在另一台宿主机上的远程 Docker 守护进程。Docker 以 root 权限运行它的守护进程,来处理普通用户无法完成的操作(如挂载文件系统)。Docker 程序是 Docker 守护进程的客户端程序,同样也需要以 root 身份运行。

  • Docker 客户端也就是我们的 docker 命令行工具,比如我们可以通过 docker run 准备运行一个容器,那么此时客户端会像服务端发送请求。它是我们和 Docker 进行交互的最主要的方式方法。

  • Docker Daemon(或者Docker 服务器)用来监听 Docker API 的请求和管理 Docker 对象,比如镜像、容器、网络和卷。默认情况 docker 客户端和 docker daemon 位于同一主机,此时 daemon 监听 /var/run/docker.sock 这个 Unix 套接字文件,来获取来自客户端的 Docker 请求。当然通过配置,也可以借助网络来实现 Docker Client 和 daemon 之间的通信,默认非 TLS 端口为 2375,TLS 默认端口为 2376。

  • Docker Registry 是用来存储 Docker 镜像的仓库。Docker Hub 是 Docker 官方提供的一个公共仓库,也是 Docker 默认查找的地方。当然,还有其他的 Docker Registry,比如自己上 Docker Hub 注册一个账号申请一个自己的 Registry,也可以使用像阿里云这些提供的 Registry。

    当使用 docker pull 的时候会先检查本地是否存在所需镜像,如果不存在就去 Docker Registry 中拉取镜像。类似的,docker push 会将镜像推送到对应的 Docker Registry 中。

  • Docker 镜像是一个制作模板,带有 Docker 容器的说明。Docker 镜像是基于联合(Union)文件系统的一种层式结构,包含了应用程序和其运行所依赖的程序库和文件(不包含内核)。一般来说,用户都会基于一些基础镜像上面再安装一些程序来构建符合自己的需求的镜像。

  • Docker 容器是镜像的一个运行实例,一个镜像可以启动多个容器,而一个容器中可以运行一个或多个进程。 Docker 一个容器内的一个或多个进程的运行是依靠宿主机的内核,但是这些进程属于自己的独立的命名空间,拥有自己的 root 文件系统、自己的网络配置、自己的进程空间和自己的用户 ID 等等,所以容器内的进程相当由于运行在一个隔离的环境里,就相当于运行在一个新的操作系统中一样。

OCI 是一个规范组织,比如制定镜像规范、容器运行时规范。

可以通过浏览 /var/lib/docker 目录来深入了解 Dcoker 的工作原理。该目录存放着 Dokcer 镜像、容器以及容器的配置。所有的容器都保存在 /var/lib/docker/containers 目录下。

3. Docker 镜像

Docker 镜像是一种层式结构,包含了应用程序和其运行所依赖的程序库和文件(不包含内核)。镜像可以理解为一种构建时结构(制作模板),而容器可以理解为一种运行时结构,用户基于镜像来运行自己的容器。你可以将其类比为程序和进程的关系,或者说是类(class)和实例对象的关系。当从某个镜像启动一个或多个容器之后,容器和镜像就成了依赖关系。在镜像上启动的容器全部停止之前,镜像是无法删除的。

容器目的是运行应用或者服务,而容器运行时需要镜像的内容,也就是镜像中应该包含应用服务运行时所需要的 OS 文件系统和应用文件。但是,容器又追求快速和小巧,所以要裁剪掉镜像中不必要的部分,保持较小的体积。因此 Docker 镜像中一般不会包含 6 个不同 shell 让读者选择,通常只有一个 shell 或者没有 shell。

另外,镜像中是不包含内核,容器都是共享所在 Docker 主机的内核,这样也保证了镜像的体积足够小。有时说的容器包含必要的操作系统,其实通常只有操作系统文件和文件系统对象。

我们需要某个镜像时 Docker 会先检查本地是否存在所需要的镜像,如果本地没有镜像的话,那么 Docker 就会连接官方维护的 Docker Hub 镜像仓库服务(默认的是镜像仓库服务是 Docker hub),查看 Docker Hub 中是否存在该镜像,一旦找到该镜像则将其保存到本地宿主机。本地镜像保存在 Docker 宿主机的 /var/lib/docker/Docker 所采用的存储驱动 目录下,比如 /var/lib/docker/overlay2 。这里更细节的是下面会提到的镜像层,假如一个镜像层在本地了,那么这个镜像层就不会被拉取了。当把镜像拉取到本地之后,我们可以使用该镜像启动一个或者多个容器。

Docker 镜像采用了联合文件系统(Union File System)技术,它可以支持对文件系统的修改作为一次提交来一层层的叠加(PS:详细的介绍,过段时间就会有了)。

3.1. 镜像分层

Docker 镜像由一些松耦合的只读镜像层组成。Docker 负责堆叠这些镜像层,并将它们表示为单个统一的对象。实际上,镜像本身就是一个配置对象,其中包含了镜像层的列表以及一些元数据信息。而镜像层才是实际数据存储的地方(比如文件等),镜像层之间是完全独立的,镜像层没有从属于某个镜像集合的概念。每个镜像的唯一标识是一个加密 ID,即配置对象本身的散列值。而每个镜像层也由一个加密 ID 区别,其值是镜像层本身内容的散列值。这意味着修改镜像的内容或其中任意的镜像层,都会导致加密散列值的变换。所以,镜像和其镜像层都是不可变的,任何改动都能轻松地被辨别。

镜像层 ID 在拉取镜像的时候会看到,SHA256 散列值可以通过 docker image inspect <image> 看到。

所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或者增加新的内容时,就会在当前镜像层之上,创建新的镜像层。举个例子,假如基于 Ubuntu 16.04 创建一个新的镜像,那么 Ubuntu 16.04 就是新镜像的第一层;如果在该镜像中添加 Python 包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层,如图所示。

并且在添加额外的镜像层的时候,镜像始终保持当前所有镜像层的组合,即所有镜像层堆叠之后的结果。如下图所示,每个镜像层包含 3 个文件,而镜像包含了来自两个镜像层的 6 个文件。再如下图所示,第三层镜像层的文件 7 是文件 5 的一个更新版本,但是在外部看来整个镜像还是只有 6 个文件,因为对外展示时相当于把所有镜像层堆叠合并。

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

另外,多个镜像之间会共享镜像层,这样可以有效节省空间并提升性能。Docker 在拉取镜像时会识别出要拉取的镜像中有哪几层已经在本地了的,如果有些镜像层已经在本地了,那么这些镜像层就不会被拉取。

Linux 上可用的存储引擎有 AUFS、Overlay2、Device Mapper 等等。每种引擎都基于 Linux 中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特性,都有自己的镜像分层、镜像层共享以及写时复制(CoW)技术的具体实现。但是,最终效果和用户体验是完全一致的。

含有引导文件系统的镜像

Docker 镜像是分层的,这是毋庸置疑的,但是最底端的说法存在不一致。有一种说法 Docker 镜像最低端是一个引导文件系统,即 bootfs,这很像典型的 Linux/Unix 的引导文件系统。Docker 用户几乎永远不会和引导文件系统有什么交互。实际上,当一个容器启动后,它将会被移到内存中,而引导文件系统则会被卸载,以留出更多的内存 供 initrd 磁盘镜像使用。Docker 镜像的第二层是 root 文件系统 rootfs,它位于引导文件系统之上。rootfs 可以是一种或多种操作系统的文件系统,比如 Ubuntu 文件系统。

在 Linux 引导过程中,root 文件系统会最先以只读的方式加载,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值