介绍 Linux 的命名空间

介绍 Linux 的命名空间

背景

从Linux 2.6.24版的内核开始,Linux 就支持6种不同类型的命名空间。它们的出现,使用户创建的进程能够与系统分离得更加彻底,从而不需要使用更多的底层虚拟化技术。

  • CLONE_NEWIPC: 进程间通信(IPC)的命名空间,可以将 SystemV 的 IPC 和 POSIX 的消息队列独立出来。
  • CLONE_NEWPID: 进程命名空间。空间内的PID 是独立分配的,意思就是命名空间内的虚拟 PID 可能会与命名空间外的 PID 相冲突,于是命名空间内的 PID 映射到命名空间外时会使用另外一个 PID。比如说,命名空间内第一个 PID 为1,而在命名空间外就是该 PID 已被 init 进程所使用。
  • CLONE_NEWNET: 网络命名空间,用于隔离网络资源(/proc/net、IP 地址、网卡、路由等)。后台进程可以运行在不同命名空间内的相同端口上,用户还可以虚拟出一块网卡。
  • CLONE_NEWNS: 挂载命名空间,进程运行时可以将挂载点与系统分离,使用这个功能时,我们可以达到 chroot 的功能,而在安全性方面比 chroot 更高。
  • CLONE_NEWUTS: UTS 命名空间,主要目的是独立出主机名和网络信息服务(NIS)。
  • CLONE_NEWUSER: 用户命名空间,同进程 ID 一样,用户 ID 和组 ID 在命名空间内外是不一样的,并且在不同命名空间内可以存在相同的 ID。

下面我们介绍一下进程命名空间和网络命名空间。

进程命名空间

本文用 C 语言介绍上述概念,因为演示进程命名空间的时候需要用到 C 语言。下面的测试过程在 Debian 6 和 Debian 7 上执行。首先,在栈内分配一页内存空间,并将指针指向内存页的末尾。这里我们使用 alloca() 函数来分配内存,不要用 malloc() 函数,它会把内存分配在堆上。


  1. void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);

然后使用 clone() 函数创建子进程,传入我们的子栈空间地址 "mem",并指定命名空间的标记。同时我们还指定“callee”作为子进程运行的函数。


  1. mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);

clone 之后我们要在父进程中等待子进程先退出,否则的话,父进程会继续运行下去,并马上进程结束,留下子进程变成孤儿进程:


  1. while (waitpid(mypid, &r, 0) < 0 && errno == EINTR)
  2. {
  3. continue;
  4. }

最后当子进程退出后,我们会回到 shell 界面,并返回子进程的退出码。


  1. if (WIFEXITED(r))
  2. {
  3. return WEXITSTATUS(r);
  4. }
  5. return EXIT_FAILURE;

上文介绍的 callee 函数功能如下:


  1. static int callee()
  2. {
  3. int ret;
  4. mount("proc", "/proc", "proc", 0, "");
  5. setgid(u);
  6. setgroups(0, NULL);
  7. setuid(u);
  8. ret = execl("/bin/bash", "/bin/bash", NULL);
  9. return ret;
  10. }

程序挂载了 /proc 文件系统,设置用户 ID 和组 ID,值都为“u”,然后运行 /bin/bash 程序,LXC 是一个操作系统级的虚拟化工具,使用 cgroups 和命名空间来完成资源的分离。现在我们把所有代码放在一起,变量“u”的值设为65534,在 Debian 系统中,这是“nobody”和“nogroup”:


  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. #include <sys/mount.h>
  8. #include <grp.h>
  9. #include <alloca.h>
  10. #include <errno.h>
  11. #include <sched.h>
  12. static int callee();
  13. const int u = 65534;
  14. int main(int argc, char *argv[])
  15. {
  16. int r;
  17. pid_t mypid;
  18. void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
  19. mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
  20. while (waitpid(mypid, &r, 0) < 0 && errno == EINTR)
  21. {
  22. continue;
  23. }
  24. if (WIFEXITED(r))
  25. {
  26. return WEXITSTATUS(r);
  27. }
  28. return EXIT_FAILURE;
  29. }
  30. static int callee()
  31. {
  32. int ret;
  33. mount("proc", "/proc", "proc", 0, "");
  34. setgid(u);
  35. setgroups(0, NULL);
  36. setuid(u);
  37. ret = execl("/bin/bash", "/bin/bash", NULL);
  38. return ret;
  39. }

