Linux-容器技术基石1-namespace

前言

我们都知道, linux 中的进程树其实只有1棵, 根是 pid1 的进程, 然而我们进入一个docker容器中, 进程树的根也是pid1, 可是这个根进程 与宿主机里的 根进程 并不是同一进程, 事实上, 这个进程在宿主机中有真正对应的进程, 可 pid 并不是 1, 而是其他值, 这是怎么做到的呢? 就是使用 Linux 内核 提供的 namespace 技术, 就像这样

在整棵树中, 从 pid8 开始, 存在一棵子树, 这棵子树有两个pid, 前者是相对于整棵树的, 而后者是相对于单独的 进程命名空间

namespace 有什么用

namespace 可以将一组进程隔离在特定的命名空间中, 让这一组进程无法看到其他命名空间的进程, 那么创建这样的隔离环境有什么好处呢? 为了安全和稳定, 这样可以防止入侵者通过某个服务进程来破坏其他进程, 而且我们可以在一个机器上创建出多个安全的隔离环境

namespace 小栗子

首先我们在宿主机中查看一下进程树, 使用命令 pstree -p

  • 查看宿主机中的进程树
    walkerjun@walkerjun:~$ pstree -p
    systemd(1)─┬─ModemManager(871)─┬─{ModemManager}(904)
               │                   ├─{ModemManager}(912)
               │                   └─{ModemManager}(916)
               ├─NetworkManager(867)─┬─{NetworkManager}(905)
               │                     ├─{NetworkManager}(907)
               │                     └─{NetworkManager}(911)
               ├─accounts-daemon(749)─┬─{accounts-daemon}(750)
               │                      ├─{accounts-daemon}(751)
               │                      └─{accounts-daemon}(818)
               ├─atop(884)
               ├─atopacctd(875)
               ├─avahi-daemon(755)───avahi-daemon(862)
               ├─bluetoothd(757)
               ├─boltd(874)─┬─{boltd}(890)
               │            ├─{boltd}(894)
               │            └─{boltd}(897)
               ├─clash(2057)─┬─{clash}(2070)
               │             ├─{clash}(2071)
               │             ├─{clash}(2073)
               │             ├─{clash}(2084)
    
    

    正如我们上面说的, 进程树是以 pid1 的进程为根节点的树, 往下开支散叶下去, 有非常多的进程, 这些进程都是在默认进程空间中, 下面我们尝试创建一个新的进程命名空间, 在新的命名空间中查看进程树, 我们希望看到不一样的结果

  • 创建新的进程命名空间

    使用命令 sudo unshare -ipf --mount-proc, 可以创建一个新的 进程命名空间, 在这之后查看进程树 pstree -p

    walkerjun@walkerjun:~$ sudo unshare -ipf --mount-proc
    [sudo] walkerjun 的密码: 
    root@walkerjun:/home/walkerjun# ps -ef
    UID          PID    PPID  C STIME TTY          TIME CMD
    root           1       0  0 17:51 pts/1    00:00:00 -bash
    root           6       1  0 17:51 pts/1    00:00:00 ps -ef
    root@walkerjun:/home/walkerjun# pstree -p
    bash(1)───pstree(7) 
    

    我们可以看到, 在新的进程命名空间下只有两个进程, pid1pid7, 因为这是新创建的, 所以这个命名空间下没有其他进程了, 这样我们就完成了一个 进程命名空间的创建, 拥有了一个进程隔离环境, 那么怎么往这个空间里面加入新程序呢? 这个命名空间的根进程 就是bash, 即当前终端bash进程, 因此接下来在这个窗口创建的程序, 都会在这个进程命名空间下, 我们来实验下

  • 往新进程命名空间中加入新程序

    直接执行 python -m http.server 开启一个 http下载服务器, 然后用 ps -ef 验证

    root@walkerjun:/home/walkerjun# python -m http.server &
    [1] 8
    root@walkerjun:/home/walkerjun# Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
    
    root@walkerjun:/home/walkerjun# 
    root@walkerjun:/home/walkerjun# ps -ef
    UID          PID    PPID  C STIME TTY          TIME CMD
    root           1       0  0 17:51 pts/1    00:00:00 -bash
    root           8       1  0 18:05 pts/1    00:00:00 python -m http.server
    root           9       1  0 18:05 pts/1    00:00:00 ps -ef
    root@walkerjun:/home/walkerjun# 127.0.0.1 - - [23/Jun/2023 18:07:03] "GET / HTTP/1.1" 
    

    我们可以看到, 我们新启动的程序, 出现在新进程命名空间 中了

namespace 的种类

上面谈论的namespacePID(进程命名空间), 除了这个还有 Cgroup, IPC, Network, Mount, Time, User, UTS

参考