【容器底层技术】 namespaces详解

这篇具有很好参考价值的文章主要介绍了【容器底层技术】 namespaces详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【容器底层技术】 namespaces详解

1.简介

namespaces是 Linux 内核的一项功能,它对内核资源进行隔离,让一组进程只能看到与自己相关的一部分资源,而另一组进程看到另一组资源, 使得处于不同 namespaces 的进程拥有独立的全局系统资源,改变一个 namespaces 中的系统资源只会影响当前 namespaces 里的进程,对其他 namespaces 中的进程没有影响。

该功能通过为一组资源和进程使用相同的namespace来工作,不同的namespace会引用不同的资源, 有些资源可能存在于多个空间中,比如进程 ID、主机名、用户 ID、文件名以及与网络访问和进程间通信相关的一些名称。

namespaces是 Linux 上容器的一个基本功能。Linux 系统以每种类型的单个namespace开始,供所有进程使用,进程可以创建额外的namespaces并加入不同的namespaces。

2.种类

截至至内核5.6,namespaces一共有8种。各个类型的namespace作用方式都是一样的:每个进程都和一个namespace相关联,而且只能看到或使用由这个namespace和它可用的子代namespaces所关联的资源,通过这种方法,每个进程对系统资源都有一个不同的视角。哪种资源被隔离取决于为给定进程组创建的namespace类型。

【容器底层技术】 namespaces详解

overview

type flag function
mnt CLONE_NEWNS 控制挂载点
pid CLONE_NEWPID 为进程提供了一组独立于其他namespaces的进程 IDs (PIDs)
net CLONE_NEWNET 用来隔离网络设备、IP地址端口等网络栈的namespace
uts CLONE_NEWUTS 允许单个系统对不同的进程具有不同的主机名和域名
user CLONE_NEWUSER 跨多组进程提供权限隔离和用户身份隔离
ipc CLONE_NEWIPC 将进程与 SysV 风格的进程间通信隔离开来
cgroup CLONE_NEWCGROUP 隐藏了进程所属的控制组的身份
time CLONE_NEWTIME 允许不同进程看到不同的系统时间

Mount(mnt) namespace

Mount namespace用来控制挂载点。不同namespace中的进程看到的文件系统层次也是不一样的。在mount namespace中调用mount(), unmount()只会影响当前namespace内的文件系统。在创建时,当前mount namespace中的挂载点被复制到新命名空间,但之后创建的挂载点不会在namespaces之间传播(如果使用共享子树,可以在命名空间之间传播挂载点 )。

用于创建这种类型的新命名空间的clone flag是 CLONE_NEWNS - “NEW NameSpace”的缩写。 这个术语不是描述性的(无法从名字看出要创建哪种命名空间),因为挂载命名空间是第一种命名空间,设计人员没有预料到还有其他命名空间。

Process ID(pid) namespace

PID namespace为进程提供了一组独立于其他namespaces的进程 IDs (PIDs)。 PID namespace是嵌套的,这意味着当创建一个新进程时,它将拥有从当前namespace到初始 PID namespace所对应的每一个 PID。 因此初始 PID namespace能够看到所有进程,尽管看到的进程PID与其他namespace看到的不同。

在 PID namespace中创建的第一个进程被分配了 1 号进程 ID,并接受与普通 init 进程基本相同的特殊处理,其中最值得注意的是此namespace内的所有孤儿进程都会附加给它(孤儿进程,Orphan Process,指的是在其父进程执行完成或被终止后仍继续运行的一类进程)这一过程被称为“收养”。这也意味着 PID 1 进程的终止也将立即终止其 PID 命名空间中的所有进程和任何后代。

Network(net) namespace

Network namespace是用来隔离网络设备、IP地址端口等网络栈的namespace。每个网络接口(物理或虚拟)只存在于 1 个namespace中,并且可以在namespace之间移动。每个namespace都有一组私有 IP 地址、它自己的路由表、套接字列表、连接跟踪表、防火墙和其他与网络相关的资源。Network namespace可以让进程拥有自己独立的(虚拟的)网络设备,每个namespace内的端口都不会冲突。

