简介
下面是酷壳给出的关于Linux Namespaces的介绍:
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。不知道你是否还记得很早以前的Unix有一个叫chroot的系统调用(通过修改根目录把用户jail到一个特定目录下),chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。
当前Linux一共实现了6种不同类型的Namespace
Namespace类型 |
系统调用参数 |
内核版本 |
隔离内容 |
Mount Namespace |
CLONE_NEWNS |
2.4.19 |
挂载点(文件系统) |
UTS Namespace |
CLONE_NEWUTS |
2.6.19 |
主机名与域名 |
IPC Namespacce |
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 |
用户和用户组 |
Namespace的API主要使用如下3个系统调用:
- clone(): 创建新进程。
- unshare(): 将进程移出某个Namespace。
- setns(): 将进程加入到Namespace中。
体验
UTS Namespace
UTS Namespace用于隔离nodename和domainname两个系统标识,即在不同的Namespace中允许拥有各自的hostname。
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
|
// 在GOPATH下新建一个名为mydocker的目录,并在该目录下新建文件main.go。
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("bash")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(cmd.Env, `PS1=\[\e[32;1m\][\u@\h \W]$>\[\e[0m\]`)
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
|
以上代码通过exec.Command(“bash”)方式fork了一个bash子进程,并且使用了CLONE_NEWUTS标识符去创建了一个Namespace。下面验证下bash子进程与mydocker父进程是否处于不同的的UTS Namespace中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 查看最初的系统hostname
$ hostname
ubuntu14.04
// 以root身份运行mydocker程序
$ sudo ./mydocker
// 在隔离的UTS Namespace下修改hostname为hello
root@ubuntu14:/home/voidint# hostname -b hello
root@ubuntu14:/home/voidint# hostname
hello
// 退出bash子进程并查看hostname是否发生变化
root@ubuntu14:/home/voidint# exit
exit
// 可以看到hostname并未发生变化
$ hostname
ubuntu14.04
|
PID Namespace
PID Namespace用于隔离进程ID,同一个进程在不同的PID Namespace中可以拥有不同的PID。以docker容器为例,每个容器对于宿主机而言都是一个进程,若在容器内部查看到该进程的PID为1,但在宿主机上查看到的PID并非为1,这就是由于容器内拥有独立的PID 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
|
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("bash")
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
cmd.Env = append(cmd.Env, `PS1=\[\e[32;1m\][\u@\h \W]$>\[\e[0m\]`)
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
|
编译并运行以上程序,查看当前Namespace的PID,PID为1。
在宿主机上新开一个shell,并通过pstree -pl查看mydocker的PID为2692。注意,这里不能使用ps命令去查看,因为ps、top之类的命令会读取/proc目录下内容,由于此处并未进行Mount Namespace的隔离,查看到的/proc目录下内容并不真实和准确。
Mount Namespace
Mount Namespace用来隔离各个进程看到的挂载点视图。在Mount Namespace中调用mount()或者umount()都仅仅只是影响当前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
|
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("bash")
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
cmd.Env = append(cmd.Env, `PS1=\[\e[32;1m\][\u@\h \W]$>\[\e[0m\]`)
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
|
运行以上程序,在启动的bash子进程中执行ps -ef,依然可以看到宿主机上的所有进程,这是由于proc文件系统是继承自宿主机。下面重新挂载proc文件系统,并再次查看ps -ef的输出。
1
2
3
4
5
6
|
$ mount -t proc proc /proc
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:11 pts/1 00:00:00 bash
root 14 1 0 18:16 pts/1 00:00:00 ps -ef
|
可以看到,重新挂载proc文件系统后,ps命令仅能看到容器内的进程了,符合预期。
参考