深入剖析Kubernetes之容器技术概念入门篇

news2024/9/23 13:27:32

文章目录

    • 进程层面
    • 隔离与限制
    • 容器镜像

容器本身没有价值,有价值的是“容器编排”。

  • 也正因为如此,容器技术生态才爆发了一场关于“容器编排”的“战争”。而这次战争,最终以 Kubernetes 项目和 CNCF 社区的胜利而告终。

容器,到底是怎么一回事儿?

  • 容器其实是一种沙盒技术。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用“装”起来的技术。这样,应用与应用之间,就因为有了边界而不至于相互干扰;而被装进集装箱的应用,也可以被方便地搬来搬去,这不就是 PaaS 最理想的状态嘛。

进程层面

  • 对于进程来说,它的静态表现就是程序,平常都安安静静地待在磁盘上;而一旦运行起来,它就变成了计算机里的数据和状态的总和,这就是它的动态表现。

  • 容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。

  • 对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段,而Namespace 技术则是用来修改进程视图的主要方法。

Cgroups 和 Namespace 这两个概念很抽象,动手实践一下,就很容易理解这两项技术了。

  • 首先创建一个容器来试试: docker run -it busybox /bin/sh
    • -it 参数告诉了 Docker 项目在启动容器后,需要给我们分配一个文本输入 / 输出环境,也就是 TTY,跟容器的标准输入相关联,这样我们就可以和这个 Docker 容器进行交互了。而 /bin/sh 就是我们要在 Docker 容器里运行的程序。
    • 所以,上面这条指令翻译成人类的语言就是:请帮我启动一个容器,在容器里执行 /bin/sh,并且给我分配一个命令行终端跟这个容器交互。
  • 执行一下 ps 指令,就会发现一些更有趣的事情: ps
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    7 root      0:00 ps
/ # 
  • 可以看到,我们在 Docker 里最开始执行的 /bin/sh,就是这个容器内部的第 1 号进程(PID=1),而这个容器里一共只有两个进程在运行。这就意味着,前面执行的 /bin/sh,以及我们刚刚执行的 ps,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中。

这究竟是怎么做到呢?

  • 本来,每当我们在宿主机上运行了一个 /bin/sh 程序,操作系统都会给它分配一个进程编号,比如 PID=100。这个编号是进程的唯一标识,就像员工的工牌一样。所以 PID=100,可以粗略地理解为这个 /bin/sh 是我们公司里的第 100 号员工,而第 1 号员工就自然是统领全局的人物。
  • 而现在,我们要通过 Docker 把这个 /bin/sh 程序运行在一个容器当中。这时候,Docker 就会在这个第 100 号员工入职时给他施一个“障眼法”,让他永远看不到前面的其他 99 个员工。这样,他就会错误地以为自己就是公司里的第 1 号员工。
  • 这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,比如 PID=1。可实际上,他们在宿主机的操作系统里,还是原来的第 100 号进程。
  • 这种技术,就是 Linux 里面的 Namespace 机制。而 Namespace 的使用方式也非常有意思:它其实只是 Linux 创建新进程的一个可选参数。

除了用到的 PID Namespace,Linux 操作系统还提供了 Mount、UTS、IPC、Network 和 User 这些 Namespace,用来对各种不同的进程上下文进行“障眼法”操作。

  • 所以说,容器,其实是一种特殊的进程而已

  • 很多人会把 Docker 项目称为“轻量级”虚拟化技术的原因,实际上就是把虚拟机的概念套在了容器上。可是这样的说法,却并不严谨。

  • 在理解了 Namespace 的工作方式之后,你就会明白,跟真实存在的虚拟机不同,在使用 Docker 的时候,并没有一个真正的“Docker 容器”运行在宿主机里面。Docker 项目帮助用户启动的,还是原来的应用进程,只不过在创建这些进程时,Docker 为它们加上了各种各样的 Namespace 参数。

  • 这时,这些进程就会觉得自己是各自 PID Namespace 里的第 1 号进程,只能看到各自 Mount Namespace 里挂载的目录和文件,只能访问到各自 Network Namespace 里的网络设备,就仿佛运行在一个个“容器”里面,与世隔绝。

隔离与限制

基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底。

  • 首先,既然容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。
    • 可以在容器里通过 Mount Namespace 单独挂载其他不同版本的操作系统文件,比如 CentOS 或者 Ubuntu,但这并不能改变共享宿主机内核的事实。
  • 其次,在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。
    • 如果你的容器中的程序使用 settimeofday(2) 系统调用修改了时间,整个宿主机的时间都会被随之修改,这显然不符合用户的预期。相比于在虚拟机里面可以随便折腾的自由度,在容器里部署应用的时候,“什么能做,什么不能做”,就是用户必须考虑的一个问题。

