Linux Namespace是内核的一个概念,它可以隔离一系列的系统资源。
每一个命名空间应该有自己的init进程(PID为1),其他进程的PID依次递增,A和B空间都有PID为1的init进程,自子命名空间的进程映射到父命名空间的进程上,父命名空间可以知道每一个子命名空间的运行状态,而子命名空间之间是隔离的。
Linux有6种Namespace,用来隔离不同的资源:
UTS
UTS Namespace主要用来隔离nodename和hostname两个系统标识。
/*
隔离hostname和nodename
*/
func main() {
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进程后,使用pstree -pl命令打印进程树。上面带编译后的可执行文件是uts。
此时可以在这个进程中使用hostname -b 新名字来修改hostname,宿主机打印hostname不受影响。
IPC
/**
隔离System V IPC和POSIX message queue
*/
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)
}
}
在宿主机使用ipcs -q来查看所有的Message Queue,然后ipcmk -Q创建一个新的MQ。在新的sh进程中使用``ipcs -q``没有发现刚才创建的MQ。
PID
/**
隔离进程ID
同一个进程在不同PID namespace下可以拥有不同的PID
*/
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)
}
}
在新的sh进程中使用echo $$会打印出进程ID,值为1;
而在宿主机上使用pstree -pl会打印出在宿主机上的进程真实ID。
真实ID为3524。
Mount
/**
隔离各个进程看到的挂载点视图
在不同namespace的进程中,看到的文件系统层次是不同的。在Mount Namespace中调用mount()和umount()仅仅只会
影响当前Namespace内的文件系统,而对全局的文件系统是没有影响的。
*/
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)
}
}
刚进入sh时挂载的文件系统还是宿主机的。mount -t proc proc /proc
-t proc是指挂载的是一个proc类型的文件系统,然后设备名是proc,目录是/proc。
User
/**
隔离用户的用户组ID
一个进程的User ID和Group ID在User namespace内外可以是不同的。
比较常用的是,在宿主机上以一个非root用户运行创建一个User namespace,然后在User namespace中映射为root用户。
*/
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_NEWUSER,
}
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: uint32(1),
Gid: uint32(1),
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
id命令可以查看uid、gid和groups。
在宿主机和新的sh进程内使用id命令返回的结果是不同的。
Network
/**
隔离网络设备、IP地址端口等网络栈
可以让每个容器拥有自己独立的(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口。
*/
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_NEWNET,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
ifconfig命令可以查看网络设备。