docker 實踐十一:docker 跨主機通訊


在上一篇了解了關於 docker 的網絡模型后,本篇就基於上一篇的基礎來實現 docker 的跨主機通信。

注:環境為 CentOS7,docker 19.03。

本篇會嘗試使用幾種不同的方式來實現跨主機方式
環境准備
准備兩台或以上的主機或者虛擬機,相關環境如下:

  • 主機1:配置兩張網卡 br0 192.168.10.10,ens33橋接br0,ens37(不需要IP),docker環境
  • 主機2:配置兩張網卡 br0 192.168.10.11,ens33橋接br0,ens37(不需要IP),docker環境

橋接方式

網絡拓撲圖如圖

ens33 作為外部網卡,使 docker 容器可以和外部通信,ens37 作為內部網卡,和 docker0 橋接(所以不需要IP)讓不同的主機間的容器可以相互通信。配置如下:

主機1上的配置

1.修改 docker 的啟動參數文件 /etc/docker/daemon.json 並重啟 docker daemon

# 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.添加容器con1

# docker run -it --rm --name con1 buysbox sh

主機2上的配置

1.修改 docker 的啟動參數文件 /etc/docker/daemon.json 並重啟 docker daemon

# vim /etc/docker/daemon.json 
{
  "bip": "172.17.0.2/16",
  "fixed-cidr": "172.17.19.1/24"
}
# systemctl restart docker

2.將 ehs33 網卡接入到 docker0 網橋中

# 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容器中,反之亦然。

注:這個實現失敗!!!

直接路由的網絡拓撲圖如下:

host1上

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

host2上

# 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

以上介紹的兩種跨主機通信方式簡單有效,但都要求主機都在同一個局域網中。

OVS 划分 VLAN

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端口。圖中,Port1、Port2、Port5、Port6、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(Port3、Port4允許VLAN ID為100、200的幀通過)。在switch2中,Port5所標記的VID幀相同,MAC地址也匹配,幀就發送到Port5中,Port5將幀的tag信息去掉,然后發給PC3。由於PC2、PC4於PC1的VLAN不同,因此收不到PC1發出的幀。

單主機docker容器的VLAN划分

docker默認網絡模式下,所有容器都連在docker0網橋上。docker0網橋是普通的Linux網橋,不支持VLAN功能,這里就使用Open vSwitch代替。

接下來我們就來嘗試在主機1上實現圖中的效果:

1.在主機上創建4個 docker 容器 con1、con2、con3、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將con1、con2划分到一個VLAN中

# pipework ovs0 con1 192.168.0.1/24 @100
# pipework ovs0 con2 192.168.0.2/24 @100

4.使用pipework將con3、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

多主機docker容器的VLAN划分

# 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 

OVS隧道模式

使用VLAN來隔離docker容器網絡有多方面的限制:

  • VLAN是在二層幀上,要求主機在同個網絡中
  • VLAN ID只有12比特單位,可用的數量只有4000多個
  • VLAN配置較繁瑣。

因此現在比較普遍的解決方式為Overlay虛擬化網絡技術。

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)。

GRE簡介

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包來說,兩個路由器之間的傳輸過程就如同單鏈路上的一樣。

GRE實現 docker 容器跨網絡通信(容器在同一子網中)

host1上

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

host2上

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.91、目的地址為10.10.105.235的IP協議頭部,再發送給host2。這里GRE協議封裝的是二層以太網幀,而非三層IP數據包。con1獲取到con2的MAC地址之后,就可以向它發送數據,發送數據包的流程和發送ARP請求的流程類似。只不過docekr0和ovs0會學習到con2的MAC地址該從哪個端口發送出去,而無需洪泛到所有端口。

GRE實現docker容器跨網絡通信(容器在不同子網中)

host1上

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 

host2上

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 

overlay配合consul實現跨主機通信

環境准備
准備兩台或以上的主機或者虛擬機,相關環境如下:

  • 主機1:配置兩張網卡 ens33 192.168.10.10,docker環境
  • 主機2:配置兩張網卡 ens33 192.168.10.11,docker環境
  • 主機3:consul 環境 192.168.10.12:8500

單通道實現容器互通

在兩主機上配置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網絡實現,不同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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM