一、Linux NameSpace
Docker这么火,喜欢技术的朋友可能也会想,如果要自己实现一个资源隔离的容器,应该从哪些方面下手呢?也许你第一反应可能就是chroot命令,这条命令给用户最直观的感觉就是使用后根目录/的挂载点切换了,即文件系统被隔离了。然后,为了在分布式的环境下进行通信和定位,容器必然需要一个独立的IP、端口、路由等等,自然就想到了网络的隔离。同时,你的容器还需要一个独立的主机名以便在网络中标识自己。想到网络,顺其自然就想到通信,也就想到了进程间通信的隔离。可能你也想到了权限的问题,对用户和用户组的隔离就实现了用户权限的隔离。最后,运行在容器中的应用需要有自己的PID,自然也需要与宿主机中的PID进行隔离。
由此,我们基本上完成了一个容器所需要做的六项隔离,Linux内核中就提供了这六种namespace隔离的系统调用,如下表所示:
系统调用参数 |
隔离内容 | |
UTS | CLONE_NEWUTS | 主机名与域名 |
IPC | CLONE_NEWIPC |
信号量、消息队列和共享内存 |
PID | CLONE_NEWPID |
进程编号 |
Network |
CLONE_NEWNET | 网络设备、网络栈、端口等等 |
Mount |
CLONE_NEWNS | 挂载点(文件系统) |
User | CLONE_NEWUSE |
用户和用户组 |
实际上,Linux内核实现namespace的主要目的就是为了实现轻量级虚拟化(容器)服务。
在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛自己置身于一个独立的系统环境中,以此达到独立和隔离的目的。
今天我们学习一下linux network namespace
1、Linux NameSpace
命名空间或名称空间
Linux Namespaces机制提供一种资源隔离方案。
PID,IPC,Network等系统资源不再是全局性的,而是属于特定的Namespace(可以理解为虚拟机)。每个Namespace里面的资源对其他Namespace都是透明的。要创建新的Namespace,只需要在调用clone时指定相应的flag。Linux Namespaces机制为实现基于容器的虚拟化技术提供了很好的基础,LXC(Linux containers)就是利用这一特性实现了资源的隔离。不同container内的进程属于不同的Namespace,彼此透明,互不干扰。
命名空间提供了虚拟化的一种轻量级形式,使得我们可以从不同的方面来查看运行系统的全局属性。该机制类似于Solaris中的zone或 FreeBSD中的jail。对该概念做一般概述之后,我将讨论命名空间框架所提供的基础设施。
1)概念
传统上,在Linux以及其他衍生的UNIX变体中,许多资源是全局管理的。
例如,系统中的所有进程按照惯例是通过PID标识的,这意味着内核必须管理一个全局的PID列表。而且,所有调用者通过uname系统调用返回的系统相关信息(包括系统名称和有关内核的一些信息)都是相同的。用户ID的管理方式类似,即各个用户是通过一个全局唯一的UID号标识。
全局ID使得内核可以有选择地允许或拒绝某些特权。虽然UID为0的root用户基本上允许做任何事,但其他用户ID则会受到限制。例如UID为n 的用户,不允许杀死属于用户m的进程(m≠ n)。但这不能防止用户看到彼此,即用户n可以看到另一个用户m也在计算机上活动。只要用户只能操纵他们自己的进程,这就没什么问题,因为没有理由不允许用户看到其他用户的进程。
但有些情况下,这种效果可能是不想要的。如果提供Web主机的供应商打算向用户提供Linux计算机的全部访问权限,包括root权限在内。传统上,这需要为每个用户准备一台计算机,代价太高。使用KVM或VMWare提供的虚拟化环境是一种解决问题的方法,但资源分配做得不是非常好。计算机的各个用户都需要一个独立的内核,以及一份完全安装好的配套的用户层应用。
命名空间提供了一种不同的解决方案,所需资源较少。在虚拟化的系统中,一台物理计算机可以运行多个内核,可能是并行的多个不同的操作系统。而命名空间则只使用一个内核在一台物理计算机上运作,前述的所有全局资源都通过命名空间抽象起来。这使得可以将一组进程放置到容器中,各个容器彼此隔离。隔离可以使容器的成员与其他容器毫无关系。但也可以通过允许容器进行一定的共享,来降低容器之间的分隔。例如,容器可以设置为使用自身的PID集合,但仍然与其他容器共享部分文件系统。
本质上,命名空间建立了系统的不同视图。此前的每一项全局资源都必须包装到容器数据结构中,只有资源和包含资源的命名空间构成的二元组仍然是全局唯一的。虽然在给定容器内部资源是自足的,但无法提供在容器外部具有唯一性的ID。
考虑系统上有3个不同命名空间的情况。命名空间可以组织为层次,假如一个命名空间是父命名空间,衍生了两个子命名空间。假定容器用于虚拟主机配置中,其中的每个容器必须看起来像是单独的一台Linux计算机。因此其中每一个都有自身的init进程,PID为0,其他进程的PID 以递增次序分配。两个子命名空间都有PID为0的init进程,以及PID分别为2和3的两个进程。由于相同的PID在系统中出现多次,PID号不是全局唯一的。
虽然子容器不了解系统中的其他容器,但父容器知道子命名空间的存在,也可以看到其中执行的所有进程。图中子容器的进程映射到父容器中,PID为4到 9。尽管系统上有9个进程,但却需要15个PID来表示,因为一个进程可以关联到多个PID。至于哪个PID是"正确"的,则依赖于具体的上下文。
如果命名空间包含的是比较简单的量,也可以是非层次的,例如下文讨论的UTS命名空间。在这种情况下,父子命名空间之间没有联系。
请注意,Linux系统对简单形式的命名空间的支持已经有很长一段时间了,主要是chroot系统调用。该方法可以将进程限制到文件系统的某一部分,因而是一种简单的命名空间机制。但真正的命名空间能够控制的功能远远超过文件系统视图。
新的命名空间可以用下面两种方法创建。
(1) 在用fork或clone系统调用创建新进程时,有特定的选项可以控制是与父进程共享命名空间,还是建立新的命名空间。
(2) unshare系统调用将进程的某些部分从父进程分离,其中也包括命名空间。
2、linux network namespace
1)概念
Network namespace主要提供了关于网络资源的隔离,包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、端口(socket)等等。
一个物理的网络设备最多存在在一个network namespace中,你可以通过创建veth pair(虚拟网络设备对:有两端,类似管道,如果数据从一端传入另一端也能接收到,反之亦然)在不同的network namespace间创建通道,以此达到通信的目的。
一般情况下,物理网络设备都分配在最初的root namespace(表示系统默认的namespace)中。但是如果你有多块物理网卡,也可以把其中一块或多块分配给新创建的network namespace。需要注意的是,当新创建的network namespace被释放时(所有内部的进程都终止并且namespace文件没有被挂载或打开),在这个namespace中的物理网卡会返回到root namespace而非创建该进程的父进程所在的network namespace。
当我们说到network namespace时,其实我们指的未必是真正的网络隔离,而是把网络独立出来,给外部用户一种透明的感觉,仿佛跟另外一个网络实体在进行通信。为了达到这个目的,容器的经典做法就是创建一个veth pair,一端放置在新的namespace中,通常命名为eth0,一端放在原先的namespace中连接物理网络设备,再通过网桥把别的设备连接进来或者进行路由转发,以此网络实现通信的目的。
2)ip netns命令的使用
注意:netns在内核实现,其控制功能由iproute所提供的netns这个OBJECT来提供;
CentOS6.6提供的iproute不具有此OBJECT,需要依赖于OpenStack Icehouse的EPEL源来提供;
也可以直接安装
[root@BAIYU_173 ~]# rpm -q iproute iproute-2.6.32-45.el6.x86_64 [root@anyfish ~]# rpm -ivh --replacefiles http://www.rendoumi.com/soft/iproute-2.6.32-130.el6ost.netns.2.x86_64.rpm Retrieving http://www.rendoumi.com/soft/iproute-2.6.32-130.el6ost.netns.2.x86_64.rpm warning: /var/tmp/rpm-tmp.eaI3zM: Header V4 RSA/SHA1 Signature, key ID d97b3247: NOKEY Preparing... ########################################### [100%] 1:iproute ########################################### [100%]
[root@BAIYU_173 ~]# ip --help Usage: ip [ OPTIONS ] OBJECT { COMMAND | help } ip [ -force ] -batch filename where OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable | tunnel | maddr | mroute | mrule | monitor | xfrm | token | netns } OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] | -f[amily] { inet | inet6 | ipx | dnet | link } | -o[neline] | -t[imestamp] | -b[atch] [filename] | -rc[vbuf] [size]} [root@BAIYU_173 ~]# ip netns help Usage: ip netns list ip netns add NAME #创建一个虚拟网络设备 ip netns delete NAME #删除 ip netns exec NAME cmd ... #在虚拟网络设备上执行命令 ip netns monitor
[root@BAIYU_173 ~]# ip netns list [root@BAIYU_173 ~]# ip netns add r0 [root@BAIYU_173 ~]# ip netns list r0 [root@BAIYU_173 ~]# ip netns exec r0 ifconfig [root@BAIYU_173 ~]# ip netns exec r0 ifconfig -a lo Link encap:Local Loopback LOOPBACK MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) [root@BAIYU_173 ~]# ip netns exec r0 ifconfig lo 127.0.0.1 [root@BAIYU_173 ~]# ip netns exec r0 ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
ip netns list
ip netns add NAME
ip netns del NAME
ip netns exec NAME COMMAND
3)使用虚拟以太网卡
ip link add FRONTEND-NAME type veth peer name BACKEND-NAME #添加一对虚拟网卡
例子:
[root@anyfish ~]# ip link help Usage: ip link add link DEV [ name ] NAME [ txqueuelen PACKETS ] [ address LLADDR ] [ broadcast LLADDR ] [ mtu MTU ] type TYPE [ ARGS ] ip link delete DEV type TYPE [ ARGS ] ip link set DEVICE [ { up | down } ] [ arp { on | off } ] [ dynamic { on | off } ] [ multicast { on | off } ] [ allmulticast { on | off } ] [ promisc { on | off } ] [ trailers { on | off } ] [ txqueuelen PACKETS ] [ name NEWNAME ] [ address LLADDR ] [ broadcast LLADDR ] [ mtu MTU ] [ netns PID ] [ netns NAME ] [ alias NAME ] [ vf NUM [ mac LLADDR ] [ vlan VLANID [ qos VLAN-QOS ] ] [ rate TXRATE ] ] ip link show [ DEVICE ] TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | can } 虚拟以太网卡 [root@BAIYU_173 ~]# ip link add veth0.0 type veth peer name veth0.1 #添加虚拟网卡 [root@BAIYU_173 ~]# ip addr 1: lo:mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:da:25:08 brd ff:ff:ff:ff:ff:ff inet 192.168.100.173/24 brd 192.168.100.255 scope global eth0 inet 192.168.100.177/24 brd 192.168.100.255 scope global secondary eth0:0 inet6 fe80::20c:29ff:feda:2508/64 scope link valid_lft forever preferred_lft forever 3: virbr0: mtu 1500 qdisc noqueue state UNKNOWN link/ether 52:54:00:6c:af:ba brd ff:ff:ff:ff:ff:ff inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 4: virbr0-nic: mtu 1500 qdisc noop state DOWN qlen 500 link/ether 52:54:00:6c:af:ba brd ff:ff:ff:ff:ff:ff 8: veth0.1: mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 82:08:18:95:22:25 brd ff:ff:ff:ff:ff:ff 9: veth0.0: mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 46:73:df:dd:f9:8c brd ff:ff:ff:ff:ff:ff [root@BAIYU_173 ~]# ip link set veth0.0 netns r0 #将虚拟网卡关联到命名空间 [root@BAIYU_173 ~]# ip addr #一个网卡在内核中只能属于一个命名空间 1: lo: mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:da:25:08 brd ff:ff:ff:ff:ff:ff inet 192.168.100.173/24 brd 192.168.100.255 scope global eth0 inet 192.168.100.177/24 brd 192.168.100.255 scope global secondary eth0:0 inet6 fe80::20c:29ff:feda:2508/64 scope link valid_lft forever preferred_lft forever 3: virbr0: mtu 1500 qdisc noqueue state UNKNOWN link/ether 52:54:00:6c:af:ba brd ff:ff:ff:ff:ff:ff inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 4: virbr0-nic: mtu 1500 qdisc noop state DOWN qlen 500 link/ether 52:54:00:6c:af:ba brd ff:ff:ff:ff:ff:ff 8: veth0.1: mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 82:08:18:95:22:25 brd ff:ff:ff:ff:ff:ff [root@BAIYU_173 ~]# ip netns exec r0 ip addr 6: lo: mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 9: veth0.0: mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 46:73:df:dd:f9:8c brd ff:ff:ff:ff:ff:ff [root@BAIYU_173 ~]# ip netns exec r0 ip link set veth0.0 name eth0 #在命名空间中更虚拟网卡名 [root@BAIYU_173 ~]# ip netns exec r0 ip addr 6: lo: mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 9: eth0: mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 46:73:df:dd:f9:8c brd ff:ff:ff:ff:ff:ff [root@BAIYU_173 ~]# ip addr add 192.168.100.100/24 dev veth0.1 #虚拟网卡后半段设置ip [root@BAIYU_173 ~]# ip addr 1: lo: mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:da:25:08 brd ff:ff:ff:ff:ff:ff inet 192.168.100.173/24 brd 192.168.100.255 scope global eth0 inet 192.168.100.177/24 brd 192.168.100.255 scope global secondary eth0:0 inet6 fe80::20c:29ff:feda:2508/64 scope link valid_lft forever preferred_lft forever 3: virbr0: mtu 1500 qdisc noqueue state UNKNOWN link/ether 52:54:00:6c:af:ba brd ff:ff:ff:ff:ff:ff inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 4: virbr0-nic: mtu 1500 qdisc noop state DOWN qlen 500 link/ether 52:54:00:6c:af:ba brd ff:ff:ff:ff:ff:ff 8: veth0.1: mtu 1500 qdisc pfifo_fast state DOWN qlen 1000 link/ether 82:08:18:95:22:25 brd ff:ff:ff:ff:ff:ff inet 192.168.10.1/24 scope global veth0.1 [root@BAIYU_173 ~]# ip netns exec r0 ifconfig eth0 192.168 10.2/24 [root@BAIYU_173 ~]# ip netns exec r0 ping 192.168.10.1 #命名空间能ping通外部 PING 192.168.10.1 (192.168.10.1) 56(84) bytes of data. 64 bytes from 192.168.10.1: icmp_seq=1 ttl=64 time=0.093 ms 64 bytes from 192.168.10.1: icmp_seq=2 ttl=64 time=0.051 ms ^C --- 192.168.10.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1541ms rtt min/avg/max/mdev = 0.051/0.072/0.093/0.021 ms [root@BAIYU_173 ~]# ping 192.168.10.2 #外部也能ping通命名空间 PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data. 64 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=0.031 ms