一文彻底搞懂docker中的namespace(代码片段)

神技圈子 神技圈子     2023-02-23     282

关键词:

什么是namespace

namespace是对全局系统资源的一种封装隔离。这样可以让不同namespace的进程拥有独立的全局系统资源。这样改变一个namespace的系统资源只会影响当前namespace中的进程,对其它namespace中的资源没有影响。以前Linux也有一个。之前有一个系统调用chroot和namespace类似。

namespace分类

chroot内部不能访问外部的内容,namespce在此基础上提供了UTS、IPC、mount、PID、network、User等隔离机制。

分类参数隔离内容
Mount namespacesCLONE_NEWNSMount points (since Linux 2.4.19)
UTS namespacesCLONE_NEWUTSHostname and NIS domain name (since Linux 2.6.19)
IPC namespacesCLONE_NEWIPCSystem V IPC, POSIX message queues (since Linux 2.6.19)
PID namespacesCLONE_NEWPIDProcess IDs (since Linux 2.6.24)
Network namespacesCLONE_NEWNETNetwork devices, stacks, ports, etc. (since Linux 2.6.24)
User namespacesCLONE_NEWUSERUser and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)

查看进程所属的namespace

Linux中每个进程都有一个/proc/[pid]/ns这样一个目录,这里面包含了该进程所属的namespace信息。如下图所示

从结果可以看到,对于每种类型的namespace,进程都会同一个namespace ID 相关联。
就ipc这个namespace类型来说,ipc -> ipc:[4026532560]中,ipc是namespace类型,4026532560是inode number。假设两个进程的ipc namespace下的inode number一样,说明这两个进程属于同一个namespace。对于其它的namespace类型也一样。

相关系统调用

clone

clone调用主要创建一个新进程并放入到namespace中。

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

flags: 
    指定一个或者多个上面的CLONE_NEW*(当然也可以包含跟namespace无关的flags), 
    这样就会创建一个或多个新的不同类型的namespace, 
    并把新创建的子进程加入新创建的这些namespace中。

setns

setns调用将当前进程加入到已有的namespace中

int setns(int fd, int nstype);

fd: 
    指向/proc/[pid]/ns/目录里相应namespace对应的文件,
    表示要加入哪个namespace

nstype:
    指定namespace的类型(上面的任意一个CLONE_NEW*):
    1. 如果当前进程不能根据fd得到它的类型,如fd由其他进程创建,
    并通过UNIX domain socket传给当前进程,
    那么就需要通过nstype来指定fd指向的namespace的类型
    2. 如果进程能根据fd得到namespace类型,比如这个fd是由当前进程打开的,
    那么nstype设置为0即可

unshare

使当前进程退出指定的namespace,并加入到新创建的namespace中。

int unshare(int flags);

flags:
    指定一个或者多个上面的CLONE_NEW*,
    这样当前进程就退出了当前指定类型的namespace并加入到新创建的namespace

unshare和clone都是创建并加入到namespace,它们的区别是clone是将本进程写入到namespace,而unshare是创建子进程让子进程。

UTS namespace

UTS namespace 是用来隔离系统的hostname以及NIS domain name。UTS的由来就是struct utsname结构体。hostname和domainname可以通过sethostname和setdomainname系统函数来设置,以及gethostname和getdomainname函数来获取。

#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

using namespace std;

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char *const child_args[] = "/bin/bash", NULL;

int ChildFunc(void *arg)

   cout << "Container - inside the container, it's pid is "  <<  getpid() << endl;
   ::sethostname("container_001", 13);
   execv(child_args[0],child_args);
   return 1;


#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);

int main()

   cout << "parent pid is: " << getpid() << endl;
   pid_t child_pid = ::clone(ChildFunc, child_stack+STACK_SIZE, CLONE_NEWUTS|SIGCHLD, NULL);

  NO_EXIT(child_pid, "clone");

  ::waitpid(child_pid, nullptr, 0);
  return 0;   

编译后运行结果如下

可以看到父进程和子进程不在同一个UTS namespace。

再来开一个将当前进程加入到namespace的例子

#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>

using namespace std;


#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);

int main(int argc, char **argv)

   //获取namespace对应的fd   
   int fd = open(argv[1], O_RDONLY);

  NO_EXIT(fd, "open");

  int ret =setns(fd, 0);

  NO_EXIT(ret, "setns");
  
  execv("/bin/bash", NULL);
  return 0;   

重新打开个shell窗口看下hostname

在上一个程序的shell窗口查看namespace的inode number(应用程序和其所属的namespace在同一namespace)。

继续回到第二个窗口运行程序,可以看到bash提示符里面的hostname以及UTS namespace的inode number和第一个shell窗口的都一样

IPC namespace