执行以下命令来运行上面的代码:


  1. root@w:~/pen/tmp# gcc -O -o ns.c -Wall -Werror -ansi -c89 ns.c
  2. root@w:~/pen/tmp# ./ns
  3. nobody@w:~/pen/tmp$ id
  4. uid=65534(nobody) gid=65534(nogroup)
  5. nobody@w:~/pen/tmp$ ps auxw
  6. USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
  7. nobody 1 0.0 0.0 4620 1816 pts/1 S 21:21 0:00 /bin/bash
  8. nobody 5 0.0 0.0 2784 1064 pts/1 R+ 21:21 0:00 ps auxw
  9. nobody@w:~/pen/tmp$

注意上面的结果,UID 和 GID 被设置成 nobody 和 nogroup 了,特别是 ps 工具只输出两个进程,它们的 ID 分别是1和5(LCTT注:这就是上文介绍 CLONE_NEWPID 时提到的功能,在线程所在的命名空间内,进程 ID 可以为1,映射到命名空间外是另外一个 PID;而命名空间外的 ID 为1的进程一直是 init)。

网络命名空间

接下来轮到使用 ip netns 来设置网络的命名空间。第一步先确定当前系统没有命名空间:


  1. root@w:~# ip netns list
  2. Object "netns" is unknown, try "ip help".

如果报了上述错误,你需要更新你的系统内核,以及 ip 工具程序。这里假设你的内核版高于2.6.24,ip 工具版本也差不多,高于2.6.24(LCTT注:ip 工具由 iproute 安装包提供,此安装包版本与内核版本相近)。更新好后,ip netns list 在没有命名空间存在的情况下不会输出任务信息。加个名为“ns1”的命名空间看看:


  1. root@w:~# ip netns add ns1
  2. root@w:~# ip netns list
  3. ns1

列出网卡:


  1. root@w:~# ip link list
  2. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000
  5. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff

创建新的虚拟网卡,并加到命名空间。虚拟网卡需要成对创建,互相关联——就像交叉电缆一样:


  1. root@w:~# ip link add veth0 type veth peer name veth1
  2. root@w:~# ip link list
  3. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000
  6. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
  7. 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
  8. link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff
  9. 4: veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
  10. link/ether f2:f7:5e:e2:22:ac brd ff:ff:ff:ff:ff:ff

这个时候 ifconfig -a 命令也能显示新添加的 veth0 和 veth1 两块网卡。

很好,现在将这两份块网卡加到命名空间中去。注意一下,下面的 ip netns exec 命令用于将后面的命令在命名空间中执行(LCTT注:下面的结果显示了在 ns1 这个网络命名空间中,只存在 lo 和 veth1 两块网卡):


  1. root@w:~# ip link set veth1 netns ns1
  2. root@w:~# ip netns exec ns1 ip link list
  3. 1: lo: mtu 65536 qdisc noop state DOWN mode DEFAULT
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
  6. link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff

这个时候 ifconfig -a 命令只能显示 veth0,不能显示 veth1,因为后者现在在 ns1 命名空间中。

如果想删除 veth0/veth1,可以执行下面的命令:


  1. ip netns exec ns1 ip link del veth1

我们可以为 veth0 分配 IP 地址:


  1. ifconfig veth0 192.168.5.5/24

在命名空间内为 veth1 分配 IP 地址:


  1. ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up

