如何管理linux设备上的bridge(网桥)和docker bridge
文章目录
什么是bridge(网桥)?
bridge是一种技术,可以把一个linux设备上的两块网卡桥接在一起,如何对外表现为一个大的网卡接口,这样做有很多用途
比如你有两台设备,但是又没有路由器,那么把他们桥接在一起,可以共享其中一台的网络,这样两台都可以上网,这两台设备也可以是vm,不一定是物理设备
还有一种用途,就是监控两个设备的网络流量,比如用wireshark来监控他们间的流量
总的来讲,桥接就是将一台计算机插入到已经与较大网络(例如Internet)建立连接的另一台计算机上,然后让这台桥接上去的计算机可以使用联网计算机的连接。
哪些软件使用了桥接技术
bridge在vm(virtual machine)的领域非常有用,比如docker, k8s, multipass等。
docker可以帮忙创建很多独立的虚拟容器,那这些容器怎么上网,相互之间怎么通信呢,docker提供多种途径,其中一种就是bridge。
查看设备上有哪些bridge,可以用brctl命令,如果你的没有这个命令,可用apt或者yum安装** sudo apt install bridge-utils**
查看bridge
brctl show
1 | bridge name bridge id STP enabled interfaces |
上面这个是docker的bridge(如果你设备上装有docker的话 就可以看到)
k8s的bridge,k8s也可以通过bridge来通信
1 | bridge name bridge id STP enabled interfaces |
k8s创建名为
cbr0
的网桥,并为每个 pod 创建了一个 veth 对,每个 pod 的主机端都连接到cbr0
。 这个 veth 对的 pod 端会被分配一个 IP 地址,该 IP 地址隶属于节点所被分配的 IP 地址范围内。节点的 IP 地址范围则通过配置或控制器管理器来设置。cbr0
被分配一个 MTU,该 MTU 匹配主机上已启用的正常接口的最小 MTU (来自k8s的官方说明)
multipass的bridge,它也是用bridge来通信
1 | bridge name bridge id STP enabled interfaces |
创建一个bridge
1 | brctl addbr br0 |
上面命令创建一个名为br0的桥
接下来我们可以把已有的网卡接口绑定到这个桥上,在这之前可以看看我们有有哪些网卡接口
可以用这个命令查看
1 | ip addr show |
假设上面查出来,有eth0和eth1两个网卡接口,下面我们把他们用命令绑定到一起
1 | brctl addif br0 eth0 eth1 # eth0和eth1的顺序不重要,不影响结果 |
这是我们再来绑定的结果
brctl show
1 | bridge name bridge id STP enabled interfaces |
就是说eth0和eth1绑定到了br0这个桥上了
上面命令是临时创建这种关系,如果要永久保留下来,需要在/etc/network/interfaces这个配置文件里面修改
sudo vim /etc/network/interfaces
1 | # The loopback network interface |
上面个并设置的接口是手动启动,要通过下面的命令来启动
1 | ifup br0 |
桥接无线网卡和以太网卡
有很多无线路由都会拒绝没有认证过的帧,那么我们的以太网卡虽然能通过bridge借用无线网卡连接网络,但是由于它发的帧并没有经过无线路由的认证(因为以太网卡发出去的frames里面MAC地址不是无线网卡向无线路由注册过的地址),所以会被拒绝掉。
这个时候要借助一种叫做etable的程序,ebtables本质上类似于iptables,不同之处在于它在OSI模型的数据链路层的MAC子层而不是网络层上运行。这允许更改所有帧的源MAC地址。
要实现这个功能,分两步走
- 配置bridge-utils
2. 配置etable的规则
配置bridge-utils
也是在/etc/network/interfaces里面配置
1 | pre-up iwconfig wlan0 essid $YOUR_ESSID # wifi的essid(一般和ssid相同) |
- 这个配置的意思是启动一个叫wlan0的无线网卡连接到essid上
2. 设置以太网网卡的MAC地址为无线网卡的MAC地址
以上这些配置命令的说明可以从bridge-tuils的文档里面找到
配置etable的规则
先安装etable
1 | apt install ebtables |
配置规则(etable的规则和iptables差不多)
1 | # 这条规则是将所有发送到AP的帧的源MAC地址设置为网桥的MAC地址 |
每次手工输入比较麻烦,也可以通过脚本来弄,参考
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干的事
1 | docker run -p 80:8080 nginx # 把容器的8080端口绑定到宿主机的80端口 |
如上面这个,把容器的8080端口绑定到宿主机的80端口,那么在进行NAT路由的时候,就可以准确找到容器对应的veth:xx,然后veth又对应到容器的eth0:xx,docker deamon就可以把数组准确的路由进容器里。
如何查看veth pair的对应关系
查找veth的关系可以通过ip命令来查
1.在容器里面执行:
1 | > ip a |
容器里面编号为55的接口eth0是@if56
2. 在宿主机里面也执行ip a
1 | > ip a | grep veth |
宿主机里面56编号的这个接口是@if55
两者刚好是对应关系,这样看比较麻烦,可以用github上这个项目,一个命令列出对应关系
1 | [root@dockervisor-1 ~]# dockerveth |
在宿主机里面查看DNAT路由规则
下面这条就是我的主机上建立的一个8081到容器80端口的DNAT规则,宿主机就是根据这条规则准确的把数据路由到172.17.0.2这台容器
1 | > sudo iptables -S -t nat | grep 8081 |
这个时候,这台容器是插入到docker0这个桥的,docker0的ip是172.17.0.1
不推荐在生成环境使用docker0 bridge
官方不推荐在生成使用docker0这个bridge,取而代之,建议在创建容器的时候使用–network来自动自己创建的network bridge.
- 先在宿主机创建自定义的bridge
1 | docker network create cluster1-net-bridge |
2. 创建容器的时候指定bridge
1 | docker run --name nginx -p 80:8080 -d nginx --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.
1 | docker network create -d bridge --subnet 192.168.5.0/24 --gateway 192.168.5.1 test_bridge1 |
用途 *brctl *看看宿主机连的桥
1 | bridge name bridge id STP enabled interfaces |
用docker network ls 看
1 | > docker network ls |
network Id对应的就是上面bridge name的id部分
用docker network inspect 一个看看, 其实br-6edc31410314就是Id前面部分
1 | > docker network inspect test_bridge1 |
一个容器如何连到多个桥接网络
现在我们创建4个新的容器分别加入到这两个network.
1 | # box 1 |
container依次从network获取的dhcp获取到ip,依次为:
1 | box 1: 192.168.5.2 |
那么box 1和box 2是相互通的,box 3和box 4互通
在box 1 ping box 2的ip,可以通信
1 | / # ping 192.168.5.3 |
在box 1 ping 的容器的名称 box2,也可以通信
1 | / # ping box2 |
在box1 ping box3的ip, 不可以通信
1 | / # ping 192.168.6.2 |
如果box1 要和box3要通信怎么办?把box 3加入到box 1的bridge
1 | docker network connect test_bridge1 box3 |
再来看看box 3的网卡, box 3多了一个网卡,ip是192.168.5.4
1 | / # ifconfig |
box3和box1通过test_bridge1也桥接起来了,可以通过brctl看看
1 | ➜ bin brctl show |
可以看到,bridge1桥接了3个网卡,bridge2桥接了2个网卡
1 | / # ping box3 |
这样 box1就可以ping 通 box3了
了解了以上这些原理之后,那么我们可以设计一个这样的网络,把前前后台的容器网络隔离开来,保证安全性