TL;DR docker 网络模型比较简单,在 k8s 中己经有了最新的解决方案,所以本文着重在 host-gw 实现。由于阿里云 vpc 不支持自定义路由,实验全部基于 virtual box
虚拟机实现。
docker 与 k8s 网络
docker 初始化时就会生成三个 net namespace
root@worker1:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
d2a9085edd71 bridge bridge local
37c243cd8d97 host host local
5536f50c051a none null local
支持很多种模式,默认是第一个网桥,docker run
时指定 --network=host|bridge|none
等等
- bridge 默认建立一个网桥设备,充当交换机的角色,一般都是所有容器的网关
- host 使用本机的网络 net ns,相当于没有任何隔离
- none 不使用网络,一般用于建立 POD 时 join pause 容器
- overlay 可以使跨宿主机的网络通信,这个是与 swarm 相关的,和后面 k8s 的还不太一样
- macvlan 使容器拥有 mac 地址,以便兼容传统虚拟机的使用模式
docker 自带的网络模型可以不用看,以后只看 k8s 的就可以了,毕竟在 k8s 一统江湖的情况下,底层的 docker 也可能换成其它的容器运行时。那么 k8s 对网络有什么要求呢?只有三点,具体怎么实现,要看 cni 网络插件
- 所有 pod 与其它 pod 可以通信,无需显示的 nat
- 所有 node 与其它 pod 可以通信,无需显示的 nat
- pod 所看见自己的 ip, 与其它看到的是一样的
当前最流行的有 flannel, weave, calico, 并且每家云厂商也有自己的网络实现。
1. 默认网桥
docker bridgedocker
支持很多网络模型,默认用的是 bridge 桥接,如上图所示。docker0
是一个链路二层的虚拟网桥,类似交换机的功能,ip 是 172.17.0.1
,默认是宿主机上所有容器的网关。
root@worker1:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:16:3e:00:75:2e brd ff:ff:ff:ff:ff:ff
inet 172.24.213.40/20 brd 172.24.223.255 scope global dynamic eth0
valid_lft 315312504sec preferred_lft 315312504sec
9: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:8f:8a:5b:5f brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/24 brd 172.17.0.255 scope global docker0
valid_lft forever preferred_lft forever
我们再看一下当前 iptables 的设置
root@worker1:~# iptables-save
# Generated by iptables-save v1.6.1 on Mon Dec 23 10:49:09 2019
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/24 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Mon Dec 23 10:49:09 2019
# Generated by iptables-save v1.6.1 on Mon Dec 23 10:49:09 2019
*filter
:INPUT ACCEPT [65:4290]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [37:19882]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Mon Dec 23 10:49:09 2019
首先最关键的就是 nat 上的一个规则
-A POSTROUTING -s 172.17.0.0/24 ! -o docker0 -j MASQUERADE
所有源地址是 172.17.0.0/24
,并且目的地址不是虚拟网桥的都要做伪装 (nat), 其实就是 snat.
再看 FORWARD
,默认全部 drop,但是允许 docker0 网桥的转发。
2. 端口转发
此时启动容器,看下有什么变化
root@worker1:~# docker run -it -p 8080:80 myubuntu /bin/bash
root@adc5c18a4942:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/24 brd 172.17.0.255 scope global eth0
valid_lft forever preferred_lft forever
启动了一个 myubuntu
容器,将 80 端口映射到宿主机的 8080 上,此时我们看下防火墙规则
root@worker1:~# iptables-save
# Generated by iptables-save v1.6.1 on Mon Dec 23 11:03:50 2019
*nat
......
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
......
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
......
略去相同的部份,可以看到非 docker0 的流量防问宿主机的 8080 端口,会被做 dnat 路由到 172.17.0.2:80
.
传统 docker 网络缺点
阿里云原生课 很棒,提到原生的 docker
网络弊端很多,都是一堆 nat 在跑,从容器出去的包要向宿主机借 ip 做 snat, 进容器的包要向容器借端口做 dnat,谁也不认识谁,而且有经验的都知道,nat 的性能相当差,做服务器的运维管理还行,跑业务还是算了
所以,
k8s
抛弃了复杂的 docker 网络模型,只要满足 k8s
网络要求的三点即可,无论是通过 vxlan 还是 tun 实现的 overlay
,还是借助宿主机网络的 underlay
,通就可以。
试验 host gw
通过给主机加路由,就可以实现所谓的 host gateway,这也是 flannel host gw 实现的原理。由于阿里云 vpc 问题,网关都被接管了,只能用虚拟机或是实体物理机来实验。当然 host gw 是有限制:
- 宿主机只能是同一个二层,链路层相通的,否则无法加路由
- 每台宿主机上的 docker0 都是不同的网段,不能冲突,并且 docker0 是所有容器的默认网关
上图是本次实验的网格拓扑,docker 启动时要添加 bip 参数,分别添加 bridge 的 172.17.4.1 和 172.17.1.1
root@worker1:~# cat /lib/systemd/system/docker.service | grep -i dockerd
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=172.17.4.1/24
root@worker2:~# cat /lib/systemd/system/docker.service | grep -i dockerd
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=172.17.1.1/24
需要关掉原有的 iptables nat
iptables -t nat -F; iptables -F
另外还要打开系统的 ip forward
echo 1 > /proc/sys/net/ipv4/ip_forward
此时注意 iptables forward 链默认是不是 ACCEPT,如果不是要么添加针对网桥的过滤规则
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 172.0.0.0/8 -j ACCEPT
iptables -A FORWARD -d 172.0.0.0/8 -j ACCEPT
要么把默认变成 ACCEPT
iptables -P FORWARD ACCEPT
最后到了关键的一步,docker0 网桥的 ip 网段在宿主机内是可以防问的,但是跨主机是不通的,根本不知道哪台机器上的网桥属于哪些网段,所以需要我们手动加路由。在 192.168.1.168 机器上添加
route add -net 172.17.1.0/24 gw 192.168.1.163 dev enp0s3
在 192.168.1.163 上添加
route add -net 172.17.4.0/24 gw 192.168.1.168 dev enp0s3
此时可以测试,跨网络 docker ping 互通,并且主机与容器之间也互通。从这里也可以看到缺点,如果宿主机非常多,每台机器都要手动加所有的路由表。
小结
接下来再测试 flannel overylay
网友评论