在命名空间内外执行 ip addr list 命令:


  1. root@w:~# ip addr list
  2. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. inet 127.0.0.1/8 scope host lo
  5. inet6 ::1/128 scope host
  6. valid_lft forever preferred_lft forever
  7. 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
  8. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
  9. inet 192.168.3.122/24 brd 192.168.3.255 scope global eth0
  10. inet6 fe80::20c:29ff:fe65:259e/64 scope link
  11. valid_lft forever preferred_lft forever
  12. 6: veth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000
  13. link/ether 86:b2:c7:bd:c9:11 brd ff:ff:ff:ff:ff:ff
  14. inet 192.168.5.5/24 brd 192.168.5.255 scope global veth0
  15. inet6 fe80::84b2:c7ff:febd:c911/64 scope link
  16. valid_lft forever preferred_lft forever
  17. root@w:~# ip netns exec ns1 ip addr list
  18. 1: lo: mtu 65536 qdisc noop state DOWN
  19. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  20. 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000
  21. link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff
  22. inet 192.168.5.10/24 brd 192.168.5.255 scope global veth1
  23. inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link
  24. valid_lft forever preferred_lft forever

在命名空间内外查看路由表:


  1. root@w:~# ip route list
  2. default via 192.168.3.1 dev eth0 proto static
  3. 192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.122
  4. 192.168.5.0/24 dev veth0 proto kernel scope link src 192.168.5.5
  5. root@w:~# ip netns exec ns1 ip route list
  6. 192.168.5.0/24 dev veth1 proto kernel scope link src 192.168.5.10

最后,将虚拟网卡连到物理网卡上,我们需要用到桥接。这里做的是将 veth0 桥接到 eth0,而 ns1 命名空间内则使用 DHCP 自动获取 IP 地址:


  1. root@w:~# brctl addbr br0
  2. root@w:~# brctl addif br0 eth0
  3. root@w:~# brctl addif br0 veth0
  4. root@w:~# ifconfig eth0 0.0.0.0
  5. root@w:~# ifconfig veth0 0.0.0.0
  6. root@w:~# dhclient br0
  7. root@w:~# ip addr list br0
  8. 7: br0: mtu 1500 qdisc noqueue state UP
  9. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
  10. inet 192.168.3.122/24 brd 192.168.3.255 scope global br0
  11. inet6 fe80::20c:29ff:fe65:259e/64 scope link
  12. valid_lft forever preferred_lft forever

为网桥 br0 分配的 IP 地址为192.168.3.122/24。接下来为命名空间分配地址:


  1. root@w:~# ip netns exec ns1 dhclient veth1
  2. root@w:~# ip netns exec ns1 ip addr list
  3. 1: lo: mtu 65536 qdisc noop state DOWN
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000
  6. link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff
  7. inet 192.168.3.248/24 brd 192.168.3.255 scope global veth1
  8. inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link
  9. valid_lft forever preferred_lft forever

现在, veth1 的 IP 被设置成 192.168.3.248/24 了。

----------------------------------------------------------------------------------------------------------------------------

原文发布时间:2015-03-10
本文来自云栖合作伙伴“linux中国”
时间: 2024-11-10 07:03:41

介绍 Linux 的命名空间的相关文章

Linux的命名空间详解--Linux进程的管理与调度(二)

命名空间概念 传统上,在Linux以及其他衍生的UNIX变体中,许多资源是全局管理的. 例如,系统中的所有进程按照惯例是通过PID标识的,这意味着内核必须管理一个全局的PID列表.而且,所有调用者通过uname系统调用返回的系统相关信息(包括系统名称和有关内核的一些信息)都是相同的.用户ID的管理方式类似,即各个用户是通过一个全局唯一的UID号标识. 全局ID使得内核可以有选择地允许或拒绝某些特权.虽然UID为0的root用户基本上允许做任何事,但其他用户ID则会受到限制.例如UID为n 的用户

介绍linux 2.6.9-42内核升级到linux 2.6.26-42的方法

这篇升级Linux内容的文章,是基于Red Hat的Linux版本,从linux 2.6.9-42内核升级到linux 2.6.26-42的方法,对于在实际使用过程中需要升级内核版本的玩家,能提供一个不错的参考方法,以供借鉴于学习之用. 在这里且不说版本的不同与区别,它所支持的功能.相信用过LINUX的人都希望自己所用的系统,内核是最新的了.接下来就来就给大家介绍linux2.6.9-42升级linux2.6.26-42的方法.首先来看下当前系统的版本吧: 接下来就开始吧,把所需文件&http:

