Linux Namespace系列(06):network namespace (CLONE_NEWNET)

network namespace用來隔離網絡設備, IP地址, 端口等. 每一個namespace將會有本身獨立的網絡棧,路由表,防火牆規則,socket等。node

每一個新的network namespace默認有一個本地環回接口,除了lo接口外,全部的其餘網絡設備(物理/虛擬網絡接口,網橋等)只能屬於一個network namespace。每一個socket也只能屬於一個network namespace。 git

當新的network namespace被建立時,lo接口默認是關閉的,須要本身手動啓動起github

標記爲"local devices"的設備不能從一個namespace移動到另外一個namespace,好比loopback, bridge, ppp等,咱們能夠經過ethtool -k命令來查看設備的netns-local屬性。docker

#這裏「on」表示該設備不能被移動到其餘network namespace
dev@ubuntu:~$ ethtool -k lo|grep netns-local
netns-local: on [fixed]

本篇全部例子都在ubuntu-server-x86_64 16.04下執行經過shell

示例

本示例將演示如何建立新的network namespace並同外面的namespace進行通訊。ubuntu

#--------------------------第一個shell窗口----------------------
#記錄默認network namespace ID
dev@ubuntu:~$ readlink /proc/$$/ns/net
net:[4026531957]

#建立新的network namespace
dev@ubuntu:~$ sudo unshare --uts --net /bin/bash
root@ubuntu:~# hostname container001
root@ubuntu:~# exec bash
root@container001:~# readlink /proc/$$/ns/net
net:[4026532478]

#運行ifconfig啥都沒有
root@container001:~# ifconfig
root@container001:~#

#啓動lo (這裏不詳細介紹ip這個tool的用法,請參考man ip)
root@container001:~# ip link set lo up
root@container001:~# 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:1
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

root@container001:~# ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.015 ms

#獲取當前bash進程的PID
root@container001:~# echo $$
15812

#--------------------------第二個shell窗口----------------------
#建立新的虛擬以太網設備,讓兩個namespace能通信
dev@ubuntu:~$ sudo ip link add veth0 type veth peer name veth1

#將veth1移動到上面第一個窗口中的namespace
#這裏15812是上面bash的PID
dev@ubuntu:~$ sudo ip link set veth1 netns 15812

#爲veth0分配IP並啓動veth0
dev@ubuntu:~$ sudo ip address add dev veth0 192.168.8.1/24
dev@ubuntu:~$ sudo ip link set veth0 up
dev@ubuntu:~$ ifconfig veth0
veth0     Link encap:Ethernet  HWaddr 9a:4d:d5:96:b5:36
          inet addr:192.168.8.1  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::984d:d5ff:fe96:b536/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)

#--------------------------第一個shell窗口----------------------
#爲veth1分配IP地址並啓動它
root@container001:~# ip address add dev veth1 192.168.8.2/24
root@container001:~# ip link set veth1 up
root@container001:~# ifconfig veth1
veth1     Link encap:Ethernet  HWaddr 6a:dc:59:79:3c:8b
          inet addr:192.168.8.2  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::68dc:59ff:fe79:3c8b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)

#鏈接成功
root@container001:~# ping 192.168.8.1
PING 192.168.8.1 (192.168.8.1) 56(84) bytes of data.
64 bytes from 192.168.8.1: icmp_seq=1 ttl=64 time=0.098 ms
64 bytes from 192.168.8.1: icmp_seq=2 ttl=64 time=0.023 ms

到目前爲止,兩個namespace之間能夠網絡通訊了,但在container001裏仍是不能訪問外網。下面將經過NAT的方式讓container001可以上外網。這部份內容徹底是網絡相關的知識,跟namespace已經沒什麼關係了。安全

#--------------------------第二個shell窗口----------------------
#回到上面示例中的第二個窗口

#確認IP forward是否已經開通,這裏1表示開通了
#若是你的機器上是0,請運行這個命令將它改成1: sudo sysctl -w net.ipv4.ip_forward=1
dev@ubuntu:~$ cat /proc/sys/net/ipv4/ip_forward
1

