本文共 5176 字,大约阅读时间需要 17 分钟。
Linux Namespace
提供了一种内核级别隔离系统资源的方法,通过将系统的全局资源放在不同的Namespace
中,来实现资源隔离的目的。不同Namespace
的程序,可以享有一份独立的系统资源。目前Linux中提供了六类系统资源的隔离机制,分别是:
Mount
: 隔离文件系统挂载点UTS
: 隔离主机名和域名信息IPC
: 隔离进程间通信PID
: 隔离进程的IDNetwork
: 隔离网络资源User
: 隔离用户和用户组的IDnamespace API 的使用方法:clone()
、unshare()
和setns()
系统调用会使用CLONE_NEW*
常量来区别六种不同的 namespace 类型。以及还有/proc
下的部分文件。
CLONE_NEWNS
: 用于指定Mount NamespaceCLONE_NEWUTS
: 用于指定UTS NamespaceCLONE_NEWIPC
: 用于指定IPC NamespaceCLONE_NEWPID
: 用于指定PID NamespaceCLONE_NEWNET
: 用于指定Network NamespaceCLONE_NEWUSER
: 用于指定User Namespace注意:可以通过挂载的方式打开文件描述符(只要文件描述符是被打开的,即使当初创建它的进程被销毁,ns会依然存在):
touch ~/mntmount --bind /proc/${pid}}/ns/mnt~/mnt
readlink /proc/${pid}/ns/net
$ ll /proc/${pid}/ns/cgroup -> cgroup:[4026531835]ipc -> ipc:[4026531839]mnt -> mnt:[4026531840]net -> net:[4026534999]pid -> pid:[4026531836]pid_for_children -> pid:[4026531836]user -> user:[4026531837]uts -> uts:[4026531838]
CLONE_NEWNET
指代的是 network namespace。
clone()
系统调用setns()
系统调用unshare()
系统调用四种模式:docker network ls
docker run
时使用 –net=
参数指定,默认是 bridge
模式。
clone
创建子进程时,使用namespace
技术,实现子进程与其他进程(包含父进程)的命名空间隔离;Kubernetes的Pod网络采用的是Docker的container模式网络(pause container --net=none)。两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。
kubernetes
中的pause容器
主要为每个业务容器提供以下功能(pause clone flags=CLONE_NEW*|):
docker run -d --name pause_test --net=none -p 8880:80 ${psimg} // default is --net=bridgedocker inspect pause_testdocker run -d --name nginx_test --net=container:pause_test --ipc=container:pause_test --pid=container:pause_test nginx# k8s 的 Pod 创建方式,以下是 CNI 的操作简要步骤ip link add dev tt0 type veth peer tt1ip link set tt0 name eth0 netns netip netns exec net ip link set lo upip netns exec net ip link set eth0 up ip netns exec net ip add add 192.168.0.10/24 dev eth0ip netns exec net ip route add default via 192.168.0.1pause 就是占坑用的(pid下使用了隔离的ns)。
fork()
等效于调用clone(2)
时仅指定flags为SIGCHLD
(共享信号句柄表)。CLONE_VM
,则调用进程和子进程在同一内存空间中运行。 特别是,由调用进程或子进程执行的内存写入在另一个进程中也是可见的。sysdeps/unix/sysv/linux/createthread.c
源码中创建线程的函数 create_thread
中使用了clone()
,并指定了相关的flags:const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | 0);
docker ps |grep ${pod_name}docker inspect ${pscid} -f { {.State.Pid}}ll /proc/{ps_pid}/ns/ // pause 容器和第二个容器共享一个netns,如果是hostnetwork的pod,则跟pid=1共享一个netns
对应的docker 命令
#define _GNU_SOURCE#include#include #include #include #include #include #include #include #include #define STACK_SIZE (1024 * 1024) int idle(void *args){ printf("I'm child process, and my pid is: %d\n", getpid()); for (;;) { sleep(1); } return 0;} pid_t clone_wrapper(int (*func)(void *), int flag, void *args){ char *stack, *stack_top; stack = (char *)malloc(STACK_SIZE); if (stack == NULL) { printf("alloc stack for child failed!\n"); return -1; } /* 栈空间为什么传尾指针,因为栈是反着的 */ stack_top = stack + STACK_SIZE; /* Assume stack grows downward */ return clone(func, stack_top, flag , args);} char *get_pid_ns(int pid){ char bytes[32]; sprintf(bytes, "/proc/%d/ns/pid", pid); return strdup(bytes);} int main(void){ pid_t childs[2]; char *ns_file; int fd; printf("I'm parent, and my pid is: %d\n", getpid()); childs[0] = clone_wrapper(idle, CLONE_NEWPID, NULL); if (childs[0] == -1) { printf("error: create child thread failed!\n"); return ; } printf("first child's pid is: %d\n", childs[0]); ns_file = get_pid_ns(childs[0]); if (!ns_file) { printf("get child pid ns failed!\n"); return -1; } fd = open(ns_file, O_RDONLY); if (fd == -1) { printf("open child pid ns failed!\n"); return -1; } if (setns(fd, 0) == -1) { printf("set ns failed!\n"); return -1; } printf("I'm parent, and my pid is: %d\n", getpid()); childs[1] = clone_wrapper(idle, 0, NULL); if (childs[1] == -1) { printf("error: create child thread failed!\n"); return -1; } printf("second child's pid is: %d\n", childs[1]); sleep(3); kill(childs[0], SIGTERM); kill(childs[1], SIGTERM); waitpid(childs[0], NULL, 0); waitpid(childs[1], NULL, 0); return 0;}
其中,父进程在创建第一个子进程时指定了 CLONE_NEWPID
,然后父进程调用 setns(), 将父进程的 pid namespace 设置为第一个子进程的 pid namespace,接下来父进程又创建了第二个子进程,则此时,第一个进程和第二个处在同一个 pid namespace 中,在父进程的 pid namespace 中,它们的 PID 分别是:18612、18613,在子进程的 pid namespace 中,它们的 PID 分别是:1、2。
转载地址:http://mviti.baihongyu.com/