欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Linux Namespace

程序员文章站 2022-11-30 19:27:17
[toc] Linux Namespace Linux Namespace 是kernel的一个功能,用于隔离系统资源。比如说PID,User ID 等。(可以想象一下变成语言变量的命名空间)这样我们就可以将 进程,网络接口,挂载点以及用户做一个隔离。 比如说我们在同一台主机上,需要将资源分给多个类 ......

目录

linux namespace

linux namespace 是kernel的一个功能,用于隔离系统资源。比如说pid,user id 等。(可以想象一下变成语言变量的命名空间)这样我们就可以将
进程,网络接口,挂载点以及用户做一个隔离。

比如说我们在同一台主机上,需要将资源分给多个类似于root的用户,而且要将其隔离开来互不影响。在我们不去使用多台主机的情况下这里namespace就
派上了用场。使用namespace可以做到uid级别的隔离,也就是说我的ubuntu账户id为5.针对我虚拟化出来一个namespace,在这个namespace了里我有
root用户的权限。在这个系统外,我就是一个普通的用户。甚至都不能访问系统外部。

除了user namespace,pid也可以被虚拟。从用户角度看,我的第一个进程pid是1。但是从系统的角度看这个1只是一个映射关系。他也许在系统上的pid
是5或者x。父命名空间可以看得到子命名空间的状态等等,但是反过来确实不行的。这样下来,我们可以虚拟处多个pid是1的进程但是在父命名空间来看他
们的pid就是a,b,c...等。(这里字母代指数字,)

linux一共实现了6中不通类型的namespace

type system call parameters 系统调用参数 kernel version
mount namespace clone_newns 2.4.19
uts namespace clone_newuts 2.6.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

namespace的api主要使用如下3个系统调用。

  • clone() 创建新进程。根据系统调用参数来判断是哪些类型的namespace被创建,而且他们的子进程也会被包含到这些namespace中。
  • unshare() 将进程移出某个namespace。
  • setns() 将进程加入到某个namespace。

uts namespace

uts namespace 主要是用来隔离nodename和domainname两个系统标识。在utsnamespace中每个namespace允许有自己的hostname

使用go来实现

package main
import (
    "os/exec"
    "syscall"
    "os"
    "log"
    
)
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)
    }
}

exec.command("sh")用来指定被fork出来的新进程内的初始命令,默认使用sh来执行。

使用clone_newuts这个标识符去创建一个uts namespace。go帮我们封装了对clone方法的调用,在这段代码执行后就会进入sh的环境

uts以下是测试

  • 使用root权限去执行该代码
  • 使用pstree -pl 查看进程树
    Linux Namespace

可以看到我们新产生的进程和它的id

  • 输出当前进程的pid使用 echo $$
  • 可以验证uts namespace 使用 readlink/proc/
  • 验证修改hostname 使用 hostname -b

Linux Namespace

可以看到pid

可以看到uts的namespace

可以验证修改后的hostname
综上:uts namespace达到的预期的效果,的确可以对hostname进行隔离。

ipc namespace

ipc namespace用来隔离system v ipc 和 posix message queues。每个ipc namespace都有自己的system v ipc 和 posix message queues。

修改一行代码就可以进行创建。

package main
import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

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)
    }
}

可以看到 仅仅增加了syscall.clone_newipc说我们希望同时创建一个新的ipc namespace这样就可以了。下面进行演示。

ipc的测试

我们将在两个窗口(一个宿主窗口sh,一个普通的用户窗口)使用ipcs -q 命令进行查看, 使用ipcmk -q命令进行创建。对比两个窗口的显示。

  • ipcs -q 命令用于查询 message queue
  • ipcms -q 用于创建 message queue

窗口sh

Linux Namespace

其他窗口

Linux Namespace

综上

  • 先使用whoami 查看用户
  • 使用$$ 查看各自所属的进程
  • 使用ipcs -q 查看message queue
  • 使用ipcmk -q 创建message queue

可以看到进行了隔离。

pid namespace

pid namespace是用来隔离进程id的。同样一个进程在不同的pid里可以拥有不同的pid。例如使用在docker容器中我们会发现每个容器都有一个进程pid
是1。但在容器外就不是1了。

