Docker 核心技术:Namespace
大家好,我是费益洲。Namespace 作为 Docker 的技术核心之一,主要作用就是对容器的资源进行隔离。容器的本质其实就是 Linux 的一个进程,容器的系统资源隔离其实就是进程的系统资源隔离,本文将从 Linux 内核源码的层面,谈谈进程是如何通过 Namespace 实现系统资源隔离的。
本文中的的内核源码版本为linux-5.10.1
,具体的源码可以自行下载查看,本文只列举关键代码。
🔗 内核源码官方地址:www.kernel.org,linux-5.10.1 源码下载地址:linux-5.10.1.tar.xz
概念
Namespace 本质是 Linux 系统内核的一种功能,其主要作用是对进程的系统资源进行全局范围的分装隔离,这些资源包括 User ID、PID(Process ID)、Network 等,这种隔离使得不同 Namespace 下的进程拥有独立的全局系统资源,改变一个 Namespace 中的系统资源只会影响当前 Namespace 中的进程,对其他 Namespace 中的进程没有影响。需要注意的是不同的命名空间是随着内核版本不断加入内核的,具体的 Namespace 的信息如下所示。
类型 | 系统调用参数 | 隔离资源 | 内核版本 |
---|---|---|---|
Mount Namespace | CLONE_NEWNS | 文件系统挂载点 | 2.4.19 |
UTS Namespace | CLONE_NEWUTS | 主机名(hostname)和域名 | 2.6.19 |
IPC Namespace | CLONE_NEWIPC | 信号量、消息队列等进程间通信资源 | 2.6.19 |
PID Namespace | CLONE_NEWPID | 进程 ID(各空间独立进程树) | 2.6.24 |
Network Namespace | CLONE_NEWNET | 网络设备、IP、端口、路由表 | 2.6.29 |
User Namespace | CLONE_NEWUSER | 用户/组 ID 映射 | 3.8 |
Cgroup Namespace | CLONE_NEWCGROUP | Cgroup 文件系统 | 4.6 |
Time Namespace | CLONE_NEWTIME | 时间 | 5.6 |
🏷️ Mount Namespace 作为第一个实现的 Namespace,当时的开发人员没有想过后续会有多个 Namespace 出现,所以标识直接定义为 CLONE_NEWNS
Namespace 生命周期和回收策略
Namespace 是随着进程创建而创建的,不存在脱离进程单独存在的 Namespace。而在 Linux 内核源码中,各类 Namespace 也是作为属性存在于进程结构体中。
Namespace 的创建过程
进程结构体task_struct
的定义在文件linux-5.10.1/include/linux/sched.h
中,如下所示:
struct task_struct {
// ...(省略部分代码)/* Namespaces: */struct nsproxy *nsproxy;// ...(省略部分代码)
}
具体的nsproxy
的定义在文件linux-5.10.1/include/linux/nsproxy.h
中,如下所示:
struct nsproxy {atomic_t count;struct uts_namespace *uts_ns;struct ipc_namespace *ipc_ns;struct mnt_namespace *mnt_ns;struct pid_namespace *pid_ns_for_children;struct net *net_ns;struct time_namespace *time_ns;struct time_namespace *time_ns_for_children;struct cgroup_namespace *cgroup_ns;
};
接下来从进程创建的过程,来说明进程的创建过程中,创建 Namespace 的过程。创建进程的系统调用函数有三个:fork、vfork、clone
具体的函数定义在文件linux-5.10.1/kernel/fork.c
中,如下所示:
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMUstruct kernel_clone_args args = {.exit_signal = SIGCHLD,};return kernel_clone(&args);
#else/* can not support in nommu mode */return -EINVAL;
#endif
}
#endif#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{struct kernel_clone_args args = {.flags = CLONE_VFORK | CLONE_VM,.exit_signal = SIGCHLD,};return kernel_clone(&args);
}
#endif#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,int __user *, parent_tidptr,unsigned long, tls,int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,int, stack_size,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls)
#endif
{struct kernel_clone_args args = {.flags = (lower_32_bits(clone_flags) & ~CSIGNAL),.pidfd = parent_tidptr,.child_tid = child_tidptr,.parent_tid = parent_tidptr,.exit_signal = (lower_32_bits(clone_flags) & CSIGNAL),.stack = newsp,.tls = tls,};return kernel_clone(&args);
}
#endif
当调用 fork()、vfork()、clone()时,最终都会调用同一个函数 kernel_clone(),和 Namespace 创建关联的关键函数调用是 copy_process()
pid_t kernel_clone(struct kernel_clone_args *args)
{...(省略部分代码)// line 2456p = copy_process(NULL, trace, NUMA_NO_NODE, args);...(省略部分代码)
}
copy_process()函数和 Namespace 创建关联的关键函数调用是 copy_namespaces()
static __latent_entropy struct task_struct *copy_process(struct pid *pid,int trace,int node,struct kernel_clone_args *args)
{// ...(省略部分代码)// line 2098retval = copy_namespaces(clone_flags, p);// ...(省略部分代码)
}
在文件linux-5.10.1/kernel/nsproxy.c
定义的函数 copy_namespaces()中,会根据标识为进程创建新的 Namespace 并进行赋值
int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{struct