【Docker 内核详解】namespace 资源隔离(一):进行 namespace API 操作的 4 种方式

namespace 资源隔离(一):进行 namespace API 操作的 4 种方式

  • 1.通过 clone() 在创建新进程的同时创建 namespace
  • 2.查看 /proc/[pid]/ns 文件
  • 3.通过 setns() 加入一个已经存在的 namespace
  • 4.通过 unshare() 在原先进程上进行 namespace 隔离
  • 5.fork() 系统调用(拓展)

当谈论 Docker 时,常常会聊到 Docker 的实现方式。很多开发者都知道,Docker 容器本质上是宿主机上的进程(容器所在的运行环境统一称为宿主机)。Docker 通过 namespace 实现了 资源隔离,通过 cgroups 实现了 资源限制,通过写时复制机制( copy-on-write)实现了 高效的文件操作。但当更进一步深入 namespacecgroups 等技术细节时,大部分开发者都会感到茫然无措。所以在这里,希望先带领大家走进 Linux 内核,了解 namespacecgroups 的技术细节。

Docker 大热之后,热衷技术的开发者就会思考,想要实现一个资源隔离的容器,应该从哪些方面下手?也许第一反应就是 chroot 命令,这条命令给用户最直观的感受就是在使用后根目录 / 的挂载点切换了,即 文件系统 被隔离了。接着,为了在分布式的环境下进行通信和定位,容器必然要有独立的 IP、端口、路由等,自然就联想到了 网络 的隔离。同时,容器还需要一个独立的 主机名 以便在网络中标识自己。有了网络,自然离不开通信,也就想到了 进程间通信 需要隔离。开发者可能也已经想到了权限的问题,对用户和用户组的隔离就实现了 用户权限 的隔离。最后,运行在容器中的应用需要有进程号(PID),自然也需要与宿主机中的 PID 进行隔离。

由此,基本上完成了一个容器所需要做的 6 项隔离,Linux 内核中提供了这 6 种 namespace 隔离的系统调用,如下表所示。当然,真正的容器还需要处理许多其他工作。

namespace系统调用参数隔离内容
UTSCLONE_ NEWUTS主机名与域名
IPCCLONE_NEWIPC信号量、消息队列和共享内存
PIDCLONE_NEWPID进程编号
NetworkCLONE_NEWNET网络设备、网络栈、端口等
MountCLONE_NEWNS挂载点(文件系统)
UserCLONE_NEWUSER用户和用户组

实际上,Linux 内核实现 namespace 的一个主要目的,就是实现轻量级虚拟化(容器)服务。在同一个 namespace 下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛自己置身于一个独立的系统环境中,以达到独立和隔离的目的。

需要说明的是,本文所讨论的 namespace 实现针对的均是 Linux 内核 3.8 3.8 3.8 及以后的版本(user namespace 在内核 3.8 3.8 3.8 版本以后才支持)。接下来,将首先介绍使用 namespace 的 API,然后对这 6 种 namespace 进行逐一讲解,并通过程序让读者切身感受隔离效果。

namespace 的 API 包括 clone()setns() 以及unshare(),还有 /proc 下的部分文件。为了确定隔离的到底是哪 6 项 namespace,在使用这些 API 时,通常需要指定以下 6 个参数中的一个或多个,通过 |(位或)操作来实现。从上表可知,这 6 个参数分别是 CLONE_NEWIPCCLONE_NEWNSCLONE_NEWNETCLONE_NEWPIDCLONE_NEWUSERCLONE_NEWUTS

1.通过 clone() 在创建新进程的同时创建 namespace

使用 clone() 来创建一个独立 namespace 的进程,是最常见的做法,也是 Docker 使用 namespace 最基本的方法,它的调用方式如下。

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

clone() 实际上是 Linux 系统调用 fork() 的一种更通用的实现方式,它可以通过 flags 来控制使用多少功能。一共有 20 多种 CLONE_*flag(标志位)参数用来控制 clone 进程的方方面面(如是否与父进程共享虚拟内存等),下面挑选与 namespace 相关的 4 个参数进行说明。

  • child func 传入子进程运行的程序主函数。
  • child stack 传入子进程使用的栈空间。
  • flags 表示使用哪些 CLONE_* 标志位,与 namespace 相关的主要包括 CLONE_NEWIPCCLONE_NEWNSCLONE_NEWNETCLONE_NEWPIDCLONE_NEWUSERCLONE_NEWUTS
  • args 则可用于传入用户参数。