在上面代码中加入 syscall.clone_nwepid, 代表为fork出来的子进程创建自己的pid namespace。

package main

import (
    "os/exec"
    "os"
    "log"
    "syscall"
)
func main() {//
    cmd := exec.command("sh")
    cmd.sysprocattr = &syscall.sysprocattr{
        cloneflags: 0x8000000|0x4000000|0x20000000, 
    }
    cmd.stdin = os.stdin
    cmd.stdout = os.stdout
    cmd.stderr = os.stderr
    if err := cmd.run(); err !=nil{
        log.fatal(err)
    }
}

pid的测试

我们同样打开两个窗口,一个是sh的一个是普通的。然后查看pid就可以很明显的看到区别了。

sh窗口

Linux Namespace

其他窗口

Linux Namespace

综上:这里可以看到该操作打印了namespace的pid其值为1.也就是说30958被映射到了namespace的1.这里不能使用ps来查看,因为ps和top等命
令会使用/proc内容。

mount namespace

mount namespace 是用来隔离各个进程的挂载点视图的。对于不同的namespace的进程中,看到的文件系统是不一样的。在mount namespace中使用
mount和umount仅仅只会影像当前namespace内的文件系统,而对全局是没有影响的。(第一个加入的namespace类型)

chroot,它也是将一个子目录变成根节点。但是mount namespace更加的方便灵活和安全。

package main

import (
    "os/exec"
    "os"
    "log"
    "syscall"
)
func main() {//
    cmd := exec.command("sh")
    cmd.sysprocattr = &syscall.sysprocattr{
        cloneflags: 0x8000000|0x4000000|0x20000000|syscall.clone_newns, 
    }
    cmd.stdin = os.stdin
    cmd.stdout = os.stdout
    cmd.stderr = os.stderr
    if err := cmd.run(); err !=nil{
        log.fatal(err)
    }
}

mount namespace测试

  • 运行代码
  • 查看/proc 下的内容。(宿主机下的)
  • 挂在到当前的mountnamespace下

user namespace

user namespace主要是隔离用户的用户组id。也就是说一个进程的user id 和 group id 在user namespace内外可以是不同的。比如说。在宿主机上
以一个非root用户运行创建一个user namespace,然后在user namespace里面被映射成root用户。从linux kernel3.8开始,非root进程也可以创建
user namespace,并且此用户在namespace里可以被映射出root,且在namespace中有root权限。

package main
import (
 "os/exec"
 "os"
 "log"
 "syscall"
)

func main() {
    cmd := exec.command("sh")
    cmd.sysprocattr = &syscall.sysprocattr{
        cloneflags: syscall.clone_newns| syscall.clone_newipc| syscall.clone_newuts|
            syscall.clone_newpid|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)
    }
    os.exit(-1)
}

nweuser 测试

在之前的基础上增加了 syscall.clone_newuser。以root来运行这个程序,看看显示结果。
宿主机的root用户

Linux Namespace

sh的用户

Linux Namespace

我们可以看到uid是不通的因此说明user namespace生效了。

network namespace

network namespace 是用来隔离网络设备,ip,port等网络栈的namespace。 network namespace可以让每个容器都有自己独立的网络设备。
而且应用可以绑定到自己的端口,每个namespace还不会冲突。在宿主机器上搭建网桥后,就能很方便的实现容器间的通信,而且不通的容器也可以使
用相同的端口。

package main

import (
    "os/exec"
    "os"
    "log"
    "syscall"
)
func main() {//
    cmd := exec.command("sh")
    cmd.sysprocattr = &syscall.sysprocattr{
        cloneflags: syscall.clone_newuser|syscall.clone_newpid|syscall.clone_newuts|
            syscall.clone_newipc|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)
    }
}

network测试

  • 先检查自己的网络设备 使用ifconfig
  • 在检查一下sh的网络设备 使用ifconfig

宿主窗口

Linux Namespace

sh窗口

Linux Namespace

综上:可以看到宿主机器是由网卡等设备的,而sh并没有。两者是隔离的。