linux 资源隔离,资源隔离之 Linux namespace

本文深入介绍了Linux Namespace的概念及其发展历程,详细解释了Namespace如何为进程提供内核资源隔离,包括Mount Namespace和PID Namespace的工作原理,并提供了实用的Python脚本示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux namespace 简称 ns,在 2002 年 2.4.19 内核中被引入,发展到今天已经有 15 个年头了。

2010 年后国内云计算爆发,紧接着 2013 年 Docker 崛起,ns 才作为不可或缺的一部分被重视起来。

ns 本身其实比较简单,它是 Linux 内核的一种机制,给进程隔离和虚拟化内核资源用的。

不同的进程是共享内核资源的。好比说大家住在同一个小区,虽然到家后关起门来谁都不影响谁。但公共场所就没办法了,如果有人破坏环境,那么势必会影响到其他人。

内核资源在这里就像是公共场所。ns 就是把公共场所隔离开来,你扔烟头到地上只影响你自己,其他人都看不到。这里的隔离不是说把公共场所分成几块儿,每个人分一小块儿,而是每个人都有一个和原来一样大的公共场所,就像是每个人都有一个四维空间一样,是不是有点玄乎啊。不要紧,下面会结合几个小例子来说明一下 :D。

目前有七种 ns 类型:

54925268b5127a21c1b4d74bdbab692e.png

Linux 初始化的时候为 init 进程(进程号为1)为每个 ns 类型创建一个实例。后面其他所有进程都可以创建新的 ns 或者加入已有的 ns。

这些 ns 实例在 /proc/[pid]/ns 下面,比如说 1 号进程的 ns:

3462171d52a815081354d2a4f8b05da8.png

Mount ns 隔离的是挂载点挂载的是文件系统。子进程创建时(clone 时使用 CLONE_NEWNS),父进程 ns 下的所有挂载点都拷贝到子进程中,Mount ns 隔离之后,Mount Point 的创建或删除都不会在 ns 之间传播(除非 mount 时使用了shared subtree) ,妈妈在也不用担心我的挂载了呢。

使用 Docker 启动一个 Container,可以查看它的挂载,有很多和 Container 所在的 Host 的不一样,因为它内部做了新的挂载,比如说 aufs 挂载到了根目录 / 下面:

c58b7dedb4659f2d6654349ce8c87041.png

还有 PID ns,分属不同 ns 的进程下可以有相同的 PID,比如说 Host 中 PID 为 1 的进程是 init,而 Container 内 PID 为 1 的进程是 bash(Docker 启动指定的命令)。

60b1c4514fe3161991a1c51b9529ff32.png

其他几个 ns 类型,可以参考 namespaces(7) - Linux manual page

我之前很好奇怎么查看在 Linux 一共有多少 ns 实例,所以就写了个简单 Python 脚本

https://

gist.github.com/wanzixy

z/53333b15d9290dd971527d61c4ee9f0c

#!/usr/bin/env python

#coding=utf-8

import os

import re

#format: pid, [namespaces], cmdline

def _get_namespace(pid):

path = '/proc/{0}/ns/'.format(pid)

namespaces = []

for ns in os.listdir(path):

namespaces.append(os.readlink(path + ns))

cmdline = open('/proc/{0}/cmdline'.format(pid)).read()

if not cmdline:

cmdline = open('/proc/{0}/comm'.format(pid)).read()

return (pid, namespaces, cmdline)

SBIN_INIT = _get_namespace(1)

OUTPUT = [SBIN_INIT]

for pid in [elt for elt in os.listdir('/proc/') if re.match('\d+', elt)]:

output = _get_namespace(pid)

if output[1] != SBIN_INIT[1]:

OUTPUT.append(output)

for val in OUTPUT:

print '{0:>10} {1} {2}'.format(

val[0],

' '.join(val[1]),

' '.join(val[2].split('\x00'))[:-1]

)

运行后结果如下:

41aa812808d940d72a127eca32a0146f.png

1 是 init 进程,21 是 kdevtmosfs,15320 和 29739 都是 Docker 启动的 Container。

之后我又很好奇,如何才能进入到 Container(其实不算是进入,只是加入 Container 的 ns,看到和 Container 一样的视图),于是就又写了一个脚本

https://

gist.github.com/wanzixy

z/8dd24aa8882bb983873274a221934cbc

#!/usr/bin/env python

#coding=utf-8

import argparse

import ctypes

import os

CLONE_NEWNS = 0x00020000# /* New mount namespace group */

CLONE_NEWCGROUP = 0x02000000# /* New cgroup namespace */

CLONE_NEWUTS = 0x04000000# /* New utsname namespace */

CLONE_NEWIPC = 0x08000000# /* New ipc namespace */

CLONE_NEWUSER = 0x10000000# /* New user namespace */

CLONE_NEWPID = 0x20000000# /* New pid namespace */

CLONE_NEWNET = 0x40000000# /* New network namespace */

parser = argparse.ArgumentParser()

parser.add_argument('--pid', type = str, help = 'process id')

args = parser.parse_args()

if not args.pid:

print 'plz input pid..'

exit(1)

#setns

libc = ctypes.CDLL('libc.so.6')

namespace = [

('ipc', CLONE_NEWIPC),

('uts', CLONE_NEWUTS),

('net', CLONE_NEWNET),

('pid', CLONE_NEWPID),

('mnt', CLONE_NEWNS),

]

for ns_type, ns_flag in namespace:

fd = os.open('/proc/{0}/ns/{1}'.format(args.pid, ns_type), os.O_RDONLY)

ret = libc.setns(fd, ns_flag)

os.close(fd)

if ret == -1:

print 'libc.setns failed'

exit(1)

#child exec shell

pid = os.fork()

if pid != 0: #father

os.waitpid(pid, 0)

else: #child

shell = os.getenv('SHELL')

os.execl(shell, os.path.basename(shell))

这里进入进程 15320 看一下:

8d34a9b4ba0ae2be2df5748649e66e36.png

肿么样亲,是不是很简单了呢? :D

https://zhuanlan.zhihu.com/p/25576438

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值