介绍完容器的“隔离”技术之后,再来研究一下容器的“限制”问题

  • 我们不是已经通过 Linux Namespace 创建了一个“容器”吗,为什么还需要对容器做“限制”呢?

    • 以 PID Namespace 为例,来解释这个问题。虽然容器内的第 1 号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第 100 号进程与其他所有进程之间依然是平等的竞争关系。这就意味着,虽然第 100 号进程表面上被隔离了起来,但是它所能够使用到的资源(比如 CPU、内存),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。当然,这个 100 号进程自己也可能把所有资源吃光。这些情况,显然都不是一个“沙盒”应该表现出来的合理行为。
  • Linux Cgroups 就是 Linux 内核中用来为进程设置资源限制的一个重要功能。 Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。此外,Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。

  • inux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。而对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。

容器镜像

一个“容器”,实际上是一个由 Linux Namespace、Linux Cgroups 和 rootfs 三种技术构建出来的进程的隔离环境。

在这里插入图片描述

  • 从这个结构中不难看出,一个正在运行的 Linux 容器,其实可以被“一分为二”地看待:
    • 一组联合挂载在 /var/lib/docker/aufs/mnt 上的 rootfs,这一部分我们称为 “容器镜像”(Container Image),是容器的静态视图;
    • 一个由 Namespace+Cgroups 构成的隔离环境,这一部分我们称为 “容器运行时”(Container Runtime),是容器的动态视图。

这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。

  • 对 Docker 项目来说,它最核心的原理实际上就是为待创建的用户进程:
    1. 启用 Linux Namespace 配置;
    2. 设置指定的 Cgroups 参数;
    3. 切换进程的根目录(Change Root)。
  • 这样,一个完整的容器就诞生了。不过,Docker 项目在最后一步的切换上会优先使用 pivot_root 系统调用,如果系统不支持,才会使用 chroot。

需要明确的是,rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像。

  • rootfs 只包括了操作系统的“躯壳”,并没有包括操作系统的“灵魂”。对于容器来说,同一台机器上的所有容器,都共享宿主机操作系统的内核。这就意味着,如果你的应用程序需要配置内核参数、加载额外的内核模块,以及跟内核进行直接的交互,你就需要注意了:这些操作和依赖的对象,都是宿主机操作系统的内核,它对于该机器上的所有容器来说是一个“全局变量”,牵一发而动全身。

  • 这也是容器相比于虚拟机的主要缺陷之一:毕竟后者不仅有模拟出来的硬件机器充当沙盒,而且每个沙盒里还运行着一个完整的 Guest OS 给应用随便折腾。

  • 不过,正是由于 rootfs 的存在,容器才有了一个被反复宣传至今的重要特性:一致性。

  • 由于云端与本地服务器环境不同,应用的打包过程,一直是使用 PaaS 时最“痛苦”的一个步骤。但有了容器之后,更准确地说,有了容器镜像(即 rootfs)之后,这个问题被非常优雅地解决了。

  • rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟。

Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。

  • 强调 Linux 容器,是因为比如 Docker on Mac,以及 Windows Docker(Hyper-V 实现),实际上是基于虚拟化技术实现的,与Linux 容器完全不同。

一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。

  • 如果指定 -net=host,就意味着这个容器不会为进程启用 Network Namespace。这就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道。
  • docker commit,实际上就是在容器运行起来后,把最上层的“可读写层”,加上原先容器镜像的只读层,打包组成了一个新的镜像。当然,下面这些只读层在宿主机上是共享的,不会占用额外的空间。而由于使用了联合文件系统,你在容器里对镜像 rootfs 所做的任何修改,都会被操作系统先复制到这个可读写层,然后再修改。这就是所谓的:Copy-on-Write

容器技术使用了 rootfs 机制和 Mount Namespace,构建出了一个同宿主机完全隔离开的文件系统环境。这时候,我们就需要考虑这样两个问题:

  • 容器里进程新建的文件,怎么才能让宿主机获取到?

  • 宿主机上的文件和目录,怎么才能让容器里的进程访问到?

  • 这正是 Docker Volume 要解决的问题:Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作