UTS namespace

UTS(UNIX 分时)namespace允许单个系统对不同的进程具有不同的主机名和域名。当一个进程创建一个新的 UTS namespace时,新 UTS namespace的主机名和域是从调用者的 UTS namespace中的相应值复制而来的。

User ID (user) namespace

user namespace是内核3.8正式推出的一项功能,可跨多组进程提供权限隔离和用户身份隔离。 借助管理协助,可以构建具有看似管理权限的容器,而无需实际授予用户进程提升的权限。与 PID 命名空间一样,user namespace是嵌套的,每个新的user namespace都被认为是创建它的user namespace的子级。

user namespace包含一个映射表,将用户 ID 从容器的角度转换为系统的角度。例如,这允许 root 用户在容器中拥有用户 id 0,但实际上系统将其视为用户id 1,400,000以进行所有权检查。类似的表用于组 ID 映射和所有权检查。

为了便于管理操作的权限隔离,一般认为每个namespace类型都由一个user namespace所拥有,这个user namespace是基于创建时的活动的user namespace。在适当的user namespace中具有管理权限的用户将被允许在该其他namespace类型中执行管理操作。例如,如果一个进程具有更改网络接口 IP 地址的管理权限,只要它自己的user namespace与拥有该net namespace的user namespace相同(或祖先),它就可以这样做。因此初始user namespace对系统中的所有namespace类型具有管理控制权。

Interprocess Communication (ipc) namespace

IPC namespace将进程与 SysV 风格的进程间通信隔离开来。 这可以防止不同 IPC namespace中的进程使用例如 SHM 系列函数在两个进程之间建立共享内存范围。 相反,每个进程将能够对共享内存区域使用相同的标识符并产生两个这样的不同区域。

Control group (cgroup) namespace

cgroup namespace类型隐藏了进程所属的控制组的身份。 在这样的命名空间中的一个进程,在检查任何进程属于哪个控制组时会看到一条实际上相对于创建时设置的控制组的路径,隐藏其真正的控制组位置和身份。 这种命名空间类型自 2016 年 3 月起在 Linux 4.6 中存在。

Time namespace

time namespace使用类似UTS namespace的方法允许不同进程看到不同的系统时间

3. 操控

Linux内核在/proc/<pid>/ns/中为每个进程与每个namespace类型指定一个符号链接(symbolic link)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35Pe2t3L-1657261001794)(.\QQ截图20220616162848.png)]

这些符号链接的格式为xxx:[inode number],其中的 xxx 为 namespace 的类型,inode number 则用来标识一个 namespace,此符号链接指向的inode number对于此namespace中的每个进程都是相同的。这样通过其符号链接之一指向的 inode 编号可以唯一地标识每个命名空间。

通过以下三个系统调用可以直接操控namespaces

  • clone:通过flags指定新进程应该被迁移到哪些新的namespaces中
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
		 int __user *, parent_tidptr,
		 unsigned long, tls,
		 int __user *, child_tidptr)
{
	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);
}

