目录
目录
一:简单理解操作系统
操作系统:
内核:
内核空间和用户空间:
二:简单理解文件系统
1:什么是文件系统
2:什么是root文件系统
三:docker
1:docker镜像
2:docker镜像的分层存储
3:容器
a:容器 = 镜像 + 可读可写层
b: 运行态容器 + 一些docker的基本操作
4:通过docker commit命令来理解镜像的构成
5:利用dockfile定制镜像
分为内核和用户空间;内核,即操作系统用于和计算机硬件通信的部分;用户空间,即用户程序运行的部分。我们知道人是无法直接和计算机进行交互的,操作系统的作用就是上通下达,它一方面接收人类给到计算机的指令,另一方面,他将这些指令转化为计算机零件可以看懂的东西(可以理解为,操作系统就是把指令转化为特定的电流,给到特定的硬件),以此实现人类和计算机得交互。我们把操作系统和计算机硬件之间得通信部分看作是内核,把操作系统和用户之间的交互看作是用户空间(用词可能不太准确,但大概意思是这样)。
参考自:(19条消息) 操作系统的内核到底是什么?_操作系统内核是什么_deepin_mq的博客-CSDN博客
内核涉及到的操作:进程调度、内存管理、文件系统、网络接口、进程通信等等这些;本质上内核是对计算机硬件的抽象。
这样说是因为这里涉及到一个更根本的问题:人机交互。如何让计算机理解人的想法,需求,并将之实现。
比如:让计算机计算一下硬盘中的一个文档的字数。我们是没有办法去打开“硬盘”翻箱倒柜找到这个文件,然后丢给“CPU”去统计,并从“CPU”那里得到这个结果。对于计算机内部真正的核心组成部分:CPU、硬盘、内存等等这些硬件,人是无法直接去面对和交互的。而计算机处理的所有工作,都是通过调用这些硬件设备来完成的。
对操作系统来说,最重要的正是管理和调度计算机内部的资源。而具体到操作系统的内部分工,则是由内核来真正完成和执行的。除了内核以外,操作系统其他所有的部件,都不需要也不关注如何计算、如何存储、如何和具体的某个硬件设备打交道。所有的部件都只需要将具体的诉求传递给内核,调用内核的接口,即可完成硬件资源的调度和使用。
内核抽象了计算机内部硬件资源,并统一管理对外提供支持,所以内核 = 计算机硬件。
参考自:用户空间和内核空间到底是什么? - 知乎 (zhihu.com)
我们知道计算机所有运行的程序以及数据其实都是放在内存中的,也只有把程序和数据放进内存 ,计算机才能进行相应的操作或者计算。那么我们以4g的内存举例,其中有1g的内存容量是专门给内核相关代码数据用的,比如就是地址从0~1g的内存空间,这部分内存块,我们称之为内核空间;而对于1~3g的内存部分,则是提供给用户的代码和数据的,我们把这部分内存块看作用户空间;
图片来自参考。
文件系统是一种数据结构,用于管理计算机中的文件和目录。在计算机中,文件系统通常是操作系统的一部分,它负责解释和执行文件系统中的文件和目录,并提供对文件和目录的访问。文件系统也可以是一个独立的软件模块,它运行在操作系统之上,提供对文件和目录的访问和管理。
常见的文件系统类型包括 FAT、FAT32、NTFS、ext2、ext3、ext4 等。
再简单一点,文件系统不是文件,是用来管理文件的的一种数据结构,它本身也是可以保存数据之类的,比如下面说到的root文件系统,它就存储了系统的根目录和根文件系统中的数据和文件。
典型的Linux文件系统由bootfs
文件系统和rootfs
文件系统组成,bootfs
会在Kernel
加载到内存后umount
掉,所以我们进入系统看到的都是rootfs。
BootFS 和 rootFS 是 Linux 内核中用于存储和管理根文件系统中的数据和文件的模块。
BootFS 是一个特殊的文件系统,它在 Linux 内核启动时自动挂载到内存中,并用于存储内核启动时需要使用的数据,例如内核镜像、内核配置信息和内核日志等。BootFS 通常使用 LUKS 加密,并且可以在内核启动时进行调整。
rootFS 是 Linux 内核中用于存储根文件系统中的数据和文件的模块。它是一个只读的文件系统,通常使用 EXT4 文件系统格式。rootFS 通常位于硬盘的最后一个扇区,并且是系统启动时第一个被加载的文件系统。在这个文件中,可以找到系统引导程序 (如 Grub) 以及用于管理系统文件的 Shell 和命令。
bootfs 和 rootfs 是 Linux 内核中用于存储和管理重要数据的文件系统,它们的作用非常重要,对于系统的稳定性和安全性都有着至关重要的作用。
root文件系统,不等同于上述的rootfs文件系统,它是linux系统下,用来管理“/”文件夹的文件系统;"/"文件夹,也就是根目录,这个目录下包括/home,/bin,/usr,/etc等文件。因为root文件系统是用来管路根目录的,也就是说root文件系统可以管理/下面所有文件,对所有文件有操作,访问的权限,所以:root 文件系统是操作系统中唯一的超级用户权限所有者所在的文件系统,因此只有 root 用户可以访问和修改它。为了保护系统的安全性,root 文件系统通常需要进行加密和分区,以便只有 root 用户可以访问和修改它。
root 文件系统通常用于存储系统启动文件,如 boot 目录、/etc 目录、/var 目录等,以及所有系统启动时需要访问的文件。
对于 Linux 而言,内核启动(就是BootFS 文件系统)后,会挂载 root
文件系统为其提供用户空间支持,这里就是上面说的,会用到bootfs。
root 文件系统是指 Linux 系统中的一个特殊文件系统,通常用于存储系统的根目录和根文件系统中的数据和文件。在 Linux 系统中,root 文件系统通常是只读的,并且只有系统管理员才能进行修改。
root 文件系统通常使用 EXT4 文件系统格式,并且在 Linux 内核中,有一个特殊的模块叫做"rootfs"或"root partition",用于加载和初始化 root 文件系统。在系统启动时,内核会首先加载 rootfs 模块,然后通过模块中的代码来加载 root 文件系统,并将其挂载到根目录下。
root 文件系统是 Linux 系统的核心,它包含了系统的启动代码、系统配置文件、系统运行所需的命令和文件等。因此,一旦 root 文件系统被损坏或破坏,可能会导致整个系统无法运行。因此,在维护 Linux 系统时,通常需要小心谨慎地操作 root 文件系统。
root 文件系统和 rootfs 文件系统是不同的,尽管它们在某些方面有相似之处。
root 文件系统是 Linux 系统中的一个重要文件系统,通常用于存储系统的根目录和根文件系统中的数据和文件。root 文件系统通常是只读的,并且只有系统管理员才能进行修改。在 Linux 系统中,root 文件系统通常使用 EXT4 文件系统格式。
而 rootfs 文件系统是在 Linux 内核中用于加载和初始化根文件系统的模块,通常也称为根文件系统模块。rootfs 文件系统模块使用特殊的命令和代码来加载和初始化 root 文件系统,并将其挂载到根目录下。与 root 文件系统不同,rootfs 文件系统通常是可写的,并且系统管理员可以使用该文件系统来更新和修改根文件系统。
因此,尽管 root 文件系统和 rootfs 文件系统都是用于存储根目录和根文件系统中的数据和文件,但它们的具体实现和功能是不同的。
参考自:镜像 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
docker镜像就相当于一个root文件系统。比如官方镜像 ubuntu:18.04
就包含了完整的一套 Ubuntu 18.04 最小系统的 root
文件系统。
题外话:这也是为什么说docker容器比虚拟机轻量的原因,因为docker只是一个root文件系统,也就是相当于只虚拟了用户空间;而虚拟机时要虚拟整个操作系统,它不仅要虚拟用户空间,还要虚拟内核。每一个虚拟机都是这样,所以很笨重。
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
注意:docker镜像相当于root文件系统,但它不是root文件系统,也不是rootfs文件系统,它就是一组只读的文件系统。
在 Docker 镜像中,数据和配置通常存储在 Docker 镜像的卷中,而不是在 rootfs 文件系统中。Docker 镜像的卷是一个或多个只读的文件系统,它们可以被映射到容器的目录中,从而为容器提供所需的数据和配置。因此,Docker 镜像并不直接等同于 rootfs 文件系统,但它们可以一起用于构建和部署容器化应用程序。
因为镜像包含操作系统完整的
root
文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。
docker镜像时一组文件系统,这组文件系统有层次划分。
镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
因此,我们可以把镜像简单理解为一组只读的文件系统:
参考:(19条消息) docker容器和镜像的区别_docker镜像和容器的区别_清风不灭的博客-CSDN博客
参考:容器 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
(19条消息) docker容器和镜像的区别_docker镜像和容器的区别_清风不灭的博客-CSDN博客
镜像(Image
)和容器(Container
)的关系,就像是面向对象程序设计中的 类
和 实例
一样,镜像是静态的定义,容器是镜像运行时的实体。再做个类比,镜像就是代码,容器就是代码运行起来的进程。(仅作类比)
图片来源于:Docker系列学习(14) -- 容器内镜像分层详解 - 掘金 (juejin.cn)
这个读写层可以读取下面镜像层的内容,但是,是只能读不能写的!!!!!
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root
文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。
运行态容器 = 容器 + 隔离的进程空间;图中灰色框表示进程
其他命令可以详细参考:(19条消息) docker容器和镜像的区别_docker镜像和容器的区别_清风不灭的博客-CSDN博客
docker commit
命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。存储记录了文件的变化以及新加入的东西等
举例如下:参考自:利用 commit 理解镜像构成 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
这里我们可以看到,容器的这个读写层,实际上最开始的时候,是直接读取的镜像保存的东西;但其实不是简单的读取!后面会细说!
这样看上去我们修改了镜像的内容,实际上是没有的,我们永远要记住,镜像是只读的!!!
那么修改的到底是什么呢?
其实是:参考自:Docker系列学习(14) -- 容器内镜像分层详解 - 掘金 (juejin.cn)
容器层有一种特效叫 copy-on-wirte, 这是指在容器层进行文件修改时,容器层会自上向下逐层扫描镜像层寻找文件,找到后会copy一份副本到容器层中,再进行修改,这样不会影响镜像
这里还要提一下对容器层删除文件的操作,上面也说了,容器层的文件是从镜像层copy来的,删除的时候删除copy的文件,但如果没有拷贝的文件,就会生成了一条删除记录,记录在without文件中,并屏蔽对该镜像层中这个文件的读取
容器层被删除时,容器层内的所有文件都会失效,数据也会被删除
同时,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。这样,上层的镜像内容就会覆盖底层的镜像内容,看上去就像是修改了底层镜像内容一样,实际上底层镜像内容并没有动!
这里我们通过commit理解了镜像的生成,但实际上我们很少直接用commit来生成镜像,因为commit'是一个黑箱操作,他会带来很多无关的内容。而且很多操作是不可知的;另外,因为我们说了,这样的方式是不会修改之前层的,我们只会在当前的容器可读可写层记录之前层文件的添加,修改之类的,所以会让镜像不断的臃肿。
待续:使用 Dockerfile 定制镜像 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
从刚才的
docker commit
的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
拿上面的例子来举例:
参考:使用 Dockerfile 定制镜像 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
在dockfile中,from用于指定基础镜像,我们说用dockfile来定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx
镜像的容器,再进行修改一样,基础镜像是必须指定的。因此一个 Dockerfile
中 FROM
是必备的指令,并且必须是第一条指令。
在 Docker Hub 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node、openjdk、python、ruby、golang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntu、debian、centos、fedora、alpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
如果你以
scratch
为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如 swarm、etcd。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接
FROM scratch
会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
参考自:使用 Dockerfile 定制镜像 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
RUN命令的作用就是新开一个容器,然后再这个容器上执行命令,执行完以后把这层可读可写层commit成一个镜像。
这里可以很好的说明为什么commit会造成镜像臃肿,像这里的命令一样,每一个RUN就是一层镜像,但我们的最终目标其实就是最后的一个RUN,而中间的RUN其实就是写编译软件呀,或者编译过程的中间文件,这些文件其实是不需要的,但是这里的RUN和commit一样都保存下来了,所以会让镜像越来越臃肿。
8:docker build建立镜像
参考:使用 Dockerfile 定制镜像 · Docker —— 从入门到实践 · 看云 (kancloud.cn)
未完待续........