2.查看 /proc/[pid]/ns 文件

3.8 3.8 3.8 版本的内核开始,用户就可以在 /proc/[pid]/ns 文件下看到指向不同 namespace 号的文件,效果如下所示,形如 [4026531839] 者即为 namespace 号。

$ ls -l /proc/$$/ns         <<--$$是shell中表示当前运行的进程ID号
total 0
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 net -> net:[4026531956]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 pid -> pid:[4026531836]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 user->user:[4026531837]
lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 uts -> uts:[4026531838]

如果两个进程指向的 namespace 编号相同,就说明它们在同一个 namespace 下,否则便在不同 namespace 里面。/proc/[pid]/ns 里设置这些 link 的另外一个作用是,一旦上述 link 文件被打开,只要打开的 文件描述符fd)存在,那么就算该 namespace 下的所有进程都已经结束,这个 namespace 也会一直存在,后续进程也可以再加入进来。在 Docker 中,通过文件描述符定位和加入一个存在的 namespace 是最基本的方式。

另外,把 /proc/[pid]/ns 目录文件使用 --bind 方式挂载起来可以起到同样的作用,命令如下:

# touch ~/uts
# mount --bind /proc/27514/ns/uts ~/uts

为了方便起见,后面的讲解中会使用这个 ~/uts 文件来代替 /proc/27514/ns/uts

注意:如果大家看到 ns 下的内容与本节所述不符,那可能是因为使用了 3.8 3.8 3.8 以前版本的内核。如在内核版本 2.6 2.6 2.6 中,该目录下存在的只有 ipcnetuts,并且以硬链接方式存在。

3.通过 setns() 加入一个已经存在的 namespace

上文提到,在进程都结束的情况下,也可以通过挂载的形式把 namespace 保留下来,保留 namespace 的目的是为以后有进程加入做准备。在 Docker 中,使用 docker exec 命令在已经运行着的容器中执行一个新的命令,就需要用到该方法。通过 setns() 系统调用,进程从原先的 namespace 加入某个已经存在的 namespace,使用方法如下。通常为了不影响进程的调用者,也为了使新加入的 pid namespace 生效,会在 setns() 函数执行后使用 clone() 创建子进程继续执行命令,让原先的进程结束运行。

int setns(int fd, int nstype);
  • 参数 fd 表示要加入 namespace 的文件描述符。上文提到,它是一个指向 /proc/[pid]/ns 目录的文件描述符,可以通过直接打开该目录下的链接或者打开一个挂载了该目录下链接的文件得到。
  • 参数 nstype 让调用者可以检查 fd 指向的 namespace 类型是否符合实际要求。该参数为 0 表示不检查。

为了把新加入的 namespace 利用起来,需要引入 execve() 系列函数,该函数可以执行用户命令,最常用的就是调用 /bin/bash 并接受参数,运行起一个 shell,用法如下。

fd = open(argv[1], O_RDONLY);    /* 获取 namespace 文件描述符 */
setns(fd, 0);                    /* 加入新的 namespace */
execvp(argv[2], &argv[2]);       /* 执行程序 */

假设编译后的程序名称为 setns-test

# ./setns-test ~/uts /bin/bash      # ~/uts 是绑定的 /proc/27514/ns/uts

至此,就可以在新加入的 namespace 中执行 shell 命令了,下文会多次使用这种方式来演示隔离的效果。

4.通过 unshare() 在原先进程上进行 namespace 隔离

最后要说明的系统调用是 unshare(),它与 clone() 很像,不同的是,unshare() 运行在原先的进程上,不需要启动一个新进程。

int unshare(int flags);

调用 unshare() 的主要作用就是,不启动新进程就可以起到隔离的效果,相当于跳出原先的 namespace 进行操作。这样,就可以在原进程进行一些需要隔离的操作。Linux 中自带的 unshare 命令,就是通过 unshare() 系统调用实现的。Docker 目前并没有使用这个系统调用,这里不做展开,读者可以自行查阅资料学习该命令的知识。

5.fork() 系统调用(拓展)

系统调用函数 fork() 并不属于 namespace 的 API,这部分内容属于延伸阅读,如果读者已经对 fork() 有足够多的了解,可以忽略该部分。