pid_t kernel_clone(struct kernel_clone_args *args)
{
	u64 clone_flags = args->flags;
	struct completion vfork;
	struct pid *pid;
	struct task_struct *p;
	int trace = 0;
	pid_t nr;

	/*
	 * For legacy clone() calls, CLONE_PIDFD uses the parent_tid argument
	 * to return the pidfd. Hence, CLONE_PIDFD and CLONE_PARENT_SETTID are
	 * mutually exclusive. With clone3() CLONE_PIDFD has grown a separate
	 * field in struct clone_args and it still doesn't make sense to have
	 * them both point at the same memory location. Performing this check
	 * here has the advantage that we don't need to have a separate helper
	 * to check for legacy clone().
	 */
	if ((args->flags & CLONE_PIDFD) &&
	    (args->flags & CLONE_PARENT_SETTID) &&
	    (args->pidfd == args->parent_tid))
		return -EINVAL;

	/*
	 * Determine whether and which event to report to ptracer.  When
	 * called from kernel_thread or CLONE_UNTRACED is explicitly
	 * requested, no event is reported; otherwise, report if the event
	 * for the type of forking is enabled.
	 */
	if (!(clone_flags & CLONE_UNTRACED)) {
		if (clone_flags & CLONE_VFORK)
			trace = PTRACE_EVENT_VFORK;
		else if (args->exit_signal != SIGCHLD)
			trace = PTRACE_EVENT_CLONE;
		else
			trace = PTRACE_EVENT_FORK;

		if (likely(!ptrace_event_enabled(current, trace)))
			trace = 0;
	}

	p = copy_process(NULL, trace, NUMA_NO_NODE, args);
	add_latent_entropy();

	if (IS_ERR(p))
		return PTR_ERR(p);

	/*
	 * Do this prior waking up the new thread - the thread pointer
	 * might get invalid after that point, if the thread exits quickly.
	 */
	trace_sched_process_fork(current, p);

	pid = get_task_pid(p, PIDTYPE_PID);
	nr = pid_vnr(pid);

	if (clone_flags & CLONE_PARENT_SETTID)
		put_user(nr, args->parent_tid);

	if (clone_flags & CLONE_VFORK) {
		p->vfork_done = &vfork;
		init_completion(&vfork);
		get_task_struct(p);
	}

	wake_up_new_task(p);

	/* forking complete and child started to run, tell ptracer */
	if (unlikely(trace))
		ptrace_event_pid(trace, pid);

	if (clone_flags & CLONE_VFORK) {
		if (!wait_for_vfork_done(p, &vfork))
			ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
	}

	put_pid(pid);
	return nr;
}

  • unshare:允许进程(或线程)解除当前与其他进程(或线程)共享的部分执行上下文的关联,主要用于使一个进程不需要创建一个新的进程就可以控制它所共享的执行上下文,相当于跳出原来的namespaces,加入到新的namespaces中。
SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)
{
	return ksys_unshare(unshare_flags);
}

/*
 * unshare allows a process to 'unshare' part of the process
 * context which was originally shared using clone.  copy_*
 * functions used by kernel_clone() cannot be used here directly
 * because they modify an inactive task_struct that is being
 * constructed. Here we are modifying the current, active,
 * task_struct.
 */
