2010 年后国内云计算爆发,紧接着 2013 年 Docker 崛起,ns 才作为不可或缺的一部分被重视起来。
ns 本身其实比较简单,它是 Linux 内核的一种机制,给进程隔离和虚拟化内核资源用的。
不同的进程是共享内核资源的。好比说大家住在同一个小区,虽然到家后关起门来谁都不影响谁。但公共场所就没办法了,如果有人破坏环境,那么势必会影响到其他人。
内核资源在这里就像是公共场所。ns 就是把公共场所隔离开来,你扔烟头到地上只影响你自己,其他人都看不到。这里的隔离不是说把公共场所分成几块儿,每个人分一小块儿,而是每个人都有一个和原来一样大的公共场所,就像是每个人都有一个四维空间一样,是不是有点玄乎啊。不要紧,下面会结合几个小例子来说明一下 :D。
目前有七种 ns 类型:
Linux 初始化的时候为 init 进程(进程号为1)为每个 ns 类型创建一个实例。后面其他所有进程都可以创建新的 ns 或者加入已有的 ns。
这些 ns 实例在 /proc/[pid]/ns 下面,比如说 1 号进程的 ns:
Mount ns 隔离的是挂载点挂载的是文件系统。子进程创建时(clone 时使用 CLONE_NEWNS),父进程 ns 下的所有挂载点都拷贝到子进程中,Mount ns 隔离之后,Mount Point 的创建或删除都不会在 ns 之间传播(除非 mount 时使用了shared subtree) ,妈妈在也不用担心我的挂载了呢。
使用 Docker 启动一个 Container,可以查看它的挂载,有很多和 Container 所在的 Host 的不一样,因为它内部做了新的挂载,比如说 aufs 挂载到了根目录 / 下面:
还有 PID ns,分属不同 ns 的进程下可以有相同的 PID,比如说 Host 中 PID 为 1 的进程是 init,而 Container 内 PID 为 1 的进程是 bash(Docker 启动指定的命令)。
其他几个 ns 类型,可以参考 namespaces(7) - Linux manual page 。
我之前很好奇怎么查看在 Linux 一共有多少 ns 实例,所以就写了个简单 Python 脚本 https://gist.github.com/wanzixyz/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]
)
运行后结果如下:
1 是 init 进程,21 是 kdevtmosfs,15320 和 29739 都是 Docker 启动的 Container。
之后我又很好奇,如何才能进入到 Container(其实不算是进入,只是加入 Container 的 ns,看到和 Container 一样的视图),于是就又写了一个脚本 https://gist.github.com/wanzixyz/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 看一下:
肿么样亲,是不是很简单了呢? :D
https://zhuanlan.zhihu.com/p/25576438