#添加NAT規則,這裏ens32是機器上鍊接外網的網卡
#關於iptables和nat都比較複雜,這裏不作解釋
dev@ubuntu:~$ sudo iptables -t nat -A POSTROUTING -o ens32 -j MASQUERADE

#--------------------------第一個shell窗口----------------------
#回到第一個窗口,添加默認網關
root@container001:~# ip route add default via 192.168.8.1
root@container001:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.8.1     0.0.0.0         UG    0      0        0 veth1
192.168.8.0     0.0.0.0         255.255.255.0   U     0      0        0 veth1

#這樣就能夠訪問外網了
#因爲測試環境的限制,因此採用下面的方式檢測網絡是否暢通
#若是網絡沒有什麼限制的話,隨便ping一個外部的IP測試就能夠了
root@container001:~# curl -I www.google.com
HTTP/1.1 200 OK
Date: Fri, 15 Jul 2016 08:12:03 GMT

network namespace的概念比較簡單,但如何作好網絡的隔離和連通卻比較難,包括性能和安全相關的考慮,須要很好的Linux網絡知識。後續在介紹docker網絡管理的時候會對Linux網絡作一個更詳細的介紹。bash

ip netns

在單獨操做network namespace時,ip netns是一個很方便的工具,而且它能夠給namespace取一個名字,而後根據名字來操做namespace。那麼給namespace取名字而且根據名字來管理namespace裏面的進程是怎麼實現的呢?請看下面的腳本(也能夠直接看它的源代碼):網絡

#開始以前,獲取一下默認network namespace的ID
dev@ubuntu:~$ readlink /proc/$$/ns/net
net:[4026531957]

#建立一個用於綁定network namespace的文件,
#ip netns將全部的文件放到了目錄/var/run/netns下,
#因此咱們這裏重用這個目錄,而且建立一個咱們本身的文件netnamespace1
dev@ubuntu:~$ sudo mkdir -p /var/run/netns
dev@ubuntu:~$ sudo touch /var/run/netns/netnamespace1

#建立新的network namespace,並在新的namespace中啓動新的bash
dev@ubuntu:~$ sudo unshare --net bash
#查看新的namespace ID
root@ubuntu:~# readlink /proc/$$/ns/net
net:[4026532448]

#bind當前bash的namespace文件到上面建立的文件上
root@ubuntu:~# mount --bind /proc/$$/ns/net /var/run/netns/netnamespace1
#經過ls -i命令能夠看到文件netnamespace1的inode號和namespace的編號相同,說明綁定成功
root@ubuntu:~# ls -i /var/run/netns/netnamespace1
4026532448 /var/run/netns/netnamespace1

#退出新建立的bash
root@ubuntu:~# exit
exit
#能夠看出netnamespace1的inode沒變,說明咱們使用了bind mount後
#雖然新的namespace中已經沒有進程了,但這個新的namespace還存在
dev@ubuntu:~$ ls -i /var/run/netns/netnamespace1
4026532448 /var/run/netns/netnamespace1

#上面的這一系列操做等同於執行了命令: ip netns add netnamespace1
#下面的nsenter命令等同於執行了命令: ip netns exec netnamespace1 bash

#咱們能夠經過nsenter命令再建立一個新的bash,並將它加入netnamespace1所關聯的namespace(net:[4026532448])
dev@ubuntu:~$ sudo nsenter --net=/var/run/netns/netnamespace1 bash
root@ubuntu:~# readlink /proc/$$/ns/net
net:[4026532448]

從上面能夠看出,給namespace取名字其實就是建立一個文件,而後經過mount --bind將新建立的namespace文件和該文件綁定,就算該namespace裏的全部進程都退出了,內核仍是會保留該namespace,之後咱們還能夠經過這個綁定的文件來加入該namespace。curl

經過這種辦法,咱們也能夠給其餘類型的namespace取名字(有些類型的 namespace可能有些特殊,本人沒有一個一個的試過)。

參考

相關文章
相關標籤/搜索