资源隔离之 Linux namespace

时间:2022-09-09 21:38:50
Linux namespace 简称 ns,在 2002 年 2.4.19 内核中被引入,发展到今天已经有 15 个年头了。

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

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

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

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

目前有七种 ns 类型:
资源隔离之 Linux namespace
Linux 初始化的时候为 init 进程(进程号为1)为每个 ns 类型创建一个实例。后面其他所有进程都可以创建新的 ns 或者加入已有的 ns。

这些 ns 实例在 /proc/[pid]/ns 下面,比如说 1 号进程的 ns:
资源隔离之 Linux namespaceMount ns 隔离的是挂载点挂载的是文件系统。子进程创建时(clone 时使用 CLONE_NEWNS),父进程 ns 下的所有挂载点都拷贝到子进程中,Mount ns 隔离之后,Mount Point 的创建或删除都不会在 ns 之间传播(除非 mount 时使用了shared subtree) ,妈妈在也不用担心我的挂载了呢。

使用 Docker 启动一个 Container,可以查看它的挂载,有很多和 Container 所在的 Host 的不一样,因为它内部做了新的挂载,比如说 aufs 挂载到了根目录 / 下面:
资源隔离之 Linux namespace还有 PID ns,分属不同 ns 的进程下可以有相同的 PID,比如说 Host 中 PID 为 1 的进程是 init,而 Container 内 PID 为 1 的进程是 bash(Docker 启动指定的命令)。
资源隔离之 Linux namespace其他几个 ns 类型,可以参考 namespaces(7) - Linux manual page

我之前很好奇怎么查看在 Linux 一共有多少 ns 实例,所以就写了个简单 Python 脚本 gist.github.com/wanzixy
#!/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]
    )

运行后结果如下:
资源隔离之 Linux namespace1 是 init 进程,21 是 kdevtmosfs,15320 和 29739 都是 Docker 启动的 Container。

之后我又很好奇,如何才能进入到 Container(其实不算是进入,只是加入 Container 的 ns,看到和 Container 一样的视图),于是就又写了一个脚本 gist.github.com/wanzixy
#!/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 看一下:

资源隔离之 Linux namespace

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

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