虛擬化網絡的原理簡述
Linux內核支持六種名稱空間,只要在用戶空間有相應的客戶端工具,都可以對對應的名稱空間進行操作。
假如物理機有四塊網卡,可以創建兩個名稱空間,此時可靈活將網卡分配到單獨的名稱空間中。
一般一個設備只能屬於一個名稱空間,如此每個名稱空間都可以配置IP地址,並且和外部通信。
假如名稱空間數量超過物理網卡數量,而名稱空間中的進程必須通過物理網卡對外通信,如何處理?可以使用虛擬網卡設備,使用純軟件的方式模擬出網卡設備。
在Linux內核級別,支持兩種模擬:二層設備和三層設備。
二層設備(鏈路層)
鏈路層,實現報文轉發的設備。
利用內核對二層設備的模擬,創建虛擬網卡接口,這種網絡接口是成對出現的,模擬爲一根網線的兩頭。其中一頭插在主機上,另一頭插在交換機上。
內核原生支持二層虛擬網橋設備,用軟件來構建交換機。比如bridge-utils工具的brctl來實現。
利用軟件交換機和軟件實現的名稱空間,如此就可模擬一個主機連接到交換機中,以實現網絡連接的功能。兩個名稱空間相當於連接到同一臺交換機中的兩臺主機。
以上就是虛擬化網絡,是網絡虛擬化技術的一種簡單實現。
三層設備(軟件交換機)
OVS:Open VSwitch 開源的虛擬交換機,能模擬實現高級的三層網絡設備如VLAN,VxLAN,GRE等等,它不屬於Linux內核本身的模塊,因此需要額外安裝。它是由思科等衆多網絡設備生產公司開發的,其功能非常強大。
SDN:軟件定義網絡/軟件驅動網絡,需要在硬件層面上支持虛擬化網絡,還需要在每個主機上構建複雜的虛擬化網絡,來運行多個虛擬機或者容器。
容器間通信
如果在同一個host上的兩個namespace需要通信,可以在host上建立虛擬交換機,使用純軟件的方式建立一對網卡,一半在容器上,一半在交換機上。此時只要容器配置同子網的IP地址,即可通信。
如果存在多個交換機,比如兩個軟件交換機,各自連接不同的容器。比如:
可以創建一對網卡,一半在SW1上,一半在容器中。(docker的默認網絡模型就是這個)
如果希望C1僅僅和C3通信,則需要在交換機中間添加路由器,而路由器是三層設備,可以在Linux內核的模塊來單獨來支持,比如第三個容器(中的內核)來實現報文轉發。
如果希望跨主機通信,比如C1和C5通信,如何實現?
方式有:
-
橋接:
把host的物理網卡當做交換機使用,各個容器都有自己的虛擬網卡/mac地址,如果需要和本host通信,則將目的mac指向host的虛擬網卡即可。如此可以讓C1和C5通信。
然而這種方式的代價很大,如果都是橋接則所有容器/主機都在同一網絡平面上,非常容易產生風暴,隔離性很差。
因此,在大規模的虛擬化或者容器的場景中,不能選用這種方式。除非使用大二層的網絡技術將其隔離,否則,都不應該直接橋接。
-
NAT
如果希望對外通信,則應該使用nat技術而不是橋接。
如果C3和C5通信,C3將網關指向了S1,物理機上打開核心轉發功能。報文:C3 -> S1 ->(路由表,轉發) -> 外部網絡。但是報文無法回來,因爲C3是私有地址。因此,在C3的報文離開主機前,將IP轉換爲S1主機上的IP地址,這就是源地址轉換。
如此C5可直接回復給S1。而S1內部的nat表可知道,該報文實際上是屬於C3的,因此會自動轉發給C3。
上述通信必須經過nat實現,而且兩級的nat代理。因爲C5可能也是nat內部。因此S1並看不到C5,除非將C5 DNAT發佈出去。
因此,比如將C5發佈到S2的地址和端口,由S2自動將其請求轉換到C5中。
經過SNAT和DNAT轉換,效率不高。而且通信的雙方並看不到真正的對方。其好處是,網絡易於管理。
-
疊加網絡Overlay Network
這種網絡方式不用完全暴露主機,也不用完全隱藏主機。其方式如下:
- 多個host,創建虛擬橋,讓VM連接到虛擬橋上。
- 在虛擬橋上創建隧道,讓C3直接看到C5
- 物理機可直接通信,C3和C5在同一個地址段內。C3先將報文送給橋,而橋知道C5並不在本地,於是將報文通過物理網卡發出去:在使用隧道轉發出去前,報文C3|C5再封裝一層IP首部:H1|H2
- H2拆開第一層封裝,看到C3|C5,於是將報文轉發給C5.
上述實現兩級的三層封裝,使用一個IP承載另一個IP,這就是隧道技術。如此C3和C5就可以直接通信。
模擬容器間通信
使用ip命令,可以在一臺主機中構建出一個網絡名稱空間,並且可模擬容器間通信。
root@eto:~# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
通過這種方式只有網絡名稱空間是隔離的,而其他名稱空間都是共享的。和容器略有不同。
-
創建名稱空間
root@eto:~# ip netns add r1 root@eto:~# ip netns add r2 root@eto:~# ip netns list r2 r1
-
創建對應的虛擬網卡對,然後分配到名稱空間中
root@eto:~# ip link add name veth1.1 type veth peer name veth1.2 root@eto:~# ip link show ... 9: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 6a:f0:ef:a0:76:80 brd ff:ff:ff:ff:ff:ff 10: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
# 把veth1.2分配到r1中去 root@eto:~# ip link set dev veth1.2 netns r1 root@eto:~# ip a s ... 10: veth1.1@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 0e:33:73:c9:73:fc brd ff:ff:ff:ff:ff:ff link-netnsid 0 root@eto:~# ip netns exec r1 ifconfig -a lo: flags=8<LOOPBACK> mtu 65536 loop txqueuelen 1000 (Local Loopback) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 veth1.2: flags=4098<BROADCAST,MULTICAST> mtu 1500 ether 6a:f0:ef:a0:76:80 txqueuelen 1000 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
-
修改網卡名稱,設置IP地址並激活
root@eto:~# ip netns exec r1 ip link set dev veth1.2 name eth0 root@eto:~# ip netns exec r1 ifconfig eth0 10.1.0.2/24 up root@eto:~# ifconfig veth1.1 10.1.0.1/24 up root@eto:~# ping 10.1.0.2 PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data. 64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.054 ms 64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.036 ms
此時兩張網卡可通信,有宿主機 -> r1
-
將另一半網卡挪到r2,並激活
root@eto:~# ip link set dev veth1.1 netns r2 root@eto:~# ip netns exec r2 ifconfig veth1.1 10.1.0.3/24 up root@eto:~# ip netns exec r2 ping 10.1.0.2 PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data. 64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.037 ms 64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.034 ms
使用這種方式來測試網絡通信模型,和虛擬機幾乎無區別。可以模擬物理橋,僅主機和nat橋。
Docker的網絡
Docker安裝完成後,自動提供三種網絡:
- bridge
- host
- none
root@eto:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
fd3713beb21e bridge bridge local
509f1fdd50fb host host local
83fea3e78ba1 none null local
Bridge:本機上創建的軟交換機,也可以當做網卡使用。當啓動容器時,可自動讓容器自動分配一對網卡,一半在容器上,一半在交換機上。比如:
# 啓動容器
root@eto:~# docker run --name t1 -it jaywin/httpd:v0.1-1
/ #
# 宿主機上,關聯到docker0上的網卡設備,其中
root@eto:~# ip a s
...
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:72:ff:55:a2 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
...
6: veth2fadfaf@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
veth2fadfaf@if5 中的if5在容器中,另一端veth2fadfaf在宿主機中。它們連接到了docker0橋上。
root@eto:~# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024272ff55a2 no veth2fadfaf(宿主機的接口)
Docker0橋默認是一個nat橋,創建容器後會自動分配iptables規則:
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
只要從docker0橋的網段發出的報文,自動選擇一個合適的源地址做SNAT(MASQUERADE)。如此同一個宿主機下的容器,都可以相互通信(屬於docker0的二層網絡),並且容器可以和外部通信(實現了SNAT)。
如果有一個nginx容器,提供服務。另外一個容器也是連接到橋上來的,則可以直接當客戶端來訪問nginx。通過如下方式:
- 物理機上也能直接訪問(可以看成是僅主機的橋)
- 如果是外部的物理機訪問nginx,它是無法看到nginx容器,因爲nat將容器隱藏在背後。此時需要使用dnat將nginx發佈
因此,當使用默認的橋接網絡時,只能通過dnat規則以便讓外部的客戶端訪問該容器。
如果同一臺主機擁有兩個web容器,如何對外做dnat?畢竟只有一個80端口,如何能正常提供訪問?如果使用默認的網絡模型,並沒有很好的解決方法。
如果使用overlay網絡,則無需有上述的困擾,直接訪問對應的web服務即可。關於overlay網絡,暫不表述。
除了上述描述的方式,還有什麼方法可以使得容器可以對外服務?比如:
讓每個容器僅擁有user,mount,pid是隔離的,兩個容器共享UTS,Net,IPC。如此,容器的文件系統,pid和用戶是隔離的,而網絡是同一組,同一個網絡協議棧,同一個域名主機名。容器間的通信可直接通過lo實現。
共享的一般是和網絡通信相關的資源。
也可以讓容器直接使用物理名稱空間。
如果容器僅使用none網絡,則容器僅有lo,沒有網卡,無網絡通信功能
Docker網絡模型
Docker創建容器時,使用選項–network來指定使用哪種網絡模型。默認default爲bridge (docker0)
查看網絡的詳細信息:
root@eto:~# docker network inspect bridge
Docker的網絡名稱空間和UTS,IPC是可以讓容器共享的,因此可構建出隔離,橋接,nat等網絡模型。
docker網絡模型
四種網絡模型:
- 封閉式容器
- 橋接式容器:容器包含虛擬網絡設備,一半在docker0上,一半在名稱空間中。默認情況下,容器是啓動爲橋接式的網絡模型。
- 聯盟式容器:容器a有自己的名稱空間,容器b共享容器a的名稱空間。可以讓a和b的容器應用在本地通信
- 容器共享宿主機的名稱空間
可以指定爲封閉式容器:
root@eto:~# docker run --name t4 -it --network none --rm busybox:latest
/ # ifconfig -a
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
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:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Bridge可以實現不同容器間通信,而主機名默認爲容器id,可使用hostname設置或者run的時候指定hostname,從容器外注入主機名。
# 查看默認主機名
root@eto:~# docker run --name t4 -it --network bridge --rm busybox:latest hostname
3ccdfa0745a3
# 注入主機名
root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --rm busybox:latest
/ # hostname
t2.jaywin.com
也可以注入解析,默認情況下容器使用宿主機的DNS配置。
/ # cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 t2.jaywin.com t2
可自定義DNS服務器
root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --dns 114.114.114.114 --rm busybox:latest cat /etc/resolv.conf
search midea.com.cn
nameserver 114.114.114.114
定義搜索域
root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --dns 114.114.114.114 --dns-search ilinux.io --rm busybox:latest cat /etc/resolv.conf
search ilinux.io
nameserver 114.114.114.114
添加hosts記錄
root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --dns 114.114.114.114 --dns-search ilinux.io --add-host www.jaywin.com:1.1.1.1 --rm busybox:latest cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
1.1.1.1 www.jaywin.com
172.17.0.2 t2.jaywin.com t2
服務的暴露方式
使用-p選項進行服務暴露
動態端口暴露
root@eto:~# docker run --name w1 --rm -p 80 jaywin/httpd:v0.2
root@eto:~# docker port w1
80/tcp -> 0.0.0.0:32768
# 暴露隨機端口
實際上,通過DNAT規則將服務發佈出去。
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:32768 to:172.17.0.2:80
# 只要訪問本機的32768端口,將DNAT到172.17.0.2:80
# 當刪除容器時,nat規則也會一併移除
固定地址的動態端口暴露
root@eto:~# docker run --name w1 --rm -p 192.168.2.10::80 jaywin/httpd:v0.2
root@eto:~# docker port w1
80/tcp -> 192.168.2.10:32768
固定端口暴露
# 將端口暴露在宿主機的指定端口:
root@eto:~# docker run --name w1 --rm -p 80:80 jaywin/httpd:v0.2
root@eto:~# docker port w1
80/tcp -> 0.0.0.0:80
-p選項可以使用多次,以暴露多個端口。
暴露所有端口
-P選項或者–publish-all,將容器所有計劃要暴露的端口全部映射至主機端口。
計劃 要暴露的端口需要使用–expose選項指定
# docker run -d -P --expose 2222 --expose 3333 --name web busybox:latest /bin/httpd -p 2222 -f
聯盟式容器Joined containers
聯盟式容器是指使用某個已存在容器的網絡接口的容器,接口被聯盟內各容器共享使用。因此,聯盟式容器彼此間完全無隔離。
聯盟式容器彼此間雖然共享同一個網絡名詞空間,但其他名稱空間如User、Mount等還是隔離的
聯盟式容器彼此間存在端口衝突的可能。因此,通常只會在多個容器上的程序需要程序loopback接口相互通信,或者對某個已存在的容器的網絡屬性進行監控時,才使用這種模式的網絡模型
創建聯盟式容器
root@eto:~# docker run --name b2 --rm --network container:w1 -it busybox:latest
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:16 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:1256 (1.2 KiB) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
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:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
# 此時b2和W1共享網絡名稱空間,而其他資源是隔離的。此時,b2可通過lo口訪問w1的httpd服務。
# 如此相當於同一主機上的兩個進程。
/ # wget -O - -q 127.0.0.1
busybox http server
# 容器和宿主機的聯盟式網絡
root@eto:~# docker run --name b2 --rm --network host -it busybox:latest
/ # ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:72:FF:55:A2
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:72ff:feff:55a2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:19 errors:0 dropped:0 overruns:0 frame:0
TX packets:38 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1289 (1.2 KiB) TX bytes:3578 (3.4 KiB)
如此一來,將十分簡便地部署應用,並利用宿主機的網絡。
修改docker0上的網絡信息
自定義docker0橋的網絡屬性信息:
root@eto:~# cat /etc/docker/daemon.json
{
"exec-opts": ["native.cgrounpdriver=systemd"],
"registry-mirrors": ["https://e2615hzs.mirror.aliyuncs.com"],
"bip": "192.168.1.0/24"
}
# 核心選項bip,即bridge ip之意,用於指定docker0橋自身的IP地址
# 修改docker0的地址後,啓動容器的地址也會隨之改變。
dockerd默認監聽在Unix Socket格式的地址:/var/run/docker.sock。如果使用TCP套接字,則修改配置文件選項爲:
"hosts": ["tcp://0.0.0.0:2375","unix:///var/run/docker.sock"]
也可以向dockerd直接傳遞"-H | --host"選項。在ubuntu上,如果docker.service指定了-H fd,此處再設置hosts時會無法啓動。此時可以將docker.service中的-H選項去掉即可。
此時客戶端就可以通過-H,指定要連接的服務器地址:
root@eto:~# docker -H 192.168.2.10:2375 ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
創建自定義橋設備
root@eto:~# docker network create -d bridge --subnet "192.168.100.0/24" --gateway "192.168.100.1" mybr0
eb582031756ecb64541e6c95dd23caf1ece1c5b87378438cf6661b250b887052
root@eto:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
f3f514b6596c bridge bridge local
509f1fdd50fb host host local
eb582031756e mybr0 bridge local
83fea3e78ba1 none null local
將容器和mybr0橋接
root@eto:~# docker run --name t4 -it --net mybr0 busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:64:02
inet addr:192.168.100.2 Bcast:192.168.100.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:12 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:1032 (1.0 KiB) TX bytes:0 (0.0 B)
再創建一個容器,橋接到docker0
root@eto:~# docker run --name b2 -it --rm busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 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:696 (696.0 B) TX bytes:0 (0.0 B)
這兩個容器可以相互通信,默認做nat地址轉換,只需要宿主機打開forward轉發,並清空此規則:
root@eto:~# iptables -F DOCKER-ISOLATION-STAGE-1
此規則包含限制不同容器網絡之間的訪問的規則:
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
如此就可以通信了:
/ # ping 192.168.100.2
PING 192.168.100.2 (192.168.100.2): 56 data bytes
ip64 bytes from 192.168.100.2: seq=125 ttl=63 time=0.133 ms
64 bytes from 192.168.100.2: seq=126 ttl=63 time=0.074 ms
64 bytes from 192.168.100.2: seq=127 ttl=63 time=0.168 ms