在上一篇了解了关于 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。