int ksys_unshare(unsigned long unshare_flags)
{
	struct fs_struct *fs, *new_fs = NULL;
	struct files_struct *new_fd = NULL;
	struct cred *new_cred = NULL;
	struct nsproxy *new_nsproxy = NULL;
	int do_sysvsem = 0;
	int err;

	/*
	 * If unsharing a user namespace must also unshare the thread group
	 * and unshare the filesystem root and working directories.
	 */
	if (unshare_flags & CLONE_NEWUSER)
		unshare_flags |= CLONE_THREAD | CLONE_FS;
	/*
	 * If unsharing vm, must also unshare signal handlers.
	 */
	if (unshare_flags & CLONE_VM)
		unshare_flags |= CLONE_SIGHAND;
	/*
	 * If unsharing a signal handlers, must also unshare the signal queues.
	 */
	if (unshare_flags & CLONE_SIGHAND)
		unshare_flags |= CLONE_THREAD;
	/*
	 * If unsharing namespace, must also unshare filesystem information.
	 */
	if (unshare_flags & CLONE_NEWNS)
		unshare_flags |= CLONE_FS;

	err = check_unshare_flags(unshare_flags);
	if (err)
		goto bad_unshare_out;
	/*
	 * CLONE_NEWIPC must also detach from the undolist: after switching
	 * to a new ipc namespace, the semaphore arrays from the old
	 * namespace are unreachable.
	 */
	if (unshare_flags & (CLONE_NEWIPC|CLONE_SYSVSEM))
		do_sysvsem = 1;
	err = unshare_fs(unshare_flags, &new_fs);
	if (err)
		goto bad_unshare_out;
	err = unshare_fd(unshare_flags, NR_OPEN_MAX, &new_fd);
	if (err)
		goto bad_unshare_cleanup_fs;
	err = unshare_userns(unshare_flags, &new_cred);
	if (err)
		goto bad_unshare_cleanup_fd;
	err = unshare_nsproxy_namespaces(unshare_flags, &new_nsproxy,
					 new_cred, new_fs);
	if (err)
		goto bad_unshare_cleanup_cred;

	if (new_cred) {
		err = set_cred_ucounts(new_cred);
		if (err)
			goto bad_unshare_cleanup_cred;
	}

	if (new_fs || new_fd || do_sysvsem || new_cred || new_nsproxy) {
		if (do_sysvsem) {
			/*
			 * CLONE_SYSVSEM is equivalent to sys_exit().
			 */
			exit_sem(current);
		}
		if (unshare_flags & CLONE_NEWIPC) {
			/* Orphan segments in old ns (see sem above). */
			exit_shm(current);
			shm_init_task(current);
		}

		if (new_nsproxy)
			switch_task_namespaces(current, new_nsproxy);

		task_lock(current);

		if (new_fs) {
			fs = current->fs;
			spin_lock(&fs->lock);
			current->fs = new_fs;
			if (--fs->users)
				new_fs = NULL;
			else
				new_fs = fs;
			spin_unlock(&fs->lock);
		}

		if (new_fd)
			swap(current->files, new_fd);

		task_unlock(current);

		if (new_cred) {
			/* Install the new user namespace */
			commit_creds(new_cred);
			new_cred = NULL;
		}
	}

	perf_event_namespaces(current);

bad_unshare_cleanup_cred:
	if (new_cred)
		put_cred(new_cred);
bad_unshare_cleanup_fd:
	if (new_fd)
		put_files_struct(new_fd);

bad_unshare_cleanup_fs:
	if (new_fs)
		free_fs_struct(new_fs);

bad_unshare_out:
	return err;
}
  • setns:通过一个文件描述符进入特定的namespace中
 SYSCALL_DEFINE2(setns, int, fd, int, nstype)
 {
     struct task_struct *tsk = current;
     struct nsproxy *new_nsproxy;
     struct file *file;
     struct ns_common *ns;
     int err;

     file = proc_ns_fget(fd);

     ns = get_proc_ns(file_inode(file));
     if (nstype && (ns->ops->type != nstype))
         goto out;

     new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);

     err = ns->ops->install(new_nsproxy, ns);

     switch_task_namespaces(tsk, new_nsproxy);
     out:
     fput(file);
     return err;
 }

当一个namespace不再被引用时,它会被删除,对所包含资源的处理则取决于namespace的类型。namespace可通过以下三种方式被引用文章来源地址https://www.toymoban.com/news/detail-465193.html

  • 通过属于该namespace的进程
  • 通过打开该namespace的文件描述符,/proc/<pid>/ns/<ns-kind>
  • namespace文件的绑定挂载,/proc/<pid>/ns/<ns-kind>

4. 源码定义

/*
 * A structure to contain pointers to all per-process
 * namespaces - fs (mount), uts, network, sysvipc, etc.
 *
 * The pid namespace is an exception -- it's accessed using
 * task_active_pid_ns.  The pid namespace here is the
 * namespace that children will use.
 *
 * 'count' is the number of tasks holding a reference.
 * The count for each namespace, then, will be the number
 * of nsproxies pointing to it, not the number of tasks.
 *
 * The nsproxy is shared by tasks which share all namespaces.
 * As soon as a single namespace is cloned or unshared, the
 * nsproxy is copied.
 */
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;
};

