1、容器網絡簡介
node
容器網絡主要解決兩大核心問題:一是容器的IP地址分配,二是容器之間的相互通訊。本文重在研究第二個問題而且主要研究容器的跨主機通訊問題。web
實現容器跨主機通訊的最簡單方式就是直接使用host網絡,這時因爲容器IP就是宿主機的IP,複用宿主機的網絡協議棧以及underlay網絡,原來的主機能通訊,容器也就天然能通訊,然而帶來的最直接問題就是端口衝突問題。docker
所以一般容器會配置與宿主機不同的屬於本身的IP地址。因爲是容器本身配置的IP,underlay平面的底層網絡設備如交換機、路由器等徹底不感知這些IP的存在,也就致使容器的IP不能直接路由出去實現跨主機通訊。shell
要解決如上問題實現容器跨主機通訊,主要有以下兩個思路:數據庫
思路一:修改底層網絡設備配置,加入容器網絡IP地址的管理,修改路由器網關等,該方式主要和SDN結合。後端
思路二:徹底不修改底層網絡設備配置,複用原有的underlay平面網絡,解決容器跨主機通訊,主要有以下兩種方式:api
Overlay隧道傳輸。把容器的數據包封裝到原主機網絡的三層或者四層數據包中,而後使用原來的網絡使用IP或者TCP/UDP傳輸到目標主機,目標主機再拆包轉發給容器。Overlay隧道如Vxlan、ipip等,目前使用Overlay技術的主流容器網絡如Flannel、Weave等。安全
修改主機路由。把容器網絡加到主機路由表中,把主機看成容器網關,經過路由規則轉發到指定的主機,實現容器的三層互通。目前經過路由技術實現容器跨主機通訊的網絡如Flannel host-gw、Calico等。bash
本文接下來將詳細介紹目前主流容器網絡的實現原理。網絡
在開始正文內容以前,先引入兩個後續會一直使用的腳本:
第一個腳本爲 docker_netns.sh
:
#!/bin/bash NAMESPACE=$1 if [[ -z $NAMESPACE ]]; then ls -1 /var/run/docker/netns/ exit 0 fi NAMESPACE_FILE=/var/run/docker/netns/${NAMESPACE} if [[ ! -f $NAMESPACE_FILE ]]; then NAMESPACE_FILE=$(docker inspect -f "{{.NetworkSettings.SandboxKey}}" $NAMESPACE 2>/dev/null) fi if [[ ! -f $NAMESPACE_FILE ]]; then echo "Cannot open network namespace '$NAMESPACE': No such file or directory" exit 1 fi shift if [[ $# -lt 1 ]]; then echo "No command specified" exit 1 fi nsenter --net=${NAMESPACE_FILE} $@
該腳本經過指定容器id、name或者namespace快速進入容器的network namespace並執行相應的shell命令。
若是不指定任何參數,則列舉全部Docker容器相關的network namespaces。
# ./docker_netns.sh # list namespaces 4-a4a048ac67 abe31dbbc394 default # ./docker_netns.sh busybox ip addr # Enter busybox namespace 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 354: eth0@if355: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 02:42:c0:a8:64:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.100.2/24 brd 192.168.100.255 scope global eth0 valid_lft forever preferred_lft forever 356: eth1@if357: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet 172.18.0.2/16 brd 172.18.255.255 scope global eth1 valid_lft forever preferred_lft forever
find_links.sh
:#!/bin/bash DOCKER_NETNS_SCRIPT=./docker_netns.sh IFINDEX=$1 if [[ -z $IFINDEX ]]; then for namespace in $($DOCKER_NETNS_SCRIPT); do printf "\e[1;31m%s: \e[0m\n" $namespace $DOCKER_NETNS_SCRIPT $namespace ip -c -o link printf "\n" done else for namespace in $($DOCKER_NETNS_SCRIPT); do if $DOCKER_NETNS_SCRIPT $namespace ip -c -o link | grep -Pq "^$IFINDEX: "; then printf "\e[1;31m%s: \e[0m\n" $namespace $DOCKER_NETNS_SCRIPT $namespace ip -c -o link | grep -P "^$IFINDEX: "; printf "\n" fi done fi
該腳本根據ifindex查找虛擬網絡設備所在的namespace:
# ./find_links.sh 354 abe31dbbc394: 354: eth0@if355: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:c0:a8:64:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
該腳本的目的是方便查找veth的對端所在的namespace位置。若是不指定ifindex,則列出全部namespaces的link設備。
Laurent Bernaille在DockerCon2017上詳細介紹了Docker原生的Overlay網絡實現原理,做者還總結了三篇乾貨文章一步一步剖析Docker網絡實現原理,最後還教你們一步一步從頭開始手動實現Docker的Overlay網絡,這三篇文章爲:
Deep dive into docker overlay networks part 1
Deep dive into docker overlay networks part 2
Deep dive into docker overlay networks part 3
建議感興趣的讀者閱讀,本節也大量參考瞭如上三篇文章的內容。
測試使用兩個Node節點:
Node名 | 主機IP |
---|---|
node-1 | 192.168.1.68 |
node-2 | 192.168.1.254 |
首先建立一個overlay網絡:
docker network create -d overlay --subnet 10.20.0.0/16 overlay
在兩個節點分別建立兩個busybox容器:
docker run -d --name busybox --net overlay busybox sleep 36000
容器列表以下:
Node名 | 主機IP | 容器IP |
---|---|---|
node-1 | 192.168.1.68 | 10.20.0.3/16 |
node-2 | 192.168.1.254 | 10.20.0.2/16 |
咱們發現容器有兩個IP,其中eth0 10.20.0.0/16爲咱們建立的Overlay網絡ip,兩個容器可以互相ping通。而不在同一個node的容器IP eth1都是172.18.0.2,所以172.18.0.0/16很顯然不能用於跨主機通訊,只能用於單個節點容器通訊。
這裏的南北流量主要是指容器與外部通訊的流量,好比容器訪問互聯網。
咱們查看容器的路由:
# docker exec busybox-node-1 ip r default via 172.18.0.1 dev eth1 10.20.0.0/16 dev eth0 scope link src 10.20.0.3 172.18.0.0/16 dev eth1 scope link src 172.18.0.2
由此可知容器默認網關爲172.18.0.1,也就是說容器是經過eth1出去的:
# docker exec busybox-node-1 ip link show eth1 77: eth1@if78: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff # ./find_links.sh 78 default: 78: vethf2de5d4@if77: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP mode DEFAULT group default link/ether 2e:6a:94:6a:09:c5 brd ff:ff:ff:ff:ff:ff link-netnsid 1
經過 find_links.sh
腳本查找ifindex爲78的link在默認namespace中,而且該link的master爲 docker_gwbridge
,也就是說該設備掛到了 docker_gwbridge
bridge
# brctl show bridge name bridge id STP enabled interfaces docker0 8000.02427406ba1a no docker_gwbridge 8000.0242bb868ca3 no vethf2de5d4
而 172.18.0.1
正是bridge docker_gwbridge
的IP,也就是說 docker_gwbridge
是該節點的全部容器的網關。
因爲容器的IP是172.18.0.0/16私有IP地址段,不能出公網,所以必然經過NAT實現容器IP與主機IP地址轉換,查看iptables nat表以下:
# iptables-save -t nat | grep -- '-A POSTROUTING' -A POSTROUTING -s 172.18.0.0/16 ! -o docker_gwbridge -j MASQUERADE
由此可驗證容器是經過NAT出去的。
咱們發現其實容器南北流量用的其實就是Docker最原生的bridge網絡模型,只是把 docker0
換成了 docker_gwbridge
。若是容器不須要出互聯網,建立Overlay網絡時能夠指定 --internal
參數,此時容器只有一個Overlay網絡的網卡,不會建立eth1。
容器東西流量指容器之間的通訊,這裏特指跨主機的容器間通訊。
顯然容器是經過eth0實現與其餘容器通訊的:
# docker exec busybox-node-1 ip link show eth0 75: eth0@if76: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue link/ether 02:42:0a:14:00:03 brd ff:ff:ff:ff:ff:ff # ./find_links.sh 76 1-19c5d1a7ef: 76: veth0@if75: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP mode DEFAULT group default \ link/ether 6a:ce:89:a2:89:4a brd ff:ff:ff:ff:ff:ff link-netnsid 1
eth0的對端設備ifindex爲76,經過 find_links.sh
腳本查找ifindex 76在 1-19c5d1a7ef
namespace下,名稱爲 veth0
,而且master爲br0,所以veth0掛到了br0 bridge下。
經過 docker_netns.sh
腳本能夠快速進入指定的namespace執行命令:
# ./docker_netns.sh 1-19c5d1a7ef ip link show veth0 76: veth0@if75: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP mode DEFAULT group default link/ether 6a:ce:89:a2:89:4a brd ff:ff:ff:ff:ff:ff link-netnsid 1 # ./docker_netns.sh 1-19c5d1a7ef brctl show bridge name bridge id STP enabled interfaces br0 8000.6ace89a2894a no veth0 vxlan0
可見除了veth0,bridge還綁定了vxlan0:
./docker_netns.sh 1-19c5d1a7ef ip -c -d link show vxlan0 74: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default link/ether 96:9d:64:39:76:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1 vxlan id 256 srcport 0 0 dstport 4789 proxy l2miss l3miss ttl inherit ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx ...
vxlan0是一個VxLan虛擬網絡設備,所以能夠推斷Docker Overlay是經過vxlan隧道實現跨主機通訊的。這裏直接引用Deep dive into docker overlay networks part 1的圖:
圖中192.168.0.0/16對應前面的10.20.0.0/16網段。
如前面所述,跨主機的兩個容器雖然是經過Overlay通訊的,但容器本身殊不知道,他們只認爲彼此都在一個二層中(同一個子網),或者說大二層。咱們知道二層是經過MAC地址識別對方的,經過ARP協議廣播學習獲取IP與MAC地址轉換。固然經過Vxlan隧道廣播ARP包理論上也沒有問題,問題是該方案將致使廣播包過多,廣播的成本會很大。
和OpenStack Neutron的L2 Population原理同樣,Docker也是經過ARP代理+靜態配置解決ARP廣播問題。咱們知道,雖然Linux底層除了經過自學習方式外沒法知道目標IP的MAC地址是什麼,可是應用卻很容易獲取這些信息,好比Neutron的數據庫中就保存着Port信息,Port中就有IP和MAC地址。Docker也同樣會把endpoint信息保存到KV數據庫中,如etcd:
有了這些數據徹底能夠實現經過靜態配置的方式填充IP和MAC地址表(neigh表)替換使用ARP廣播的方式。所以vxlan0還負責了本地容器的ARP代理:
./docker_netns.sh 2-19c5d1a7ef ip -d -o link show vxlan0 | grep proxy_arp
而vxlan0代理回覆時直接查找本地的neigh表回覆便可,而本地neigh表則是Docker靜態配置,可查看Overlay網絡namespaced neigh表:
# ./docker_netns.sh 3-19c5d1a7ef ip neigh 10.20.0.3 dev vxlan0 lladdr 02:42:0a:14:00:03 PERMANENT 10.20.0.4 dev vxlan0 lladdr 02:42:0a:14:00:04 PERMANENT
記錄中的 PERMANENT
說明是靜態配置而不是經過學習獲取的,IP 10.20.0.三、10.20.0.4正是另外兩個容器的IP地址。
每當有新的容器建立時,Docker經過Serf以及Gossip協議通知節點更新本地neigh ARP表。
前面介紹的ARP代理屬於L2層問題,而容器的數據包最終仍是經過Vxlan隧道傳輸的,那天然須要解決的問題是這個數據包應該傳輸到哪一個node節點?若是隻是兩個節點,建立vxlan隧道時能夠指定本地ip(local IP)和對端IP(remote IP)創建點對點通訊,但實際上顯然不可能只有兩個節點。
咱們不妨把Vxlan出去的物理網卡稱爲VTEP(VXLAN Tunnel Endpoint),它會有一個可路由的IP,即Vxlan最終封裝後的外層IP。經過查找VTEP表決定數據包應該傳輸到哪一個remote VTEP:
容器MAC地址 | Vxlan ID | Remote VTEP |
---|---|---|
02:42:0a:14:00:03 | 256 | 192.168.1.254 |
02:42:0a:14:00:04 | 256 | 192.168.1.245 |
... | ... | ... |
VTEP表和ARP表相似,也能夠經過廣播洪泛的方式學習,但顯然一樣存在性能問題,實際上也不多使用這種方案。在硬件SDN中一般使用BGP E***技術實現Vxlan的控制平面。
而Docker解決的辦法和ARP相似,經過靜態配置的方式填充VTEP表,咱們能夠查看容器網絡namespace的轉發表(Forward database,簡稱fdb),
./docker_netns.sh 3-19c5d1a7ef bridge fdb ... 02:42:0a:14:00:04 dev vxlan0 dst 192.168.1.245 link-netnsid 0 self permanent 02:42:0a:14:00:03 dev vxlan0 dst 192.168.1.254 link-netnsid 0 self permanent ...
可見MAC地址02:42:0a:14:00:04的對端VTEP地址爲192.168.1.245,而02:42:0a:14:00:03的對端VTEP地址爲192.168.1.254,兩條記錄都是 permanent
,即靜態配置的,而這些數據來源依然是KV數據庫,endpoint中 locator
即爲容器的node IP。
容器使用Docker原生Overlay網絡默認會建立兩張虛擬網卡,其中一張網卡經過bridge以及NAT出容器外部,即負責南北流量。另外一張網卡經過Vxlan實現跨主機容器通訊,爲了減小廣播,Docker經過讀取KV數據靜態配置ARP表和FDB表,容器建立或者刪除等事件會經過Serf以及Gossip協議通知Node更新ARP表和FDB表。
weave是weaveworks公司提供的容器網絡方案,實現上和Docker原生Overlay網絡有點相似。
初始化三個節點192.168.1.6八、192.168.1.25四、192.168.1.245以下:
weave launch --ipalloc-range 172.111.222.0/24 192.168.1.68 192.168.1.254 192.168.1.245
分別在三個節點啓動容器:
# node-1 docker run -d --name busybox-node-1 --net weave busybox sleep 3600 # node-2 docker run -d --name busybox-node-2 --net weave busybox sleep 3600 # node-3 docker run -d --name busybox-node-3 --net weave busybox sleep 3600
在容器中咱們相互ping:
從結果發現,Weave實現了跨主機容器通訊,另外咱們容器有兩個虛擬網卡,一個是Docker原生的橋接網卡eth0,用於南北通訊,另外一個是Weave附加的虛擬網卡ethwe0,用於容器跨主機通訊。
另外查看容器的路由:
# docker exec -t -i busybox-node-$NODE ip r default via 172.18.0.1 dev eth0 172.18.0.0/16 dev eth0 scope link src 172.18.0.2 172.111.222.0/24 dev ethwe0 scope link src 172.111.222.128 224.0.0.0/4 dev ethwe0 scope link
其中 224.0.0.0/4
是一個組播地址,可見Weave是支持組播的,參考Container Multicast Networking: Docker & Kubernetes | Weaveworks.
咱們只看第一個容器的ethwe0,VETH對端ifindex爲14:
# ./find_links.sh 14 default: 14: vethwl816281577@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue master weave state UP mode DEFAULT group default link/ether de:12:50:59:f0:d9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
可見ethwe0的對端在default namespace下,名稱爲 vethwl816281577
,該虛擬網卡橋接到 weave
bridge下:
# brctl show weave bridge name bridge id STP enabled interfaces weave 8000.d2939d07704b no vethwe-bridge vethwl81628157
weave
bridge下除了有 vethwl816281577
,還有 vethwe-bridge
:
# ip link show vethwe-bridge 9: vethwe-bridge@vethwe-datapath: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue master weave state UP mode DEFAULT group default link/ether 0e:ee:97:bd:f6:25 brd ff:ff:ff:ff:ff:ff
可見 vethwe-bridge
與 vethwe-datapath
是一個VETH對,咱們查看對端 vethwe-datapath
:
# ip -d link show vethwe-datapath 8: vethwe-datapath@vethwe-bridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue master datapath state UP mode DEFAULT group default link/ether f6:74:e9:0b:30:6d brd ff:ff:ff:ff:ff:ff promiscuity 1 veth openvswitch_slave addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
vethwe-datapath
的master爲 datapath
,由 openvswitch_slave
可知 datapath
應該是一個openvswitch bridge,而 vethwe-datapath
掛到了 datapath
橋下,做爲 datapath
的port。
爲了驗證,經過ovs-vsctl查看:
# ovs-vsctl show 96548648-a6df-4182-98da-541229ef7b63 ovs_version: "2.9.2"
使用 ovs-vsctl
發現並無 datapath
這個橋。官方文檔中fastdp how it works中解釋爲了提升網絡性能,沒有使用用戶態的OVS,而是直接操縱內核的datapath。使用 ovs-dpctl
命令能夠查看內核datapath:
# ovs-dpctl show system@datapath: lookups: hit:109 missed:1508 lost:3 flows: 1 masks: hit:1377 total:1 hit/pkt:0.85 port 0: datapath (internal) port 1: vethwe-datapath port 2: vxlan-6784 (vxlan: packet_type=ptap)
可見datapath相似於一個OVS bridge設備,負責數據交換,該設備包含三個port:
port 0: datapath (internal)
port 1: vethwe-datapath
port 2: vxlan-6784
除了 vethwe-datapath
,還有一個 vxlan-6784
,由名字可知這是一個vxlan:
# ip -d link show vxlan-6784 10: vxlan-6784: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65535 qdisc noqueue master datapath state UNKNOWN mode DEFAULT group default qlen 1000 link/ether d2:21:db:c1:9b:28 brd ff:ff:ff:ff:ff:ff promiscuity 1 vxlan id 0 srcport 0 0 dstport 6784 nolearning ttl inherit ageing 300 udpcsum noudp6zerocsumtx udp6zerocsumrx external openvswitch_slave addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size
最後Weave的網絡流量圖以下:
Flannel網絡是目前最主流的容器網絡之一,同時支持overlay(如vxlan)和路由(如host-gw)兩種模式。
Flannel和Weave以及Docker原生overlay網絡不一樣的是,後者的全部Node節點共享一個子網,而Flannel初始化時一般指定一個16位的網絡,而後每一個Node單獨分配一個獨立的24位子網。因爲Node都在不一樣的子網,跨節點通訊本質爲三層通訊,也就不存在二層的ARP廣播問題了。
另外,我認爲Flannel之因此被認爲很是簡單優雅的是,不像Weave以及Docker Overlay網絡須要在容器內部再增長一個網卡專門用於Overlay網絡的通訊,Flannel使用的就是Docker最原生的bridge網絡,除了須要爲每一個Node配置subnet(bip)外,幾乎不改變原有的Docker網絡模型。
咱們首先以Flannel Overlay網絡模型爲例,三個節點的IP以及Flannel分配的子網以下:
Node名 | 主機IP | 分配的子網 |
---|---|---|
node-1 | 192.168.1.68 | 40.15.43.0/24 |
node-2 | 192.168.1.254 | 40.15.26.0/24 |
node-3 | 192.168.1.245 | 40.15.56.0/24 |
在三個集成了Flannel網絡的Node環境下分別建立一個 busybox
容器:
docker run -d --name busybox busybox:latest sleep 36000
容器列表以下:
Node名 | 主機IP | 容器IP |
---|---|---|
node-1 | 192.168.1.68 | 40.15.43.2/24 |
node-2 | 192.168.1.254 | 40.15.26.2/24 |
node-3 | 192.168.1.245 | 40.15.56.2/24 |
查看容器namespace的網絡設備:
# ./docker_netns.sh busybox ip -d -c link 416: eth0@if417: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:28:0f:2b:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
和Docker bridge網絡同樣只有一張網卡eth0,eth0爲veth設備,對端的ifindex爲417.
咱們查找下ifindex 417的link信息:
# ./find_links.sh 417 default: 417: veth1cfe340@if416: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 26:bd:de:86:21:78 brd ff:ff:ff:ff:ff:ff link-netnsid 0
可見ifindex 417在default namespace下,名稱爲 veth1cfe340
而且master爲docker0,所以掛到了docker0的bridge下。
# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242d6f8613e no veth1cfe340 vethd1fae9d docker_gwbridge 8000.024257f32054 no
和Docker原生的bridge網絡沒什麼區別,那它是怎麼解決跨主機通訊的呢?
實現跨主機通訊,要麼Overlay隧道封裝,要麼靜態路由,顯然docker0沒有看出有什麼overlay的痕跡,所以只能經過路由實現了。
不妨查看下本地路由以下:
# ip r default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.68 metric 100 40.15.26.0/24 via 40.15.26.0 dev flannel.1 onlink 40.15.43.0/24 dev docker0 proto kernel scope link src 40.15.43.1 40.15.56.0/24 via 40.15.56.0 dev flannel.1 onlink ...
咱們只關心40.15開頭的路由,忽略其餘路由,咱們發現除了40.15.43.0/24直接經過docker0直連外,其餘均路由轉發到了 flannel.1
。而40.15.43.0/24爲本地Node的子網,所以在同一宿主機的容器直接經過docker0通訊便可。
咱們查看 flannel.1
的設備類型:
413: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UNKNOWN mode DEFAULT group default link/ether 0e:08:23:57:14:9a brd ff:ff:ff:ff:ff:ff promiscuity 0 vxlan id 1 local 192.168.1.68 dev eth0 srcport 0 0 dstport 8472 nolearning ttl inherit ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
可見 flannel.1
是一個Linux Vxlan設備,其中 .1
爲VNI值,不指定默認爲1。
因爲不涉及ARP所以不須要proxy參數實現ARP代理,而本節點的容器通訊因爲在一個子網內,所以直接ARP本身學習便可,不須要Vxlan設備學習,所以有個nolearning參數。
而 flannel.1
如何知道對端VTEP地址呢?咱們依然查看下轉發表fdb:
bridge fdb | grep flannel.1 4e:55:ee:0a:90:38 dev flannel.1 dst 192.168.1.245 self permanent da:17:1b:07:d3:70 dev flannel.1 dst 192.168.1.254 self permanent
其中192.168.1.24五、192.168.1.254正好是另外兩個Node的IP,即VTEP地址,而 4e:55:ee:0a:90:38
以及 da:17:1b:07:d3:70
爲對端的 flannel.1
設備的MAC地址,因爲是 permanent
表,所以可推測是由flannel靜態添加的,而這些信息顯然能夠從etcd獲取:
# for subnet in $(etcdctl ls /coreos.com/network/subnets); do etcdctl get $subnet;done {"PublicIP":"192.168.1.68","BackendType":"vxlan","BackendData":{"VtepMAC":"0e:08:23:57:14:9a"}} {"PublicIP":"192.168.1.254","BackendType":"vxlan","BackendData":{"VtepMAC":"da:17:1b:07:d3:70"}} {"PublicIP":"192.168.1.245","BackendType":"vxlan","BackendData":{"VtepMAC":"4e:55:ee:0a:90:38"}}
所以Flannel的Overlay網絡實現原理簡化如圖:
可見除了增長或者減小Node,須要Flannel配合配置靜態路由以及fdb表,容器的建立與刪除徹底不須要Flannel干預,事實上Flannel也不須要知道有沒有新的容器建立或者刪除。
前面介紹Flannel經過Vxlan實現跨主機通訊,其實Flannel支持不一樣的backend,其中指定backend type爲host-gw支持經過靜態路由的方式實現容器跨主機通訊,這時每一個Node都至關於一個路由器,做爲容器的網關,負責容器的路由轉發。
須要注意的是,若是使用AWS EC2,使用Flannel host-gw網絡須要禁用MAC地址欺騙功能,如圖:
使用OpenStack則最好禁用Neutron的port security功能。
一樣地,咱們在三個節點分別建立busybox容器,結果以下:
Node名 | 主機IP | 容器IP |
---|---|---|
node-1 | 192.168.1.68 | 40.15.43.2/24 |
node-2 | 192.168.1.254 | 40.15.26.2/24 |
node-3 | 192.168.1.245 | 40.15.56.2/24 |
咱們查看192.168.1.68的本地路由:
# ip r default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.68 metric 100 40.15.26.0/24 via 192.168.1.254 dev eth0 40.15.43.0/24 dev docker0 proto kernel scope link src 40.15.43.1 40.15.56.0/24 via 192.168.1.245 dev eth0 ...
咱們只關心40.15前綴的路由,發現40.15.26.0/24的下一跳爲192.168.1.254,正好爲node2 IP,而40.15.43.0/24的下一跳爲本地docker0,由於該子網就是node所在的子網,40.15.56.0/24的下一跳爲192.168.1.245,正好是node3 IP。可見,Flannel經過配置靜態路由的方式實現容器跨主機通訊,每一個Node都做爲路由器使用。
host-gw的方式相對Overlay因爲沒有vxlan的封包拆包過程,直接路由就過去了,所以性能相對要好。不過正是因爲它是經過路由的方式實現,每一個Node至關因而容器的網關,所以每一個Node必須在同一個LAN子網內,不然跨子網因爲鏈路層不通致使沒法實現路由致使host-gw實現不了。
前面介紹的host-gw是經過修改主機路由表實現容器跨主機通訊,若是能修改主機網關的路由固然也是沒有問題的,尤爲是和SDN結合方式動態修改路由。
目前不少雲平臺均實現了自定義路由表的功能,好比OpenStack、AWS等,Flannel藉助這些功能實現了不少公有云的VPC後端,經過直接調用雲平臺API修改路由表實現容器跨主機通訊,好比阿里雲、AWS、Google雲等,不過很惋惜官方目前好像尚未實現OpenStack Neutron後端。
下面以AWS爲例,建立了以下4臺EC2虛擬機:
node-1: 197.168.1.68/24
node-2: 197.168.1.254/24
node-3: 197.168.1.245/24
node-4: 197.168.0.33/24
注意第三臺和其他兩臺不在同一個子網。
三臺EC2均關聯了flannel-role,flannel-role關聯了flannel-policy,policy的權限以下:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:CreateRoute", "ec2:DeleteRoute", "ec2:ModifyInstanceAttribute", "ec2:DescribeRouteTables", "ec2:ReplaceRoute" ], "Resource": "*" } ] }
即EC2實例須要具備修改路由表等相關權限。
以前一直很疑惑AWS的role如何與EC2虛擬機關聯起來的。換句話說,如何實現虛擬機無需配置Key和Secretd等認證信息就能夠直接調用AWS API,經過awscli的 --debug
信息可知awscli首先經過metadata獲取role信息,再獲取role的Key和Secret:
關於AWS如何知道調用metadata的是哪一個EC2實例,可參考以前的文章OpenStack虛擬機如何獲取metadata.
另外全部EC2實例均禁用了MAC地址欺騙功能(Change Source/Dest Check),安全組容許flannel網段40.15.0.0/16經過,另外增長了以下iptables規則:
iptables -I FORWARD --dest 40.15.0.0/16 -j ACCEPT iptables -I FORWARD --src 40.15.0.0/16 -j ACCEPT
flannel配置以下:
# etcdctl get /coreos.com/network/config | jq . { "Network": "40.15.0.0/16", "Backend": { "Type": "aws-vpc" } }
啓動flannel,自動爲每一個Node分配24位子網,網段以下:
Node名 | 主機IP | 容器IP |
---|---|---|
node-1 | 192.168.1.68 | 40.15.16.0/24 |
node-2 | 192.168.1.254 | 40.15.64.0/24 |
node-3 | 192.168.1.245 | 40.15.13.0/24 |
node-4 | 192.168.0.33 | 40.15.83.0/24 |
咱們查看node-一、node-二、node-3關聯的路由表如圖:
node-4關聯的路由表如圖:
因而可知,每增長一個Flannel節點,Flannel就會調用AWS API在EC2實例的子網關聯的路由表上增長一條記錄,Destination爲該節點分配的Flannel子網,Target爲該EC2實例的主網卡。
在4個節點分別建立一個busybox容器,容器IP以下:
Node名 | 主機IP | 容器IP |
---|---|---|
node-1 | 192.168.1.68 | 40.15.16.2/24 |
node-2 | 192.168.1.254 | 40.15.64.2/24 |
node-3 | 192.168.1.245 | 40.15.13.2/24 |
node-4 | 192.168.0.33 | 40.15.83.2/24 |
全部節點ping node-4的容器,如圖:
咱們發現全部節點都能ping通node-4的容器。可是node-4的容器卻ping不通其他容器:
這是由於每一個Node默認只會添加本身所在路由的記錄。node-4沒有node-1 ~ node-3的路由信息,所以不通。
可能有人會問,node1 ~ node3也沒有node4的路由,那爲何能ping通node4的容器呢?這是由於個人環境中node1 ~ node3子網關聯的路由是NAT網關,node4是Internet網關,而NAT網關的子網正好是node1 ~ node4關聯的子網,所以node1 ~ node3雖然在本身所在的NAT網關路由沒有找到node4的路由信息,可是下一跳到達Internet網關的路由表中找到了node4的路由,所以可以ping通,而node4找不到node1 ~ node3的路由,所以都ping不通。
以上只是默認行爲,Flannel能夠經過 RouteTableID
參數配置Node須要更新的路由表,咱們只須要增長以下兩個子網的路由以下:
# etcdctl get /coreos.com/network/config | jq . { "Network": "40.15.0.0/16", "Backend": { "Type": "aws-vpc", "RouteTableID": [ "rtb-0686cdc9012674692", "rtb-054dfd5f3e47102ae" ] } }
重啓Flannel服務,再次查看兩個路由表:
咱們發現兩個路由表均添加了node1 ~ node4的Flannel子網路由。
此時四個節點的容器可以相互ping通。
從中咱們發現,aws-vpc解決了host-gw不能跨子網的問題,Flannel官方也建議若是使用AWS,推薦使用aws-vpc替代overlay方式,可以獲取更好的性能:
When running within an Amazon VPC, we recommend using the aws-vpc backend which, instead of using encapsulation, manipulates IP routes to achieve maximum performance. Because of this, a separate flannel interface is not created.
The biggest advantage of using flannel AWS-VPC backend is that the AWS knows about that IP. That makes it possible to set up ELB to route directly to that container.
另外,因爲路由是添加到了主機網關上,所以只要關聯了該路由表,EC2實例是能夠從外面直接ping通容器的,換句話說,同一子網的EC2虛擬機能夠直接與容器互通。
不過須要注意的是,AWS路由表默認最多支持50條路由規則,這限制了Flannel節點數量,不知道AWS是否支持增長配額功能。另外目前最新版的Flannel v0.10.0好像對aws-vpc支持有點問題,再官方修復如上問題以前建議使用Flannel v0.8.0版本。
Calico和Flannel host-gw相似都是經過路由實現跨主機通訊,區別在於Flannel經過flanneld進程逐一添加主機靜態路由實現,而Calico則是經過BGP實現節點間路由規則的相互學習廣播。
這裏不詳細介紹BGP的實現原理,僅研究容器是如何通訊的。
建立了3個節點的calico集羣,ip pool配置以下:
# calicoctl get ipPool -o yaml - apiVersion: v1 kind: ipPool metadata: cidr: 197.19.0.0/16 spec: ipip: enabled: true mode: cross-subnet nat-outgoing: true - apiVersion: v1 kind: ipPool metadata: cidr: fd80:24e2:f998:72d6::/64 spec: {}
Calico分配的ip以下:
for host in $(etcdctl --endpoints $ENDPOINTS ls /calico/ipam/v2/host/); do etcdctl --endpoints $ENDPOINTS ls $host/ipv4/block | awk -F '/' '{sub(/-/,"/",$NF)}{print $6,$NF}' done | sort int32bit-docker-1 197.19.38.128/26 int32bit-docker-2 197.19.186.192/26 int32bit-docker-3 197.19.26.0/26
由此可知,Calico和Flannel同樣,每一個節點分配一個子網,只不過Flannel默認分24位子網,而Calico分的是26位子網。
三個節點分別建立busybox容器:
Node名 | 主機IP | 容器IP |
---|---|---|
node-1 | 192.168.1.68 | 197.19.38.136 |
node-2 | 192.168.1.254 | 197.19.186.197 |
node-3 | 192.168.1.245 | 197.19.26.5/24 |
相互ping通沒有問題。
咱們查看容器的link設備以及路由:
# ./docker_netns.sh busybox ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 14: cali0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 197.19.38.136/32 brd 197.19.38.136 scope global cali0 valid_lft forever preferred_lft forever # ./docker_netns.sh busybox ip r default via 169.254.1.1 dev cali0 169.254.1.1 dev cali0 scope link
有以下幾點我的感受很神奇:
全部容器的MAC地址都是 ee:ee:ee:ee:ee:ee
,
網關地址是169.254.1.1,然而我找盡了全部的namespaces也沒有找到這個IP。
這兩個問題在Calico官方的faq中有記錄#1 Why do all cali* interfaces have the MAC address ee:ee:ee:ee:ee:ee?、#2 Why can’t I see the 169.254.1.1 address mentioned above on my host?。
針對第一個問題,官方認爲不是全部的內核都能支持自動分配MAC地址,因此乾脆Calico本身指定MAC地址,而Calico徹底使用三層路由通訊,MAC地址是什麼其實無所謂,所以直接都使用 ee:ee:ee:ee:ee:ee
。
第二個問題,回顧以前的網絡模型,大多數都是把容器的網卡經過VETH鏈接到一個bridge設備上,而這個bridge設備每每也是容器網關,至關於主機上多了一個虛擬網卡配置。Calico認爲容器網絡不該該影響主機網絡,所以容器的網卡的VETH另外一端沒有通過bridge直接掛在默認的namespace中。而容器配的網關其實也是假的,經過proxy_arp修改MAC地址模擬了網關的行爲,因此網關IP是什麼也無所謂,那就直接選擇了local link的一個ip,這還節省了容器網絡的一個IP。咱們能夠抓包看到ARP包:
能夠看到容器網卡的對端 calia2656637189
直接代理回覆了ARP,所以出去網關時容器的包會直接把MAC地址修改成 06:66:26:8e:b2:67
,即僞網關的MAC地址。
有人可能會說那若是在同一主機的容器通訊呢?他們應該在同一個子網,容器的MAC地址都是同樣那怎麼進行二層通訊呢?仔細看容器配置的IP掩碼竟然是32位的,那也就是說跟誰都不在一個子網了,也就不存在二層的鏈路層直接通訊了。
前面提到Calico經過BGP動態路由實現跨主機通訊,咱們查看主機路由以下,其中197.19.38.13九、197.19.38.140是在本機上的兩個容器IP:
# ip r | grep 197.19 197.19.26.0/26 via 192.168.1.245 dev eth0 proto bird blackhole 197.19.38.128/26 proto bird 197.19.38.139 dev calia2656637189 scope link 197.19.38.140 dev calie889861df72 scope link 197.19.186.192/26 via 192.168.1.254 dev eth0 proto bird
咱們發現跨主機通訊和Flannel host-gw徹底同樣,下一跳直接指向hostIP,把host看成容器的網關。不同的是到達宿主機後,Flannel會經過路由轉發流量到bridge設備中,再由bridge轉發給容器,而Calico則爲每一個容器的IP生成一條明細路由,直接指向容器的網卡對端。所以若是容器數量不少的話,主機路由規則數量也會愈來愈多,所以纔有了路由反射,這裏不過多介紹。
裏面還有一條blackhole路由,若是來的IP是在host分配的容器子網197.19.38.128/26中,而又不是容器的IP,則認爲是非法地址,直接丟棄。
在同一個集羣上能夠同時建立多個Calico網絡:
# docker network ls | grep calico ad7ca8babf01 calico-net-1 calico global 5eaf3984f69d calico-net-2 calico global
咱們使用另外一個Calico網絡calico-net-2建立一個容器:
docker run -d --name busybox-3 --net calico-net-2 busybox sleep 36000 # docker exec busybox-3 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 24: cali0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff inet 197.19.38.141/32 brd 197.19.38.141 scope global cali0 valid_lft forever preferred_lft forever # ip r | grep 197.19 197.19.26.0/26 via 192.168.1.245 dev eth0 proto bird blackhole 197.19.38.128/26 proto bird 197.19.38.139 dev calia2656637189 scope link 197.19.38.140 dev calie889861df72 scope link 197.19.38.141 dev calib12b038e611 scope link 197.19.186.192/26 via 192.168.1.254 dev eth0 proto bird
咱們發如今同一個主機不在同一個網絡的容器IP地址在同一個子網,那不是能夠通訊呢?
咱們發現雖然兩個跨網絡的容器分配的IP在同一個子網,但竟然實現了隔離。
若是使用諸如vxlan的overlay網絡,很好猜想是怎麼實現隔離的,無非就是使用不一樣的VNI。但Calico沒有使用overlay,直接使用路由通訊,並且不一樣網絡的子網仍是重疊的,它是怎麼實現隔離的呢。
要在同一個子網實現隔離,咱們猜想實現方式只能是邏輯隔離,即經過本地防火牆如iptables實現。
查看了下Calico生成的iptables規則發現太複雜了,各類包mark。因爲決定包的放行或者丟棄一般是在filter表實現,而不是發往主機的本身的包應該在FORWARD鏈中,所以咱們直接研究filter表的FORWARD表。
# iptables-save -t filter | grep -- '-A FORWARD' -A FORWARD -m comment --comment "cali:wUHhoiAYhphO9Mso" -j cali-FORWARD ...
Calico把cali-FORWARD子鏈掛在了FORWARD鏈上,comment中的一串看起來像隨機字符串 cali:wUHhoiAYhphO9Mso
不知道是幹嗎的。
# iptables-save -t filter | grep -- '-A cali-FORWARD' -A cali-FORWARD -i cali+ -m comment --comment "cali:X3vB2lGcBrfkYquC" -j cali-from-wl-dispatch -A cali-FORWARD -o cali+ -m comment --comment "cali:UtJ9FnhBnFbyQMvU" -j cali-to-wl-dispatch -A cali-FORWARD -i cali+ -m comment --comment "cali:Tt19HcSdA5YIGSsw" -j ACCEPT -A cali-FORWARD -o cali+ -m comment --comment "cali:9LzfFCvnpC5_MYXm" -j ACCEPT ...
cali+
表示全部以cali爲前綴的網絡接口,即容器的網卡對端設備。因爲咱們只關心發往容器的流量方向,即從caliXXX發往容器的流量,所以咱們只關心條件匹配的 -o cali+
的規則,從如上能夠看出全部從 cali+
出來的流量都跳轉到了 cali-to-wl-dispatch
子鏈處理,其中 wl
是workload的縮寫,workload即容器。
# iptables-save -t filter | grep -- '-A cali-to-wl-dispatch' -A cali-to-wl-dispatch -o calia2656637189 -m comment --comment "cali:TFwr8sfMnFH3BUla" -g cali-tw-calia2656637189 -A cali-to-wl-dispatch -o calib12b038e611 -m comment --comment "cali:ZbRb0ozg-GGeUfRA" -g cali-tw-calib12b038e611 -A cali-to-wl-dispatch -o calie889861df72 -m comment --comment "cali:5OoGv50NzX0sKdMg" -g cali-tw-calie889861df72 -A cali-to-wl-dispatch -m comment --comment "cali:RvicCiwAy9cIEAKA" -m comment --comment "Unknown interface" -j DROP
從子鏈名字也能夠看出 cali-to-wl-dispatch
是負責流量的分發的,即根據具體的流量出口引到具體的處理流程子鏈,從X出來的,由cali-tw-X處理,從Y出來的,由cali-tw-Y處理,依次類推,其中 tw
爲 to workload
的簡寫。
咱們假設是發往busybox 197.19.38.139這個容器的,對應的主機虛擬設備爲 calia2656637189
,則跳轉子鏈爲 cali-tw-calia2656637189
:
# iptables-save -t filter | grep -- '-A cali-tw-calia2656637189' -A cali-tw-calia2656637189 -m comment --comment "cali:259EHpBvnovN8_q6" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A cali-tw-calia2656637189 -m comment --comment "cali:YLokMEiVkZggfg9R" -m conntrack --ctstate INVALID -j DROP -A cali-tw-calia2656637189 -m comment --comment "cali:pp8a6fGxqaALtRK5" -j MARK --set-xmark 0x0/0x1000000 -A cali-tw-calia2656637189 -m comment --comment "cali:bgw2sCtlIfZjhXLA" -j cali-pri-calico-net-1 -A cali-tw-calia2656637189 -m comment --comment "cali:1Z2NvhoS27pP03Ll" -m comment --comment "Return if profile accepted" -m mark --mark 0x1000000/0x1000000 -j RETURN -A cali-tw-calia2656637189 -m comment --comment "cali:mPb8hORsTXeVt7yC" -m comment --comment "Drop if no profiles matched" -j DROP
其中第一、2條規則在深刻淺出OpenStack安全組實現原理中介紹過,再也不贅述。
第三條規則注意使用的是 set-xmark
而不是 set-mark
,爲何不用 set-mark
,這是因爲 set-mark
會覆蓋原來的值。而 set-xmark value/netmask
,表示 X=(X&(~netmask))^value
, --set-xmark0x0/0x1000000
的意思就是把X的第25位重置爲0,其餘位保留不變。
這個mark位的含義我在官方中沒有找到,在Calico網絡的原理、組網方式與使用這篇文章找到了相關資料:
node一共使用了3個標記位,0x7000000對應的標記位
0x1000000: 報文的處理動做,置1表示放行,默認0表示拒絕
0x2000000: 是否已經通過了policy規則檢測,置1表示已通過
0x4000000: 報文來源,置1,表示來自host-endpoint
即第25位表示報文的處理動做,爲1表示經過,0表示拒絕,第五、6條規則也能夠看出第25位的意義,匹配0x1000000/0x1000000直接RETRUN,不匹配的直接DROP。
所以第3條規則的意思就是清空第25位標誌位從新評估,誰來評估呢?這就是第4條規則的做用,根據虛擬網絡設備cali-XXX所處的網絡跳轉到指定網絡的子鏈中處理,因爲 calia2656637189
屬於calico-net-1,所以會跳轉到 cali-pri-calico-net-1
子鏈處理。
咱們觀察 cali-pri-calico-net-1
的規則:
# iptables-save -t filter | grep -- '-A cali-pri-calico-net-1' -A cali-pri-calico-net-1 -m comment --comment "cali:Gvse2HBGxQ9omCdo" -m set --match-set cali4-s:VFoIKKR-LOG_UuTlYqcKubo src -j MARK --set-xmark 0x1000000/0x1000000 -A cali-pri-calico-net-1 -m comment --comment "cali:0vZpvvDd_5bT7g_k" -m mark --mark 0x1000000/0x1000000 -j RETURN
規則很簡單,只要IP在cali4-s:VFoIKKR-LOG_UuTlYqcKubo在這個ipset集合中就設置mark第25位爲1,而後RETURN,不然若是IP不在ipset中則直接DROP(子鏈的默認行爲爲DROP)。
# ipset list cali4-s:VFoIKKR-LOG_UuTlYqcKubo Name: cali4-s:VFoIKKR-LOG_UuTlYqcKubo Type: hash:ip Revision: 4 Header: family inet hashsize 1024 maxelem 1048576 Size in memory: 280 References: 1 Number of entries: 4 Members: 197.19.38.143 197.19.26.7 197.19.186.199 197.19.38.144
到這裏終於真相大白了,Calico是經過iptables + ipset實現多網絡隔離的,同一個網絡的IP會加到同一個ipset集合中,不一樣網絡的IP放到不一樣的ipset集合中,最後經過iptables的set模塊匹配ipset集合的IP,若是src IP在指定的ipset中則容許經過,不然DROP。
咱們知道Flannel host-gw不支持Node主機跨網段,Calico是否支持呢,爲此我增長了一個node-4(192.168.0.33/24),顯然和其餘三個Node不在同一個子網。
在新的Node中啓動一個busybox:
docker run -d --name busybox-node-4 --net calico-net-1 busybox sleep 36000 docker exec busybox-node-4 ping -c 1 -w 1 197.19.38.144 PING 197.19.38.144 (197.19.38.144): 56 data bytes 64 bytes from 197.19.38.144: seq=0 ttl=62 time=0.539 ms --- 197.19.38.144 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.539/0.539/0.539 ms
驗證發現容器通訊時沒有問題的。
查看node-1路由:
# ip r | grep 197.19 197.19.26.0/26 via 192.168.1.245 dev eth0 proto bird blackhole 197.19.38.128/26 proto bird 197.19.38.142 dev cali459cc263d36 scope link 197.19.38.143 dev cali6d0015b0c71 scope link 197.19.38.144 dev calic8e5fab61b1 scope link 197.19.65.128/26 via 192.168.0.33 dev tunl0 proto bird onlink 197.19.186.192/26 via 192.168.1.254 dev eth0 proto bird
和其餘路由不同的是,咱們發現197.19.65.128/26是經過tunl0出去的:
# ip -d link show tunl0 5: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0 ipip any remote any local any ttl inherit nopmtudisc addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 # ip -d tunnel show tunl0: any/ip remote any local any ttl inherit nopmtudisc
由此可知,若是節點跨網段,則Calico經過ipip隧道傳輸,至關於走的是overlay。
對比Flannel host-gw,除了靜態與BGP動態路由配置的區別,Calico還經過iptables + ipset解決了多網絡支持問題,經過ipip隧道實現了節點跨子網通訊問題。
另外,某些業務或者POD須要固定IP,好比POD從一個節點遷移到另外一個節點保持IP不變,這種狀況下可能致使容器的IP不在節點Node上分配的子網範圍內,Calico能夠經過添加一條32位的明細路由實現,Flannel不支持這種狀況。
所以相對來講Calico實現的功能相對要多些,可是,最終也致使Calico相對Flannel要複雜得多,運維難度也較大,光一堆iptables規則就不容易理清了。
Kuryr是OpenStack中一個較新的項目,其目標是「Bridge between container framework networking and storage models to OpenStack networking and storage abstractions.」,即實現容器與OpenStack的網絡集成,該方案實現了與虛擬機、裸機相同的網絡功能和互通,好比多租戶、安全組等。
網絡模型和虛擬機基本同樣,惟一區別在於虛擬機是經過TAP設備直接掛到虛擬機設備中的,而容器則是經過VETH鏈接到容器的namespace
vm Container whatever | | | tapX tapY tapZ | | | | | | qbrX qbrY qbrZ | | | --------------------------------------------- | br-int(OVS) | --------------------------------------------- | ---------------------------------------------- | br-tun(OVS) | ----------------------------------------------