服务器网络架构
网络架构比较固定,都是大同小异。
K8s网络模型:Pod网络
Pod是K8s最小调度单元,一个Pod由一个容器或多个容器组成,当多个容器时,怎么都用这一个Pod IP?
实现:k8s会在每个Pod里先启动一个infra container小容器,然后让其他的容器连接进来这个网络命名空间,然后其他容器看到的网络就完全一样了。即网络设备、IP地址、Mac地址等。在Pod的IP地址就是infra container的IP地址。
K8s网络模型:Pod网络
在 Kubernetes 中,每一个 Pod 都有一个真实的 IP 地址,并且每一个 Pod 都可以使用此 IP 地址与 其他 Pod 通信。
- 两个Pod在同一个Node上
- 两个Pod在不同Node上
veth主要解决不同网络命名空间通信,docker0网桥主要解决同主机不同容器之间通信
引发的一些网络问题
1、谁来为每个节点分配一个网段(提前规划好,做好记录),保障该节点上每个pod分配不一样的ip?
2、pod1怎么知道pod2在哪个节点?
3、知道了在哪个节点,怎么实现这个转发?
应对∶
1、提前规划好,做好记录
2、iptables、写静态路由
- 一个Pod对应一个IP
- 所有的 Pod 可以与任何其他 Pod 直接通信
- 所有节点可以与所有 Pod 直接通信
- Pod 内部获取到的 IP 地址与其他 Pod 或节点与其通信时的 IP 地址是同一个
K8s网络模型: CNI(容器网络接口)
CNI(Container Network Interface,容器网络接口):是一个容器网络规范,Kubernetes网络采用的就是这个CNI规 范,负责初始化infra容器的网络设备。
- 项目地址:https://github.com/containernetworking/cni
以Flannel网络组件为例,当部署Flanneld后,会在每台宿主机上生成它对应的CNI配置文件(它其实是一个ConfigMap),从而告诉Kubernetes要使用 Flannel 作为容器网络方案。
- CNI配置文件默认路径:/etc/cni/net.d
- CNI二进制程序默认路径:/opt/cni/bin/
当 kubelet 组件需要创建 Pod 的时候,先调用dockershim它先创建一个 Infra 容器。然后调用 CNI 插件为 Infra 容器配置网络。
这两个路径可在kubelet启动参数中定义:
--network-plugin=cni
--cni-conf-dir=/etc/cni/net.d
--cni-bin-dir=/opt/cni/bin
下面放置着CNI网络插件的二进制程序,通过这些程序完成与cni的对接
[root@k8s-master ~]# ls /opt/cni/bin/
bandwidth dhcp flannel host-local loopback portmap sbr tuning
bridge firewall host-device ipvlan macvlan ptp static vlan
CNI配置文件默认路径:/etc/cni/net.d
[root@k8s-master ~]# ls /etc/cni/net.d/
10-flannel.conflist
[root@k8s-master ~]# 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
}
}
]
}
这里面就包含了配置内容,所以cni参数都是在kubelet上面配置的,以为它负责创建容器并且为其配置容器网络
CNI的配置是有下面三个参数,第一个就是启用cni网络插件,也就是k8s要使用cni就必须启用
简单说cni就是网络组件的接口,好让第三方的网络都能接入到k8s网络里面来。
--network-plugin=cni
--cni-conf-dir=/etc/cni/net.d
--cni-bin-dir=/opt/cni/bin
Flannel
Flannel是由CoreOS开发的项目,是CNI插件早期的入门产品,简单易用。
Flannel使用Kubernetes集群的现有etcd集群来存储其状态信息,从而不必提供专用的数据存储,只需要在每个节点上运行flanneld来守护进程。
每个节点都被分配一个子网,为该节点上的Pod分配IP地址。
同一主机内的Pod可以使用网桥进行通信,而不同主机上的Pod将通过flanneld将其流量封装在UDP数据包中,以路由到适当的目的地。
封装方式默认和推荐的方法是使用VxLAN,因为它具有良好的性能,并且比其他选项要少些人为干预。虽然使用VxLAN之类的技术封装的解决方案效果很好,但缺点就是该过程使流量跟踪变得困难。
vxlan:当你一个数据包过来要发出去,比如你是tcp的请求,这个请求要往外面发,vxlan就要在你原始的数据包发出去之前,在包之上再加上一层udp的包头。
每个节点都会有flanneld的进程,它会在每个节点上面创建vxlan的设备,所有要转发的包都需要经过这个vxlan的设备去做封装。会在原始的tcp的包头里面再加上一层udp的包头,那么这个包就变成了vxlan的包。
udp包的原始地址和目标地址就是容器主机所运行的地址。那么既然外层包变成的主机地址,那么就可以在不同的节点之间传输了。
所谓的vxlan就是其实就是overlay的技术,要在原始的数据包上加一个包头。这个包传到对端之后就要去做一次额外的解包,所以内核要在处理这些数据包的时候就多了额外的封包和解包的过程,那么整个效率就不会很高。
所以在生产系统上面,小型的系统对网络性能要求不太高的场景下用的比较多。对于要追求性能的时候就没有那么广泛了。
Flannel
Flannel是由CoreOS开发的项目,是CNI插件早期的入门产品,简单易用。
Flannel使用Kubernetes集群的现有etcd集群来存储其状态信息,从而不必提供专用的数据存储,只需要在每个节点上运行flanneld来守护进程。
每个节点都被分配一个子网,为该节点上的Pod分配IP地址。
同一主机内的Pod可以使用网桥进行通信,而不同主机上的Pod将通过flanneld将其流量封装在UDP数据包中,以路由到适当的目的地。
封装方式默认和推荐的方法是使用VxLAN,因为它具有良好的性能,并且比其他选项要少些人为干预。虽然使用VxLAN之类的技术封装的解决方案效果很好,但缺点就是该过程使流量跟踪变得困难。
vxlan:如果一个数据包过来,要发出去,比如你是TCP的请求,这个请求要往外发,在你原始的数据包发出去之前,在你的包之上再加上一层udp的包头,所谓的flannel就是这样一个插件。
运行在每个节点,由container runtime去调用,flanneld会在每个节点上创建vxlan的设备,所有要去转发的包都需要经过vxlan的设备去做封装,会在原始的包头上面再加上udp的包,那么这个包就变为了vxlan的包。
加的最外层udp的包原始地址和目标地址就会是容器运行主机的这个地址,也就是宿主机的地址。那么外层包变为了主机的地址,那么就可以在不同节点传输了。
vxlan是overlay的技术,就是在原始数据包上面再加一个包头,这个包传到对端要去做一次额外的解包,在内核处理这些数据包的时候,就多了额外的封包和解包的过程,那么它的整个效率就不会很高。
可能小型的网络,对这种性能要求不高的场景下面用的比较多,但是要追求性能的时候,用的就没有那么广泛了。
Flannel是CoreOS维护的一个网络组件,Flannel为每个Pod提供全局唯一的IP,Flannel使用ETCD来存储Pod子网与Node IP之间的关系。flanneld守护进程在每台主机上运行,并负责维护ETCD信息和路由数据包。
- 这句话体现的几点:
- (1)守护进程
- (2)使用存储etcd来pod子网和nodeip之前关系,使用ETCD存储子网
- (3)提供全局的IP
项目地址: https://github.com/coreos/flannel
YAML地址:https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Flannel:部署
在这个文件当中需要了解这个,配置网络相关的信息,配置子网和工作模式,这两个你可能需要修改,Backend:指定工作模式
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
使用的是deamonset部署的
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-flannel-ds-amd64
namespace: kube-system
在部署之前需要做的事情
10.244.0.0/16 这是一个大的子网,会确保每个node都分配到独立的网段
• Network:指定Pod IP分配的网段,与controller-manager配置的保持一样
--allocate-node-cidrs=true
--cluster-cidr=10.244.0.0/16
• kubeadm部署:/etc/kubernetes/manifests/kube-controller-manager.yaml
• 二进制部署:/opt/kubernetes/cfg/kube-controller-manager.conf
我这里部署集群默认是配置好了,就不需要修改了
[root@k8s-master ~]# vim /etc/kubernetes/manifests/kube-controller-manager.yaml
- command:
- kube-controller-manager
- --allocate-node-cidrs=true
- --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
- --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
- --bind-address=127.0.0.1
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --cluster-cidr=10.244.0.0/16
[root@k8s-master ~]# cat /opt/kubernetes/cfg/kube-controller-manager.conf
KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=false \
--v=2 \
--log-dir=/opt/kubernetes/logs \
--leader-elect=true \
--master=127.0.0.1:8080 \
--bind-address=127.0.0.1 \
--allocate-node-cidrs=true \
--cluster-cidr=10.244.0.0/16 \
--service-cluster-ip-range=10.0.0.0/24 \
--cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \
--cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem \
--root-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \
--experimental-cluster-signing-duration=87600h0m0s"
如果部署了calico那么先删除,在一个k8s网络当中只能使用一个网络组件
[root@k8s-master ~]# kubectl delete -f calico.yaml
[root@k8s-master ~]# rm -f /etc/cni/net.d/10-calico.conflist /etc/cni/net.d/calico-kubeconfig
[root@k8s-master ~]# kubectl apply -f kube-flannel.yml
[root@k8s-master ~]# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-amd64-lb6vm 1/1 Running 0 139d
kube-flannel-ds-amd64-lxxdq 1/1 Running 0 139d
kube-flannel-ds-amd64-vl4fn 1/1 Running 0 139d
K8s网络组件之Flannel:工作模式
- UDP:最早支持的一种方式,由于性能最差,目前已经弃用。
- VXLAN:Overlay Network方案,源数据包封装在另一种网络包里面进行路由转发和通信
- Host-GW:Flannel通过在各个节点上的Agent进程,将容器网络的路由信息写到主机的路由表上,这样一来所有的主机都有整个容器网络的路由数据了。
- Directrouting:同时支持VXLAN和Host-GW工作模式
- 公有云VPC:ALIYUN,AWS
总结来说也就两种,一种路由方案(基于现有的宿主机作为路由器来使用,负责数据包的转发),一种隧道方案(overlay)。
K8s网络组件之Flannel:VXLAN模式
隧道模式内部自己实现了封装方式,VETP设备用于封包和解包
VTEP设备进行封装和解封装的对象是二层数据帧,这个工作是在Linux内核中完成的。
[root@k8s-master ~]# ifconfig
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.1 netmask 255.255.255.0 broadcast 10.244.0.255
inet6 fe80::ec98:8bff:fea1:554d prefixlen 64 scopeid 0x20<link>
ether ee:98:8b:a1:55:4d txqueuelen 1000 (Ethernet)
RX packets 35973 bytes 9023583 (8.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 40472 bytes 28071561 (26.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:b9:2e:09:e2 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.179.99 netmask 255.255.255.0 broadcast 192.168.179.255
inet6 fe80::54c7:3ae8:3659:6b97 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:b4:88:32 txqueuelen 1000 (Ethernet)
RX packets 5674009 bytes 2565682866 (2.3 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6213197 bytes 4945068979 (4.6 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.0 netmask 255.255.255.255 broadcast 0.0.0.0
inet6 fe80::2027:5aff:fef0:9316 prefixlen 64 scopeid 0x20<link>
ether 22:27:5a:f0:93:16 txqueuelen 0 (Ethernet)
RX packets 3604 bytes 2986402 (2.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2830 bytes 585691 (571.9 KiB)
TX errors 1 dropped 8 overruns 0 carrier 1 collisions 0
vethf5cad454: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet6 fe80::6401:bbff:fe31:76bd prefixlen 64 scopeid 0x20<link>
ether 66:01:bb:31:76:bd txqueuelen 0 (Ethernet)
RX packets 6 bytes 509 (509.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 13 bytes 1000 (1000.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@k8s-master ~]# ip route
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
宿主机查看目标网段址10.244.2.10,那么就会走这个路由表
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
这个路由表的下一条是 10.244.1.0
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.0
K8s网络组件之Flannel
- 项目地址: https://github.com/coreos/flannel
- YAML地址:https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
下面看看vxlan模式下,不同主机的pod通信
Pod1容器当中发出了数据包,这个数据包会到达宿主机上面的cni0这个网桥,前面的流程和docker流程一样,只要发送数据包肯定要到达cni0,cni0在这里充当了网桥的作用,二层交换,容器以cni0的网桥作为网关,不管是不是处于同网段都会到达cni0网桥这里,这是第一步
第二步 数据包已经到达了宿主机上面,宿主机上面的网络协议栈可以看到这个数据包的,所以会根据路由表转发到flannel.1这张虚拟网卡,也即是来到了隧道的入口
[root@k8s-master ~]# ip route
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
宿主机查看目标网段址10.244.2.10,那么就会走这个路由表
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
这个路由表的下一条是 10.244.1.0
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.0
这样数据包来到宿主机查看宿主机上面的路由表发到下一跳即flannel.1设备
flannel.1设备这个设备是完成vetp这个设备封装的,就相当于隧道的入口,之后就要使用xvlan对其进行封装,封装之后再走宿主机上面的网络,这里数据的封包是二层的,封装包必须要有目标的mac地址
flannel.1就是vetp设备,基于二层去封装,那么必须知道目标mac地址是谁,这里源IP目标IP都知道
查看这个网卡的arp信息,这两个就是地址就是另外两台机器上面的 flannel.1 mac地址
[root@k8s-master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kubia-859d757f8c-74g6s 1/1 Running 0 124d 10.244.2.4 k8s-node2 <none> <none>
kubia-859d757f8c-97znt 1/1 Running 0 124d 10.244.0.14 k8s-master <none> <none>
kubia-859d757f8c-9mjf9 1/1 Running 0 124d 10.244.1.5 k8s-node1 <none> <none>
[root@k8s-master ~]# ifconfig
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.0
[root@k8s-node1 ~]# ifconfig
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.1.0
[root@k8s-node2 ~]# ifconfig
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.2.0
其实在flanneld进程启动后,就会自动添加其他节点ARP记录,可以通过ip neigh show dev flannel.1命令查看
[root@k8s-master ~]# ip neigh show dev flannel.1
10.244.2.0 lladdr 4a:5a:f0:f5:19:5e PERMANENT
10.244.1.0 lladdr b6:68:f7:5e:af:9c PERMANENT
这个走的是二层的
[root@k8s-master ~]# bridge fdb show dev flannel.1
b6:68:f7:5e:af:9c dst 192.168.179.100 self permanent
4a:5a:f0:f5:19:5e dst 192.168.179.101 self permanent
找到宿主机的IP就可以向这个宿主机发送UDP的数据包
K8s网络组件之Flannel:VXLAN模式
[root@k8s-master ~]# kubectl exec -it dns-test sh
/ # ip route
default via 10.244.0.1 dev eth0
10.244.0.0/24 dev eth0 scope link src 10.244.0.17
10.244.0.0/16 via 10.244.0.1 dev eth0
2.主机路由:数据包进入到宿主机虚拟网卡cni0,根据路由表转发到flannel.1虚拟网卡,也就是来到了隧道的入口。
[root@k8s-master ~]# ip route
default via 192.168.111.2 dev eno16777736 proto static metric 100
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
[root@k8s-master ~]# ip neigh show dev flannel.1
10.244.1.0 lladdr aa:f8:47:ae:13:4b PERMANENT
10.244.2.0 lladdr ae:30:2e:1a:b3:72 PERMANENT
4.二次封包:知道了目的MAC地址,Linux内核就可以进行二层封包了。但是,对于宿主机网络来说这个二层帧并不能在宿主机二层网络里传输。所以接下来,Linux内核还要把这个数据帧进一步封装成为宿主机网络的一个普通数据帧,好让它载着内部数据帧,通过宿主机的eth0网卡进行传输。 数据格式如下图:
5.封装到UDP包发出去:在封装成宿主机网络可传输的数据帧时,还缺少目标宿主机MAC地址,也就是说这个UDP包该发给哪台宿主机呢?flanneld进程也维护着一个叫做FDB的转发数据库,可以通过bridge fdb show dev flannel.1命令查看。可以看到,上面用的对方flannel.1的MAC地址对应宿主机IP,也就是UDP要发往的目的地。所以使用这个目的IP与MAC地址进行封装。
可以看到在flannel vetp设备里面看到的是原始的数据包
[root@k8s-master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dns-test 1/1 Running 5 118d 10.244.0.17 k8s-master <none> <none>
nginx 1/1 Running 0 3m 10.244.2.8 k8s-node2 <none> <none>
/ # ping 10.244.2.8
PING 10.244.2.8 (10.244.2.8): 56 data bytes
64 bytes from 10.244.2.8: seq=0 ttl=62 time=0.924 ms
64 bytes from 10.244.2.8: seq=1 ttl=62 time=2.803 ms
64 bytes from 10.244.2.8: seq=2 ttl=62 time=0.693 ms
^C
--- 10.244.2.8 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.693/1.473/2.803 ms
[root@k8s-node2 ~]# tcpdump -i flannel.1 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes
09:55:59.936783 IP 10.244.0.17 > 10.244.2.8: ICMP echo request, id 6656, seq 0, length 64
09:55:59.936832 IP 10.244.2.8 > 10.244.0.17: ICMP echo reply, id 6656, seq 0, length 64
09:56:00.941898 IP 10.244.0.17 > 10.244.2.8: ICMP echo request, id 6656, seq 1, length 64
09:56:00.941966 IP 10.244.2.8 > 10.244.0.17: ICMP echo reply, id 6656, seq 1, length 64
09:56:01.942700 IP 10.244.0.17 > 10.244.2.8: ICMP echo request, id 6656, seq 2, length 64
09:56:01.942767 IP 10.244.2.8 > 10.244.0.17: ICMP echo reply, id 6656, seq 2, length 64
可以看到是overylay的网络,可以看到宿主机的IP,内部IP也即是容器IP,并且是overylay的网络。
[root@k8s-node2 ~]# tcpdump udp -i eno16777736 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eno16777736, link-type EN10MB (Ethernet), capture size 262144 bytes
10:00:34.073167 IP 192.168.111.3.53527 > 192.168.111.5.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.244.0.17 > 10.244.2.8: ICMP echo request, id 6912, seq 0, length 64
10:00:34.073255 IP 192.168.111.5.42769 > 192.168.111.3.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.244.2.8 > 10.244.0.17: ICMP echo reply, id 6912, seq 0, length 64
10:00:35.083230 IP 192.168.111.3.53527 > 192.168.111.5.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.244.0.17 > 10.244.2.8: ICMP echo request, id 6912, seq 1, length 64
10:00:35.083503 IP 192.168.111.5.42769 > 192.168.111.3.8472: OTV, flags [I] (0x08), overlay 0, instance 1
IP 10.244.2.8 > 10.244.0.17: ICMP echo reply, id 6912, seq 1, length 64