自 Docker 技术诞生以来,采用容器技术用于开发、测试甚至是生产环境的企业或组织与日俱增.然而,将容器技术应用于生产环境时如何确定合适的网络方案依然是亟待解决的最大问题,这也曾是主机虚拟化时代的著名难题之一,它不仅涉及了网络中各组件的互连互通,还需要将容器与不相关的其他容器进行有效隔离以确保其安全性.本章将主要讲述容器网络模型的进化、Kubernetes 的网络模型、常用网络插件以及网络策略等相关的话题.Docker 的传统网络模型在应用至日趋复杂的实际业务场景时必将导致复杂性的几何级数上升,由此,Kubernetes 设计了一种网络模型,它要求所有容器都能够通过一个扁平的网络平面直接进行通信(在同一IP网络中),无论它们是否运行于集群中的同一节点.不过,在 Kubernetes 集群中,IP地址分配是以 Pod 对象为单位,而非容器同一 Pod 内的所有容器共享同一网络名称空间.
docker 网络模型
Docker容器网络的原始模型主要有三种:Bridge(桥接)、Host(主机)及Container(容器)Bridge模型借助于虚拟网桥设备为容器建立网络连接,Host模型则设定容器直接共享使用节点主机的网络名称空间,而Container模型则是指多个容器共享同一个网络名称空间,从而彼此之间能够以本地通信的方式建立连接.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TZh6zjiB-1624628163214)(https://raw.githubusercontent.com/ma823956028/image/master/picgo/20200920203145.png)]
传统的解决方案中,多节点上的 Docker 容器间通信依赖于 NAT 机制转发实现.这种解决方案在网络规模庞大时将变得极为复杂、对系统资源的消耗较大且转发效率低下.此外,docker host 的端口也是一种稀缺资源,静态分配和映射极易导致冲突,而动态分配又很容易导致模型的进一步复杂化,Docker 网络也可借助于第三方解决方案来规避NAT通信模型导致的复杂化问题,后来还发布了 CNM(Container Network Model)规范,现在已经被 Cisco Contiv、Kuryr、Open Virtual Networking(OVN)、Project Calico、VMware 或 Weave 这些公司和项目所采纳.不过,这种模型被采用时,也就属于了容器编排的范畴
kubernetes 网络模型
分为四大类:
-
容器间通信: 同一个 pod 内多个容器间通过 LO 口完成通信
-
pod 与 pod 通信: 要求必须是 pod_ip 与 pod_ip 通信,不能经过任何的转发
-
pod 与 svc 通信: pod_ip 通过 iptable 或者 lvs 规则到 cluster_ip,lvs 不能代替 iptable 比如不能做 nat
-
svc 与集群外部通信: 通过 NodePort 或 LoadBalancer 的 svc,或 Ingress 来实现
CNI 接口
kubernetes 的网络功能并不是自身来实现的,而是通过 CNI 接口接入进来的其他插件来实现,当前比较主流的第三方插件比如 flannel、calio、canel 以及 k8s 自己的插件 kube-router … …但大多数的插件解决四种问题的方式无非通过
-
虚拟网桥: 纯软件的方式实现一个虚拟网卡接到网桥上,叠加网络可以实现功能更为强大的控制网络
-
多路复用: MacVLAN,基于 Mac 的方式创建 VLAN,为每一个虚拟接口创建独有的 Mac 地址,一个物理网卡可以可以承载多个容器
-
硬件交换: SR-IOV,单根的 IO 虚拟化,性能很高
CNI 本身只是规范
网络模型的实现方式
通过加载 /etc/cni/net.d
目录下的配置文件来生成配置
[root@master-0 ~]# cat /etc/cni/net.d/10-flannel.conflist
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true # 支持端口映射(叠加网络)
}
}
]
}
网络插件一个重要的功能是要实现网络策略,但 flannel 并不支持网络策略的功能,calio 即支持 ip 分配也支持网络策略且地址转发的方式可以使用 BGP 来实现三层路由,性能要比 flannel 要好,而平常使用是可以二者共同使用
flannel 参数
为了跟踪各子网分配信息等,flannel 使用 etcd 来存储虚拟 IP 和主机 IP 之间的映射,各个节点上运行的 flanneld 守护进程负责监视 etcd 中的信息并完成报文路由,默认情况下,flannel 的配置信息保存于 etcd 的键名 /coreos.com/network/config
之下,可以使用 etcd 服务的客户端工具来设定或修改其可用的相关配置.config 的值是一个 JSON格 式的字典数据结构,它可以使用的键包含以下几个
- Network: flannel 于全局使用的 CIDR 格式的 IPv4 网络,字符串格式,此为必选键,余下的均为可选.
- SubnetLen: 将 Network 属性指定的 IPv4 网络基于指定位的掩码切割为供各节点使用的子网,此网络的掩码小于24时(如16),其切割子网时使用的掩码默认为24位.
- SubnetMin: 可用作分配给节点使用的起始子网,默认为切分完成后的第一个子网,字符串格式.
- SubnetMax: 可用作分配给节点使用的最大子网,默认为切分完成后最大的一个子网,字符串格式.
- Backend: flannel 要使用的后端类型,以及后端的相关配置,字典格式;VxLAN、host-gw和UDP后端各有其相关的参数.
flannel
-
flannel 默认使用 VxLAN 的方式来作为后端网络传输机制,基于 VxLAN 技术需要封装 Vxlan 守护、UDP 守护、IP 守护、以太网守护,好处在于独立管理网络,彼此之间与物理网络不相干扰
[root@master-0 ~]# ifconfig flannel.1 # 用于封装隧道协议报文 flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 # mtu 1450 用于预留隧道使用 inet 10.244.0.0 netmask 255.255.255.255 broadcast 0.0.0.0 inet6 fe80::b8ac:f2ff:fec6:3d6a prefixlen 64 scopeid 0x20<link> ether ba:ac:f2:c6:3d:6a txqueuelen 0 (Ethernet) RX packets 3470 bytes 609309 (595.0 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 6108 bytes 1877416 (1.7 MiB) TX errors 8 dropped 10 overruns 0 carrier 8 collisions 0 [root@master-0 ~]# ifconfig cni0 # flannel 隧道协议在本机通信是使用的接口,只有创建完 pod 才会有 cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 10.244.0.1 netmask 255.255.255.0 broadcast 0.0.0.0 inet6 fe80::5097:daff:fe30:1bb6 prefixlen 64 scopeid 0x20<link> ether da:bc:14:d9:dc:8f txqueuelen 0 (Ethernet) RX packets 636228 bytes 44012320 (41.9 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 669993 bytes 246800073 (235.3 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 [root@master-0 ~]# kubectl get pod -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES myapp-deploy-5d645d645-2dsjq 1/1 Running 5 19d 10.244.2.127 slave-0.shared <none> <none> myapp-deploy-5d645d645-65ftw 1/1 Running 3 19d 10.244.1.90 slave-1.shared <none> <none> myapp-deploy-5d645d645-hfxqf 1/1 Running 5 19d 10.244.2.126 slave-0.shared <none> <none> pod-vol-hostpath 1/1 Running 1 7d2h 10.244.1.89 slave-1.shared <none> <none> [root@master-0 ~]# kubectl exec -it myapp-deploy-5d645d645-2dsjq -- /bin/bash root@myapp-deploy-5d645d645-2dsjq:/# ping 10.244.1.90 PING 10.244.1.90 (10.244.1.90): 48 data bytes 56 bytes from 10.244.1.90: icmp_seq=0 ttl=62 time=0.507 ms 56 bytes from 10.244.1.90: icmp_seq=1 ttl=62 time=0.463 ms [root@slave-1 yum.repos.d]# tcpdump -i cni0 -nn icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on cni0, link-type EN10MB (Ethernet), capture size 262144 bytes 09:11:39.295485 IP 10.244.2.127 > 10.244.1.90: ICMP echo request, id 3840, seq 21762, length 56 09:11:39.295537 IP 10.244.1.90 > 10.244.2.127: ICMP echo reply, id 3840, seq 21762, length 56 09:11:40.296879 IP 10.244.2.127 > 10.244.1.90: ICMP echo request, id 3840, seq 22018, length 56 09:11:40.296924 IP 10.244.1.90 > 10.244.2.127: ICMP echo reply, id 3840, seq 22018, length 56 [root@slave-1 yum.repos.d]# tcpdump -i eth0 -nn host 10.211.55.36 # 可以看到被封装成了 overlay tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 09:21:04.270105 IP 10.211.55.36.47369 > 10.211.55.37.8472: OTV, flags [I] (0x08), overlay 0, instance 1 IP 10.244.2.127 > 10.244.1.90: ICMP echo request, id 3840, seq 35076, length 56 09:21:04.270221 IP 10.211.55.37.47768 > 10.211.55.36.8472: OTV, flags [I] (0x08), overlay 0, instance 1 [root@slave-0 ~]# yum install bridge-utils -y [root@slave-0 ~]# brctl show cni0 bridge name bridge id STP enabled interfaces cni0 8000.62314d8d19f1 no veth18d456d4 veth8e64eb05 vethc8cb2c05 [root@slave-0 ~]# ifconfig # 可以看到 bridge 模拟出的网线接口接到了 cni0 上 veth18d456d4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet6 fe80::20eb:95ff:fefc:5838 prefixlen 64 scopeid 0x20<link> ether 22:eb:95:fc:58:38 txqueuelen 0 (Ethernet) RX packets 137599 bytes 32318828 (30.8 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 188385 bytes 65971206 (62.9 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 veth8e64eb05: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet6 fe80::c6:a7ff:fe72:d9bc prefixlen 64 scopeid 0x20<link> ether 02:c6:a7:72:d9:bc txqueuelen 0 (Ethernet) RX packets 1 bytes 42 (42.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 27 bytes 1758 (1.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 vethc8cb2c05: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet6 fe80::6cac:1ff:fed6:dc73 prefixlen 64 scopeid 0x20<link> ether 6e:ac:01:d6:dc:73 txqueuelen 0 (Ethernet) RX packets 12787 bytes 1124910 (1.0 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 12740 bytes 1120056 (1.0 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
-
flannel 支持的 Host-gw:HostGateway 网络机制,将物理机本机的网口当做 GW,报文通过网卡传输走路由表,指向到报文中的目标网络,这样如果集群很大会让路由表增大,但性能比 VxLAN 好,前提条件是要求各节点工作于同一个三层网络,必须同一个网段,且没有网络隔离万一发一个广播报文会很受影响
[root@master-0 ~]# kubectl edit cm -n kube-system kube-flannel-cfg net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "host-gw" } configmap/kube-flannel-cfg edited
-
直接路由的 VxLAN,如果节点和节点通信且在同一个网络直接通过 host-gw 方式转发,目标节点与当前节点中间隔了路由设备则自动使用叠加网络
[root@master-0 ~]# kubectl edit cm -n kube-system kube-flannel-cfg net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan", "Directrouting": true } configmap/kube-flannel-cfg edited
-
UDP 使用纯粹的普通 UDP 报文转发,比 VxLAN 性能更差,如果在极低的 Linux 版本中不支持 VxLAN,才考虑使用这种技术
-
flannel 插件运行的节点取决于拥有 kubelet 的节点,flannel 自身可以是 pod 也可以是系统级的守护进程,对于 pod 的形式来说,他的控制器一定是一个 DaemonSet 只设置一个副本,且共享宿主机的网络名称空间
[root@master-0 ~]# kubectl get ds -A NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-system kube-flannel-ds-amd64 3 3 3 3 3 <none> 171d kube-system kube-flannel-ds-arm 0 0 0 0 0 <none> 171d kube-system kube-flannel-ds-arm64 0 0 0 0 0 <none> 171d kube-system kube-flannel-ds-ppc64le 0 0 0 0 0 <none> 171d kube-system kube-flannel-ds-s390x 0 0 0 0 0 <none> 171d kube-system kube-proxy 3 3 3 3 3 kubernetes.io/os=linux 172d [root@master-0 ~]# kubectl get node NAME STATUS ROLES AGE VERSION master-0.shared Ready master 172d v1.18.0 slave-0.shared Ready <none> 171d v1.18.0 slave-1.shared Ready <none> 171d v1.18.0 [root@master-0 ~]# kubectl get pod -nkube-system -owide | grep flannel kube-flannel-ds-amd64-jnzfh 1/1 Running 30 172d 10.211.55.36 slave-0.share kube-flannel-ds-amd64-jwwkt 1/1 Running 35 172d 10.211.55.35 master-0.shared # master 上也存在 klannel 是因为 kubeadm 部署的,托管自身的 k8s (跑在 pod 里,所以 master 上也存在 kubelet) kube-flannel-ds-amd64-z4pzx 1/1 Running 25 172d 10.211.55.37 slave-1.shared