总体架构
功能介绍
总体来看,Kube-OVN 作为 Kubernetes 和 OVN 之间的一个桥梁,将成熟的 SDN 和云原生相结合。 这意味着 Kube-OVN 不仅通过 OVN 实现了 Kubernetes 下的网络规范,例如 CNI,Service 和 Networkpolicy,还将大量的 SDN 领域能力带入云原生,例如逻辑交换机,逻辑路由器,VPC,网关,QoS,ACL 和流量镜像。
同时 Kube-OVN 还保持了良好的开放性可以和诸多技术方案集成,例如 Cilium,Submariner,Prometheus,KubeVirt 等等。
![](https://img.haomeiwen.com/i189732/87110c5af3e61bf4.png)
网络拓扑
ovn-kubernetes 是一个将 OVN 网络方案引入到 k8s 体系兼容 CNI 标准的一套代码,将 pod、service 网络都用 OVN 网络来实现。
ovn-kubernetes 组件是以 k8s 对象的形式在 k8s 集群内部署, 网络架构如下
![](https://img.haomeiwen.com/i189732/3e528d2135632831.png)
以上 logical router / logical switch 都是逻辑/虚拟的,每添加一台新的 node,ovn-kubernetes 会新建一台用 nodename 命名的 logical switch 连接到全局唯一的 OvnClusterRouter 上,每当有新的 pod 调度到 node,pod 就连接到对应 node 的 logical switch 上,用来形成 pod 网络(overlay / 东西向流量)。
添加新 node 的同时,ovn-kubernetes 还会建立一个绑定到每一台 node 的 gateway router,连接到 join(join 是与 OvnClusterRouter 相连的 logical switch),用来连接 pod 网络(overlay)和 node 网络(underlay)(南北向流量)
组件介绍
以kubesphere部署的kubernetes为例(使用ovn网络组件),其中ovn相关服务如下
root@node1:~# kubectl -n=kube-system get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
kube-ovn-controller 1/1 1 1 3h43m
kube-ovn-monitor 1/1 1 1 3h43m
ovn-central 1/1 1 1 3h43m
root@node1:~# kubectl -n=kube-system get daemonset
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE
kube-ovn-cni 1 1 1 1 1
kube-ovn-pinger 1 1 1 1 1
ovs-ovn 1 1 1 1 1
核心组件
ovn-central
ovn-central
Deployment 运行 OVN 的管理平面组件,包括 ovn-nb
, ovn-sb
, 和 ovn-northd
。
-
ovn-nb
: 保存虚拟网络配置,并提供 API 进行虚拟网络管理。kube-ovn-controller
将会主要和ovn-nb
进行交互配置虚拟网络。 -
ovn-sb
: 保存从ovn-nb
的逻辑网络生成的逻辑流表,以及各个节点的实际物理网络状态。 -
ovn-northd
:将ovn-nb
的虚拟网络翻译成ovn-sb
中的逻辑流表。
多个 ovn-central
实例会通过 Raft 协议同步数据保证高可用。
ovs-ovn
ovs-ovn
以 DaemonSet 形式运行在每个节点,在 Pod 内运行了 openvswitch
, ovsdb
, 和 ovn-controller
。这些组件作为 ovn-central
的 Agent 将逻辑流表翻译成真实的网络配置。
kube-ovn-controller
该组件为一个 Deployment 执行所有 Kubernetes 内资源到 OVN 资源的翻译工作,其作用相当于整个 Kube-OVN 系统的控制平面。 kube-ovn-controller
监听了所有和网络功能相关资源的事件,并根据资源变化情况更新 OVN 内的逻辑网络。主要监听的资源包括: Pod,Service,Endpoint,Node,NetworkPolicy,VPC,Subnet,Vlan,ProviderNetwork。
以 Pod 事件为例, kube-ovn-controller
监听到 Pod 创建事件后,通过内置的内存 IPAM 功能分配地址,并调用 ovn-central
创建 逻辑端口,静态路由和可能的 ACL 规则。接下来 kube-ovn-controller
将分配到的地址,和子网信息例如 CIDR,网关,路由等信息写会到 Pod 的 annotation 中。该 annotation 后续会被 kube-ovn-cni
读取用来配置本地网络。
kube-ovn-cni
该组件为一个 DaemonSet 运行在每个节点上,实现 CNI 接口,并操作本地的 OVS 配置单机网络。
该 DaemonSet 会复制 kube-ovn
二进制文件到每台机器,作为 kubelet
和 kube-ovn-cni
之间的交互工具,将相应 CNI 请求 发送给 kube-ovn-cni
执行。该二进制文件默认会被复制到 /opt/cni/bin
目录下。
kube-ovn-cni
会配置具体的网络来执行相应流量操作,主要工作包括: 1. 配置 ovn-controller
和 vswitchd
。 2. 处理 CNI add/del 请求: 1. 创建删除 veth 并和 OVS 端口绑定。 2. 配置 OVS 端口信息。 3. 更新宿主机的 iptables/ipset/route 等规则。 3. 动态更新容器 QoS. 4. 创建并配置 ovn0
网卡联通容器网络和主机网络。 5. 配置主机网卡来实现 Vlan/Underlay/EIP 等功能。 6. 动态配置集群互联网关。
监控与扩展组件
该部分组件主要提供监控,诊断,运维操作以及和外部进行对接,对 Kube-OVN 的核心网络能力进行扩展,并简化日常运维操作。
kube-ovn-speaker
该组件为一个 DaemonSet 运行在特定标签的节点上,对外发布容器网络的路由,使得外部可以直接通过 Pod IP 访问容器。
更多相关使用方式请参考 BGP 支持。
kube-ovn-pinger
该组件为一个 DaemonSet 运行在每个节点上收集 OVS 运行信息,节点网络质量,网络延迟等信息,收集的监控指标可参考 Kube-OVN 监控指标。
kube-ovn-monitor
该组件为一个 Deployment 收集 OVN 的运行信息,收集的监控指标可参考 Kube-OVN 监控指标。
kubectl-ko
该组件为 kubectl 插件,可以快速运行常见运维操作,更多使用请参考 kubectl 插件使用。
内核中,每个宿主机一个
|
源码分析
东西向流量连通
主要分析容器创建时ovn为该容器设置标签,设置网卡信息并配置网卡接入主机的ovn网络的流程。
在该节点下接入ovs网络的容器 东西向网络即可联通。
容器用例
下面是在ovn作为网络插件时的业务pod。该集群使用开源项目kubesphere 部署,网络插件选择ovn。
ovn为该容器中被设置了如ovn.kubernetes.io/xxx 的注解,标识容器接入ovn网络的配置。
ovn最终根据注解中网卡的分配信息,为该容器具体绑定网卡并接入ovn网络。
apiVersion: v1
kind: Pod
metadata:
annotations:
ovn.kubernetes.io/allocated: "true"
ovn.kubernetes.io/cidr: 10.233.64.0/18
ovn.kubernetes.io/gateway: 10.233.64.1
ovn.kubernetes.io/ip_address: 10.233.64.4
ovn.kubernetes.io/logical_router: ovn-cluster
ovn.kubernetes.io/logical_switch: ovn-default
ovn.kubernetes.io/mac_address: "00:00:00:38:41:33"
ovn.kubernetes.io/pod_nic_type: veth-pair
ovn.kubernetes.io/routed: "true"
creationTimestamp: "2024-12-04T02:49:03Z"
generateName: ks-installer-5594ffc86d-
labels:
app: ks-installer
pod-template-hash: 5594ffc86d
name: ks-installer-5594ffc86d-tg7d8
namespace: kubesphere-system
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/kubesphereio/ks-installer:v3.4.1
imagePullPolicy: IfNotPresent
name: installer
...
容器网卡设置
当容器创建时,kube-ovn-controller将根据pod信息查询与该pod关联的subnet配置,使用该配置参考 动态生成ovn网卡配置信息,该配置将设置在pod的注解中。
下面是用例容器与空间以及subnet配置的关联关系
root@node1:~# kubectl get ns kubesphere-system -o yaml
apiVersion: v1
kind: Namespace
metadata:
annotations:
ovn.kubernetes.io/cidr: 10.233.64.0/18
ovn.kubernetes.io/exclude_ips: 10.233.64.1
ovn.kubernetes.io/logical_switch: ovn-default
spec:
finalizers:
- kubernetes
status:
phase: Active
空间注解指向的subnet
root@node1:~# kubectl get subnets.kubeovn.io ovn-default
NAME PROVIDER VPC PROTOCOL CIDR PRIVATE NAT DEFAULT GATEWAYTYPE V4USED V4AVAILABLE V6USED V6AVAILABLE EXCLUDEIPS
ovn-default ovn ovn-cluster IPv4 10.233.64.0/18 true true distributed 14 16367 0 0 ["10.233.64.1"]
Pod创建时将调用handleAddOrUpdatePod 回调事件函数
func (c *Controller) handleAddOrUpdatePod(key string) (err error) {
// ...
// 查询该容器关联的ovn 关联的subnet 配置, 用于参考为pod动态分配ovn网络
// 先从pod上寻找 ovn.kubernetes.io/logical_switch注解对应值
// 再尝试从空间下寻找 ovn.kubernetes.io/logical_switch 注解
// 此外还会使用 v1.multus-cni.io/default-network 注解 查询pod额外关联的多
// 网卡配置,此处不重点分析
podNets, err := c.getPodKubeovnNets(pod)
if err != nil {
klog.Errorf("failed to get pod nets %v", err)
return err
}
// ...
needAllocatePodNets := needAllocateSubnets(pod, podNets)
if len(needAllocatePodNets) != 0 {
// 使用获取到的kube-ovn subnet 配置信息,为该pod生成ovn网卡配置信息
if cachedPod, err = c.reconcileAllocateSubnets(cachedPod, pod, needAllocatePodNets); err != nil {
klog.Error(err)
return err
}
//...
}
// ...
}
// 根据subnet具体为pod分配网卡配置信息
func (c *Controller) reconcileAllocateSubnets(cachedPod, pod *v1.Pod, needAllocatePodNets []*kubeovnNet) (*v1.Pod, error) {
namespace := pod.Namespace
name := pod.Name
//...
// Avoid create lsp for already running pod in ovn-nb when controller restart
for _, podNet := range needAllocatePodNets {
// 根据subnet中的网段,分配地址
v4IP, v6IP, mac, subnet, err := c.acquireAddress(pod, podNet)
if err != nil {
c.recorder.Eventf(pod, v1.EventTypeWarning, "AcquireAddressFailed", err.Error())
klog.Error(err)
return nil, err
}
ipStr := util.GetStringIP(v4IP, v6IP)
// 将生成的动态生成的地址,以及关联的sunbet中的基本信息,通过注解的方式设置到pod中。
pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] = ipStr
if mac == "" {
delete(pod.Annotations, fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName))
} else {
pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = mac
}
pod.Annotations[fmt.Sprintf(util.CidrAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.CIDRBlock
pod.Annotations[fmt.Sprintf(util.GatewayAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.Gateway
if isOvnSubnet(podNet.Subnet) {
pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)] = subnet.Name
if pod.Annotations[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] == "" {
pod.Annotations[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] = c.config.PodNicType
}
} else {
delete(pod.Annotations, fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName))
delete(pod.Annotations, fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName))
}
//...
}
设置的部分关键注解对应标签
PodNicAnnotationTemplate = "%s.kubernetes.io/pod_nic_type"
RoutesAnnotationTemplate = "%s.kubernetes.io/routes"
MacAddressAnnotationTemplate = "%s.kubernetes.io/mac_address"
IPAddressAnnotationTemplate = "%s.kubernetes.io/ip_address"
CidrAnnotationTemplate = "%s.kubernetes.io/cidr"
GatewayAnnotationTemplate = "%s.kubernetes.io/gateway"
IPPoolAnnotationTemplate = "%s.kubernetes.io/ip_pool"
LogicalSwitchAnnotationTemplate = "%s.kubernetes.io/logical_switch"
LogicalRouterAnnotationTemplate = "%s.kubernetes.io/logical_router"
VlanIDAnnotationTemplate = "%s.kubernetes.io/vlan_id"
容器网卡接入
在创建pod创建时kubelet通过读取/etc/cni/net.d下 各个cni的配置信息。直接调用cni插件对应的二进制程序。发起各cni组件的网络创建的请求。
![](https://img.haomeiwen.com/i189732/060507e5f4dbf037.png)
Kube-ovn-cni 作为daemonset 运行在各个节点上,当服务启动时,将kube-ovn 的cni 配置文件拷贝至/etc/cni/net.d/目录下。
func CmdMain() {
// 拷贝配置文件至 /etc/cni/net.d/01-kube-ovn.conflist(默认配置)
if err := mvCNIConf(config.CniConfDir, config.CniConfFile, config.CniConfName); err != nil {
util.LogFatalAndExit(err, "failed to mv cni config file")
}
//...
}
cni配置内容
//etc/cni/net.d/01-kube-ovn.conflist
{
"name":"kube-ovn",
"cniVersion":"0.3.1",
"plugins":[
{
"type":"kube-ovn",
"server_socket":"/run/openvswitch/kube-ovn-daemon.sock"
},
{
"type":"portmap",
"capabilities":{
"portMappings":true
}
}
]
}
对应cni执行文件
root@node1:~# ls /opt/cni/bin/kube-ovn -la
-rwxr-xr-x 1 root root 56786944 12月 4 11:33 /opt/cni/bin/kube-ovn
root@node1:~#
kubelet创建容器,在创建网络时将执行bin文件并调用下面的函数
func cmdAdd(args *skel.CmdArgs) error {
// 加载配置 /etc/cni/net.d/01-kube-ovn.conflist
netConf, cniVersion, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
// 向 cni 服务发起为pod创建ovn网络的请求
client := request.NewCniServerClient(netConf.ServerSocket)
response, err := client.Add(request.CniRequest{
CniType: netConf.Type,
PodName: podName,
PodNamespace: podNamespace,
ContainerID: args.ContainerID,
NetNs: args.Netns,
IfName: args.IfName,
Provider: netConf.Provider,
Routes: netConf.Routes,
DNS: netConf.DNS,
DeviceID: netConf.DeviceID,
VfDriver: netConf.VfDriver,
VhostUserSocketVolumeName: netConf.VhostUserSocketVolumeName,
VhostUserSocketName: netConf.VhostUserSocketName,
VhostUserSocketConsumption: netConf.VhostUserSocketConsumption,
})
//...
}
最终请求会调用至cni常驻服务接口
pkg/daemon/server.go
在daemon中可见注册了容器创建时调用的CNI接口
func createHandler(csh *cniServerHandler) http.Handler {
//...
// 容器创建时调用
ws.Route(
ws.POST("/add").
To(csh.handleAdd).
Reads(request.CniRequest{}))
//...
return wsContainer
}
// 添加接口的具体逻辑
func (csh cniServerHandler) handleAdd(req *restful.Request, resp *restful.Response) {
podRequest := request.CniRequest{}
if err := req.ReadEntity(&podRequest); err != nil {
//...
}
klog.V(5).Infof("request body is %v", podRequest)
podSubnet, exist := csh.providerExists(podRequest.Provider)
if !exist {
//...
}
//...
for i := 0; i < 20; i++ {
// ifName是容器内部tap的接口名,可见默认为eth0
if ifName = podRequest.IfName; ifName == "" {
ifName = "eth0"
}
switch {
case podRequest.DeviceID != "":
nicType = util.OffloadType
case podRequest.VhostUserSocketVolumeName != "":
nicType = util.DpdkType
if err = createShortSharedDir(pod, podRequest.VhostUserSocketVolumeName, podRequest.VhostUserSocketConsumption, csh.Config.KubeletDir); err != nil {
klog.Error(err.Error())
if err = resp.WriteHeaderAndEntity(http.StatusInternalServerError, request.CniResponse{Err: err.Error()}); err != nil {
klog.Errorf("failed to write response: %v", err)
}
return
}
default:
// nictype 是来自本用例pod注解的 ovn.kubernetes.io/pod_nic_type: veth-pair
nicType = pod.Annotations[fmt.Sprintf(util.PodNicAnnotationTemplate, podRequest.Provider)]
}
break
}
// ...
var mtu int
routes = append(podRequest.Routes, routes...)
if strings.HasSuffix(podRequest.Provider, util.OvnProvider) && subnet != "" {
//...
podNicName = ifName
switch nicType {
case util.InternalType:
podNicName, routes, err = csh.configureNicWithInternalPort(podRequest.PodName, podRequest.PodNamespace, podRequest.Provider, podRequest.NetNs, podRequest.ContainerID, ifName, macAddr, mtu, ipAddr, gw, isDefaultRoute, detectIPConflict, routes, podRequest.DNS.Nameservers, podRequest.DNS.Search, ingress, egress, podRequest.DeviceID, nicType, latency, limit, loss, jitter, gatewayCheckMode, u2oInterconnectionIP)
case util.DpdkType:
err = csh.configureDpdkNic(podRequest.PodName, podRequest.PodNamespace, podRequest.Provider, podRequest.NetNs, podRequest.ContainerID, ifName, macAddr, mtu, ipAddr, gw, ingress, egress, getShortSharedDir(pod.UID, podRequest.VhostUserSocketVolumeName), podRequest.VhostUserSocketName, podRequest.VhostUserSocketConsumption)
routes = nil
default:
// 使用获取到的 veth-pair 模式
routes, err = csh.configureNic(podRequest.PodName, podRequest.PodNamespace, podRequest.Provider, podRequest.NetNs, podRequest.ContainerID, podRequest.VfDriver, ifName, macAddr, mtu, ipAddr, gw, isDefaultRoute, detectIPConflict, routes, podRequest.DNS.Nameservers, podRequest.DNS.Search, ingress, egress, podRequest.DeviceID, nicType, latency, limit, loss, jitter, gatewayCheckMode, u2oInterconnectionIP, oldPodName)
}
}
//...
}
pkg/daemon/ovs_linux.go
func (csh cniServerHandler) configureNic(podName, podNamespace, provider, netns, containerID, vfDriver, ifName, mac string, mtu int, ip, gateway string, isDefaultRoute, detectIPConflict bool, routes []request.Route, _, _ []string, ingress, egress, deviceID, nicType, latency, limit, loss, jitter string, gwCheckMode int, u2oInterconnectionIP, oldPodName string) ([]request.Route, error) {
var err error
var hostNicName, containerNicName string
if deviceID == "" {
// 使用linux接口创建tap网卡对
hostNicName, containerNicName, err = setupVethPair(containerID, ifName, mtu)
///...
} else {
...
}
ipStr := util.GetIPWithoutMask(ip)
ifaceID := ovs.PodNameToPortName(podName, podNamespace, provider)
ovs.CleanDuplicatePort(ifaceID, hostNicName)
// Add veth pair host end to ovs port
// hostNicName 的网卡绑定ovs的端口, 而 containerNicName 为容器使用
output, err := ovs.Exec(ovs.MayExist, "add-port", "br-int", hostNicName, "--",
"set", "interface", hostNicName, fmt.Sprintf("external_ids:iface-id=%s", ifaceID),
fmt.Sprintf("external_ids:vendor=%s", util.CniTypeName),
fmt.Sprintf("external_ids:pod_name=%s", podName),
fmt.Sprintf("external_ids:pod_namespace=%s", podNamespace),
fmt.Sprintf("external_ids:ip=%s", ipStr),
fmt.Sprintf("external_ids:pod_netns=%s", netns))
if err != nil {
return nil, fmt.Errorf("add nic to ovs failed %v: %q", err, output)
}
// ...
// 获取容器对应的空间
podNS, err := ns.GetNS(netns)
if err != nil {
err = fmt.Errorf("failed to open netns %q: %v", netns, err)
klog.Error(err)
return nil, err
}
// 打开容器空间,设置 containerNicName 的网卡到该空间使用, 默认将别名网卡名为ifname
finalRoutes, err := configureContainerNic(containerNicName, ifName, ip, gateway, isDefaultRoute, detectIPConflict, routes, macAddr, podNS, mtu, nicType, gwCheckMode, u2oInterconnectionIP)
if err != nil {
klog.Error(err)
return nil, err
}
return finalRoutes, nil
}
按照该流程,调度到节点上的容器都将创建并关联该节点的ovs网络端口,从而节点内的容器可以相互通信。
下面分析节点间网络的连接配置,从而完成节点间容器的通信。
南北向流量连通
节点用例
下面是在ovn作为网络插件时的集群节点。该集群使用开源项目kubesphere 部署,网络插件选择ovn。
root@node1:~# kubectl get nodes node1 -o yaml
apiVersion: v1
kind: Node
metadata:
annotations:
ovn.kubernetes.io/chassis: ebdfd1ea-142c-4dad-8e4c-99ba4269a65b
ovn.kubernetes.io/cidr: 100.64.0.0/16
ovn.kubernetes.io/gateway: 100.64.0.1
ovn.kubernetes.io/ip_address: 100.64.0.2
ovn.kubernetes.io/logical_switch: join
ovn.kubernetes.io/mac_address: 00:00:00:FE:82:93
ovn.kubernetes.io/port_name: node-node1
volumes.kubernetes.io/controller-managed-attach-detach: "true"
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kube-ovn/role: master
kubernetes.io/arch: amd64
kubernetes.io/hostname: node1
kubernetes.io/os: linux
node-role.kubernetes.io/control-plane: "true"
node-role.kubernetes.io/master: "true"
node-role.kubernetes.io/worker: ""
name: node1
spec:
podCIDR: 10.233.64.0/24
podCIDRs:
- 10.233.64.0/24
status:
addresses:
- address: 192.168.41.131
type: InternalIP
- address: node1
type: Hostname
subnet资源
root@node1:~# kubectl get subnet
NAME PROVIDER VPC PROTOCOL CIDR PRIVATE NAT DEFAULT GATEWAYTYPE V4USED V4AVAILABLE V6USED V6AVAILABLE EXCLUDEIPS
join ovn ovn-cluster IPv4 100.64.0.0/16 distributed 1 65532 0 0 ["100.64.0.1"]
ovn-default ovn ovn-cluster IPv4 10.233.64.0/18 true true distributed 14 16367 0 0 ["10.233.64.1"]
vpc资源
root@node1:~# kubectl get vpc
NAME STANDBY SUBNETS NAMESPACES
ovn-cluster true ["ovn-default","join"]
节点添加处理
pkg/controller/node.go
节点添加事件将进入下列函数进行处理
func (c *Controller) handleAddNode(key string) error {
//...
// 从节点的node.Status.Addresses 中查找internalIP, 即主机地址
nodeIPv4, nodeIPv6 := util.GetNodeInternalIP(*node)
//...
if err = c.handleNodeAnnotationsForProviderNetworks(node); err != nil {
klog.Errorf("failed to handle annotations of node %s for provider networks: %v", node.Name, err)
return err
}
// 获取节点使用的subnet,kubesphere 安装的ovn 集群中,该subnet 为 join(100.64.0.0/16)
subnet, err := c.subnetsLister.Get(c.config.NodeSwitch)
if err != nil {
klog.Errorf("failed to get node subnet: %v", err)
return err
}
var v4IP, v6IP, mac string
// 拼接出节点的portname, 规则为 node-节点名
portName := util.NodeLspName(key)
if node.Annotations[util.AllocatedAnnotation] == "true" && node.Annotations[util.IPAddressAnnotation] != "" && node.Annotations[util.MacAddressAnnotation] != "" {
macStr := node.Annotations[util.MacAddressAnnotation]
//为节点申请静态地址
v4IP, v6IP, mac, err = c.ipam.GetStaticAddress(portName, portName, node.Annotations[util.IPAddressAnnotation],
&macStr, node.Annotations[util.LogicalSwitchAnnotation], true)
if err != nil {
klog.Errorf("failed to alloc static ip addrs for node %v: %v", node.Name, err)
return err
}
} else {
// 为节点分配动态地址
v4IP, v6IP, mac, err = c.ipam.GetRandomAddress(portName, portName, nil, c.config.NodeSwitch, "", nil, true)
if err != nil {
klog.Errorf("failed to alloc random ip addrs for node %v: %v", node.Name, err)
return err
}
}
// 将ipv4 ipv6 的所有地址使用逗号分割连接成字符串
ipStr := util.GetStringIP(v4IP, v6IP)
// 在连接节点的逻辑交换机上为该节点创建 端口, 端口名为 node-节点名
if err := c.OVNNbClient.CreateBareLogicalSwitchPort(c.config.NodeSwitch, portName, ipStr, mac); err != nil {
klog.Errorf("failed to create logical switch port %s: %v", portName, err)
return err
}
for _, ip := range strings.Split(ipStr, ",") {
if ip == "" {
continue
}
nodeIP, af := nodeIPv4, 4
protocol := util.CheckProtocol(ip)
if protocol == kubeovnv1.ProtocolIPv6 {
nodeIP, af = nodeIPv6, 6
}
// 将目标为internalIP 的包,即主机地址的包
// 下一跳转向该节点的ovn网卡地址
if nodeIP != "" {
var (
match = fmt.Sprintf("ip%d.dst == %s", af, nodeIP)
action = kubeovnv1.PolicyRouteActionReroute
externalIDs = map[string]string{
"vendor": util.CniTypeName,
"node": node.Name,
"address-family": strconv.Itoa(af),
}
)
klog.Infof("add policy route for router: %s, match %s, action %s, nexthop %s, externalID %v", c.config.ClusterRouter, match, action, ip, externalIDs)
// match internalIP 的包 路由的 nexthop 为 为节点申请的ip地址
if err = c.addPolicyRouteToVpc(
c.config.ClusterRouter,
&kubeovnv1.PolicyRoute{
Priority: util.NodeRouterPolicyPriority,
Match: match,
Action: action,
NextHopIP: ip,
},
externalIDs,
); err != nil {
klog.Errorf("failed to add logical router policy for node %s: %v", node.Name, err)
return err
}
// ...
}
}
// 路由器配置 端口 ovn-default to join 的路由
if err := c.addNodeGatewayStaticRoute(); err != nil {
klog.Errorf("failed to add static route for node gw: %v", err)
return err
}
// 为节点更新 将配置信息同步更新到节注解上
annotations := map[string]any{
util.IPAddressAnnotation: ipStr,
util.MacAddressAnnotation: mac,
util.CidrAnnotation: subnet.Spec.CIDRBlock,
util.GatewayAnnotation: subnet.Spec.Gateway,
util.LogicalSwitchAnnotation: c.config.NodeSwitch,
util.AllocatedAnnotation: "true",
util.PortNameAnnotation: portName,
}
if err = util.UpdateNodeAnnotations(c.config.KubeClient.CoreV1().Nodes(), node.Name, annotations); err != nil {
klog.Errorf("failed to update annotations of node %s: %v", node.Name, err)
return err
}
//...
}
配置结果
进入ovn-ovs 组件对应容器,可使用命令 ovn-nbctl show
查询到ovn的整体配置拓扑信息
-
东西向流量: router(ovn-cluster) 连接了swtich(ovn-cluster), 该switch 可能有多个,每个switch对应一个节点,该交换机为所属节点上的每个pod提供一个网络端口,供pod连接入ovn网络。
-
南北向流量: router(ovn-cluster) 连接了swtich(json), 该switch将为每个加入ovn集群的节点提供一个端口。
-
东西 南北 向流量连通:router承载跨网段访问的连接,将两个方向的流量进行连接。
# ovn-nbctl show
switch d8549759-d2f4-4d10-82db-40bc6033826e (join)
port join-ovn-cluster
type: router
router-port: ovn-cluster-join
port node-node1
addresses: ["00:00:00:FE:82:93 100.64.0.2"]
switch 6cbc5f72-9ac1-4473-a15d-5de7348ba768 (ovn-default)
port kube-ovn-pinger-dghxg.kube-system
addresses: ["00:00:00:F4:E2:64 10.233.64.5"]
port ovn-default-ovn-cluster
type: router
router-port: ovn-cluster-ovn-default
port default-http-backend-5bf68ff9b8-8d8r8.kubesphere-controls-system
addresses: ["00:00:00:F1:EC:D4 10.233.64.9"]
port prometheus-operator-8955bbd98-g4fv4.kubesphere-monitoring-system
addresses: ["00:00:00:CD:D0:EE 10.233.64.11"]
port alertmanager-main-0.kubesphere-monitoring-system
addresses: ["00:00:00:78:F3:21 10.233.64.15"]
port ks-apiserver-7fd66f7885-cqr55.kubesphere-system
addresses: ["00:00:00:DD:03:C6 10.233.64.10"]
router a2313806-4ad7-4165-b1d9-38b608911fb7 (ovn-cluster)
port ovn-cluster-ovn-default
mac: "00:00:00:81:42:01"
networks: ["10.233.64.1/18"]
port ovn-cluster-join
mac: "00:00:00:4A:F8:D5"
networks: ["100.64.0.1/16"]
Router ovn-cluster的路由信息
# ovn-nbctl lr-route-list ovn-cluster
IPv4 Routes
Route Table <main>:
0.0.0.0/0 100.64.0.1 dst-ip
节点网关连接交换机
进入节点添加时调用的函数 CreateBareLogicalSwitchPort
// CreateBareLogicalSwitchPort create logical switch port with basic configuration
func (c *OVNNbClient) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac string) error {
// lsname 即交换机名称默认为 josin
// lspName 为node-节点名
//...
/* create logical switch port */
// 为节点xxx 在join 交换机上创建了 名为 node-xxx 的端口,该端口需最终与节点实体连同则需要
// 关联节点上的端口
lsp := &ovnnb.LogicalSwitchPort{
UUID: ovsclient.NamedUUID(),
Name: lspName,
Addresses: []string{strings.TrimSpace(strings.Join(addresses, " "))}, // addresses is the first element of addresses
}
ops, err := c.CreateLogicalSwitchPortOp(lsp, lsName)
if err != nil {
klog.Error(err)
return err
}
if err = c.Transact("lsp-add", ops); err != nil {
klog.Error(err)
return fmt.Errorf("create logical switch port %s: %w", lspName, err)
}
return nil
}
通过节点上创建的ovn0 端口作为与 全局节点逻辑交换机join的连接端口, 关注网关初始函数
// InitNodeGateway init ovn0
func InitNodeGateway(config *Configuration) error {
var portName, ip, cidr, macAddr, gw, ipAddr string
for {
nodeName := config.NodeName
node, err := config.KubeClient.CoreV1().Nodes().Get(context.Background(), nodeName, metav1.GetOptions{})
// ...
macAddr = node.Annotations[util.MacAddressAnnotation]
ip = node.Annotations[util.IPAddressAnnotation]
cidr = node.Annotations[util.CidrAnnotation]
portName = node.Annotations[util.PortNameAnnotation]
gw = node.Annotations[util.GatewayAnnotation]
break
}
mac, err := net.ParseMAC(macAddr)
if err != nil {
return fmt.Errorf("failed to parse mac %s %w", mac, err)
}
ipAddr, err = util.GetIPAddrWithMask(ip, cidr)
if err != nil {
klog.Errorf("failed to get ip %s with mask %s, %v", ip, cidr, err)
return err
}
// portName 即为在join交换机上为该节点分配的端口, 该函数负责在节点创建端口ovn0并连接上
// 交换机上为该节点分配的 node-xxx 端口
// br-int(on node)--->ovn0--->node-xxx--->switch(join)
return configureNodeNic(portName, ipAddr, gw, cidr, mac, config.MTU)
}
func configureNodeNic(portName, ip, gw, joinCIDR string, macAddr net.HardwareAddr, mtu int) error {
ipStr := util.GetIPWithoutMask(ip)
// util.NodeNic 为节点上的ovn0
// external_ids:iface-id=portName 中的portName 即为 join交换机上为该节点开的端口node-xxx
raw, err := ovs.Exec(ovs.MayExist, "add-port", "br-int", util.NodeNic, "--",
"set", "interface", util.NodeNic, "type=internal", "--",
"set", "interface", util.NodeNic, fmt.Sprintf("external_ids:iface-id=%s", portName),
fmt.Sprintf("external_ids:ip=%s", ipStr))
if err != nil {
klog.Errorf("failed to configure node nic %s: %v, %q", portName, err, raw)
return errors.New(raw)
}
if err = configureNic(util.NodeNic, ip, macAddr, mtu, false, false, true); err != nil {
klog.Error(err)
return err
}
//...
}
参考文章
https://juejin.cn/post/7164211658766680100
https://www.kancloud.cn/digest/openvswitch/117251
https://kubesphere.io/zh/blogs/use-kubekey-to-install-and-deploy-kubernetes-and-kubeovn/
https://atbug.com/how-kubelete-container-runtime-work-with-cni/
网友评论