• docker 原理之 namespace (上)



    1. namespace 资源隔离

    namespace 是内核实现的一种资源隔离技术,docker 使用 namespace 实现了资源隔离。

    Liunx 内核提供 6 种 namespace 隔离的系统调用,如下表所示:

    namespace 系统调用参数 隔离内容
    UTS CLONE_NEWUTS 主机名与域名
    IPC CLONE_NEWIPC 信号量,消息队列和共享内存
    PID CONE_NEWPID 进程编号
    Network CLONE_NEWNET 网络设备,网络栈,端口等
    Mount CLONE_NEWNS 挂载点(文件系统)
    User CLONE_NEWUSER 用户和用户组

    Liunx 提供了对 namespace API 调用的三种方式,通过这几种方式可以调用系统调用参数实现对应 namespace 资源的隔离。这三种方式分别是:

    1. clone(): clone 在创建新进程的同时创建 namespace。
    2. setns(): setns 加入一个已经存在的 namespace。
    3. unshare(): unshare 在原先进程上进行 namespace 隔离。

    有了系统调用参数和调用 namespace API 的方式,我们就可以构造各种类型的 namespace 了。

    1.1 UTS namespace

    UTS(UNIX Time-sharing System) namespace 提供主机名和域名的隔离,这里使用 unshare 实现 UTS namespace:

    root@chunqiu:~# hostname
    chunqiu
    root@chunqiu:~# echo $$
    31124
    root@chunqiu:~# readlink /proc/$$/ns/uts
    uts:[4026531838]
    
    root@chunqiu:~# unshare --uts /bin/bash
    
    root@chunqiu:~# hostname
    chunqiu
    root@chunqiu:~# hostname demo
    root@chunqiu:~# hostname
    demo
    root@chunqiu:~# echo $$
    31332
     
    root@chunqiu:~# readlink /proc/$$/ns/uts
    uts:[4026532208]
    
    root@chunqiu:~# ps -ef | grep 31332 | grep -v grep
    root     31332 31124  0 07:51 pts/0    00:00:00 /bin/bash
    root     31345 31332  0 07:52 pts/0    00:00:00 ps -ef
    root@chunqiu:~# exit
    exit
    root@chunqiu:~# hostname
    chunqiu
    

    1.2 IPC namespace

    IPC(Inter-Process Communication) 进程间通信涉及的 IPC 资源包括信号量,消息队列和共享内存。这里使用 clone() 实现 IPC namespace 的隔离:

    /* ipc.c */
    #define _GNU_SOURCE
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <stdio.h>
    #include <sched.h>
    #include <signal.h>
    #include <unistd.h>
    
    #define STACK_SIZE (1024 * 1024)
    static char container_stack[STACK_SIZE];
    
    char* const container_args[] = {
        "/bin/bash",
        NULL
    };
    
    int container_main(void* arg)
    {
        printf("Container - inside the container
    ");
        execv(container_args[0], container_args);
        printf("Something's wrong!
    ");
        return 1;
    }
    
    int main()
    {
        printf("start a container:
    ");
        int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWIPC | SIGCHLD, NULL);
        waitpid(container_pid, NULL, 0);
        printf("container stopped!
    ");
        return 0;
    }
    

    编译并运行 ipc.c:

    root@chunqiu:~/chunqiu/docker/container# echo $$
    31124
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/ipc
    ipc:[4026531839]
    root@chunqiu:~/chunqiu/docker/container# ipcmk -Q
    Message queue id: 0
    root@chunqiu:~/chunqiu/docker/container# ipcs -q
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages
    0xad26491d 0          root       644        0            0
    
    root@chunqiu:~/chunqiu/docker/container# gcc ipc.c -Wall  -o ipc.o && ./ipc.o
    start a container:
    Container - inside the container
    root@chunqiu:~/chunqiu/docker/container# echo $$
    31961
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/ipc
    ipc:[4026532208]
    /* different ipc namesapce */
    root@chunqiu:~/chunqiu/docker/container# ipcs -q
    
    ------ Message Queues --------
    key        msqid      owner      perms      used-bytes   messages
    

    1.3 PID namespace

    1.3.1 父子 PID namespace

    PID namespace 对进程 PID 重新标号实现隔离效果,使用 clone 方式实现 PID namespace 的隔离:

    /* 代码与 IPC namespace 基本一模一样,只将系统调用参数换成 CLONE_NEWPID */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWPID | SIGCHLD, NULL);
    

    编译并运行 pid.c:

    root@chunqiu:~/chunqiu/docker/container# echo $$
    32090
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026531836]
    
    root@chunqiu:~/chunqiu/docker/container# gcc pid.c -Wall -o pid.o && ./pid.o
    start a container:
    Container - inside the container
    root@chunqiu:~/chunqiu/docker/container# echo $$
    1
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026531836]
    
    root@chunqiu:~/chunqiu/docker/container# mount -t proc proc /proc
    
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026532208]
    root@chunqiu:~/chunqiu/docker/container# ps -ef
    UID        PID  PPID  C STIME TTY          TIME CMD
    root         1     0  0 08:41 pts/0    00:00:00 /bin/bash
    root        14     1  0 08:42 pts/0    00:00:00 ps -ef
    root@chunqiu:~/chunqiu/docker/container#
    

    有一点需要注意的是:在 PID namespace 中 init 进程号为 1,但是 readlink 显示还是和父进程一样,这是因为没有对文件系统挂载点进行隔离。在 PID namespace 内使用 mount 挂载 proc 文件系统,重新执行 readlink 发现 pid 不一样了。

    那么,PID namespace 中的 init 进程对应到父 PID namespace 中的哪个进程呢?查看父 PID namespace:

    root@chunqiu:~# ps -ef
    Error, do this: mount -t proc proc /proc
    /* proc 文件系统在子 PID namespace 中,需要重新 mount */
    root@chunqiu:~# mount -t proc proc /proc
    
    root@chunqiu:~# ps -ef | grep 32090 | grep -v grep
    root     32090 32075  0 08:23 pts/0    00:00:00 -bash
    root     32659 32090  0 08:41 pts/0    00:00:00 ./pid.o
    root@chunqiu:~# ps -ef | grep 32659 | grep -v grep
    root     32659 32090  0 08:41 pts/0    00:00:00 ./pid.o
    root     32660 32659  0 08:41 pts/0    00:00:00 /bin/bash
    

    可以看到 PID 为 32660 的 bash 进程即为子 PID namespace 的 init 进程。

    1.3.2 init 进程与 dockerfile

    在 PID namespace 中,init 进程负责收养成为孤儿进程的子进程。因此,init 进程应该是具有资源监控与回收等管理能力的进程,如 bash 进程。
    dockerfile 中执行 CMD 命令产生的进程将会成为 PID namespace 中的 init 进程。以 httpd container 为例,其 dockerfile 如下:

    ...
    COPY httpd-foreground /usr/local/bin/
    
    EXPOSE 80
    CMD ["httpd-foreground"]
    

    运行 httpd container,ps 查看进程号:

    $ ps -ef | grep httpd | grep -v grep
    root      7469  7446  0 09:50 ?        00:00:00 httpd -DFOREGROUND
    $ ps -ef | grep 7446 | grep -v grep
    root      7446   842  0 09:50 ?        00:00:00 containerd-shim -namespace moby -workdir ...
    root      7469  7446  0 09:50 ?        00:00:00 httpd -DFOREGROUND
    $ ps -ef | grep 842 | grep -v grep
    root       842     1  0 09:32 ?        00:00:00 /usr/bin/containerd
    

    可以看到进程号为 7469 的 httpd -DFOREGROUND 进程即为 httpd container 中的 init 进程。

    PID namespace 嵌套

    PID namespace 是一种层级体系,这里构造一种在子 PID namespace 中嵌套 PID namespace 的场景,如下:

    root@chunqiu:~/chunqiu/docker/container# echo $$
    32090
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026531836]
    
    root@chunqiu:~/chunqiu/docker/container# ./pid.o
    start a container:
    Container - inside the container
    root@chunqiu:~/chunqiu/docker/container# echo $$
    1
    root@chunqiu:~/chunqiu/docker/container# mount -t proc proc /proc
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026532209]
    
    /* 子 PID namespace 中创建 PID namespace */
    root@chunqiu:~/chunqiu/docker/container# ./pid.o
    start a container:
    Container - inside the container
    root@chunqiu:~/chunqiu/docker/container# echo $$
    1
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026532209]
    
    root@chunqiu:~/chunqiu/docker/container# mount -t proc proc /proc
    root@chunqiu:~/chunqiu/docker/container# readlink /proc/$$/ns/pid
    pid:[4026532214]
    
    root@chunqiu:~/chunqiu/docker/container# exit
    exit
    container stopped!
    root@chunqiu:~/chunqiu/docker/container# exit
    exit
    container stopped!
    

    1.4 Network namespace

    network namespace 参看 这里

    芝兰生于空谷,不以无人而不芳。
  • 相关阅读:
    博客开通第77天
    guzzlephp使用教程
    Mac下php70memcache安装
    opcache开启和关闭
    php的getimagesize方法详解
    Yii2框架解剖
    AES加密:PHP与Java互通问题
    公司网页监控到的各种不能识别的浏览器userAgent,都是些什么啊
    document.write
    js模版引擎v6注解
  • 原文地址:https://www.cnblogs.com/xingzheanan/p/14724572.html
Copyright © 2020-2023  润新知