-
-
Docker实现原理
-
发表于: 2022-4-1 10:28 1160
-
用了这么久的docker,对docker的实现原理挺感兴趣的,在对Linux下docker的实现原理了解之后,我没有用过Windows下的docker,更加好奇Windows下的docker是如何实现的(它并不开源),问了问owefsad师傅,说是可能用到了hyperV,那么可能类似Vmware吗?不知道啊。
另外说一下,其实这篇文章大部分都是cv的,算是一个知识整合加上个人的理解,我每看的一篇文章,都放在了最后的
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。从Unix开始,有一个chroot命令,
也就是说,原先我们的root目录在/,那么我们在tmp目录使用chroot后,那么我们的/目录就在tmp/下,在docker中有一种逃逸方式,在docker启动的时候,挂在宿主机的根目录,假设,启动时,把宿主机的根目录挂载在了docker中的/UzJu/目录
那么此时,我们进入到docker中,使用chroot,将根目录切换至/UzJu/目录下
此时我们就可以使用crontab等多种方式获取宿主机权限
那么chroot提供的就是一种简单的隔离环境,chroot内部的内容无法访问外部的,Linux NameSpace在这个基础上,又提供了对以下内容的隔离机制
在Linux文档中我们可以看到,目前,Linux 实现了六种不同类型的命名空间。每个命名空间的目的是将特定的全局系统资源包装在一个抽象中,使命名空间内的进程看起来拥有自己的全局资源隔离实例。命名空间的总体目标之一是支持容器的实现,容器是一种用于轻量级虚拟化(以及其他目的)的工具,它为一组进程提供了它们是系统上唯一进程的错觉。
用来隔离两个系统标识符,nodename和domainname,由uname()调用返回,名称是使用sethostname()和setdomainname()系统调用设置的,在容器的上下文中,UTS NameSpace允许每个容器拥有自己的主机名和NIS域名。
ps: UTS这个名称源自传递给uname()系统调用的结构名称:struct utsname,该结构的名称又源于Unxi分时系统
HostName
1、什么是HostName主机名
无论在局域网还是INTERNET上,每台主机都有一个IP地址,是为了区分此台主机和彼台主机,也就是说IP地址就是主机的门牌号。但IP地址不方便记忆,所以又有了域名。域名只是在公网(INtERNET)中存在(以实验为目的的局域网域网实验性除外),每个域名都对应一个IP地址,但一个IP地址可有对应多个域名。域名类型 linuxsir.org 这样的;
主机名是用于什么的呢?在一个局域网中,每台机器都有一个主机名,用于主机与主机之间的便于区分,就可以为每台机器设置主机名,以便于以容易记忆的方法来相互访问。比如我们在局域网中可以为根据每台机器的功用来为其命名。
2、HostName和Domain的区别
主机名就机器本身的名字,域名是用来解析到IP的。但值得一说的是在局域网中,主机名也是可以解析到IP上的;
NIX(Network Information System)
NIS 是一个基于远程过程调用 (RPC) 的客户端/服务器系统,它允许 NIS 域中的一组机器共享一组通用的配置文件。这允许系统管理员仅使用最少的配置数据设置 NIS 客户端系统,并从单个位置添加、删除或修改配置数据。
GoLang的实现
虽然程序内和程序外的HostName没有区别,都是uzju,但是我们可以通过查看/proc来进行判断
Tips:/proc
Linux内核提供了一种通过 proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc 文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
/proc下有三个重要的目录
其中sys目录是可写的,可以通过他来访问或者修改内核参数,而net和scsi则依赖于内核配置,例如,如果系统不支持scsi,那么scsi目录就不会存在
在宿主机中查看9815的父进程
那么他的父进程就是9811,子进程就是9815
可以看到两个进程之间的uts ID并不相同,实现了UTS NameSpaces的隔离
Tips: /proc/$PID/ns/uts目录,启示版本是Linux3.0,表示的是进程的UTS NameSpace
下面表格是其他的文件名已经含义
用于隔离某些进程之间的通信,sysvipc对象和POSIX消息队列,这些IPC机制的共同特征是IPC对象由文件系统路径名以外的机制标识,每个IPC NameSpaces都有一组自己的 sys vipc标识符和自己的POSIX消息队列文件系统。
sysvipc
POSIX
Tips:
1、msgget(2) 获取 Sys v 消息队列标识符
2、msgsnd(2) 获取sys v 消息队列操作
3、msgrcv sys v消息队列操作
https://man7.org/linux/man-pages/man2/msgrcv.2.html
在Go代码实现之前,需要了解进程间的通信IPC(Inter-Process Communication)
这里的解释来自于企鹅大佬的公众号文章,我在这只是把一些精简的复制过来了,完整的可点击链接https://mp.weixin.qq.com/s/WgZaS5w5IXa3IBGRsPKtbQ
其中,最初 Unix IPC 包括:管道、FIFO、信号;
System V IPC 包括:System V 消息队列、System V 信号灯、System V 共享内存区;
Posix IPC 包括: Posix 消息队列、Posix 信号灯、Posix 共享内存区。
简单说明一下,现有大部分 Unix 和流行版本都是遵循 POSIX 标准的,而 Linux 从一开始就遵循 POSIX 标准。
进程间的七大通信方式
IPC常用命令
Go的实现
在go build 后运行程序,查看ipcs
新建一个消息队列
会宿主机中无法看到创建的消息队列
用于隔离进程ID号,换句话说,不同的PID NameSces中的进程可以有一模一样的PID,PID命名空间的主要好处之一就是容器可以在主机之间迁移,同时为容器内的进程保持相同的进程PID,PID NameSpaces 还允许每个容器又用自己的init(PID 1),也就是Linux中最大的父进程,管理各种系统初始化任务并在他们终止前kill掉其他的子进程。
当我们创建一个新的PID NameSpace的时候,第一个进程的PID会被赋值为1,进程退出时,内核就会kill NameSpace内的其他进程
在go build 后我们输出当前进程的PID可以发现为1。
提供与网络相关的系统资源隔离,因此每个NetWork NameSpaces都有自己的网络设备,IP地址,IP路由表,/proc/net目录,端口号等
那么从网络的角度来看,NetWork NameSpaces使每个容器都可以拥有自己的(虚拟)网络设备和绑定到每个命名空间端口号空间的应用程序,主机系统中合适的路由规则可以将网络数据包定向到与特定容器相关的网络设备,例如:可以在同一个主机系统上拥有多个容器化的Web服务器,每个服务器都绑定到每个容器的NetWork NameSpaces中的80端口
Go的实现
可以看到隔离之后,只有一个link/loopback本地回环
用于隔离用户和组ID号的空间,换句话说:进程的用户和组ID在User NameSpaces内部和外部可以不同,这里最有意思的是:一个进程可以在User NameSpaces之外拥有一个普通的非特权用户ID,同时在命名空间内拥有一个用户ID为0,这意味着该进程对User NameSpaces内的操作具有完全的root权限,但对命名空间外的操作没有特权
从Linux 3.8开始,非特权进程可以创建User NameSpaces,这给应用程序开启了许多有意思的可能性:由于原本非特权进程可以在User NameSpaces内拥有root权限,因此非特权应用程序现在可以访问以前仅限于根路径,Eric Biederman 为使User NameSpaces实现安全和正确付出了很多努力。然而,这项工作带来的变化是微妙而广泛的。因此,User NameSpaces可能会出现一些尚待发现和修复的未知安全问题。
ps: 这里我理解的意思是,一个普通权限的用户,可以创建一个User NameSpaces,在这个User NameSpaces外面,它只是一个普通权限的用户,但是在User NameSpaces中拥有绝对的root权限
Go的实现
上面的代码中有一个GidMappings和UidMappings
从这个结构体的解释中我们可以发现,一个是用来映射用户ID的,一个是用来映射用户组的
可以运行后查看用户的隔离性
隔离一组进程看到的文件系统挂载点集。因此,不同挂载命名空间中的进程可以具有文件系统层次结构的不同视图。随着挂载命名空间的添加,mount() 和umount() 系统调用停止在系统上所有进程可见的全局挂载点集上运行,而是执行仅影响与调用进程关联的挂载命名空间的操作。
简单来说:挂载命名空间的一种用法是创建类似于chroot的环境,但是,与使用chroot()相比,挂载命名空间更加的灵活,并且安全,挂载命令空间的其他更复杂的用途也是有可能的,例如:允许安装在一个命名空间中的光盘设备自动出现在其他的命名空间
ps: Mount namespaces 是第一个进入Linux内核的namespace,首次出现在Linux 2.4.19版本。它们隔离了每个进程可以看到的挂载点列表,或者换句话说,每个Mount namespace都有它们自己的挂载点列表,意味着在不同namespace中的进程都可以看到且控制不同的目录层次结构(目录树)。
rootfs(root filesystem)是分层文件树的顶端,包含对系统运行至关重要的文件和目录,包括设备目录和用于启动系统的程序,rootfs还包含了许多挂载点,其他文件系统可以通过这些挂载点连接到rootfs的文件数中,rootfs通常由linux 发行版提供,一个典型的rootfs内容如下
ps: rootfs包含了一般系统上的常见目录结构,类似于/dev, /proc, /bin等等以及一些基本的文件和命令。
系统启动时,初始化进程会将rootfs挂载到/目录,之后再挂载其他的文件系统到其子目录中,这期间所有的mount系统调用都会被记录到初始化的mount table中,宿友进程都有一张独立的mount table,记录在/proc/$PID/mounts中,但一般情况下,系统中的所有进程都会被使用初始化进程的mount table
创建一个tmp/uzju的目录
我们回到原来宿主机的终端可以发现并没有uzju.txt的文件
包含了bootloader和linux内核。用户是不能对这层作任何修改的。在内核启动之后,bootfs实际上会unmount掉。
1、如果我们在一台服务器上启动多个服务,那么他们之前互相可以看到互相的进程,文件等,可能会造成相互的影响,同时他们也都可以访问宿主机的文件,那么隔离进程能让一台主机,部署多个服务,并且互相不冲突,并且在同一台服务器可以有很高的扩展性与多样性
2、既然在一台机器上部署了多个服务,那么如果其中有一个Web网站被入侵,容器之间没有隔离,或与宿主机之间没有隔离,就会导致一个被入侵,全部服务都被入侵的情况
3、服务器的弹性扩展,如果一个服务就需要一台服务器的话,那么如果有1000个,10000个怎么办,而且就算真的开10000个服务器,那么服务器上的资源性能肯定会过剩,导致高服务扛不住,低服务性能跑不满的情况,当然这个说法也不确定,因为现在毕竟服务器有弹性伸缩,算是各有各的好处吧
其中有一个PID为1的/sbin/init进程
和一个PID为2的kthreadd进程
这两个进程由上帝进程idle创建
1.简单的说idle是一个进程, 贴切一点说是系统创建的第一个进程(idle进程由系统自动创建, 运行在内核态 ),其pid号为 0。其前身是系统创建的第一个进程,也是唯一一个没有通过fork()产生的进程, 完成加载系统后,演变为进程调度、交换, idle进程最终调用了cpu_idle()函数。
2.主处理器上的idle由原始进程(pid=0)演变而来。从处理器上的idle由init进程fork得到,可是它们的pid都为0。
3.Idle进程为最低优先级。且不參与调度。仅仅是在执行队列为空的时候才被调度。
4.Idle循环等待need_resched置位。默认使用hlt节能。
PID为1的/sbin/init的进程主要负责执行内核的一部分初始化工作和系统配置,也会创建一些类似 getty 的注册进程
PID为2的负责管理和调度其他的内核进程。
我们在宿主机中可以看到很多的进程,但是我们现在启动一个docker看看
当前的Docker 容器成功将容器内的进程与宿主机器中的进程隔离,此时我们回到宿主机,使用ps -ef | grep docker 可以看到
Linux进程必须具备的四大要素
1、 程序代码不一定是进程专有,可以与其它进程共用。
2、系统堆栈空间,这是进程专用的
3、在内核中维护相应的进程控制块。只有这样,该进程才能成为内核调度的基本单位,接受调度。并且,该结构也记录了进程所占用的各项资源。
4、有独立的存储空间,表明进程拥有专有的用户空间。
以上四条,缺一不可。如果缺少第四条,那么就称其为“线程”。如果完全没有用户空间,称其为“内核线程”;如果是共享用户空间,则称其为“用户线程”。
fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值:
1、CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
2、CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
3、CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
4、CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
5、CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
6、CLONE_PTRACE 若父进程被trace,子进程也被trace
7、CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
8、CLONE_VM 子进程与父进程运行于相同的内存空间
9、CLONE_PID 子进程在创建时PID与父进程一致
10、CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
上述代码,我们需要编译,环境在Ubuntu21.10中,Go的版本为1.8
ps: 执行./main的时候,必须要是root权限
不过此时进程之类的都还没有隔离,还是可以操作宿主机的资源,进程,磁盘等
此时已经创建了一个UTS NameSpace,那么来仔细看一下上面这段代码。
首先是第一部分
导入了四个模块,分别fmt,os,os/exec,syscall,其中最重要的就是syscall
在main函数中,使用Switch来判断接收的控制台第一个参数是不是run,如果不是则显示pass me an argument please,那么我们的传参就应该是./main run
我们来到run()函数,定了cmd的标准输入输出错误等,并且还定义了Env,主要是为了在执行程序的时候区分NameSPace等信息
最重要的就是下面这段代码
共有3个系统调用组成命名空间API
1、Clone(2)
创建一个新的进程,如果调用的时候传进去了一个或者多个标志参数,那么这些命名参数就是给创建的子进程的
2、Setns(2)
允许调用进程加入新的一个存在的命名空间
3、unshare(2)
unshare(2)系统调用将调用进程移动到一个新的NameSpace,如果在创建的时候,指定了一个或者多个CLONE_NEW*熬制,那么首先会为每个标志创建新的NameSpace,并且调用进程会成为这些NameSpace的成员
调用clone()的时候,可以传递多个CLONE*标志,这里传递了一个CLONE_NEWUTS,那么还有其他的例如:CLONE_NEWNS, CLONE_NEWIPC, CLONE_NEWPID, CLONE_NEWNET, CLONE_NEWUSER和CLONE_NEWCGROUP
虽然NameSpace解决了环境隔离上的问题,但是并没有解决主机上资源的隔离,虽然可以通过NameSpace把单个容器关到一个特定的环境中,但是单个容器对其中的进程使用的CPU,内存,磁盘等这些计算资源其实都是可以操作的,所以对进程进行资源上的限制或者控制,这就Linux Cgroup的作用
Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词太过广泛,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中去。
Linux Cgroup主要提供以下功能
1、Resource limitation:限制资源的使用
2、Prioritization:优先级控制
3、Accounting 一些审计和一些统计
4、Control
Cgroup主要限制的资源
cgroups 的全称是control groups,cgroups为每种可以控制的资源定义了一个子系统。典型的子系统介绍如下:
Linux 内核通过一个叫做 cgroupfs的伪文件系统来提供管理 cgroup 的接口,我们可以通过 lscgroup命令来列出系统中已有的 cgroup,该命令实际上遍历了/sys/fs/cgroup/目录中的文件:
ps: 如果没有lscgroup的话,需要使用sudo apt install cgroup-tools
举个例子
如果安装docker之后,在每个子系统下都会有一个docker的目录
其中的4355等都是docker容器,启动这个容器时,Docker 会为这个容器创建一个与容器标识符相同的 CGroup,在当前的主机上 CGroup 就会有以下的层级关系:
每一个 CGroup 下面都有一个 tasks 文件,其中存储着属于当前控制组的所有进程的 pid,作为负责 cpu 的子系统,cpu.cfs_quota_us 文件中的内容能够对 CPU 的使用作出限制,如果当前文件的内容为 50000,那么当前控制组中的全部进程的 CPU 占用率不能超过 50%。
其实docker就是一个linux下的进程,通过Linux NameSpaces对不同的容器进行隔离,为了保证宿主机与容器资源上的隔离,与资源占用的比例,所有使用Cgroup对进程进行资源上的限制或者控制
1、docker就是一个linux系统的进程, 它通过 Linux 的 namespaces 对不同的容器实现了资源隔离,然后上面再跑一rootfs文件系统当容器使用的时候为了单个容器不榨干系统资源,所以就使用cgroup来做控制。
Linux 的NameSpace和Cgroup分别解决了不同资源隔离的问题,前者解决了进程、网络以及文件系统的隔离,后者实现了 CPU、内存等资源的隔离。
- Docker实现原理学习笔记
- 前言
- 一、Linux NameSpace
- 1、什么是Linux NameSpace
- **UTS NameSpaces**
- **IPC NameSpaces**
- PID NameSpaces
- NetWork NameSpaces
- User NameSpaces
- **Mount namespaces**
- 什么是rootfs
- 什么是bootfs
- 2、为什么要使用Linux NameSpace隔离进程
- 个人理解
- 举个例子
- 上帝进程idle
- Linux Clone
- 3、Go实现NameSpaces
- NameSpace Api
- 二、Linux Cgourp
- 1、什么是Linux Cgroup
- 2、Cgourp子系统
- 2、Cgroup在什么时候创建
- 3、Cgroup如何限制资源
- 4、Cgroup支持的文件种类
- 总结
- docker的实现原理
- docker的实现原理\(互联网\)
- Linux NameSpace和Cgroup解决了什么?
- 参考与引用
docker run
-
it
-
v
/
:
/
uzju
/
ubuntu:
18.04
docker run
-
it
-
v
/
:
/
uzju
/
ubuntu:
18.04
chroot
/
uzju
/
chroot
/
uzju
/
Mount namespaces | CLONE_NEWNS | Linux内核2.4.19 |
---|---|---|
UTS Namespaces | CLONE_NEWUTS | Linux内核2.6.19 |
IPC NameSpaces | CLONE_NEWIPC | Linux内核2.6.19 |
PID namespaces | CLONE_NEWPID | Linux内核2.6.24 |
Network namespaces | CLONE_NEWNET | Linux内核2.6.24->2.6.29 |
User namespaces | CLONE_NEWUSER | Linux内核2.6.23 |
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的UTS命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
/
/
注释这一行前后,对比一下UTS的隔离情况
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的UTS命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
/
/
注释这一行前后,对比一下UTS的隔离情况
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
ls
-
alh
/
proc
ls
-
alh
/
proc
ls alh
/
proc
/
9811
/
ls alh
/
proc
/
9811
/
# 查看当前进程ID
echo $$
# 查看当前进程ID
echo $$
px aux
-
H
px aux
-
H
文件名称 | 起始版本 | 描述 |
---|---|---|
/proc/[pid]/ns/cgroup | Linux 4.6 | 进程的 cgroup namespace |
/proc/[pid]/ns/ipc | Linux 3.0 | 进程的 IPC namespace |
/proc/[pid]/ns/mnt | Linux 3.8 | 进程的 mount namespace |
/proc/[pid]/ns/net | Linux 3.0 | 进程的 network namespace |
/proc/[pid]/ns/pid | Linux 3.8 | 进程的 PID namespace在进程的整个生命周期里是不变的 |
/proc/[pid]/ns/pid_for_children | Linux 4.12 | 进程创建子进程的 PID namespace这个文件与 /proc/[pid]/ns/pid 不一定一致。 |
/proc/[pid]/ns/time | Linux 5.6 | 进程的 time namespace |
/proc/[pid]/ns/time_for_children | Linux 5.6 | 进程创建子进程的 time namespace |
/proc/[pid]/ns/user | Linux 3.8 | 进程的 user namespace |
/proc/[pid]/ns/uts | Linux 3.0 | 进程的 UTS namespace |
#include <sys/msg.h>
int
msgget(key_t key ,
int
msgflg );
#include <sys/msg.h>
int
msgget(key_t key ,
int
msgflg );
#include <sys/msg.h>
int
msgsnd(
int
msqid, const void
*
msgp, size_t msgsz,
int
msgflg);
ssize_t msgrcv(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp,
int
msgflg);
#include <sys/msg.h>
int
msgsnd(
int
msqid, const void
*
msgp, size_t msgsz,
int
msgflg);
ssize_t msgrcv(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp,
int
msgflg);
#include <sys/msg.h>
int
msgsnd(
int
msqid , const void
*
msgp , size_t msgsz ,
int
msgflg );
ssize_t msgrcv(
int
msqid , void
*
msgp , size_t msgsz ,
long
msgtyp ,
int
msgflg);
#include <sys/msg.h>
int
msgsnd(
int
msqid , const void
*
msgp , size_t msgsz ,
int
msgflg );
ssize_t msgrcv(
int
msqid , void
*
msgp , size_t msgsz ,
long
msgtyp ,
int
msgflg);
# 查看
ipcs
# 创建
ipcmk
# 删除
ipcrm
# 查看
ipcs
# 创建
ipcmk
# 删除
ipcrm
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的IPC命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWIPC,
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的IPC命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWIPC,
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的PID命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWPID,
/
/
注释这一行前后,对比一下PID的隔离情况
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的PID命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWPID,
/
/
注释这一行前后,对比一下PID的隔离情况
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的network命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNET,
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的network命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNET,
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的user命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID:
0
,
HostID:
0
,
Size:
1
,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID:
0
,
HostID:
0
,
Size:
1
,
},
},
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout
if
err :
=
cmd.Run(); err !
=
nil {
log.Fatal(err)
}
}
package main
import
(
"log"
"os"
"os/exec"
"syscall"
)
func main() {
/
/
fork出新的进程。并执行sh命令
cmd :
=
exec
.Command(
"sh"
)
/
/
设置Cloneflags。 clone新的user命名空间
cmd.SysProcAttr
=
&syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID:
0
,
HostID:
0
,
Size:
1
,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID:
0
,
HostID:
0
,
Size:
1
,
},
},
}
/
/
输入输出
cmd.Stdin
=
os.Stdin
cmd.Stderr
=
os.Stderr
cmd.Stdout
=
os.Stdout