IPC全称 Inter-Process Communication,是Unix/Linux下进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。所以,为了隔离,我们也需要把IPC给隔离开来,这样,只有在同一个Namespace下的进程才能相互通信。要启动IPC隔离,我们只需要在调用clone时加上CLONE_NEWIPC参数就可以了。
下面来看一个例子
首先创建一个Queue,执行ipcmk创建队列,并用ipcs查看结果。正常情况下子进程也能看到该队列。
这个两个命令解释如下

  • ipcmk :创建shared memory segments, message queues, 和semaphore arrays
  • ipcs : 查看shared memory segments, message queues, 和semaphore arrays的相关信息

    对创建UTS程序进行修改,添加CLONE_NEWIPC参数

    运行结果如下:

运行程序后可以看到IPC已经被隔离

PID namespace

PID namespaces用来隔离进程的ID空间,使得不同pid namespace里的进程ID可以重复且相互之间不影响。
PID namespace可以嵌套,也就是说有父子关系,当前创建的所有新namespace都是当前namespace的子namespace,父namespace可以看到所有孙子的namespace中的进程信息。
继续来看一个例子吧:

#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

using namespace std;

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char *const child_args[] = "/bin/bash", NULL;

int ChildFunc(void *arg)

   cout << "Container - inside the container, it's pid is "  <<  getpid() << endl;
   ::sethostname("container_001", 13);
   execv(child_args[0],child_args);
   return 1;


#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);

int main()

//添加了CLONE_NEWPID参数
   pid_t child_pid = ::clone(ChildFunc, child_stack+STACK_SIZE, CLONE_NEWUTS|CLONE_NEWPID|SIGCHLD, NULL);

  NO_EXIT(child_pid, "clone");

  ::waitpid(child_pid, nullptr, 0);
  return 0;   

运行结果如下

结果打印出来看到PID为1,1就是init进程,它是所有进程的父进程。如果子进程脱离了父进程,那么init就会负责回收资源并结束这个子进程。所以,要做到进程隔离,首先就得创建PID为1的进程(类似chroot)。但是这并没有完全隔离,比如/proc 文件系统父子进程还是一样的。

mount namespace

Mount namespace用来隔离文件系统的挂载点, 使得不同的mount namespace拥有自己独立的挂载点信息,不同的namespace之间不会相互影响,这对于构建用户或者容器自己的文件系统目录非常有用。
Mount namespace是第一个被加入Linux的namespace,由于当时没想到还会引入其它的namespace,所以取名为CLONE_NEWNS,而没有叫CLONE_NEWMOUNT。
下面例子继续对示例代码进行修改

#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

using namespace std;

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char *const child_args[] = "/bin/bash", NULL;

int ChildFunc(void *arg)

   cout << "Container - inside the container, it's pid is "  <<  getpid() << endl;
   ::sethostname("container_001", 13);
   system("mount -t proc proc /proc");
   execv(child_args[0],child_args);
   return 1;


#define NO_EXIT(code, msg); if(code==-1)perror(msg); exit(-1);

int main()

   cout << "parent pid is: " << getpid() << endl;
   pid_t child_pid = ::clone(ChildFunc, child_stack+STACK_SIZE, CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWNS|SIGCHLD, NULL);

  NO_EXIT(child_pid, "clone");

  ::waitpid(child_pid, nullptr, 0);
  return 0;   



可以看到程序运行后用ps查看只有两个进程了,而进程1就是/bin/bash进程。子进程中新namespace的所有mount操作都只影响自身的文件系统,做到了真正意义上的隔离。

一文带你彻底搞懂docker中的cgroup(代码片段)

前言进程在系统中使用CPU、内存、磁盘等计算资源或者存储资源还是比较随心所欲的,我们希望对进程资源利用进行限制,对进程资源的使用进行追踪。这就让cgroup的出现成为了可能,它用来统一将进程进行分组࿰... 查看详情

一文带你彻底搞懂docker中的cgroup(代码片段)

前言进程在系统中使用CPU、内存、磁盘等计算资源或者存储资源还是比较随心所欲的,我们希望对进程资源利用进行限制,对进程资源的使用进行追踪。这就让cgroup的出现成为了可能,它用来统一将进程进行分组࿰... 查看详情

一文彻底搞懂前端沙箱(代码片段)

什么是“沙箱”沙箱(Sandbox)[1]也称作:“沙箱/沙盒/沙盘”。沙箱是一种安全机制,为运行中的程序提供隔离环境。通常是作为一些来源不可信、具破坏力或无法判定程序意图的程序提供实验之用。沙箱能够安全的执行不受信... 查看详情

一文彻底搞懂slam技术(代码片段)

