美文网首页
[mydocker]---Linux Namespace

[mydocker]---Linux Namespace

作者: nicktming | 来源:发表于2019-05-11 09:54 被阅读0次

    前言

    namespace是实现容器隔离的基础. namespace的本质就是把原来所有进程全局共享的资源拆分成了很多个一组一组进程共享的资源.

    root@nicktming:~# ls -l /proc/self/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 27 21:57 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 27 21:57 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 27 21:57 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 27 21:57 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 27 21:57 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 27 21:57 uts -> uts:[4026531838]
    

    接下来将用实践的例子来简单看一下这些namespace的作用.
    后面的例子需要用到unshare命令

    root@nicktming:~# unshare
    
    Usage:
     unshare [options] <program> [args...]
    
    Options:
     -h, --help        usage information (this)
     -m, --mount       unshare mounts namespace
     -u, --uts         unshare UTS namespace (hostname etc)
     -i, --ipc         unshare System V IPC namespace
     -n, --net         unshare network namespace
    
    For more information see unshare(1).
    

    UTS Namespace

    UTS Namespace 主要用来隔离nodenamedomainname两个系统标识.

    终端执行例子

    root@nicktming:~# echo $$
    13563
    root@nicktming:~# ls -l /proc/13563/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 27 22:34 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 27 22:34 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 27 22:34 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 27 22:34 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 27 22:34 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 27 22:34 uts -> uts:[4026531838]
    root@nicktming:~# unshare -u /bin/sh
    # echo $$
    14029
    # ps -ef | grep 14029
    root     14029 13563  0 22:35 pts/2    00:00:00 /bin/sh
    root     14091 14029  0 22:36 pts/2    00:00:00 ps -ef
    root     14092 14029  0 22:36 pts/2    00:00:00 grep 14029
    # ls -l /proc/14029/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 27 22:36 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 27 22:36 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 27 22:36 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 27 22:36 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 27 22:36 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 27 22:36 uts -> uts:[4026532166]
    # hostname
    nicktming
    # hostname -b bird
    # hostname
    bird
    # uname -a
    Linux bird 3.13.0-128-generic #177-Ubuntu SMP Tue Aug 8 11:40:23 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
    

    由上可以看到进程13563中启动了一个子进程14029并且有uts namespace, 所以可以看到这父子进程共享了ipc mnt net pid user namespace,但是并不在同一个uts namspace中.
    打开另外一个终端执行.

    root@nicktming:~# hostname 
    nicktming
    root@nicktming:~# uname -a
    Linux nicktming 3.13.0-128-generic #177-Ubuntu SMP Tue Aug 8 11:40:23 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
    

    可以看到在14029进程新建的一个uts namespace里改变hostname并不会影响主机里面的hostname, 更不会影响到其他的namespace中的hostname, 进而达到一种隔离的状态.

    go实现

    go实现只需要在启子进程的时候在SysProcAttr定义中加入需要创建的namespace即可, uts namespace对应的是syscall.CLONE_NEWUTS.

    func main()  {
            cmd := exec.Command("/bin/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.Printf("Run error:%v\n", err)
                    log.Fatal(err)
            }
    }
    

    执行如下:

    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# hostname
    nicktming
    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run utsNamespace.go 
    # hostname
    nicktming
    # hostname -b bird
    # hostname
    bird
    

    打开另外一个终端执行hostname,可以看到效果与上面终端例子一样.

    root@nicktming:~# hostname
    nicktming
    

    MNT Namespace

    Mount Namespace用来隔离各个进程看到的挂载点视图。在不同Namespace的进程中,看到的文件系统层次是不一样的。在Mount Namespace 中调用mount()umount()仅仅只会影响当前Namespace内的文件系统,而对全局的文件系统是没有影响的.

    终端执行例子

    root@nicktming:~# echo $$
    8217
    root@nicktming:~# ls -l /proc/8217/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 28 21:23 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 28 21:23 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 28 21:23 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 28 21:23 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 28 21:23 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 28 21:23 uts -> uts:[4026531838]
    root@nicktming:~# mkdir -p /tmp/test_mnt_namespace
    root@nicktming:~# unshare --mount /bin/sh
    # echo $$
    8493
    # ls -l /proc/8493/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 28 21:25 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 28 21:25 mnt -> mnt:[4026532166]
    lrwxrwxrwx 1 root root 0 Mar 28 21:25 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 28 21:25 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 28 21:25 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 28 21:25 uts -> uts:[4026531838]
    # mount -t tmpfs tmpfs /tmp/test_mnt_namespace
    # cd /tmp/test_mnt_namespace
    # echo "pid:8493 mnt namespace" > test01.txt
    # ls
    test01.txt
    

    可以看到父子进程不在同一个mnt namespace.重新打开一个terminal.

    root@nicktming:~# ls -l /tmp/test_mnt_namespace/
    total 0
    root@nicktming:~# unshare --mount /bin/sh
    # ls /tmp/test_mnt_namespace
    # mount -t tmpfs tmpfs /tmp/test_mnt_namespace
    # cd /tmp/test_mnt_namespace
    # echo $$    
    8996
    # echo "pid:8996 mnt namespace" > test02.txt
    # ls
    test02.txt
    

    go实现

    利用syscall.CLONE_NEWNS这个字段.

    func main()  {
        cmd := exec.Command("/bin/sh")
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Cloneflags: syscall.CLONE_NEWNS,
        }
        cmd.Stdin  = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
    
        if err := cmd.Run(); err != nil {
            log.Printf("Run error:%v\n", err)
            log.Fatal(err)
        }
    }
    

    PID Namespace

    PID Namespace是用来隔离进程ID的. 同样一个进程在不同的PID Namespace中有不同的pid.

    go实现

    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# echo $$
    8848
    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run pidNamespace.go 
    # echo $$
    1
    # ps -l
    F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
    4 S     0  8848  8796  0  80   0 -  5343 wait   pts/2    00:00:00 bash
    4 S     0 12834  8848  0  80   0 - 45700 futex_ pts/2    00:00:00 go
    4 S     0 12852 12834  0  80   0 -   810 wait   pts/2    00:00:00 pidNamespace
    0 S     0 12855 12852  0  80   0 -  1111 wait   pts/2    00:00:00 sh
    0 R     0 12908 12855  0  80   0 -  2185 -      pts/2    00:00:00 ps
    # mount -t proc proc /proc
    # ps -l
    F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
    0 S     0     1     0  0  80   0 -  1111 wait   pts/2    00:00:00 sh
    0 R     0     4     1  0  80   0 -  2185 -      pts/2    00:00:00 ps
    

    可以看到进行该子进程进入了一个新的mnt pid namespace, 在宿主机中该子进程的pid12855,但是在其自己的namespace中它的pid1.

    NET Namespace

    Network Namespace 是用来隔离网络设备、IP 地址端口等网络械的Namespace . Network Namespace可以让每个容器拥有自己独立的(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口,每个Namespace 内的端口都不会互相冲突。在宿主机上搭建网桥后,就能很方便地实现容器之间的通信,而且不同容器上的应用可以使用相同的端口。

    终端执行例子

    root@nicktming:~# echo $$
    1222
    root@nicktming:~# ls -l /proc/1222/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 28 22:34 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 28 22:34 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 28 22:34 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 28 22:34 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 28 22:34 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 28 22:34 uts -> uts:[4026531838]
    root@nicktming:~# unshare --net /bin/sh
    # echo $$
    1353
    # ls -l /proc/1353/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 28 22:35 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 28 22:35 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 28 22:35 net -> net:[4026532161]
    lrwxrwxrwx 1 root root 0 Mar 28 22:35 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 28 22:35 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 28 22:35 uts -> uts:[4026531838]
    # ifconfig
    # ip addr
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default 
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    # ping 127.0.0.1
    connect: Network is unreachable
    # ip link set lo up
    # ip addr
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host 
           valid_lft forever preferred_lft forever
    # ping 127.0.0.1
    PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
    64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.016 ms
    

    可以看到父子进程在不同的network namespace中.

    go实现

    syscall.CLONE_NEWNET进行标识.

    func main()  {
        cmd := exec.Command("/bin/sh")
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Cloneflags: syscall.CLONE_NEWNET,
        }
        cmd.Stdin  = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
    
        if err := cmd.Run(); err != nil {
            log.Printf("Run error:%v\n", err)
            log.Fatal(err)
        }
    }
    

    执行结果如下:

    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run netNamespace.go 
    # ifconfig
    # ip addr
    1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default 
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    

    USER Namespace

    User Namespace主要是隔离用户的用户组ID . 也就是说, 一个进程的User IDGroup IDUser Namespace内外可以是不同的。比较常用的是,在宿主机上以一个非root用户运行创建一个User Namespace , 然后在User Namespace 里面却映射成root用户。这意味着 这个进程在User Namespace里面有root权限,但是在User Namespace外面却没有root的权限。从Linux Kernel 3.8 开始,非root进程也可以创建User Namespace, 并且此用户在Namespace里面可以被映射成root,且在Namespace内有root权限。

    func main()  {
        cmd := exec.Command("/bin/sh")
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Cloneflags: syscall.CLONE_NEWUSER,
        }
        cmd.Stdin  = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
    
        if err := cmd.Run(); err != nil {
            log.Printf("Run error:%v\n", err)
            log.Fatal(err)
        }
    }
    

    执行结果如下:

    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# id
    uid=0(root) gid=0(root) groups=0(root)
    root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run userNamespace.go 
    $ id
    uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
    

    IPC Namespace

    IPC Namespace用来隔离System V IPCPOSIX message queues.

    终端执行例子

    root@nicktming:~# ipcs
    
    ------ Shared Memory Segments --------
    key        shmid      owner      perms      bytes      nattch     status      
    
    ------ Semaphore Arrays --------
    key        semid      owner      perms      nsems     
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages    
    
    root@nicktming:~# echo $$
    3792
    root@nicktming:~# ls -l /proc/3792/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 28 22:53 ipc -> ipc:[4026531839]
    lrwxrwxrwx 1 root root 0 Mar 28 22:53 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 28 22:53 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 28 22:53 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 28 22:53 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 28 22:53 uts -> uts:[4026531838]
    root@nicktming:~# unshare --ipc /bin/sh
    # echo $$
    3861
    # ls -l /proc/3861/ns
    total 0
    lrwxrwxrwx 1 root root 0 Mar 28 22:54 ipc -> ipc:[4026532160]
    lrwxrwxrwx 1 root root 0 Mar 28 22:54 mnt -> mnt:[4026531840]
    lrwxrwxrwx 1 root root 0 Mar 28 22:54 net -> net:[4026531956]
    lrwxrwxrwx 1 root root 0 Mar 28 22:54 pid -> pid:[4026531836]
    lrwxrwxrwx 1 root root 0 Mar 28 22:54 user -> user:[4026531837]
    lrwxrwxrwx 1 root root 0 Mar 28 22:54 uts -> uts:[4026531838]
    # ipcs
    
    ------ Shared Memory Segments --------
    key        shmid      owner      perms      bytes      nattch     status      
    
    ------ Semaphore Arrays --------
    key        semid      owner      perms      nsems     
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages    
    
    # ipcmk -Q
    Message queue id: 0
    # ipcs -q
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages    
    0x695dae8c 0          root       644        0            0           
    
    # ipcs
    
    ------ Shared Memory Segments --------
    key        shmid      owner      perms      bytes      nattch     status      
    
    ------ Semaphore Arrays --------
    key        semid      owner      perms      nsems     
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages    
    0x695dae8c 0          root       644        0            0           
    

    打开另外一个terminal,发现宿主机上并没有刚刚创建的queue,所以ipc namespace隔离已经成功.

    root@nicktming:~# ipcs
    
    ------ Shared Memory Segments --------
    key        shmid      owner      perms      bytes      nattch     status      
    
    ------ Semaphore Arrays --------
    key        semid      owner      perms      nsems     
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages    
    

    go实现

    func main()  {
        cmd := exec.Command("/bin/sh")
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Cloneflags: syscall.CLONE_NEWIPC,
        }
        cmd.Stdin  = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
    
        if err := cmd.Run(); err != nil {
            log.Printf("Run error:%v\n", err)
            log.Fatal(err)
        }
    }
    

    参考

    1. 自己动手写docker.(基本参考此书,加入一些自己的理解,加深对docker的理解)

    全部内容

    mydocker.png

    1. [mydocker]---环境说明
    2. [mydocker]---urfave cli 理解
    3. [mydocker]---Linux Namespace
    4. [mydocker]---Linux Cgroup
    5. [mydocker]---构造容器01-实现run命令
    6. [mydocker]---构造容器02-实现资源限制01
    7. [mydocker]---构造容器02-实现资源限制02
    8. [mydocker]---构造容器03-实现增加管道
    9. [mydocker]---通过例子理解存储驱动AUFS
    10. [mydocker]---通过例子理解chroot 和 pivot_root
    11. [mydocker]---一步步实现使用busybox创建容器
    12. [mydocker]---一步步实现使用AUFS包装busybox
    13. [mydocker]---一步步实现volume操作
    14. [mydocker]---实现保存镜像
    15. [mydocker]---实现容器的后台运行
    16. [mydocker]---实现查看运行中容器
    17. [mydocker]---实现查看容器日志
    18. [mydocker]---实现进入容器Namespace
    19. [mydocker]---实现停止容器
    20. [mydocker]---实现删除容器
    21. [mydocker]---实现容器层隔离
    22. [mydocker]---实现通过容器制作镜像
    23. [mydocker]---实现cp操作
    24. [mydocker]---实现容器指定环境变量
    25. [mydocker]---网际协议IP
    26. [mydocker]---网络虚拟设备veth bridge iptables
    27. [mydocker]---docker的四种网络模型与原理实现(1)
    28. [mydocker]---docker的四种网络模型与原理实现(2)
    29. [mydocker]---容器地址分配
    30. [mydocker]---网络net/netlink api 使用解析
    31. [mydocker]---网络实现
    32. [mydocker]---网络实现测试

    相关文章

      网友评论

          本文标题:[mydocker]---Linux Namespace

          本文链接:https://www.haomeiwen.com/subject/onbpvqtx.html