一、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时指定相应的flagLinux Namespaces机制为实现基于容器的虚拟化技术提供了很好的基础,LXCLinux containers)就是利用这一特性实现了资源的隔离。不同container内的进程属于不同的Namespace,彼此透明,互不干扰。

命名空间提供了虚拟化的一种轻量级形式,使得我们可以从不同的方面来查看运行系统的全局属性。该机制类似于Solaris中的zone FreeBSD中的jail。对该概念做一般概述之后,我将讨论命名空间框架所提供的基础设施。

1)概念

传统上,在Linux以及其他衍生的UNIX变体中,许多资源是全局管理的。

例如,系统中的所有进程按照惯例是通过PID标识的,这意味着内核必须管理一个全局的PID列表。而且,所有调用者通过uname系统调用返回的系统相关信息(包括系统名称和有关内核的一些信息)都是相同的。用户ID的管理方式类似,即各个用户是通过一个全局唯一的UID号标识。

全局ID使得内核可以有选择地允许或拒绝某些特权。虽然UID0root用户基本上允许做任何事,但其他用户ID则会受到限制。例如UID的用户,不允许杀死属于用户m的进程(m n)。但这不能防止用户看到彼此,即用户n可以看到另一个用户m也在计算机上活动。只要用户只能操纵他们自己的进程,这就没什么问题,因为没有理由不允许用户看到其他用户的进程。

但有些情况下,这种效果可能是不想要的。如果提供Web主机的供应商打算向用户提供Linux计算机的全部访问权限,包括root权限在内。传统上,这需要为每个用户准备一台计算机,代价太高。使用KVMVMWare提供的虚拟化环境是一种解决问题的方法,但资源分配做得不是非常好。计算机的各个用户都需要一个独立的内核,以及一份完全安装好的配套的用户层应用。

命名空间提供了一种不同的解决方案,所需资源较少。在虚拟化的系统中,一台物理计算机可以运行多个内核,可能是并行的多个不同的操作系统。而命名空间则只使用一个内核在一台物理计算机上运作,前述的所有全局资源都通过命名空间抽象起来。这使得可以将一组进程放置到容器中,各个容器彼此隔离。隔离可以使容器的成员与其他容器毫无关系。但也可以通过允许容器进行一定的共享,来降低容器之间的分隔。例如,容器可以设置为使用自身的PID集合,但仍然与其他容器共享部分文件系统。

本质上,命名空间建立了系统的不同视图。此前的每一项全局资源都必须包装到容器数据结构中,只有资源和包含资源的命名空间构成的二元组仍然是全局唯一的。虽然在给定容器内部资源是自足的,但无法提供在容器外部具有唯一性的ID

考虑系统上有3个不同命名空间的情况。命名空间可以组织为层次,假如一个命名空间是父命名空间,衍生了两个子命名空间。假定容器用于虚拟主机配置中,其中的每个容器必须看起来像是单独的一台Linux计算机。因此其中每一个都有自身的init进程,PID0,其他进程的PID 以递增次序分配。两个子命名空间都有PID0init进程,以及PID分别为23的两个进程。由于相同的PID在系统中出现多次,PID号不是全局唯一的。

虽然子容器不了解系统中的其他容器,但父容器知道子命名空间的存在,也可以看到其中执行的所有进程。图中子容器的进程映射到父容器中,PID4 9。尽管系统上有9个进程,但却需要15PID来表示,因为一个进程可以关联到多个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