/* nsproxy中没有user_namespace变量,是因为user_namespace比较特殊,它用于进程的认证
 * 而nsproxy会被所有共享namespaces的进程共享,
 * 在struct task_struct的成员变量cred中有 struct user_namespace user_ns用于身份识别
 */
 struct user_namespace {
     struct uid_gid_map	uid_map;
     struct uid_gid_map	gid_map;
     struct uid_gid_map	projid_map;
     atomic_t		count;
     struct user_namespace	*parent;
     int			level;
     kuid_t			owner;
     kgid_t			group;
     struct ns_common	ns;
     unsigned long		flags;

     /* Register of per-UID persistent keyrings for this namespace */
     #ifdef CONFIG_PERSISTENT_KEYRINGS
     struct key		*persistent_keyring_register;
     struct rw_semaphore	persistent_keyring_register_sem;
     #endif
 };

struct mnt_namespace {
	atomic_t		count;
	struct ns_common	ns;
	struct mount *	root;
	struct list_head	list;
	struct user_namespace	*user_ns;
	struct ucounts		*ucounts;
	u64			seq;	/* Sequence number to prevent loops */
	wait_queue_head_t poll;
	u64 event;
	unsigned int		mounts; /* # of mounts in the namespace */
	unsigned int		pending_mounts;
}

struct uts_namespace {
	struct kref kref;
	struct new_utsname name;
	struct user_namespace *user_ns;
	struct ucounts *ucounts;
	struct ns_common ns;
}

struct ipc_namespace {
	refcount_t	count;
	struct ipc_ids	ids[3];

	int		sem_ctls[4];
	int		used_sems;

	unsigned int	msg_ctlmax;
	unsigned int	msg_ctlmnb;
	unsigned int	msg_ctlmni;
	atomic_t	msg_bytes;
	atomic_t	msg_hdrs;

	size_t		shm_ctlmax;
	size_t		shm_ctlall;
	unsigned long	shm_tot;
	int		shm_ctlmni;
	/*
	 * Defines whether IPC_RMID is forced for _all_ shm segments regardless
	 * of shmctl()
	 */
	int		shm_rmid_forced;

	struct notifier_block ipcns_nb;

	/* The kern_mount of the mqueuefs sb.  We take a ref on it */
	struct vfsmount	*mq_mnt;

	/* # queues in this ns, protected by mq_lock */
	unsigned int    mq_queues_count;

	/* next fields are set through sysctl */
	unsigned int    mq_queues_max;   /* initialized to DFLT_QUEUESMAX */
	unsigned int    mq_msg_max;      /* initialized to DFLT_MSGMAX */
	unsigned int    mq_msgsize_max;  /* initialized to DFLT_MSGSIZEMAX */
	unsigned int    mq_msg_default;
	unsigned int    mq_msgsize_default;

	/* user_ns which owns the ipc ns */
	struct user_namespace *user_ns;
	struct ucounts *ucounts;

	struct ns_common ns;
}

struct pid_namespace {
	struct kref kref;
	struct idr idr;
	struct rcu_head rcu;
	unsigned int pid_allocated;
	struct task_struct *child_reaper;
	struct kmem_cache *pid_cachep;
	unsigned int level;
	struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
	struct vfsmount *proc_mnt;
	struct dentry *proc_self;
	struct dentry *proc_thread_self;
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
	struct fs_pin *bacct;
#endif
	struct user_namespace *user_ns;
	struct ucounts *ucounts;
	struct work_struct proc_work;
	kgid_t pid_gid;
	int hide_pid;
	int reboot;	/* group exit code if this pidns was rebooted */
	struct ns_common ns;
}

struct cgroup_namespace {
	refcount_t		count;
	struct ns_common	ns;
	struct user_namespace	*user_ns;
	struct ucounts		*ucounts;
	struct css_set          *root_cset;
};

struct net {
	refcount_t		passive;	/* To decided when the network
						 * namespace should be freed.
						 */
	refcount_t		count;		/* To decided when the network
						 *  namespace should be shut down.
						 */
	spinlock_t		rules_mod_lock;

	atomic64_t		cookie_gen;

