什么是bridge(网桥)?
bridge是一种技术,能够把一个linux设备上的两块网卡桥接在一块儿,如何对外表现为一个大的网卡接口,这样作有不少用途html
好比你有两台设备,可是又没有路由器,那么把他们桥接在一块儿,能够共享其中一台的网络,这样两台均可以上网,这两台设备也能够是vm,不必定是物理设备linux
还有一种用途,就是监控两个设备的网络流量,好比用wireshark来监控他们间的流量nginx
总的来说,桥接就是将一台计算机插入到已经与较大网络(例如Internet)创建链接的另外一台计算机上,而后让这台桥接上去的计算机可使用联网计算机的链接。git
哪些软件使用了桥接技术
bridge在vm(virtual machine)的领域很是有用,好比docker, k8s, multipass等。github
docker能够帮忙建立不少独立的虚拟容器,那这些容器怎么上网,相互之间怎么通讯呢,docker提供多种途径,其中一种就是bridge。docker
查看设备上有哪些bridge,能够用brctl命令,若是你的没有这个命令,可用apt或者yum安装** sudo apt install bridge-utils**vim
查看bridge
brctl show数组
bridge name bridge id STP enabled interfaces docker0 8000.0242a8576763 no
上面这个是docker的bridge(若是你设备上装有docker的话 就能够看到)安全
k8s的bridge,k8s也能够经过bridge来通讯服务器
bridge name bridge id STP enabled interfaces cbr0 8000.125a96a00d6c no veth461c2da3 veth4cd7f98e veth62b53b2e vethb0c3d926
k8s建立名为
cbr0
的网桥,并为每一个 pod 建立了一个 veth 对,每一个 pod 的主机端都链接到cbr0
。 这个 veth 对的 pod 端会被分配一个 IP 地址,该 IP 地址隶属于节点所被分配的 IP 地址范围内。节点的 IP 地址范围则经过配置或控制器管理器来设置。cbr0
被分配一个 MTU,该 MTU 匹配主机上已启用的正常接口的最小 MTU (来自k8s的官方说明)
multipass的bridge,它也是用bridge来通讯
bridge name bridge id STP enabled interfaces mpqemubr0 8000.065897b80a53 no mpqemubr0-dummy tap-166f18d0005 tap-1e3511f1cea tap-2cc8b34e9b0 tap-313bf980cb9 tap-416f9717aeb tap-4524e0d94ea tap-5abadd4a100 tap-62fe19d7992 tap-c1b397bb0d
建立一个bridge
brctl addbr br0
上面命令建立一个名为br0的桥
接下来咱们能够把已有的网卡接口绑定到这个桥上,在这以前能够看看咱们有有哪些网卡接口
能够用这个命令查看
ip addr show
假设上面查出来,有eth0和eth1两个网卡接口,下面咱们把他们用命令绑定到一块儿
brctl addif br0 eth0 eth1 # eth0和eth1的顺序不重要,不影响结果
这是咱们再来绑定的结果
brctl show
bridge name bridge id STP enabled interfaces br0 8000.001ec952d26b yes eth0 eth1
就是说eth0和eth1绑定到了br0这个桥上了
上面命令是临时建立这种关系,若是要永久保留下来,须要在/etc/network/interfaces这个配置文件里面修改
sudo vim /etc/network/interfaces
# The loopback network interface auto lo iface lo inet loopback # 上面这两行是系统默认有的, 意思是定义一个回环设备,而且自动启动 # Set up interfaces manually, avoiding conflicts with, e.g., network manager iface eth0 inet manual # 定义eth0接口 手动启动 iface eth1 inet manual # 定义eth1接口 手动启动 # Bridge setup iface br0 inet dhcp # 定义br0这个接口,而且从dhcp获取ip bridge_ports eth0 eth1 # 绑定上面定义那两个接口进来 # 若是但愿给这个bridge配置静态ip,能够用下面这个段配置代替上面的bridge配置 #iface br0 inet static # bridge_ports eth0 eth1 # address 192.168.1.2 # broadcast 192.168.1.255 # netmask 255.255.255.0 # gateway 192.168.1.1
上面个并设置的接口是手动启动,要经过下面的命令来启动
ifup br0
桥接无线网卡和以太网卡
有不少无线路由都会拒绝没有认证过的帧,那么咱们的以太网卡虽然能经过bridge借用无线网卡链接网络,可是因为它发的帧并无通过无线路由的认证(由于以太网卡发出去的frames里面MAC地址不是无线网卡向无线路由注册过的地址),因此会被拒绝掉。
这个时候要借助一种叫作etable的程序,ebtables本质上相似于iptables,不一样之处在于它在OSI模型的数据链路层的MAC子层而不是网络层上运行。这容许更改全部帧的源MAC地址。
要实现这个功能,分两步走
- 配置bridge-utils
2. 配置etable的规则
配置bridge-utils
也是在/etc/network/interfaces里面配置
pre-up iwconfig wlan0 essid $YOUR_ESSID # wifi的essid(通常和ssid相同) bridge_hw $MAC_ADDRESS_OF_YOUR_WIRELESS_CARD # 无线网卡地址
- 这个配置的意思是启动一个叫wlan0的无线网卡链接到essid上
2. 设置以太网网卡的MAC地址为无线网卡的MAC地址
以上这些配置命令的说明能够从bridge-tuils的文档里面找到
配置etable的规则
先安装etable
apt install ebtables
配置规则(etable的规则和iptables差很少)
# 这条规则是将全部发送到AP的帧的源MAC地址设置为网桥的MAC地址 ebtables -t nat -A POSTROUTING -o wlan0 -j snat --to-src $MAC_OF_BRIDGE --snat-arp --snat-target ACCEPT # 下面这两条配规则,要求你得知道网桥后面的每台计算机的MAC和IP ebtables -t nat -A PREROUTING -p IPv4 -i wlan0 --ip-dst $IP -j dnat --to-dst $MAC --dnat-target ACCEPT ebtables -t nat -A PREROUTING -p ARP -i wlan0 --arp-ip-dst $IP -j dnat --to-dst $MAC --dnat-target ACCEPT
每次手工输入比较麻烦,也能够经过脚原本弄,参考
docker的bridge
docker容器的bridge比咱们上面认识到的bridge要复杂
首先docker的提供给容器用的网络模式有好多种:
- none: 没有任何的网卡接口,除了容器自身的loobpack设备lo,一般这种容器用来链接定制的网络驱动
- host: 给独立的容器直接使用宿主机的网络,这种可能会有冲突,容器间没有隔离。
- overlay:覆盖网络,这种网络能够用在swarm集群和独立容器(如networking=host)或者多个docker守护进程上,把他们经过覆盖来相互通讯。
- macvlan: 这个涉及到vlan这种虚拟局域网的技术,经过macvlan能够直接给容器分配MAC网卡地址,对于物理网络来讲,它就是一个物理的网卡设备(其实是一个虚拟网卡),容器的守护进程经过这个网卡地址准确的把流量路由给这个容器,至关于容器直接链接到咱们的物理网络
- third-party network plugin: 第三方网络插件
- bridge:docker的bridge,这个是咱们要个讲的
看了网上不少关于docker bridge的介绍,上面这个示意图比较正确,(来自这里)
- 首先docker会在宿主机里面建立一个docker0的linux bridge
- 而后在建立容器的时候使用veth pair对的技术,会对应在宿主虚拟建立一个虚拟网卡,而后这个网卡和容器里面的网卡一一对应起来,docker的守护(deamon)进程会正确的把发给虚拟网卡的数据路由给对应的容器(一一对应),同时,全部的虚拟网卡会绑定到docker0 这个linux bridge里面(见上面关于linux bridge的介绍),这样的话容器之间就能够相互通讯了
那么宿主机又是怎么样把外部的请求正确的发给veth呢,这是由于在docker0 bridge前面还有一层NAT的技术,经过网络地址端口转换的技术,把发给特定端口的数据,转发给容器,既然如此,那么就必须绑定docker对外暴露什么端口,这就是咱们平时定义docker的时候的端口bind干的事
docker run -p 80:8080 nginx # 把容器的8080端口绑定到宿主机的80端口
如上面这个,把容器的8080端口绑定到宿主机的80端口,那么在进行NAT路由的时候,就能够准确找到容器对应的veth:xx,而后veth又对应到容器的eth0:xx,docker deamon就能够把数组准确的路由进容器里。
如何查看veth pair的对应关系
查找veth的关系能够经过ip命令来查
1.在容器里面执行:
> 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 55: eth0@if56: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
容器里面编号为55的接口eth0是@if56
2. 在宿主机里面也执行ip a
> ip a | grep veth 56: veth243b8f2@if55: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
宿主机里面56编号的这个接口是@if55
二者恰好是对应关系,这样看比较麻烦,能够用github上这个项目,一个命令列出对应关系
[root@dockervisor-1 ~]# dockerveth CONTAINER ID VETH NAMES 60d27ce962ff vethe353e93 hopeful_bhaskara d07a2979e69a vethe4c3cee silly_meitner 1e8656e195ba veth1ce04be thirsty_meitner
在宿主机里面查看DNAT路由规则
下面这条就是个人主机上创建的一个8081到容器80端口的DNAT规则,宿主机就是根据这条规则准确的把数据路由到172.17.0.2这台容器
> sudo iptables -S -t nat | grep 8081 -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80
这个时候,这台容器是插入到docker0这个桥的,docker0的ip是172.17.0.1
不推荐在生成环境使用docker0 bridge
官方不推荐在生成使用docker0这个bridge,取而代之,建议在建立容器的时候使用--network来自动本身建立的network bridge.
- 先在宿主机建立自定义的bridge
docker network create cluster1-net-bridge
2. 建立容器的时候指定bridge
docker run --name nginx -p 80:8080 -d nginx --network cluster1-net-bridge docker run --name mongo -d mongo:tag --network cluster1-net-bridge
为何官方会这样推荐呢,缘由是:
- docker0在全部的容器里面共享配置,包括MTU和防火墙规则等,对于不一样的集群咱们可能但愿可隔离开来
- docker0默认不能经过容器的名称来通讯,而本身建立的桥,如cluster1-net-bridge,能够能够容许经过名称来通讯,也就是本身建立的bridge具有dns解析功能,而docker0若是须要有这种功能须要用--link来在建立容器的时候指定,若是有复杂的容器关系,那会很是难以维护。
- 自定义桥能够热插拔,在生产状态下改变
--link 这个参数在官方文档中已经被标记为过时的参数,再也不建议使用
如上面的两个容器,若是不指定—network,而是用默认的docker0,那么
在nginx里面 ping mongo是不行的,而若是挂到cluster-net-bridge这个桥,ping mongo 是能够的
也就是我能够在nginx这个容器里面直接经过 "mongo" 这个名称来链接mongo服务,而不须要经过子网的ip来连.那么这种默认的约定在运维的时候会很是方便,再结合使用swam和docker-compose.yaml一块儿来启动,维护会更方便。
建立docker bridge network
接下来咱们试一下建立两个docker bridge network, 而后建立新的容器来加入到这两个network.
docker network create -d bridge --subnet 192.168.5.0/24 --gateway 192.168.5.1 test_bridge1 docker network create -d bridge --subnet 192.168.6.0/24 --gateway 192.168.6.1 test_bridge2
用途 **brctl **看看宿主机连的桥
bridge name bridge id STP enabled interfaces br-6edc31410314 8000.02423e98f193 no br-c9608a22917c 8000.0242206cc645 no
用docker network ls 看
> docker network ls brctlNETWORK ID NAME DRIVER SCOPE 6edc31410314 test_bridge1 bridge local c9608a22917c test_bridge2 bridge local
network Id对应的就是上面bridge name的id部分
用docker network inspect 一个看看, 其实br-6edc31410314就是Id前面部分
> docker network inspect test_bridge1 [ { "Name": "test_bridge1", "Id": "6edc31410314709178acb728cd448b17f32f2f6a7646299f0c67369329e81a64", "Created": "2020-03-08T20:33:25.480769489+08:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "192.168.5.0/24", "Gateway": "192.168.5.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} } ]
一个容器如何连到多个桥接网络
如今咱们建立4个新的容器分别加入到这两个network.
# box 1 docker run --name box1 -p 8082:80 -it --rm --network=test_bridge1 busybox sh # box 2 docker run --name box2 -p 8083:80 -it --rm --network=test_bridge1 busybox sh # box 3 docker run --name box3 -p 8084:80 -it --rm --network=test_bridge2 busybox sh # box 4 docker run --name box4 -p 8085:80 -it --rm --network=test_bridge2 busybox sh
container依次从network获取的dhcp获取到ip,依次为:
box 1: 192.168.5.2 box 2: 192.168.5.3 box 3: 192.168.6.2 box 4: 192.168.6.3
那么box 1和box 2是相互通的,box 3和box 4互通
在box 1 ping box 2的ip,能够通讯
/ # ping 192.168.5.3 PING 192.168.5.3 (192.168.5.3): 56 data bytes 64 bytes from 192.168.5.3: seq=0 ttl=64 time=0.211 ms
在box 1 ping 的容器的名称 box2,也能够通讯
/ # ping box2 PING box2 (192.168.5.3): 56 data bytes 64 bytes from 192.168.5.3: seq=0 ttl=64 time=0.177 ms 64 bytes from 192.168.5.3: seq=1 ttl=64 time=0.172 ms
在box1 ping box3的ip, 不能够通讯
/ # ping 192.168.6.2 PING 192.168.6.2 (192.168.6.2): 56 data bytes
若是box1 要和box3要通讯怎么办?把box 3加入到box 1的bridge
docker network connect test_bridge1 box3
再来看看box 3的网卡, box 3多了一个网卡,ip是192.168.5.4
/ # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:06:02 inet addr:192.168.6.2 Bcast:192.168.6.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:172 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:28211 (27.5 KiB) TX bytes:0 (0.0 B) eth1 Link encap:Ethernet HWaddr 02:42:C0:A8:05:04 inet addr:192.168.5.4 Bcast:192.168.5.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:43 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:6368 (6.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)
box3和box1经过test_bridge1也桥接起来了,能够经过brctl看看
➜ bin brctl show bridge name bridge id STP enabled interfaces br-6edc31410314 8000.02423e98f193 no veth4003153 veth8e06926 vethc2edaec br-c9608a22917c 8000.0242206cc645 no veth5ff4051 veth62200c6
能够看到,bridge1桥接了3个网卡,bridge2桥接了2个网卡
/ # ping box3 PING box3 (192.168.5.4): 56 data bytes 64 bytes from 192.168.5.4: seq=0 ttl=64 time=0.210 ms 64 bytes from 192.168.5.4: seq=1 ttl=64 time=0.206 ms 64 bytes from 192.168.5.4: seq=2 ttl=64 time=0.138 ms
这样 box1就能够ping 通 box3了
了解了以上这些原理以后,那么咱们能够设计一个这样的网络,把前先后台的容器网络隔离开来,保证安全性