linux namespace原理及在linux容器中的应用

文章目录

  • 一、namespace简介
  • 二、RTFSC
    • 1.namespace与进程
    • 2.创建ns流程
    • 3.设置ns流程
    • 4.对资源的操作
  • 三、在lxc中的使用


一、namespace简介

在linux中具有父子关系的进程,通常情况下是共享大多数信息的,如网络信息,用户信息,文件句柄,信号处理等;namespace,作为linux容器技术的基础技术之一,可以实现多个进程之间的资源隔离,拥有独立地址空间的进程,会产生错觉,认为自己置身于一个独立的系统中,从而达到隔离的目的。单从使用效果来说也是比较好理解的,与namespace相关的系统调用有unshare和setns,至于在linux经典容器lxc中如何使用的,我们对照kernel(本文内核代码为linux-5.10.0)关于ns的实现,就变得很好理解了。

二、RTFSC

1.namespace与进程

既然namespace用于限制进程的资源访问,我们说task_struct用于描述进程,那二者之间肯定有些千丝万缕的关系。task_struct中使用nsproxy成员描述namespace信息,其类型为struct nsproxy,其主要成员如下:

struct nsproxy {
	atomic_t count; // 引用计数
	struct uts_namespace *uts_ns; // 主机名与NIS域名
	struct ipc_namespace *ipc_ns; // System V IPC和POSIX message queue
	struct mnt_namespace *mnt_ns; // 文件系统挂载点
	struct pid_namespace *pid_ns_for_children; // 进程号
	struct net 	     *net_ns; // 网络设备、网络栈、端口号等
	struct time_namespace *time_ns; // 系统单调时间及REAL_TIME
	struct cgroup_namespace *cgroup_ns; // cgroup资源
};

2.创建ns流程

进程namespace的创建有两个途径,一是使用系统调用clone()创建子进程时通过clone_flags指定,二是通过系统调用unshare()指定,代码流程为

kernel_clone(kernel/fork.c) -> copy_process -> copy_namespaces(kernel/nsproxy.c) -> create_new_namespaces
ksys_unshare(kernel/fork.c) -> unshare_nsproxy_namespaces(kernel/nsproxy.c) -> create_new_namespaces
static struct nsproxy *create_new_namespaces(unsigned long flags,
	struct task_struct *tsk, struct user_namespace *user_ns,
	struct fs_struct *new_fs)
{
	struct nsproxy *new_nsp;
	...
	new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
	new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
	new_nsp->ipc_ns = copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
	....
}

nsproxy中的所有namespace都在此处进行创建,以utsname为例,具体实现如下

kernel/utsname.c:copy_utsname()->clone_uts_ns()
static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns,
					  struct uts_namespace *old_ns)
{
	struct uts_namespace *ns;
	...
	ns = create_uts_ns(); /* 使用kmem_cache_alloc分配空间,名为uts_ns_cache */
	ns->ns.ops = &utsns_operations;
	/* 初始信息与父进程的保持一致 */
	memcpy(&ns->name, &old_ns->name, sizeof(ns->name));
	/* UTS ns中包含user namespace信息 */
	ns->user_ns = get_user_ns(user_ns);
	...
}

3.设置ns流程

进程的namespace信息可以通过/proc/$pid/ns/下得到,中括号中的数字可以理解为namespace的ID,如果相同则表示在同一namespace中,

[root@localhost ~]# ll /proc/7167/ns/
总用量 0
lrwxrwxrwx 1 root root 0 1114 15:36 cgroup -> 'cgroup:[4026531835]'
...
lrwxrwxrwx 1 root root 0 1114 15:37 uts -> 'uts:[4026531838]'

static int nsfs_show_path(struct seq_file *seq, struct dentry *dentry)
{
	struct inode *inode = d_inode(dentry);
	const struct proc_ns_operations *ns_ops = dentry->d_fsdata;

	seq_printf(seq, "%s:[%lu]", ns_ops->name, inode->i_ino);
	return 0;
}

可以使用setns()系统调用将当前进程加入到指定的namespace中,sys_setns第一个参数fd即为打开/proc/$pid/ns/的文件句柄,主要实现流程如下:

kernel/nsproxy.c:sys_setns()->validate_nsset()->validate_ns()
static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
{
	return ns->ops->install(nsset, ns);
}

static int utsns_install(struct nsset *nsset, struct ns_common *new)
{
	struct nsproxy *nsproxy = nsset->nsproxy;
	struct uts_namespace *ns = to_uts_ns(new);
	...
	nsproxy->uts_ns = ns; /* 将对应的namespace结构进行赋值 */
	return 0;
}

4.对资源的操作

前面的步骤为进程创建或设置了私有的namespace,之后再对进程进行相应操作时,内核实际在对应的namespace中生效,如UTS namespace创建之后,再设置hostname,其实际修改的是进程的uts ns中的nodename信息:

kernel/sys.c
SYSCALL_DEFINE2(sethostname, char __user *, name, int, len)
{
	char tmp[__NEW_UTS_LEN];
	...
	copy_from_user(tmp, name, len)
	struct new_utsname *u = utsname(); // ¤t->nsproxy->uts_ns->name;
	memcpy(u->nodename, tmp, len);
}

获取hostname时,实际获取的也是uts ns中的nodename:

SYSCALL_DEFINE2(gethostname, char __user *, name, int, len)
{
	char tmp[__NEW_UTS_LEN + 1];
	struct new_utsname *u = utsname(); // ¤t->nsproxy->uts_ns->name;
	memcpy(tmp, u->nodename, i);
	copy_to_user(name, tmp, i);
}

三、在lxc中的使用

lxc(linux container)中主要有两个地方会设置进程的namespace信息

  • 创建容器时会通过用户配置来指定容器的namespace,如挂载信息
lxc-4.0.3/src/lxc/start.c: lxc_start()->__lxc_start()
int __lxc_start(struct lxc_handler *handler, struct lxc_operations *ops,
                void *data, const char *lxcpath, bool daemonize, int *error_num)
{
	...
	unshare(CLONE_NEWNS);
	lxc_setup_rootfs_prepare_root(conf, name, lxcpath);
	...
}
lxc_setup_rootfs_prepare_root()->lxc_mount_rootfs()
static int lxc_mount_rootfs(struct lxc_conf *conf)
{
	...
	mount("", "/", NULL, MS_SLAVE | MS_REC, 0);
	...
}
  • 使用lxc-attach时也会为新进程设置namespace信息
lxc-4.0.3/src/lxc/attach.c: lxc_attach()->lxc_attach_to_ns()
static int lxc_attach_to_ns(pid_t pid, struct lxc_proc_context_info *ctx)
{
	...
	for (int i = 0; i < LXC_NS_MAX; i++) {
		ret = setns(ctx->ns_fd[i], ns_info[i].clone_flag);
	}
	...
}

lxc-4.0.3/src/lxc/attach.c: lxc_attach()->attach_child_main()->lxc_attach_remount_sys_proc()
int lxc_attach_remount_sys_proc(void)
{
	...
	unshare(CLONE_NEWNS);
	umount2("/proc", MNT_DETACH);
	mount("none", "/proc", "proc", 0, NULL);
	umount2("/sys", MNT_DETACH);
	mount("none", "/sys", "sysfs", 0, NULL)
	...
}

关于namespace更详细的原理还得深入到各个子系统中,除了linux container使用了namespace之外,unshare命令可以快速直接的使用namespace,具体的命令此处不做演示。

你可能感兴趣的:(容器技术,linux,容器)