什么是SLAM?SLAM (simultaneouslocalizationandmapping),也称为CML(ConcurrentMappingandLocalization),即时定位与地图构建,或并发建图与定位。问题可以描述为:将一个机器人放入未知环境中的未知位置,是否有办法让机器人一边逐步描... 查看详情

一文彻底搞懂slam技术(代码片段)

什么是SLAM?SLAM (simultaneouslocalizationandmapping),也称为CML(ConcurrentMappingandLocalization),即时定位与地图构建,或并发建图与定位。问题可以描述为:将一个机器人放入未知环境中的未知位置,是否有办法让机器人一边逐步描... 查看详情

java中的线程池如何实现,一文彻底搞懂(代码片段)

前言为什么要用线程池一键获取线程相关资料,还可获取最新java面试真题库在HotSpotVM的线程模型中,Java线程被一对一映射为内核线程。Java在使用线程执行程序时,需要调用操作系统内核的API,创建一个内核线程&... 查看详情

一文彻底搞懂python中的装饰器偏函数(代码片段)

装饰器要讲清楚装饰器,首先要知道一些前置概念。下文涉及到这些概念的地方,会展开讲述。什么是装饰器?装饰器是一种AOP(面向切面编程)的设计模式。面向对象编程往往需要通过继承或组合依赖等方... 查看详情

一文彻底搞懂python中的描述器反射(代码片段)

描述器什么是描述器?一个类中定义了如下一个或多个魔术方法,这个类的实例就是描述器:__get__,__set__,__delete__通常需要两个类来构建描述器:如果类B的类属性x,指向另一个类A的实例。被指向的A... 查看详情

一文彻底搞懂python中的描述器反射(代码片段)

描述器什么是描述器?一个类中定义了如下一个或多个魔术方法,这个类的实例就是描述器:__get__,__set__,__delete__通常需要两个类来构建描述器:如果类B的类属性x,指向另一个类A的实例。被指向的A... 查看详情

一文彻底搞懂leveldb架构(代码片段)

leveldbleveldb是一个写性能十分优秀的存储引擎,是典型的LSM-tree的实现。LSM的核心思想是为了换取最大的写性能而放弃掉部分读性能。那么,为什么leveldb写性能高?简单来说它就是尽量减少随机写的次数。leveldb首先将... 查看详情

一文彻底搞懂leveldb架构(代码片段)

leveldbleveldb是一个写性能十分优秀的存储引擎,是典型的LSM-tree的实现。LSM的核心思想是为了换取最大的写性能而放弃掉部分读性能。那么,为什么leveldb写性能高?简单来说它就是尽量减少随机写的次数。leveldb首先将... 查看详情

一文彻底搞懂leveldb架构(代码片段)

leveldbleveldb是一个写性能十分优秀的存储引擎,是典型的LSM-tree的实现。LSM的核心思想是为了换取最大的写性能而放弃掉部分读性能。那么,为什么leveldb写性能高?简单来说它就是尽量减少随机写的次数。leveldb首先将... 查看详情

flink总结之一文彻底搞懂时间和窗口(代码片段)

Flink总结之一文彻底搞懂时间和窗口文章目录Flink总结之一文彻底搞懂时间和窗口一、Flink中时间概念1.事件时间(EventTime)2.处理时间(ProcessingTime)3.摄入时间(IngestionTime)二、水位线(Watermark)1.... 查看详情

一文彻底搞懂zookeeper(代码片段)

本文是基于CentOS7.9系统环境,进行Zookeeper的学习和使用1.Zookeeper简介1.1什么是ZookeeperZookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。本质上,就是文件系统+通知机制1.2Zookeeper工作机制Zookeepe... 查看详情

一文彻底搞懂zookeeper(代码片段)

本文是基于CentOS7.9系统环境,进行Zookeeper的学习和使用1.Zookeeper简介1.1什么是ZookeeperZookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。本质上,就是文件系统+通知机制1.2Zookeeper工作机制Zookeepe... 查看详情

python入门到精通一文让你彻底搞懂python的函数(代码片段)

🚀作者:“大数据小禅”🚀粉丝福利:加入小禅的大数据社群🚀欢迎小伙伴们点赞👍、收藏⭐、留言💬目录Python中的函数及其调用对于函数的理解:python中的自定义函数自定义空函数Python特性之... 查看详情

一文彻底搞懂kafka(代码片段)

Kafka的学习和使用本文是基于CentOS7.9系统环境,进行Kafka的学习和使用一、Kafka的简介1.1Kafka基本概念(1)什么是KafkaKafka是一个分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域(2)消息队列点对点模式... 查看详情

一文彻底搞懂hbase(代码片段)

本文是基于CentOS7.9系统环境,进行HBase的学习和使用一、HBase的简介1.1HBase基本概念HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库,可以解决HDFS随机写的问题1.2HBase数据模型逻辑上,HBase的数据模型同关系... 查看详情