【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)实现了
高效的文件操作。但当更进一步深入
namespace 和
cgroups 等技术细节时,大部分开发者都会感到茫然无措。所以在这里,希望先带领大家走进 Linux 内核,了解
namespace 和
cgroups 的技术细节。
Docker 大热之后,热衷技术的开发者就会思考,想要实现一个资源隔离的容器,应该从哪些方面下手?也许第一反应就是 chroot 命令,这条命令给用户最直观的感受就是在使用后根目录 / 的挂载点切换了,即 文件系统 被隔离了。接着,为了在分布式的环境下进行通信和定位,容器必然要有独立的 IP、端口、路由等,自然就联想到了 网络 的隔离。同时,容器还需要一个独立的 主机名 以便在网络中标识自己。有了网络,自然离不开通信,也就想到了 进程间通信 需要隔离。开发者可能也已经想到了权限的问题,对用户和用户组的隔离就实现了 用户权限 的隔离。最后,运行在容器中的应用需要有进程号(PID),自然也需要与宿主机中的 PID 进行隔离。
由此,基本上完成了一个容器所需要做的 6 项隔离,Linux 内核中提供了这 6 种 namespace 隔离的系统调用,如下表所示。当然,真正的容器还需要处理许多其他工作。
| namespace | 系统调用参数 | 隔离内容 |
|---|---|---|
| UTS | CLONE_ NEWUTS | 主机名与域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
| PID | CLONE_NEWPID | 进程编号 |
| Network | CLONE_NEWNET | 网络设备、网络栈、端口等 |
| Mount | CLONE_NEWNS | 挂载点(文件系统) |
| User | CLONE_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_NEWIPC、CLONE_NEWNS、CLONE_NEWNET、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_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_NEWIPC、CLONE_NEWNS、CLONE_NEWNET、CLONE_NEWPID、CLONE_NEWUSER和CLONE_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 中,该目录下存在的只有 ipc、net 和 uts,并且以硬链接方式存在。
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 进行详细的解析。
相关文章:
【Docker 内核详解】namespace 资源隔离(一):进行 namespace API 操作的 4 种方式
namespace 资源隔离(一):进行 namespace API 操作的 4 种方式 1.通过 clone() 在创建新进程的同时创建 namespace2.查看 /proc/[pid]/ns 文件3.通过 setns() 加入一个已经存在的 namespace4.通过 unshare() 在原先进程上进行 namespace 隔离5…...
【技术研究】环境可控型原子力显微镜超高真空度精密控制解决方案
摘要:针对原子力显微镜对真空度和气氛环境精密控制要求,本文提出了精密控制解决方案。解决方案基于闭环动态平衡法,在低真空控制时采用恒定进气流量并调节排气流量的方法,在高真空和超高真空控制时则采用恒定排气流量并调节进气流…...
【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用
一、导言 1、引言 Vuex是一个用于Vue.js应用程序的状态管理模式和库。它建立在Vue.js的响应式系统之上,提供了一种集中管理应用程序状态的方式。使用Vuex,您可以将应用程序的状态存储在一个单一的位置(即“存储”)中,…...
python经典百题之简单加密数据
题目:某个公司采用公用电话传递数据,数据是四位的整数,在传递过程中是加密的,加密规则如下: 每位数字都加上5,然后用和除以10的余数代替该数字,再将第一位和第四位交换,第二位和第三位交换 程序分析 对于…...
登陆认证权限控制(1)——从session到token认证的变迁 session的问题分析 + CSRF攻击的认识
前言 登陆认证,权限控制是一个系统必不可少的部分,一个开放访问的系统能否在上线后稳定持续运行其实很大程度上取决于登陆认证和权限控制措施是否到位,不然可能系统刚刚上线就会夭折。 本篇博客回溯登陆认证的变迁历史,阐述sess…...
单点接地、多点接地、混合接地
有三种基本的信号接地方式:浮地、单点接地、多点接地。 浮地:目的是使电路或设备与公共地线可能引起环流的公共导线隔离起来,浮地还使不同电位的电路之间配合变得容易。缺点:容易出现静电积累引起强烈的静电放电。折中方案:接入泄…...
【C++初阶(一)】学习前言 命名空间与IO流
本专栏内容为:C学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚代码仓库:小小unicorn的代码仓库&…...
flask vue跨域问题
问题: 调试时候跨域访问报: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response. 解决办法: 安装flask_cros from flask_cors import CORS CORS(app) app.after_request def a…...
stm32(二十)IAP升级优化(双缓存,可恢复)
这次主要对STM32F103/Keil和LPC2478/IAR加了一个IAP在线升级功能, 主要记录一下自己的思路,无代码,实在是代码感觉没啥写的,都是一些网上很多流传的东西。 1、开发环境 Keilstm32f103JLINK 2、程序思路 在升级中,必…...
HDLbits:Exams/ece241 2013 q4
本题是一个实际的应用问题,一个水库,有三个传感器S1、S2、S3提供输入,经过控制电路,四个输出给到四个流量阀。也就是说,本题想让我们根据水位去控制流量阀。 问题的关键在于把什么抽象成state,答案是&…...
什么是React的虚拟DOM(Virtual DOM)?它的作用是什么?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...
Response Status Code 301、302
目录 Information Django redirect Influence Information HTTP状态码301、302和304分别表示以下情况: codeinformation301(Moved Permanently) 永久重定向。当请求的资源已经被永久地移动到了一个新的URI时,服务器会返回这个…...
import { ref, onMounted, reactive } from ‘vue‘
ref, onMounted, reactive 用于创建和操作响应式数据、生命周期钩子。 1.ref 用来创建一个响应式的引用(Reactive Reference)的函数,主要用于创建基本数据类型(如数字、字符串等)的响应式数据。 通过 ref 创建的变…...
【TB作品】基于MSP430G2553单片机的超声波测距与报警系统,原理图,PCB
功能: 1 超声波测距显示 2 按键设置报警上下限 3 蜂鸣器报警 原理图: PCB样式: 实物: 代码: https://github.com/xddun/blog_code_search...
npm install报错
在命令提示符窗口下载npm,报错如下: $npm install报错信息如下: npm WARN old lockfile npm WARN old lockfile The package-lock.json file was created with an old version of npm, npm WARN old lockfile so supplemental metadata must…...
Flutter自定义model实体类
在某些场景下,我们可能需要自定义Flutter model实体类,来创建更加结构化和有组织的代码,提高代码的可重用性,并增强Flutter应用程序的整体可维护性。 自定义小部件:在创建自己的小部件时,可能需要定义自定义数据类型来…...
java项目实现不停服更新的4种方案(InsCode AI 创作助手)
文章目录 1. Blue-Green 部署2. 滚动更新3. 使用负载均衡器4. 灰度发布 在软件开发和维护中,不停机更新是确保应用程序持续可用的关键任务之一。以下是四种常见的不停机更新策略及其示例: 1. Blue-Green 部署 概念: Blue-Green 部署是一种部…...
7.1 yolov5优化模型时,自动标注xml数据
yolov5优化模型时,一般需要继续标注一些检测错误的图片,将其标为xml数据。以下是根据训练好的模型自动标注xml数据的python代码: 注意:代码中包含了本人的yolov5的测试过程,测试过程可以自己根据yolov5的测试文件自行…...
开发者职场“生存状态”大调研报告分析 - 第一版
听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…...
在MySQL中使用!=还能走索引吗?
在MySQL中使用!还能走索引吗? 一般情况下,我们会在一个索引上较多的使用等值查询或者范围查询,此时索引大多可以帮助我们极快的查询出我们需要的数据。 那当我们在where条件中对索引列使用!查询,索引还能发挥他的作用吗…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
