在上一篇瞭解了關於 docker 的網絡模型後,本篇就基於上一篇的基礎來實現 docker 的跨主機通訊。docker
注:環境爲 CentOS7,docker 19.03。json
本篇會嘗試使用幾種不一樣的方式來實現跨主機方式 環境準備 準備兩臺或以上的主機或者虛擬機,相關環境以下:vim
網絡拓撲圖如圖 網絡
ens33 做爲外部網卡,使 docker 容器能夠和外部通訊,ens37 做爲內部網卡,和 docker0 橋接(因此不須要IP)讓不一樣的主機間的容器能夠相互通訊。配置以下:tcp
1.修改 docker 的啓動參數文件 /etc/docker/daemon.json 並重啓 docker daemonide
# vim /etc/docker/daemon.json { "bip": "172.17.0.1/16", "fixed-cidr": "172.17.18.1/24" } # systemctl restart docker
2.將 ehs33 網卡接入到 docker0 網橋中學習
# brctl addif docker0 ens37
3.添加容器con1spa
# docker run -it --rm --name con1 buysbox sh
1.修改 docker 的啓動參數文件 /etc/docker/daemon.json 並重啓 docker daemon3d
# vim /etc/docker/daemon.json { "bip": "172.17.0.2/16", "fixed-cidr": "172.17.19.1/24" } # systemctl restart docker
2.將 ehs33 網卡接入到 docker0 網橋中unix
# brctl addif docker0 ens37
3.添加容器con2
# docker run -it --rm --name con1 busybox sh
這時容器 con1 和 con2 既能彼此通訊,也能夠訪問外部IP。 容器con1向容器con2發送數據的過程是這樣的:首先,經過查看自己的路由表發現目的地址和本身處於同一網段,那麼就不須要將數據發往網關,能夠直接發給con2,con1經過ARP廣播獲取到con2的MAC地址;而後,構造以太網幀發往con2便可。此過程數據中docker0網橋充當普通的交換機轉發數據幀。
直接路由方式是經過在主機中添加靜態路由實現。例若有兩臺主機host1和host2,兩主機上的Docker容器是獨立的二層網絡,將con1發往con2的數據流先轉發到主機host2上,再由host2轉發到其上的Docker容器中,反之亦然。
注:這個實現失敗!!!
直接路由的網絡拓撲圖以下:
1.配置 docker0 ip
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "bip": "172.17.1.254/24" } # systemctl restart docker
2.添加路由,將目的地址爲172.17.2.0/24的包轉發到host2
# route add -net 172.17.2.0 netmask 255.255.255.0 gw 192.168.10.11
3.配置iptables規則
# iptables -t nat -F POSTROUTING # iptables -t nat -A POSTROUTING s 172.17.1.0/24 ! -d 172.17.0.0/16 -j MASQUERADE
4.打開端口轉發
# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
5.啓動容器con1
# docker run -it --name --rm --name con1 busybox sh
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "bip": "172.17.2.254/24" } # systemctl restart docker
2.添加路由,將目的地址爲172.17.1.0/24的包轉發到host1
# route add -net 172.17.1.0 netmask 255.255.255.0 gw 192.168.10.10
3.配置iptables規則
# iptables -t nat -F POSTROUTING # iptables -t nat -A POSTROUTING s 172.17.2.0/24 ! -d 172.17.0.0/16 -j MASQUERADE
4.打開端口轉發
# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
5.啓動容器con2
# docker run -it --name --rm --name con2 busybox sh
以上介紹的兩種跨主機通訊方式簡單有效,但都要求主機都在同一個局域網中。
VLAN(Virtual Local Area Network)即虛擬局域網,按照功能、部門等因素將網絡中的機器進行劃分,使之屬於不一樣的部分,每個部分造成一個虛擬的局域網絡,共享一個單獨的廣播域,從而避免因一個網絡內的主機過多而產生的廣播風暴問題。
VLAN如何在一個二層網絡中區分不一樣流量呢?IEEE 的802.1q協議規定了VLAN的實現方法,即在傳統的以太網幀中在添加一個VLAN tag字段,用於標識不一樣的VLAN。這樣,支持VLAN的交換機在轉發幀時,不只會關注MAC地址,還會考慮到VLAN tag字段。VLAN tag中包含TPID、PCP、CFI、VID,其中VID(VLAN ID)部分用來具體指出幀屬於哪一個VLAN。VID佔12爲,因此取值範圍爲0到4095。
交換機有兩種類型的端口access端口和trunk端口。圖中,Port一、Port二、Port五、Port六、Port8爲access端口,每一個access端口都會分配一個VLAN ID,標識它所鏈接的設備屬於哪個VLAN。當數據幀從外界經過access端口進入交換機時,數據幀本來是不帶tag的,access端口給數據幀打上tag(VLAN ID即爲access端口所分配的VLAN ID);當數據幀從交換機內部經過access端口發送時,數據幀的VLAN ID必須和access端口的VLAN ID一致,access端口才接收此幀,接着access端口將幀的tag信息去掉,再發送出去。Port3,Port4爲trunk端口,trunk端口不屬於某個特定的VLAN,而是交換機和交換機之間多個VLAN的通道。trunk端口聲明瞭一組VLAN ID,代表只容許帶有這些VLAN ID的數據幀經過,從trunk端口進入和出去的數據幀都是帶tag的(不考慮默認VLAN的狀況)。PC1和PC3屬於VLAN100,PC2和PC4屬於VLAN200,因此PC1和PC3處於同一個二層網絡中,PC2和PC4處於同一個二層網絡中。儘管PC1和PC2鏈接在同一臺交換機中,但它們之間的通訊時須要通過路由器的。
VLAN tag又是如何發揮做用的呢?當PC1向PC3發送數據時,PC1將IP包封裝在以太幀中,幀在目的MAC地址爲PC3的地址,此時幀並無tag信息。當幀到達Port1是,Port1給幀打上tag(VID=100),幀進入switch1,而後幀經過Port3,Port4到達Switch2(Port三、Port4容許VLAN ID爲100、200的幀經過)。在switch2中,Port5所標記的VID幀相同,MAC地址也匹配,幀就發送到Port5中,Port5將幀的tag信息去掉,而後發給PC3。因爲PC二、PC4於PC1的VLAN不一樣,所以收不到PC1發出的幀。
docker默認網絡模式下,全部容器都連在docker0網橋上。docker0網橋是普通的Linux網橋,不支持VLAN功能,這裏就使用Open vSwitch代替。
接下來咱們就來嘗試在主機1上實現圖中的效果:
1.在主機上建立4個 docker 容器 con一、con二、con三、con4。
# docker run -itd --name con1 busybox sh # docker run -itd --name con2 busybox sh # docker run -itd --name con3 busybox sh # docker run -itd --name con4 busybox sh
2.下載open vswitch,並啓動
# yum install -y openvswitch # systemctl start openvswitch
3.使用pipework將con一、con2劃分到一個VLAN中
# pipework ovs0 con1 192.168.0.1/24 @100 # pipework ovs0 con2 192.168.0.2/24 @100
4.使用pipework將con三、con4劃分到一個VLAN中
# pipework ovs0 con3 192.168.0.3/24 @200 # pipework ovs0 con4 192.168.0.4/24 @200
這樣一來con1和con2,con3和con4就也是互相通訊,但con1和con3是互相隔離的。 使用Open vSwitch配置VLAN,建立access端口和trunk端口的命令以下:
# ovs-vsctl add port ovs0 port1 tag=100 # ovs-vsctl add port ovs0 port2 trunk=100,200
# host1上 # docker run -itd --net=none --name con1 busybox sh # docker run -itd --net=none --name con2 busybox sh # pipework ovs0 con1 192.168.0.1/24 @100 # pipework ovs0 con2 192.168.0.2/24 @200 # ovs-vsctl add-port ovs0 eth1 # host2上 # docker run -itd --net=none --name con3 busybox sh # docker run -itd --net=none --name con4 busybox sh # pipework ovs0 con3 192.168.0.3/24 @100 # pipework ovs0 con4 192.168.0.4/24 @200 # ovs-vsctl add-port ovs0 eth1
使用VLAN來隔離docker容器網絡有多方面的限制:
所以如今比較廣泛的解決方式爲Overlay虛擬化網絡技術。
Overlay網絡也就是隧道技術,它將一種網絡協議包裝在另外一種協議中傳輸。隧道被普遍應用於鏈接因使用不一樣網絡而被隔離的主機和網絡,使用隧道技術搭建的網絡就是所謂的Overlay網絡,它能有效地覆蓋在基礎網絡上。在傳輸過程當中,將以太幀封裝在IP包中,經過中間地因特網,最後傳輸到目的網絡中再解封裝,這樣來保證二層幀頭再傳輸過程當中不改變。在多租戶狀況下,就採用不一樣租戶不一樣隧道地方式進行隔離。
目前Overlay技術有VXLAN(Virtual Extensible LAN)和NVGRE(Network Virtualization using Generic Routing Encapsulation)。VXLAN是將以太網報文封裝在UDP傳輸層上地一種隧道轉發模式,它採用24位比特標識二層網絡分段,稱爲VNI(VXLAN Network Idenfier),相似於VLAN ID地做用。NVGRE同VXLAN相似,它使用GRE方法來打通二層與三層之間的通路,採用24位比特的GRE key來做爲網絡標識(TNI)。
NVGRE使用GRE協議來封裝須要傳送的數據。GRE協議能夠用來封裝其餘網絡層的協議。
如圖的網絡如何通訊呢?經過在雙方路由器上配置GRE隧道實現。從IP地址爲192.168.1.1/24的主機A ping IP地址爲192.168.2.1/24的主機B中,主機A構造好IP包後,經過查看路由表發現目的地址和自己不在同一個子網中,要將其轉發到默認網關192.168.1.254上。主機A將IP包封裝在以太網幀中,源MAC地址爲自己網卡的MAC地址,目的MAC地址爲網關的MAC地址。
網關路由器收到數據幀後,去掉幀頭,將IP取出,匹配目的IP地址和自身的路由表,肯定包須要從GRE隧道設備發出,這就對這個IP包作GRE封裝,即加上GRE協議頭部。封裝完成後,該包是不能直接發往互聯網的,須要生成新的IP包做爲載體來運輸GRE數據包,新IP包的源地址爲1.1.1.1,目的地址爲2.2.2.2。並裝在新的廣域網二層幀中發出去。在傳輸過程當中,中間的節點僅能看到最外層的IP包。當IP包到達2.2.2.2的路由器後,路由器將外層IP頭部和GRE頭部去掉,獲得原始的IP數據包,再將其發往192.168.2.1。對於原始IP包來講,兩個路由器之間的傳輸過程就如同單鏈路上的同樣。
1.修改docker配置文件
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "bip": "172.17.42.1/16", "fixed-cidr": "172.17.1.1/24" } # systemctl restart docker
2.建立ovs0網橋,並將ovs0連在docker0上
# ovs-vsctl add-br ovs0 # brctl addif docker0 ovs0
3.在ovs0上建立GRE隧道
# ovs-vsctl add-port ovs0 gre0 -- set interface gre0 type=gre options:remote_ip=10.10.105.235
1.修改docker配置文件
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "bip": "172.17.42.1/16", "fixed-cidr": "172.17.1.1/24" } # systemctl restart docker
2.建立ovs0網橋,並將ovs0連在docker0上
# ovs-vsctl add-br ovs0 # brctl addif docker0 ovs0
3.在ovs0上建立GRE隧道
# ovs-vsctl add-port ovs0 gre0 -- set interface gre0 type=gre options:remote_ip=10.10.105.91
建立容器
# host1上 # docker run -it --rm --name con1 busybox sh # ping 172.17.2.0 # host2上 # docker run -it --rm --name con2 busybox sh # ping 172.17.1.0
con1向con2發送數據時,會發送ARP請求獲取con2的MAC地址。APR請求會被docker0網橋洪泛到全部端口,包括和ovs0網橋相連的ovs0端口。ARP請求達到ovs0網橋後,繼續洪泛,經過gre0隧道端口到達host2上的ovs0中,最後到達con2。host1和host2處在不一樣網絡中,該ARP請求如何跨越中間網絡到達host2呢?ARP請求通過gre0時,會首先加上一個GRE協議的頭部,而後再加上一個源地址爲10.10.105.9一、目的地址爲10.10.105.235的IP協議頭部,再發送給host2。這裏GRE協議封裝的是二層以太網幀,而非三層IP數據包。con1獲取到con2的MAC地址以後,就能夠向它發送數據,發送數據包的流程和發送ARP請求的流程相似。只不過docekr0和ovs0會學習到con2的MAC地址該從哪一個端口發送出去,而無需洪泛到全部端口。
1.修改docker配置文件
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "bip": "172.17.1.254/16", "fixed-cidr": "172.17.1.1/24" } # systemctl restart docker
2.建立ovs0網橋,並將ovs0連在docker0上
# ovs-vsctl add-br ovs0 # brctl addif docker0 ovs0
3.在ovs0上建立一個internal類型的端口rou0,並分配一個不引發衝突的私有IP
# ovs-vsctl add-port ovs0 rou0 -- set interface rou0 type=internal # ifconfig rou0 192.168.1.1/24 # 將通往docker容器的流量路由到rou0 # route add -net 172.17.0.0/16 dev rou0
4.在ovs0上建立GRE隧道
# ovs-vsctl add-port ovs0 gre0 -- set interface gre0 type=gre options:remote_ip=10.10.105.235
5.刪除並建立iptables規則
# iptables -t nat -D POSTROUTING -s 172.17.0.0/24 ! -o ens33 -j MASQUERADE # iptables -t nat -A POSTROUTING -s 172.17.0.0/24 ! -o ens33 -j MASQUERADE
1.修改docker配置文件
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "bip": "172.17.2.254/16", "fixed-cidr": "172.17.2.1/24" } # systemctl restart docker
2.建立ovs0網橋,並將ovs0連在docker0上
# ovs-vsctl add-br ovs0 # brctl addif docker0 ovs0
3.在ovs0上建立一個internal類型的端口rou0,並分配一個不引發衝突的私有IP
# ovs-vsctl add-port ovs0 rou0 -- set interface rou0 type=internal # ifconfig rou0 192.168.1.1/24 # 將通往docker容器的流量路由到rou0 # route add -net 172.17.0.0/16 dev rou0
4.在ovs0上建立GRE隧道
# ovs-vsctl add-port ovs0 gre0 -- set interface gre0 type=gre options:remote_ip=10.10.103.91
5.刪除並建立iptables規則
# iptables -t nat -D POSTROUTING -s 172.17.0.0/24 ! -o eth0 -j MASQUERADE # iptables -t nat -A POSTROUTING -s 172.17.0.0/24 ! -o eth0 -j MASQUERADE
環境準備 準備兩臺或以上的主機或者虛擬機,相關環境以下:
在兩主機上配置docker啓動的配置文件
# vim /etc/docker/daemon.json { "registry-mirrors": ["https://dhq9bx4f.mirror.aliyuncs.com"], "cluster-store": "consul://192.168.10.12:8500", "cluster-advertise": "ens33:2376" } # systemctl restart docker
一樣生效的是啓動腳本:/etc/systemd/system/docker.service.d/10-machine.conf
[Service] ExecStart= ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic --cluster-store=consul://192.168.10.12:8500 --cluster-advertise=ens33:2376 Environment=
這個時候,在192.168.10.12:8500的consul上就存儲了docker的註冊信息了
在主機1上建立網絡
# docker network create -d overlay ov-net1 # docker network ls NETWORK ID NAME DRIVER SCOPE ce13051e6bff bridge bridge local ff8cf4a7552a host host local 8514568883d2 none null local adef0a0c4d39 ov-net1 overlay global
這是咱們在主機2上也能看到新建的網絡ov-net1,由於ov-net1網絡已經存儲到consul上,兩個主機均可以看到該網絡
兩個主機上容器
# host1上 # docker run -itd --name con1 --network ov-net1 busybox # host2上 # docker run -itd --name con2 --network ov-net1 busybox
容器上分別生成兩張網卡eth0 10.0.0.2/24,eth1 172.18.0.1/24,eth0鏈接外部網絡,走overlay模式;eth1鏈接內部網絡,鏈接docker-gwbridge。
那overlay這麼實現網絡的隔離呢?其實也很簡單,就是利用多個overlay網絡實現,不一樣overlay網絡之間就存在隔離
# docker network create -d overlay --subnet 10.22.1.0/24 ov-net2
--subnet 參數用來限制分配子網的範圍。
# docker run -itd --name con3 --network ov-net2 busybox # docker exec -it con3 ping 172.18.0.2 PING 172.18.0.2 (172.18.0.2): 56 data bytes
建立容器con3並綁定網絡ov-net2,con3不能ping通con1。