	struct list_head	list;		/* list of network namespaces */
	struct list_head	exit_list;	/* To linked to call pernet exit
						 * methods on dead net (
						 * pernet_ops_rwsem read locked),
						 * or to unregister pernet ops
						 * (pernet_ops_rwsem write locked).
						 */
	struct llist_node	cleanup_list;	/* namespaces on death row */

	struct user_namespace   *user_ns;	/* Owning user namespace */
	struct ucounts		*ucounts;
	spinlock_t		nsid_lock;
	struct idr		netns_ids;

	struct ns_common	ns;

	struct proc_dir_entry 	*proc_net;
	struct proc_dir_entry 	*proc_net_stat;

#ifdef CONFIG_SYSCTL
	struct ctl_table_set	sysctls;
#endif

	struct sock 		*rtnl;			/* rtnetlink socket */
	struct sock		*genl_sock;

	struct uevent_sock	*uevent_sock;		/* uevent socket */

	struct list_head 	dev_base_head;
	struct hlist_head 	*dev_name_head;
	struct hlist_head	*dev_index_head;
	unsigned int		dev_base_seq;	/* protected by rtnl_mutex */
	int			ifindex;
	unsigned int		dev_unreg_count;

	/* core fib_rules */
	struct list_head	rules_ops;

	struct list_head	fib_notifier_ops;  /* Populated by
						    * register_pernet_subsys()
						    */
	struct net_device       *loopback_dev;          /* The loopback */
	struct netns_core	core;
	struct netns_mib	mib;
	struct netns_packet	packet;
	struct netns_unix	unx;
	struct netns_ipv4	ipv4;
#if IS_ENABLED(CONFIG_IPV6)
	struct netns_ipv6	ipv6;
#endif
#if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN)
	struct netns_ieee802154_lowpan	ieee802154_lowpan;
#endif
#if defined(CONFIG_IP_SCTP) || defined(CONFIG_IP_SCTP_MODULE)
	struct netns_sctp	sctp;
#endif
#if defined(CONFIG_IP_DCCP) || defined(CONFIG_IP_DCCP_MODULE)
	struct netns_dccp	dccp;
#endif
#ifdef CONFIG_NETFILTER
	struct netns_nf		nf;
	struct netns_xt		xt;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
	struct netns_ct		ct;
#endif
#if defined(CONFIG_NF_TABLES) || defined(CONFIG_NF_TABLES_MODULE)
	struct netns_nftables	nft;
#endif
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
	struct netns_nf_frag	nf_frag;
	struct ctl_table_header *nf_frag_frags_hdr;
#endif
	struct sock		*nfnl;
	struct sock		*nfnl_stash;
#if IS_ENABLED(CONFIG_NETFILTER_NETLINK_ACCT)
	struct list_head        nfnl_acct_list;
#endif
#if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT)
	struct list_head	nfct_timeout_list;
#endif
#endif
#ifdef CONFIG_WEXT_CORE
	struct sk_buff_head	wext_nlevents;
#endif
	struct net_generic __rcu	*gen;

	struct bpf_prog __rcu	*flow_dissector_prog;

	/* Note : following structs are cache line aligned */
#ifdef CONFIG_XFRM
	struct netns_xfrm	xfrm;
#endif
#if IS_ENABLED(CONFIG_IP_VS)
	struct netns_ipvs	*ipvs;
#endif
#if IS_ENABLED(CONFIG_MPLS)
	struct netns_mpls	mpls;
#endif
#if IS_ENABLED(CONFIG_CAN)
	struct netns_can	can;
#endif
	struct sock		*diag_nlsk;
	atomic_t		fnhe_genid;
}

struct time_namespace {
	struct user_namespace	*user_ns;
	struct ucounts		*ucounts;
	struct ns_common	ns;
	struct timens_offsets	offsets;
	struct page		*vvar_page;
	/* If set prevents changing offsets after any task joined namespace. */
	bool			frozen_offsets;
}

