Appearance
十六 网络通信
K8S 的网络通信完全由 CNI 接口上的插件来实现,插件需要实现以下集中通信模型。
目前比较流行的插件有:flannel、calico、canel、kube-router ...
- 如何加载插件
k8s 在启动的时候会去:/etc/cni/net.d/ 目录下寻找网络插件的配置文件,POD 在创建时候 k8s 调用这个配置文件,由插件根据这个配置文件进行创建网络。
16.1 通信模型
- 容器间通信:同一个 POD 内多个容器间的通信,使用 lo 网卡通信
- POD间通信:POD IP 直接与 POD IP 通信
- POD 与 Service:POD IP 直接与 Cluster IP
- Service 与集群外部客户端的通信,ingress、NodePort、Loadbacer
16.2 通信模型底层
无论哪一种网络插件,它们用到的底层方案都是以下几种:
- 虚拟网桥:brg,用纯软件实现一个虚拟网卡,一端在POD上,一端在宿主机上接入到网桥或物理接口桥上,称为隧道网络。
- 多路复用:MacVLAN,基于 MAC 的方式创建 VLAN ,为每个虚拟接口配置一个独立的 MAC 地址,使得一个物理网卡承载多个容器使用,这样容器直接使用物理网卡,基于 MacVLAN 进行跨节点通信。
- 硬件交换:网卡支持硬件交换,SR-IOV (单根-IO虚拟化) 方式,这种网卡支持直接在物理级别虚拟出多个接口,高性能。
16.3 K8S 名称空间
K8S 名称空间与 POD 网络名称空间不在一个维度,所以即使在不同的 K8S 集群名称空间内创建的不同 POD,也可以通过网络直接通信。
而目前应用最广的 flannel 网络插件,是不支持这种不同集群命名空间的网络隔离策略的。
calico 支持地址分配,也支持不同集群命名空间的网络隔离策略,但是它使用较为复杂,支持 BGP 三层网络转发,性能比 flannel 强。
也可以使用 flannel 来做网络管理,再安装 calico 仅仅做集群命名空间网路隔离策略,这种搭配方案。
16.4 K8S网络拓扑
所有 POD 连接到,本机 cni0 接口这个网络,cni0 接口发出的报文到达 flannel.1 这个接口,这个接口将报文封装为隧道协议,通过本机的真实的物理网卡发出。
- 查看本机的接口
1: lo: # 本地回环
2: ens33: # 主机物理网卡
3: docker0: # docker 默认的桥接网络,在 k8s 中无用可以删除
4: dummy0: #
5: kube-ipvs0: #
6: flannel.1: # flannel 虚拟网卡,封装隧道报文
7: cni0: # 所有容器处于这个网桥
8: veth0c014b8b@if3: # 容器的网卡连接到 cni0
9: veth97c048e5@if3: # 容器的网卡连接到 cni0
11: vethd2f0bf2b@if3: # 容器的网卡连接到 cni0
12: veth648a500f@if3: # 容器的网卡连接到 cni0
1: lo: # 本地回环
2: ens33: # 主机物理网卡
3: docker0: # docker 默认的桥接网络,在 k8s 中无用可以删除
4: dummy0: #
5: kube-ipvs0: #
6: flannel.1: # flannel 虚拟网卡,封装隧道报文
7: cni0: # 所有容器处于这个网桥
8: veth0c014b8b@if3: # 容器的网卡连接到 cni0
9: veth97c048e5@if3: # 容器的网卡连接到 cni0
11: vethd2f0bf2b@if3: # 容器的网卡连接到 cni0
12: veth648a500f@if3: # 容器的网卡连接到 cni0
- 下载 bridge-utils 包使用命令 brctl show cni0 查看 cni0 接口
bridge name bridge id STP enabled interfaces
cni0 8000.9a6ec95f8285 no veth0c014b8b
veth648a500f
veth7a3f56b7
veth97c048e5
vethd2f0bf2b
bridge name bridge id STP enabled interfaces
cni0 8000.9a6ec95f8285 no veth0c014b8b
veth648a500f
veth7a3f56b7
veth97c048e5
vethd2f0bf2b
16.5 flannel
flannel 是一个专为 kubernetes 定制的三层网络解决方案,主要用于解决容器的跨主机通信问题。
16.5.1 flannel 工作模式
- flannel.1 这个虚拟网卡支持多种传输模式:VxLAN、host-gw、Directrouting、udp
模式 | 介绍 |
---|---|
VXLAN | 使用 VxLAN 作为隧道封装报文 |
host-gw | 不使用叠加网络,而是在主机的路由表中创建到其他主机 subnet 的路由条目,性能较好,缺陷是:所有 node 节点必须处于同一个二层网络中。 |
DirectRouting | 当主机位于同一子网时启用直接路由,不在回退到 VxLAN。 |
UDP | 直接使用 UDP 协议,性能差 |
16.5.2 VXLAN 通信过程
Flannel VXLAN 实质上是一种 “覆盖网络(overlay network)” ,也就是将TCP数据包装在另一种网络包里面进行路由转发和通信,目前已经支持UDP、VxLAN、AWS VPC和GCE路由等数据转发方式。
- flannel VXLAN 通信过程
在 K8S 上 POD 与 POD 是直接通过对方的 IP 地址进行通信的,POD 发出的报文经过 cni0 网桥到达 flannel ,flannel 将报文封装上一层 VxLAN 的首部,外层又被封装一层 UDP 协议的首部,发送给本机物理网卡,本机物理网卡又将 flannel 发过来的报文外层封装上 IP 首部和以太网帧首部(MAC)由网卡发出,另外一个 node 节点收到报文,内核发现是一个 VxLAN 的包,拆掉 IP 首部送给 flannel 应用程序,flannel 拆掉 VxLAN 首部并将内部的数据发送给,cni0 网桥,cni0 收到后转发给 POD。
| | |
|<------------------ VxLAN封装 ----------------->|<----------- 原始报文 ------------->|
+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| node 网络 | node网络 | node 网络 | VxLan | POD MAC | POD IP | data |
| 帧首部MAC | IP首部 | UDP 首部 | 首部 | 首部 | 首部 | Payload |
+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| | |
|<------------------ VxLAN封装 ----------------->|<----------- 原始报文 ------------->|
+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| node 网络 | node网络 | node 网络 | VxLan | POD MAC | POD IP | data |
| 帧首部MAC | IP首部 | UDP 首部 | 首部 | 首部 | 首部 | Payload |
+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
16.5.3 flannel 部署方式
- 在 k8s 集群启动前,flannel 直接部署到节点上,作为一个守护进程运行。
任何一个部署了 kubelet 的节点都应该部署 flannel ,因为 kubelet 要借助 flannel 为 POD 设置网络接口
任何一个部署了 kubelet 的节点都应该部署 flannel ,因为 kubelet 要借助 flannel 为 POD 设置网络接口
- 使用 kube-admin 直接将 k8s 自己的组件包括 flannel 运行在 k8s 之上的静态 POD。
必须配置为共享 node 节点网络名称空间的 POD,所以 flannel POD 控制器为 DaemonSet。
必须配置为共享 node 节点网络名称空间的 POD,所以 flannel POD 控制器为 DaemonSet。
16.5.4flannel 配置文件
- 配置文件选项含义
{
"Network": "10.244.0.0/16", // flannel 使用的 CIDR 格式的网络地址,用于为 POD 配置网络功能
"SubnetLen": 24, // 把 Network 切分为子网供各 node 节点使用时,使用多长的掩码切分,默认为 24
"SubnetMin": "10.244.10.0/24", // 用于分配给 node 的子网起始地址,从这个网络开始分配网络
"SubnetMax": "10.244.255.0/24" // 用于分配给 nide 的子网结束位置,这个是最大分配的网路
"Backend": { // 指明 POD 与 POD 跨节点通信时候使用的 flannel 工作模式
"Type": "vxlan", // 工作模式
"Directrouting": true // 是否使用直接路由模式
}
}
{
"Network": "10.244.0.0/16", // flannel 使用的 CIDR 格式的网络地址,用于为 POD 配置网络功能
"SubnetLen": 24, // 把 Network 切分为子网供各 node 节点使用时,使用多长的掩码切分,默认为 24
"SubnetMin": "10.244.10.0/24", // 用于分配给 node 的子网起始地址,从这个网络开始分配网络
"SubnetMax": "10.244.255.0/24" // 用于分配给 nide 的子网结束位置,这个是最大分配的网路
"Backend": { // 指明 POD 与 POD 跨节点通信时候使用的 flannel 工作模式
"Type": "vxlan", // 工作模式
"Directrouting": true // 是否使用直接路由模式
}
}
- flannel 托管到 k8s 上的配置文件,处于 kube-flannel-cfg 这个 configmap 中。
kubectl get configmap kube-flannel-cfg -n kube-system -o json
kubectl get configmap kube-flannel-cfg -n kube-system -o json
16.5.5 修改工作模式
- 修改 flannel 工作模式,添加 Directrouting,这个操作应该在刚刚部署完 k8s 集群时候修改,推荐修改
kubectl edit configmap kube-flannel-cfg -n kube-system
kubectl edit configmap kube-flannel-cfg -n kube-system
"Backend": {
"Type": "vxlan",
"Directrouting": true
}
"Backend": {
"Type": "vxlan",
"Directrouting": true
}
- 查看本机路由表
ip route show
ip route show
default via 172.16.100.254 dev ens33 proto static metric 100
10.244.1.0/24 via 10.244.1.0 dev ens33 # 必须为 dev 物理网卡接口,否则 Directrouting 没有设置成功
10.244.2.0/24 via 10.244.2.0 dev ens33 # 必须为 dev 物理网卡接口,否则 Directrouting 没有设置成功
172.16.100.0/24 dev ens33 proto kernel scope link src 172.16.100.101 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
default via 172.16.100.254 dev ens33 proto static metric 100
10.244.1.0/24 via 10.244.1.0 dev ens33 # 必须为 dev 物理网卡接口,否则 Directrouting 没有设置成功
10.244.2.0/24 via 10.244.2.0 dev ens33 # 必须为 dev 物理网卡接口,否则 Directrouting 没有设置成功
172.16.100.0/24 dev ens33 proto kernel scope link src 172.16.100.101 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
16.6 Calico
Calico 创建和管理⼀个扁平的三层网络(不需要 overlay),每个容器会分配一个可路由的 ip。由于通信时不需要解包和封包,网络性能损耗小,易于排查,且易于水平扩展。
小规模部署时可以通过 bgp client 直接互联,大规模下可通过指定的 BGP route reflector 来完成,这样保证所有的数据流量都是通过 IP 路由的方式完成互联的。
Calico 基于 iptables 还提供了丰富而灵活的网络 Policy,保证通过各个节点上的 ACLs 来提供 Workload 的多租户隔离、安全组以及其他可达性限制等功能。
有个新的项目:canel,它集合了 flannel 和 calico 的优点。
- 注意
Calico 目前不支持工作在 iptables 下的 kube-proxy,下面介绍 canal 网络策略的使用
16.6.1 安装 canal
- 下载清单文件,需要翻墙
kubectl apply -f https://docs.projectcalico.org/v3.6/getting-started/kubernetes/installation/hosted/canal/canal.yaml
kubectl apply -f https://docs.projectcalico.org/v3.6/getting-started/kubernetes/installation/hosted/canal/canal.yaml
16.6.2 清单定义
- 清单格式,详见:kubectl explain networkpolicy
egress <[]Object> # 出站规则的对象列表
ports <[]Object> # 目标端口的对象列表
port <string> # 数字形式或者是命名的端口
protocol # 协议 TCP、UDP
to <[]Object> # 目标地址对象列表
ipBlock <Object> # 一组 IP 地址
cidr <string> # CIDR 表示的 IP 范围
except <[]string> # 排除 CIDR 中的某些地址
namespaceSelector <Object> # 名称空间选择器
podSelector <Object> # POD 选择器,目标地址可以也是一组 POD
ingress <[]Object> # 入站规则的对象列表
from <[]Object> # 源地址对象列表
ipBlock <Object> # 一组 IP 地址
cidr <string> # CIDR 表示的 IP 范围
except <[]string> # 排除 CIDR 中的某些地址
namespaceSelector <Object> # 名称空间选择器
podSelector <Object> # POD 选择器,源地址也可以是一组 POD
ports <[]Object> # POD 自己的端口,表示控制自己的端口是否可以被访问,的对象列表
port # 数字形式或者是命名的端口
protocol # 协议 TCP、UDP
podSelector <Object> # POD 选择器决定规则应用在哪些 POD 上
policyTypes <[]string> # 可以是 "Ingress", "Egress", 或者 "Ingress,Egress" ,表示放行满足这些规则访问
egress <[]Object> # 出站规则的对象列表
ports <[]Object> # 目标端口的对象列表
port <string> # 数字形式或者是命名的端口
protocol # 协议 TCP、UDP
to <[]Object> # 目标地址对象列表
ipBlock <Object> # 一组 IP 地址
cidr <string> # CIDR 表示的 IP 范围
except <[]string> # 排除 CIDR 中的某些地址
namespaceSelector <Object> # 名称空间选择器
podSelector <Object> # POD 选择器,目标地址可以也是一组 POD
ingress <[]Object> # 入站规则的对象列表
from <[]Object> # 源地址对象列表
ipBlock <Object> # 一组 IP 地址
cidr <string> # CIDR 表示的 IP 范围
except <[]string> # 排除 CIDR 中的某些地址
namespaceSelector <Object> # 名称空间选择器
podSelector <Object> # POD 选择器,源地址也可以是一组 POD
ports <[]Object> # POD 自己的端口,表示控制自己的端口是否可以被访问,的对象列表
port # 数字形式或者是命名的端口
protocol # 协议 TCP、UDP
podSelector <Object> # POD 选择器决定规则应用在哪些 POD 上
policyTypes <[]string> # 可以是 "Ingress", "Egress", 或者 "Ingress,Egress" ,表示放行满足这些规则访问
16.6.3 policyTypes
- 首先定义 名称空间
kubectl create namespace dev
kubectl create namespace prod
kubectl create namespace dev
kubectl create namespace prod
- 在两个命名空间分别创建一个 POD
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: dev
labels:
app: myapp
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
---
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: prod
labels:
app: myapp
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: dev
labels:
app: myapp
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
---
apiVersion: v1
kind: Pod
metadata:
name: pod1
namespace: prod
labels:
app: myapp
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
kubectl apply -f pod-a.yaml -n dev
kubectl apply -f pod-a.yaml -n dev
- 拒绝所有 dev 空间的报文
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: dev
spec:
podSelector: {} # {} 空的选择器表示选择全部
policyTypes:
- Ingress # 指明 Ingress 规则生效,匹配 Ingress 将被放行,如果没定义 Ingress 则不能匹配所有,会拒绝全部
# policyTypes 没有 Egress 表示不控制 Egress ,默认为允许
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: dev
spec:
podSelector: {} # {} 空的选择器表示选择全部
policyTypes:
- Ingress # 指明 Ingress 规则生效,匹配 Ingress 将被放行,如果没定义 Ingress 则不能匹配所有,会拒绝全部
# policyTypes 没有 Egress 表示不控制 Egress ,默认为允许
- 在指定命名空间应用规则文件
kubectl apply -f deny-all-ingress.yaml -n dev
kubectl apply -f deny-all-ingress.yaml -n dev
- 查看规则
kubectl get networkpolicy -n dev
kubectl get networkpolicy -n dev
- 查看 dev 空间中的 POD 地址并访问,结果是不能访问,因为这个命名空间拒绝外部访问
kubectl get pods -n dev -o wide
kubectl get pods -n dev -o wide
curl 10.244.1.2
curl 10.244.1.2
- 查看 prod 空间中的 POD 地址并访问,结果可以访问,因为这个命名空间没有定义规则
kubectl get pods -n dev -o wide
kubectl get pods -n dev -o wide
curl 10.244.2.2
curl 10.244.2.2
- 允许指定网段的 POD 访问本 POD 的 80 端口
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-80-ingress
namespace: dev
spec:
podSelector:
matchLabels:
app: myapp
ingress:
- from:
- ipBlock: # 指定源地址为 IP 地址块
cidr: 10.244.0.0/16 # 掩码形式指出源地址 IP 地址范围
except: # 排除 cidr 范围内的某个地址
- 10.244.1.2/32
ports:
- port: 80 # 入栈且目标端口为 80 的则匹配
protocol: TCP
- port: 443
protocol: TCP
policyTypes:
- Ingress # 指明 Ingress 规则生效,匹配 Ingress 将被放行,如果没定义 Ingress 则不能匹配所有,拒绝全部
# policyTypes 没有 Egress 表示不控制 Egress ,默认为允许
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-80-ingress
namespace: dev
spec:
podSelector:
matchLabels:
app: myapp
ingress:
- from:
- ipBlock: # 指定源地址为 IP 地址块
cidr: 10.244.0.0/16 # 掩码形式指出源地址 IP 地址范围
except: # 排除 cidr 范围内的某个地址
- 10.244.1.2/32
ports:
- port: 80 # 入栈且目标端口为 80 的则匹配
protocol: TCP
- port: 443
protocol: TCP
policyTypes:
- Ingress # 指明 Ingress 规则生效,匹配 Ingress 将被放行,如果没定义 Ingress 则不能匹配所有,拒绝全部
# policyTypes 没有 Egress 表示不控制 Egress ,默认为允许
- 查看规则
kubectl get networkpolicy -n dev
kubectl get networkpolicy -n dev
- 拒绝出栈的所有请求
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
namespace: prod
spec:
podSelector: {} # {} 空的选择器表示选择全部
policyTypes:
- Egress # 指明 Egress 规则生效,匹配 Egress 将被放行,如果没定义 Egress 则不能匹配所有,拒绝全部
# policyTypes 没有 Ingress 表示不控制 Egress ,默认为允许
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
namespace: prod
spec:
podSelector: {} # {} 空的选择器表示选择全部
policyTypes:
- Egress # 指明 Egress 规则生效,匹配 Egress 将被放行,如果没定义 Egress 则不能匹配所有,拒绝全部
# policyTypes 没有 Ingress 表示不控制 Egress ,默认为允许