容器底层 --- 超细节的 Namespace 机制讲解

Namespace

Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法。这种隔离机制和 chroot 很类似,chroot 是把某个目录修改为根目录,从而无法访问外部的内容。Linux Namesapce 在此基础之上,提供了对 UTS、IPC、Mount、PID、Network、User 等的隔离机制,如下所示。

分类 系统调用参数 相关内核版本
Mount Namespaces CLONE_NEWNS Linux 2.4.19
UTS Namespaces CLONE_NEWUTS Linux 2.6.19
IPC Namespaces CLONE_NEWIPC Linux 2.6.19
PID Namespaces CLONE_NEWPID Linux 2.6.19
Network Namespaces CLONE_NEWNET 始于Linux 2.6.24 完成于 Linux 2.6.29
User Namespaces CLONE_NEWUSER 始于 Linux 2.6.23 完成于 Linux 3.8)

Linux Namespace 官方文档:Namespaces in operation

namespace 有三个系统调用可以使用:

  • clone() --- 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。

  • unshare() --- 使某个进程脱离某个 namespace

  • setns(int fd, int nstype) --- 把某进程加入到某个 namespace

下面使用这几个系统调用来演示 Namespace 的效果,更加详细地可以看 DOCKER基础技术:LINUX NAMESPACE(上)、 DOCKER基础技术:LINUX NAMESPACE(下)。

UTS Namespace

UTS Namespace 主要是用来隔离主机名的,也就是每个容器都有自己的主机名。我们使用如下的代码来进行演示。注意:假如在容器内部没有设置主机名的话会使用主机的主机名的;假如在容器内部设置了主机名但是没有使用 CLONE_NEWUTS 的话那么改变的其实是主机的主机名。

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.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 [%5d] - inside the container!\n", getpid());
    sethostname("container_dawn", 15);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main() {
    printf("Parent [%5d] - start a container!\n", getpid());
    int container_id = clone(container_main, container_stack + STACK_SIZE, 
                                CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(container_id, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

PID Namespace

每个容器都有自己的进程环境中,也就是相当于容器内进程的 PID 从 1 开始命名,此时主机上的 PID 其实也还是从 1 开始命名的,就相当于有两个进程环境:一个主机上的从 1 开始,另一个容器里的从 1 开始。

为啥 PID 从 1 开始就相当于进程环境的隔离了呢?因此在传统的 UNIX 系统中,PID 为 1 的进程是 init,地位特殊。它作为所有进程的父进程,有很多特权。另外,其还会检查所有进程的状态,我们知道如果某个进程脱离了父进程(父进程没有 wait 它),那么 init 就会负责回收资源并结束这个子进程。所以要想做到进程的隔离,首先需要创建出 PID 为 1 的进程。

但是,【kubernetes 里面的话】

int container_main(void* arg) {
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container_dawn", 15);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main() {
    printf("Parent [%5d] - start a container!\n", getpid());
    int container_id = clone(container_main, container_stack + STACK_SIZE, 
                                CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
    waitpid(container_id, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

如果此时你在子进程的 shell 中输入 ps、top 等命令,我们还是可以看到所有进程。这是因为,ps、top 这些命令是去读 /proc 文件系统,由于此时文件系统并没有隔离,所以父进程和子进程通过命令看到的情况都是一样的。

IPC Namespace

常见的 IPC 有共享内存、信号量、消息队列等。当使用 IPC Namespace 把 IPC 隔离起来之后,只有同一个 Namespace 下的进程才能相互通信,因为主机的 IPC 和其他 Namespace 中的 IPC 都是看不到了的。而这个的隔离主要是因为创建出来的 IPC 都会有一个唯一的 ID,那么主要对这个 ID 进行隔离就好了。

想要启动 IPC 隔离,只需要在调用 clone 的时候加上 CLONE_NEWIPC 参数就可以了。

int container_main(void* arg) {
    printf("Container
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值