当程序调用 fork() 函数时,系统会创建新的进程,为其分配资源,例如存储数据和代码的空间,然后把原来进程的所有值都复制到新进程中,只有少量数值与原来的进程值不同,相当于复制了本身。那么程序的后续代码逻辑要如何区分自己是新进程还是父进程呢?

fork() 的神奇之处在于它仅仅被调用一次,却能够返回两次(父进程与子进程各返回一次),通过返回值的不同就可以区分父进程与子进程。它可能有以下 3 种不同的返回值:

  • 在父进程中,fork() 返回新创建子进程的进程 ID;
  • 在子进程中,fork() 返回 0;
  • 如果出现错误,fork() 返回一个负值。

下面给出一段实例代码,命名为 fork_example.c

#include <unistd.h>
#include <stdio.h>
int main (){pid_t fpid; // fpid 表示 fork 函数返回的值int count=0;fpid=fork();if (fpid < 0) printf("error in fork!");else if (fpid == 0) {printf("I am child. Process id is %d\n",getpid());}else {printf("i am parent. Process id is %d\n",getpid());}	return 0;
}

编译并执行,结果如下。

[root@local:~#] gcc -Wall fork_example.c && ./a.out
I am parent. Process id is 28365
I am child. Process id is 28366

代码执行过程中,在语句 fpid=fork() 之前,只有一个进程在执行这段代码,在这条语句之后,就变成父进程和子进程同时执行了。这两个进程几乎完全相同,将要执行的下一条语句都是 if (fpid < 0),同时 fpid=fork() 的返回值会依据所属进程返回不同的值。

使用 fork() 后,父进程有义务监控子进程的运行状态,并在子进程退出后自己才能正常退出,否则子进程就会成为 “孤儿” 进程。


后续将根据 Docker 内部对 namespace 资源隔离使用的方式分别对 6 种 namespace 进行详细的解析。

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

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

相关文章

Webapck 解决:[webpack-cli] Error: Cannot find module ‘vue-loader/lib/plugin‘ 的问题

1、问题描述&#xff1a; 其一、报错为&#xff1a; [webpack-cli] Error: Cannot find module vue-loader/lib/plugin 中文为&#xff1a; [webpack-cli] 错误&#xff1a;找不到模块“vue-loader/lib/plugin” 其二、问题描述为&#xff1a; 在项目打包的时候 npm run …

c++视觉图像----扩充边界

图像扩充边界 #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp>int main() {// 读取图像cv::Mat image cv::imread("1.jpg", cv::IMREAD_COLOR);if (image.empty()) {std::cerr << "Could not open or find the imag…

Java项目调用Python脚本(基于idea)

前期准备 1.首先需要在本地环境中安装配置python环境 Python(含PyCharm及配置)下载安装以及简单使用(Idea) 博主本次使用python版本为py3.7.3 2.idea安装python插件 位置&#xff1a;File->Settings->Plugins->python->安装后重启即可 3.引入jython依赖 &l…

力扣 -- 1312. 让字符串成为回文串的最少插入次数

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int minInsertions(string s) {int ns.size();vector<vector<int>> dp(n,vector<int>(n));//无需初始化//填表for(int in-1;i>0;i--){for(int ji;j<n;j){//状态转移方程if(s[i]s[…

如何在 Spring Boot 中进行数据备份

在Spring Boot中进行数据备份 数据备份是确保数据安全性和可恢复性的关键任务之一。Spring Boot提供了多种方法来执行数据备份&#xff0c;无论是定期备份数据库&#xff0c;还是将数据导出到外部存储。本文将介绍在Spring Boot应用程序中进行数据备份的不同方法。 方法1: 使用…

【深度学习实验】循环神经网络(一):循环神经网络(RNN)模型的实现与梯度裁剪

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 数据处理 2. rnn 测试 3. grad_clipping 4. 代码整合 经验是智慧之父&#xff0c;记忆是智慧之母。 ——谚语 一、实验介绍 本实验介绍了一个简单的循环神经网络…

如何优化前端图像和多媒体资源?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

电子书制作软件Vellum mac中文版特点

Vellum mac是一款专业的电子书制作软件&#xff0c;它可以帮助用户将文本文件转换为高质量的电子书&#xff0c;支持多种格式&#xff0c;包括EPUB、MOBI、PDF等。Vellum具有直观的用户界面和易于使用的工具&#xff0c;可以让用户快速地创建和发布电子书。 Vellum mac软件特点…

追求极致性能!Qwik 1.0版本发布

前言 偶然发现 Qwik 这个 Node.js 后端框架&#xff0c;感觉非常新奇&#xff0c;它构建的网站&#xff0c;能够在谷歌的网站评测工具中跑出100分满分的成绩&#xff0c;而且还是移动端&#xff08;一般情况下&#xff0c;移动端分值要低于PC端&#xff09;&#xff01;不得不…

IDEA XML文件里写SQL比较大小条件

背景 最近开发的时候&#xff0c;有一个需求的查询需要支持范围查询[a,b)&#xff0c;并且查询的结果要求查询的范围含头端点不含尾端点。因为between…and…查询的范围是含头含尾的&#xff0c;因而不能使用。 因此打算直接使用>和<来比较实现&#xff0c;使用>的时…

【Redis】Set集合内部编码方式

内部编码 集合类型的内部编码有两种&#xff1a; intset&#xff08;整数集合&#xff09;&#xff1a;当集合中的元素都是整数并且元素的个数⼩于set-max-intset-entries配置&#xff08;默认512个&#xff09;时&#xff0c;Redis会选⽤intset来作为集合的内部实现&#xf…

与艺术同频!卡萨帝在海外崭露头角

在品牌全球化步伐日益加快的当下&#xff0c;高端品牌如何真正实现业务全球化、品牌全球化乃至用户圈层全球化&#xff1f; 作为国际高端家电引领者&#xff0c;卡萨帝今年以来在全球范围内展开了一系列的品牌布局活动。1月&#xff0c;卡萨帝于巴基斯坦召开品牌发布会&#x…

hyperf框架WebSocket 服务

1&#xff1a;安装 composer require hyperf/websocket-server2&#xff1a;配置 Server 修改 config/autoload/server.php&#xff0c;增加以下配置。 return [servers > [[name > ws,type > Server::SERVER_WEBSOCKET,host > 0.0.0.0,port > 9502,sock_typ…

分类预测 | MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输入分类预测

分类预测 | MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输入分类预测 目录 分类预测 | MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输入分类预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输…

[JAVA版本] Websocket获取B站直播弹幕——基于直播开放平台

教程 B站直播间弹幕Websocket获取 — 哔哩哔哩直播开放平台 基于B站直播开放平台开放且未上架时&#xff0c;只能个人使用。 代码实现 1、相关依赖 fastjson2用于解析JSON字符串&#xff0c;可自行替换成别的框架。 hutool-core用于解压zip数据&#xff0c;可自行替换成别的…

手写Spring系列【一】IOC的简单实现笔记

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;项目专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

大日志(大文件)查看工具

一款很不错的日志查看工具&#xff0c; 优势是能查看很大的日志文档。 无需安装&#xff0c;解压后运行即可&#xff1b; 有注册版&#xff0c;不注册也可以使用。 官方地址&#xff1a; LogViewer - Home page 一个下载地址&#xff1a; 日志查看工具UVviewsoft LogViewer(超大…

makefile编译举例

makefile编译举例 # 定义编译器和编译选项 CC gcc CFLAGS -Wall -Werror # 定义目标文件名 TARGET myprogram # 定义需要编译的源文件目录和文件名 SRC_DIR1 src1 SRC_DIR2 src2 OBJ_DIR1 obj1 OBJ_DIR2 obj2 SRC_FILES1 file1.c file2.c SRC_FILES2…

电脑如何查看是否支持虚拟化及如何开启虚拟化

什么是虚拟化? Intel Virtualization Technology就是以前众所周知的“Vanderpool”技术&#xff08;简称VT&#xff0c;中文译为虚拟化技术&#xff09;&#xff0c;这种技术可以让一个CPU工作起来就像多个CPU并行运行&#xff0c;从而使得在一部电脑内同时运行多个操作系统成…

MyBatis的xml里#{}的参数为null报错、将null作为参数传递报错问题

今天在调试的过程中发现一个bug&#xff0c;把传入的参数写到查询分析器中执行没有问题&#xff0c;但是在程序中执行就报错&#xff1a;org.springframework.jdbc.UncategorizedSQLException : Error setting null parameter. Most JDBC drivers require that the JdbcType m…