Docker 又是如何做到把一个宿主机上的目录或者文件,挂载到容器里面去呢?难道又是 Mount Namespace 的黑科技吗?

  • 当容器进程被创建之后,尽管开启了 Mount Namespace,但是在它执行 chroot(或者 pivot_root)之前,容器进程一直可以看到宿主机上的整个文件系统。而宿主机上的文件系统,也自然包括了我们要使用的容器镜像。这个镜像的各个层,保存在 /var/lib/docker/aufs/diff 目录下,在容器进程启动后,它们会被联合挂载在 /var/lib/docker/aufs/mnt/ 目录中,这样容器所需的 rootfs 就准备好了。
  • 所以,我们只需要在 rootfs 准备好之后,在执行 chroot 之前,把 Volume 指定的宿主机目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对应的目录(即 /var/lib/docker/aufs/mnt/[可读写层 ID]/test)上,这个 Volume 的挂载工作就完成了
  • 更重要的是,由于执行这个挂载操作时,“容器进程”已经创建了,也就意味着此时 Mount Namespace 已经开启了。所以,这个挂载事件只在这个容器里可见。你在宿主机上,是看不见容器内部的这个挂载点的。这就保证了容器的隔离性不会被 Volume 打破。

这里提到的 " 容器进程 ",是 Docker 创建的一个容器初始化进程 (dockerinit),而不是应用进程 (ENTRYPOINT + CMD)。dockerinit 会负责完成根目录的准备、挂载设备和目录、配置 hostname 等一系列需要在容器内进行的初始化操作。最后,它通过 execv() 系统调用,让应用进程取代自己,成为容器里的 PID=1 的进程。

  • 这里要使用到的挂载技术,就是 Linux 的绑定挂载(bind mount)机制。它的主要作用就是,允许你将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上。并且,这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐藏起来且不受影响。

你知道的越多,你不知道的越多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/931282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux常用命令_网络命令、关机重启命令

文章目录 1. 网络命令1.1 网络命令: write1.2 网络命令: wall1.3 网络命令: ping1.4 网络命令: ifconfig1.5 网络命令: mail1.6 网络命令: last1.7 网络命令: lastlog1.8 网络命令: traceroute1.9 网络命令: netstat1.10 网络命令: setup1.11 挂载命令 2. 关机重启命令2.1 shut…

基于Java+SpringBoot+Vue前后端分离线上辅导班系统设计和实现

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

移植U8g2图形库—驱动OLED显示(模拟IIC)

目录 1. 所需器件工具 2. IO端口配置 3. 移植步骤 3.1 精简c源码 3.1.1 去掉无用的驱动文件 3.1.2 精简u8g2_d_setup.c 3.1.3 精简u8g2_d_memory.c 3.2 创建函数 3.2.1 创建回调函数 3.2.2 创建测试函数 4. 添加u8g2源码到工程 4.1 项目文件配置 4.2 主函数main.c…

xsschallenge通关(11-15)

level 11 老规矩&#xff0c;先查看源码&#xff0c;做代码审计&#xff1a; <?php ini_set("display_errors", 0); $str $_GET["keyword"]; $str00 $_GET["t_sort"]; $str11$_SERVER[HTTP_REFERER]; $str22str_replace(">&quo…