到了这里,关于【容器底层技术】 namespaces详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Docker 内核详解】namespace 资源隔离(一):进行 namespace API 操作的 4 种方式

    【Docker 内核详解 - namespace 资源隔离】 系列包含: namespace 资源隔离(一):进行 namespace API 操作的 4 种方式 namespace 资源隔离(二):UTS namespace IPC namespace namespace 资源隔离(三):PID namespace namespace 资源隔离(四):Mount namespace Network namespace namespace 资源隔离(五):User

    2024年02月06日
    浏览(29)
  • C++ 之命名空间namespace【详解】

    命名空间(Namespace)在编程中的作用主要有以下几点意义 避免命名冲突:命名空间提供了一种将标识符(如变量、函数、类等)组织和隔离的方式。不同的命名空间可以拥有相同名称的标识符,避免了不同模块或库之间的命名冲突。 提高可读性和可维护性:通过使用命名空

    2024年02月16日
    浏览(29)
  • 详解C++中的命名空间(namespace)

    个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】 在C++中,总共有63个,大家还记得在C语言中有多少个吗,没错,在C语言中总共有32个。 下面是C++的: asm do if auto return try contin

    2023年04月19日
    浏览(29)
  • 【Kubernetes资源篇】Namespace名称空间详解

    K8s中文手册: K8s名称空间中文官方手册: Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。 这些虚拟集群被称为命名空间。 命名空间namespace是k8s集群级别的资源,可以给不同的用户、租户、环境或项目创建对应的命名空间。 在Kubernetes中,名称空间可以用来:

    2024年02月12日
    浏览(29)
  • k8s的Namespace详解

    简介 在一个K8s集群中可以拥有多个命名空间,它们在逻辑上彼此隔离 namespaces是对一组资源和对象的抽象集合,比如可以将系统内部的对象划分为不同的项目组或用户组 K8s在集群启动之后,会默认创建几个namespace 默认namespace default:所有未指定Namespace的对象都会被分配在de

    2024年02月14日
    浏览(30)
  • Kubernetes(k8s):Namespace详解

    💖The Begin💖点点关注,收藏不迷路💖 在Kubernetes(K8s)中,Namespace是一种用于在集群中创建多个虚拟集群的方式。它将集群资源进行逻辑分组,使得不同的团队或项目可以独立地使用资源,避免资源冲突和混乱。本篇博客将详细介绍Kubernetes的Namespace概念、用法和实战应用场

    2024年02月03日
    浏览(45)
  • k8s学习笔记(四):命名空间(namespace)详解

    Kubernetes中的Namespace是一种用于在 集群内部组织和隔离资源 的机制。一个Namespace可以看作是一个 虚拟的集群 ,它将物理集群划分为多个逻辑部分,每个部分都有自己的一组资源(如Pod、Service、ConfigMap等)。 Namespace 适合用于 隔离不同用户创建的资源 用于给集群中的 任何对

    2024年02月12日
    浏览(28)
  • k8s资源管理命令与Namespace使用详解

    目录 一、前言 二、k8s概述 三、k8s常用操作管理命令 3.1 kubectl 命令用法

    2023年04月16日
    浏览(45)
  • C++ stl容器string的底层模拟实现

    目录 前言: 1.成员变量 2.构造函数与拷贝构造函数 3.析构函数 4.赋值重载 5.[]重载 6.比较关系重载 7.reserve 8.resize 9.push_back,append和重载+= 10.insert 11.erase 12.find 14.迭代器 15.流插入,流提取重载 16.swap 17.c_str 18.完整代码+测试 总结: 1.成员变量 首先注意的就是_str,不能是const类型

    2024年04月23日
    浏览(29)
  • C++ stl容器list的底层模拟实现

    目录 前言: 1.创建节点 2.普通迭代器的封装 3.反向迭代器的封装 为什么要对正向迭代器进行封装? 4.const迭代器 5.构造函数 6.拷贝构造 7.赋值重载 8.insert 9.erase 10.析构 11.头插头删,尾插尾删 12.完整代码+简单测试 总结: 1.创建节点 注意给缺省值,这样全缺省就会被当做默认

    2024年04月23日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包