Linux Namespace

Linux Namespace是容器的基石,可以说没有Linux Namespace就不存在现在的容器技术。容器最大的功能就是对资源进行隔离,容器正是通过Linux Namespace技术实现了各种资源的隔离。

Linux Namespace总共有6种类别的Namespace,分别为

Namespace system call flag kernal version
UTS Namespace CLONE_NEWUTS 2.6.19
Mount Namespace CLONE_NEWNS 2.4.19
IPC Namespace CLONE_NEWIPC 2.6.19
PID Namespace CLONE_NEWPID 2.6.24
Network Namespace CLONE_NEWNET 2.6.29
User Namespace CLONE_NEWUSER 3.8

1.UTS Namespace

UTSUNIX Time Sharing的缩写。主要用来隔离hostname,在每个UTS Namespace下,每个Namespace都可以拥有自己的Namespace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

// 2020-06-11
// UTS Namespace主要用来隔离hostname(主机名用于标识主机)和domainname两个系统标识(已淘汰)
// pstree 命令

// https://www.jianshu.com/p/049f13e55840
import (
"os/exec"
"syscall"
"os"
"log"
)

func main() {
//指定被fork出来的新进程内的初始命令
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil{
log.Fatal(err)
}
}

执行这段代码会进入一个sh运行运行环境。
echo $$是输出当前的PIDreadlink查看对应的进程是否是在同一个Namespace
hostname -b bird修改hostnamebird,重新打开一个终端,输入hostname发现宿主机的hostname并没有被改变。

2.IPC Namespace

用于隔离System V IPCPOSIX message queues

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}

左边为宿主机,右边为新建的sh环境。新建的message queue在右边的隔离Namespace并不能看到。

1
2
ipcs -q   //查看ipc message queue
ipcmk -Q //新建一个message queue

3.PID Namespace

PID Namespace用于隔离进程ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID,
}

cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
```
执行上述代码`sudo go /opt/go/bin/go run pidNamespace`。
`echo $$`发现当前的PID为1,而通过`pstree`命令发现运行执行上述代码的PID为58329,而隔离命名空间的PID却为1,说明这个58329在新的命令空间PID映射为1。

![](http://img.hysyeah.top/2020/6/11/20200611221910-pid-namespace.png)


#### 4.Mount Namespace
`Mount Namespace`用于隔离各个进程看到的挂载点视图,是`Linux`实现的第一个`Namespace`类型,在不同的`Namespace`中看到的文件系统是不一样的。
```go
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC |syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run();err != nil{
log.Fatal(err)
}
}

执行上述代码,然后执行ls /proc发现/proc中的内容还是宿主机上的。
通过命令mount -t proc proc /proc/proc mount到新建的命名空间,此时再执行ls /proc发现少了很多文件。
在当前Namespace中,sh进程是PID为1的进程。这就说明当前的Mount Namespace中的mount和外部空间是隔离的,mount操作并没有影响到外部。Docker volume正是利用了这个特性。

5.User Namespace

User Namespace主要是用于隔离用户的用户组ID。一个进程的User ID和Group IDUser Namespace内外是不同的。常用的场景是在宿主机以一个非root用户运行创建一个User Namespace`,然后在命名空间里面却映射成root用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
}
// 加上下面这行代码会报没有权限的错误
//cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1000), Gid: uint32(1000)}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
log.Fatal(err)
}
os.Exit(-1)
}

可以看到在宿主机和User Namespace中它们的UID是不同的。

6.Network Namespace

Network Namespace用于隔离网络设备,IP地址,端口,路由表,防火墙规则等网络栈的Namespace。每个容器独占一个Network Namespace,每个容器都能随意使用自己的端口而不会产生冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER |syscall.CLONE_NEWNET,
}
// 加上下面这行代码会报没有权限的错误
//cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1000), Gid: uint32(1000)}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
log.Fatal(err)
}
os.Exit(-1)
}

分别在宿主机和新建sh环境上分别查看网络设备,发现网络设备是不一样的,说明网络已经隔离。


Network Namespace中的进程如何与宿主机进行通信
1
2
3
4
5
➜  ~ ip netns            // 查看Network Namespace列表

➜ ~ sudo ip netns add netns1 //创建一个名称为netns1的Network Namespace
➜ ~ ip netns
netns1

查询名为netns1的网络命名空间下的网络设备,此时网卡状态是DOWN

进行netns1执行本志回环地址,发现网络不通,因为此时设备的状态是DOWN,通过命令ip netns exec netns1 ip link set dev lo lup启动设备后可以PING通。但此时只能PING通Network Namespace中的本地地址,还不能与宿主机进行通信。

我们可以新建一对虚拟的网卡veth pair与宿主机进行通信。veth pair总是成对出现且相互连接,报文从一端进去就会从另一端出来。
创建一对虚拟网卡,并查看网络设备;此时veth0veth1都在宿主机的网络命名空间内。

veth1加入到网络命名空间netns1ip link set veth1 netns netns1,此时再查看网络设备。发现veth1已到了netns下。

设置网卡状态并绑定IP。
从宿主机PING netns1下的veth1.

netns1下PING 宿主机中的veth0

netns1中的路由表与防火墙规则也是隔离的


Ref:
1.https://book.douban.com/subject/27082348/
2.https://book.douban.com/subject/34855927/