[C++][C#]yolox TensorRT C++ C#部署

YOLOX是一种新型的高性能探测器&#xff0c;由开发者Zheng Ge、Songtao Liu、Feng Wang、Zeming Li和Jian Sun在《YOLOX: Exceeding YOLO Series in 2021》首次提出。与YOLOV5和YOLOV8相比&#xff0c;YOLOX具有更高的性能和更好的平衡&#xff0c;在速度和精度方面都表现出优越…

无涯教程-机器学习 - 数据统计

在进行机器学习项目时&#xff0c;通常无涯教程会忽略两个最重要的部分&#xff0c;分别是 数学 和 数据 。这是因为知道ML是一种数据驱动的方法&#xff0c;并且ML模型只会产生与提供给它的数据一样好的或坏的输出。 在上一章中&#xff0c;讨论了如何将CSV数据上传到ML项目中…

GMP原理与调度

GMP原理和调度 1.Golang"调度器"的由来1.1单进程时代不需要调度器1.2多进程/线程时代有了调度器需求1.3协程来提高cpu利用率 1.Golang"调度器"的由来 1.1单进程时代不需要调度器 早期的操作系统每个程序就是一个进程&#xff0c;直到一个程序运行完毕&am…

Leetcode81. 搜索旋转排序数组 II

已知存在一个按非降序排列的整数数组 nums &#xff0c;数组中的值不必互不相同。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转 &#xff0c;使数组变为 [nums[k], nums[k1], ..., nums[n-1], nu…

C++ 网络编程项目fastDFS分布式文件系统(七)--qss样式表,项目文件的上传和下载。

目录 1 单例模式 2. 如何在单例类中存储数据? 3. QSS样式表 3.1 选择器类型 3.2 QSS的使用步骤 3.3 登录窗口设置 4. 客户端post方式上传数据 4.1 常用的四种方式 5. 上传协议 1 单例模式 #include<iostream> #include<vector> #include<mutex> …

Python加入Excel--生产力大提高|微软的全方面办公

Python作为一种功能强大的编程语言&#xff0c;已经逐渐成为了数据分析、机器学习、Web开发等领域的主流语言之一。而将Python集成到Excel中&#xff0c;则可以为Excel用户提供更加强大的数据处理和分析能力&#xff0c;同时也可以为Python开发者提供更加便捷的数据处理和可视化…

法雷奥Valeo EDI解决方案

法雷奥集团&#xff08;Valeo&#xff09;是一家总部位于法国的专业致力于汽车零部件、系统、模块的设计、开发、生产及销售的工业集团。公司业务涉及原配套业务及售后业务&#xff0c;是世界领先的汽车零部件供应商&#xff0c;为世界上所有的主要汽车厂提供配套。作为一家高科…

UI位置与布局

UI位置与布局 引言 发现UGUI的RectTransform定位还是很复杂的&#xff0c;感觉有必要详细了解一下 RectTransform 继承自Transform。他的local position由其他几个变量控制。建议不要直接设置position 目的是为了实现UI自动布局。这套方法将绝对定位&#xff0c;相对定位&a…

若依移动端Ruoyi-App 项目的后端项目入门

后端项目运行 运行报错 Error creating bean with name sysConfigServiceImpl: Invocation of init method failed 数据库创建了。 代码连接数据库地方了也匹配上了。但是还是报错。 分析 &#xff1a; 想起来我电脑从来没有安装过redis 下载安装redis到windows 链接&…

C++简单的检测内存泄漏的代码(visual studio)

看了网上很多都需要安装这个库那个库&#xff0c;就很无语&#xff0c;一个初学者&#xff0c;给段代码不好么&#xff0c;然后我偶然发现了微软官方给的代码&#xff0c;链接如下 使用 CRT 库查找内存泄漏 | Microsoft Learn 代码如下 // debug_malloc.cpp // compile by u…

量子非凡暴风去广告接口

>>>https://videos.centos.chat/lzffbf.php/?url 免费提供综合去广告接口&#xff0c;各位请友好调用

033 - date 和 time

date类型&#xff1a; 该DATE类型用于具有日期部分但没有时间部分的值。MySQL检索并DATE以 格式显示 值 。支持的范围是 到。 YYYY-MM-DD1000-01-019999-12-31 -- 创建表&#xff0c;字段类型是date&#xff1a; create table test_date01 (a date); -- 正确格式插入数据 in…

学习ts(十一)本地存储与发布订阅模式

localStorage实现过期时间 目录 准备 安装 npm i rollup typescript rollup-plugin-typescript2// tsconfig.json"module": "ESNext","moduleResolution": "node", "strict": false, // rollup.config.js import …

Python语言实现React框架

迷途小书童的 Note 读完需要 6分钟 速读仅需 2 分钟 1 reactpy 介绍 reactpy 是一个用 Python 语言实现的 ReactJS 框架。它可以让我们使用 Python 的方式来编写 React 的组件&#xff0c;构建用户界面。 reactpy 的目标是想要将 React 的优秀特性带入 Python 领域&#xff0c;…

元类(metaclass)

目录 一、引言 二、什么是元类 三、为什么用元类 四、内置函数exec(储备) 五、class创建类 5.1 type实现 六、自定义元类控制类的创建 6.1 应用 七、__call__(储备) 八、__new__(储备) 九、自定义元类控制类的实例化 一十、自定义元类后类的继承顺序 十一、练习 p…

mysql my.ini、登录、用户相关操作、密码管理、权限管理、权限表,角色管理

my.ini 配置文件格式 登录mysql mysql -h hostname | IP -P port -u username -p database -e “select 语句”&#xff1b; 创建用户、修改用户、删除用户 create user ‘zen’ identified by ‘密码’ ## host 默认是 % create user ‘zen’‘localhost’ identified by ‘密…