介绍Linux操作系统下修改系统时间的方法

我们一般使用"date -s"命令来修改系统时间.比如将系统时间设定成2007年8月19日的命令如下. #date -s 08/19/2007 将系统时间设定成下午11点20分0秒的命令如下. #date -s 11:20:00 注意,这里说的是系统时间,是linux由操作系统维护的. 在系统启动时,Linux操作系统将时间从CMOS中读到系统时间变量中,以后修改时间通过修改系统时间实现.为了保持系统时间与CMOS时间的一致性,Linux每隔一段时间会将系统时间写入CMOS.由于该同步

详细介绍Linux的定时任务crontab_unix linux

一.前言 crontab命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于"crontab"文件中(/var/spool/cron/以用户命名的文件),以供之后读取和执行.该词来源于希腊语 chronos(χρνο),原意是时间. 通常,crontab储存的指令被守护进程 - crond激活在后台运行,每一分钟检查是否有预定的作业需要执行.这类作业一般称为cron jobs. 二.启动crond进程 service cr

详细介绍linux动态网络和静态网络和克隆后的网络配置_Linux

建议设置网卡NAT模式 动态网络配置: 1.一定要开启本地DHCP服务 2.在虚拟网络编辑器中选择NAT模式选中DHCP项如下图 3.ifup eth0静态网络配置 : 注释:ifcfg-eth0部分参数说明 DEVICE=eth0#描述网卡对应的设备别名 HWADDR=00:0C:29:42:DD:51#对应的网卡物理地址 TYPE=Ethernet#网络类型(通常是Ethemet) UUID=514fda06-1fc6-4d19-8a74-74030ed83956 ONBOOT=yes#系统

Linux命名空间学习教程(四)NS(FS)

本文讲的是Linux命名空间学习教程(四)NS(FS),[编者的话]Docker核心解决的问题是利用LXC来实现类似VM的功能,从而利用更加节省的硬件资源提供给用户更多的计算资源.而 LXC所实现的隔离性主要是来自内核的命名空间, 其中pid.net.ipc.mnt.uts 等命名空间将容器的进程.网络.消息.文件系统和hostname 隔离开.本文是Linux命名空间系列教程的第四篇,重点介绍NS(FS)命名空间.DockerOne在撸代码的基础上进行了校对和整理. 继上一篇 关于PID na

Linux命名空间学习教程(一) UTS

本文讲的是Linux命名空间学习教程(一) UTS,[编者的话]Docker核心解决的问题是利用LXC来实现类似VM的功能,从而利用更加节省的硬件资源提供给用户更多的计算资源.而 LXC所实现的隔离性主要是来自内核的命名空间, 其中pid.net.ipc.mnt.uts 等命名空间将容器的进程.网络.消息.文件系统和hostname 隔离开.本文是Linux命名空间系列教程的第一篇,通过一个简单的例子介绍了Linux容器以及UTS命名空间.DockerOne在撸代码的基础上进行了校对和整理. 我

Linux shell脚本基础学习详细介绍(完整版)

Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提. 1. Linux 脚本编写基础 ◆1.1 语法基本介绍1.1.1 开头 程序必须以下面的行开始(必须方在文件的第一行): #!/bin/sh 符号#!用来告诉系统它后面的参数是用来执行该文件的程序.在这个例子中我们使用/bin/sh来执行程序. 当编辑好脚本时,如果要执行该脚本,还必须使其可执行. 要使脚本可

【翻译】Linux网络名字空间(Network Namespace)介绍

翻译自 http://blog.scottlowe.org/2013/09/04/introducing-linux-network-namespaces/ 在这篇文章中,我将向您介绍Linux网络名字空间的概念.虽然它现在似乎有点深奥的,但相信我,向你介绍网络名字空间是有原因的--如果你像我一样,想更好的理解OpenStack,你迟早会再次遇到网络名字空间. 那么什么是网络民资空间?一般的说法是包括了Linux共享的一组网络接口和路由表条目的装置.您可以修改路由表条目使用策略路由.(这里有我写