kube-ovn源碼分析

總體架構(gòu)

功能介紹

總體來(lái)看,Kube-OVN 作為 Kubernetes 和 OVN 之間的一個(gè)橋梁炒俱,將成熟的 SDN 和云原生相結(jié)合放妈。 這意味著 Kube-OVN 不僅通過(guò) OVN 實(shí)現(xiàn)了 Kubernetes 下的網(wǎng)絡(luò)規(guī)范诡曙,例如 CNI谁不,Service 和 Networkpolicy钠导,還將大量的 SDN 領(lǐng)域能力帶入云原生震嫉,例如邏輯交換機(jī),邏輯路由器牡属,VPC票堵,網(wǎng)關(guān),QoS逮栅,ACL 和流量鏡像悴势。

同時(shí) Kube-OVN 還保持了良好的開放性可以和諸多技術(shù)方案集成,例如 Cilium措伐,Submariner瞳浦,Prometheus,KubeVirt 等等废士。

image.png

網(wǎng)絡(luò)拓?fù)?/h2>

ovn-kubernetes 是一個(gè)將 OVN 網(wǎng)絡(luò)方案引入到 k8s 體系兼容 CNI 標(biāo)準(zhǔn)的一套代碼叫潦,將 pod、service 網(wǎng)絡(luò)都用 OVN 網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)官硝。

ovn-kubernetes 組件是以 k8s 對(duì)象的形式在 k8s 集群內(nèi)部署, 網(wǎng)絡(luò)架構(gòu)如下

image.png

以上 logical router / logical switch 都是邏輯/虛擬的矗蕊,每添加一臺(tái)新的 node,ovn-kubernetes 會(huì)新建一臺(tái)用 nodename 命名的 logical switch 連接到全局唯一的 OvnClusterRouter 上氢架,每當(dāng)有新的 pod 調(diào)度到 node傻咖,pod 就連接到對(duì)應(yīng) node 的 logical switch 上,用來(lái)形成 pod 網(wǎng)絡(luò)(overlay / 東西向流量)岖研。

添加新 node 的同時(shí)卿操,ovn-kubernetes 還會(huì)建立一個(gè)綁定到每一臺(tái) node 的 gateway router,連接到 join(join 是與 OvnClusterRouter 相連的 logical switch)孙援,用來(lái)連接 pod 網(wǎng)絡(luò)(overlay)和 node 網(wǎng)絡(luò)(underlay)(南北向流量)

組件介紹

以kubesphere部署的kubernetes為例(使用ovn網(wǎng)絡(luò)組件)害淤,其中ovn相關(guān)服務(wù)如下

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 運(yùn)行 OVN 的管理平面組件,包括 ovn-nb, ovn-sb, 和 ovn-northd拓售。

  • ovn-nb: 保存虛擬網(wǎng)絡(luò)配置窥摄,并提供 API 進(jìn)行虛擬網(wǎng)絡(luò)管理。kube-ovn-controller 將會(huì)主要和 ovn-nb 進(jìn)行交互配置虛擬網(wǎng)絡(luò)础淤。

  • ovn-sb: 保存從 ovn-nb 的邏輯網(wǎng)絡(luò)生成的邏輯流表崭放,以及各個(gè)節(jié)點(diǎn)的實(shí)際物理網(wǎng)絡(luò)狀態(tài)哨苛。

  • ovn-northd:將 ovn-nb 的虛擬網(wǎng)絡(luò)翻譯成 ovn-sb 中的邏輯流表。

多個(gè) ovn-central 實(shí)例會(huì)通過(guò) Raft 協(xié)議同步數(shù)據(jù)保證高可用币砂。

ovs-ovn

ovs-ovn 以 DaemonSet 形式運(yùn)行在每個(gè)節(jié)點(diǎn)建峭,在 Pod 內(nèi)運(yùn)行了 openvswitch, ovsdb, 和 ovn-controller。這些組件作為 ovn-central 的 Agent 將邏輯流表翻譯成真實(shí)的網(wǎng)絡(luò)配置决摧。

kube-ovn-controller

該組件為一個(gè) Deployment 執(zhí)行所有 Kubernetes 內(nèi)資源到 OVN 資源的翻譯工作亿蒸,其作用相當(dāng)于整個(gè) Kube-OVN 系統(tǒng)的控制平面。 kube-ovn-controller 監(jiān)聽了所有和網(wǎng)絡(luò)功能相關(guān)資源的事件蜜徽,并根據(jù)資源變化情況更新 OVN 內(nèi)的邏輯網(wǎng)絡(luò)。主要監(jiān)聽的資源包括: Pod票摇,Service拘鞋,Endpoint,Node矢门,NetworkPolicy盆色,VPC,Subnet祟剔,Vlan隔躲,ProviderNetwork。

以 Pod 事件為例物延, kube-ovn-controller 監(jiān)聽到 Pod 創(chuàng)建事件后宣旱,通過(guò)內(nèi)置的內(nèi)存 IPAM 功能分配地址,并調(diào)用 ovn-central 創(chuàng)建 邏輯端口叛薯,靜態(tài)路由和可能的 ACL 規(guī)則浑吟。接下來(lái) kube-ovn-controller 將分配到的地址,和子網(wǎng)信息例如 CIDR耗溜,網(wǎng)關(guān)组力,路由等信息寫會(huì)到 Pod 的 annotation 中。該 annotation 后續(xù)會(huì)被 kube-ovn-cni 讀取用來(lái)配置本地網(wǎng)絡(luò)抖拴。

kube-ovn-cni

該組件為一個(gè) DaemonSet 運(yùn)行在每個(gè)節(jié)點(diǎn)上燎字,實(shí)現(xiàn) CNI 接口,并操作本地的 OVS 配置單機(jī)網(wǎng)絡(luò)阿宅。

該 DaemonSet 會(huì)復(fù)制 kube-ovn 二進(jìn)制文件到每臺(tái)機(jī)器候衍,作為 kubeletkube-ovn-cni 之間的交互工具,將相應(yīng) CNI 請(qǐng)求 發(fā)送給 kube-ovn-cni 執(zhí)行洒放。該二進(jìn)制文件默認(rèn)會(huì)被復(fù)制到 /opt/cni/bin 目錄下脱柱。

kube-ovn-cni 會(huì)配置具體的網(wǎng)絡(luò)來(lái)執(zhí)行相應(yīng)流量操作,主要工作包括: 1. 配置 ovn-controllervswitchd拉馋。 2. 處理 CNI add/del 請(qǐng)求: 1. 創(chuàng)建刪除 veth 并和 OVS 端口綁定榨为。 2. 配置 OVS 端口信息惨好。 3. 更新宿主機(jī)的 iptables/ipset/route 等規(guī)則。 3. 動(dòng)態(tài)更新容器 QoS. 4. 創(chuàng)建并配置 ovn0 網(wǎng)卡聯(lián)通容器網(wǎng)絡(luò)和主機(jī)網(wǎng)絡(luò)随闺。 5. 配置主機(jī)網(wǎng)卡來(lái)實(shí)現(xiàn) Vlan/Underlay/EIP 等功能日川。 6. 動(dòng)態(tài)配置集群互聯(lián)網(wǎng)關(guān)。

監(jiān)控與擴(kuò)展組件

該部分組件主要提供監(jiān)控矩乐,診斷龄句,運(yùn)維操作以及和外部進(jìn)行對(duì)接,對(duì) Kube-OVN 的核心網(wǎng)絡(luò)能力進(jìn)行擴(kuò)展散罕,并簡(jiǎn)化日常運(yùn)維操作分歇。

kube-ovn-speaker

該組件為一個(gè) DaemonSet 運(yùn)行在特定標(biāo)簽的節(jié)點(diǎn)上,對(duì)外發(fā)布容器網(wǎng)絡(luò)的路由欧漱,使得外部可以直接通過(guò) Pod IP 訪問(wèn)容器职抡。

更多相關(guān)使用方式請(qǐng)參考 BGP 支持

kube-ovn-pinger

該組件為一個(gè) DaemonSet 運(yùn)行在每個(gè)節(jié)點(diǎn)上收集 OVS 運(yùn)行信息误甚,節(jié)點(diǎn)網(wǎng)絡(luò)質(zhì)量缚甩,網(wǎng)絡(luò)延遲等信息,收集的監(jiān)控指標(biāo)可參考 Kube-OVN 監(jiān)控指標(biāo)窑邦。

kube-ovn-monitor

該組件為一個(gè) Deployment 收集 OVN 的運(yùn)行信息擅威,收集的監(jiān)控指標(biāo)可參考 Kube-OVN 監(jiān)控指標(biāo)

kubectl-ko

該組件為 kubectl 插件冈钦,可以快速運(yùn)行常見運(yùn)維操作郊丛,更多使用請(qǐng)參考 kubectl 插件使用

內(nèi)核中瞧筛,每個(gè)宿主機(jī)一個(gè)

|

源碼分析

東西向流量連通

主要分析容器創(chuàng)建時(shí)ovn為該容器設(shè)置標(biāo)簽宾袜,設(shè)置網(wǎng)卡信息并配置網(wǎng)卡接入主機(jī)的ovn網(wǎng)絡(luò)的流程。

在該節(jié)點(diǎn)下接入ovs網(wǎng)絡(luò)的容器 東西向網(wǎng)絡(luò)即可聯(lián)通驾窟。

容器用例

下面是在ovn作為網(wǎng)絡(luò)插件時(shí)的業(yè)務(wù)pod庆猫。該集群使用開源項(xiàng)目kubesphere 部署,網(wǎng)絡(luò)插件選擇ovn绅络。

ovn為該容器中被設(shè)置了如ovn.kubernetes.io/xxx 的注解月培,標(biāo)識(shí)容器接入ovn網(wǎng)絡(luò)的配置。

ovn最終根據(jù)注解中網(wǎng)卡的分配信息恩急,為該容器具體綁定網(wǎng)卡并接入ovn網(wǎng)絡(luò)杉畜。

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
    ...

容器網(wǎng)卡設(shè)置

當(dāng)容器創(chuàng)建時(shí),kube-ovn-controller將根據(jù)pod信息查詢與該pod關(guān)聯(lián)的subnet配置衷恭,使用該配置參考 動(dòng)態(tài)生成ovn網(wǎng)卡配置信息此叠,該配置將設(shè)置在pod的注解中。

下面是用例容器與空間以及subnet配置的關(guān)聯(lián)關(guān)系

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創(chuàng)建時(shí)將調(diào)用handleAddOrUpdatePod 回調(diào)事件函數(shù)

func (c *Controller) handleAddOrUpdatePod(key string) (err error) {
    // ...
    // 查詢?cè)撊萜麝P(guān)聯(lián)的ovn 關(guān)聯(lián)的subnet 配置, 用于參考為pod動(dòng)態(tài)分配ovn網(wǎng)絡(luò)
    // 先從pod上尋找 ovn.kubernetes.io/logical_switch注解對(duì)應(yīng)值
    // 再嘗試從空間下尋找 ovn.kubernetes.io/logical_switch 注解
    // 此外還會(huì)使用 v1.multus-cni.io/default-network 注解 查詢pod額外關(guān)聯(lián)的多
    // 網(wǎng)卡配置随珠,此處不重點(diǎn)分析
    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網(wǎng)卡配置信息
        if cachedPod, err = c.reconcileAllocateSubnets(cachedPod, pod, needAllocatePodNets); err != nil {
            klog.Error(err)
            return err
        }
       //...
    }
    // ...

}

// 根據(jù)subnet具體為pod分配網(wǎng)卡配置信息
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 {
        // 根據(jù)subnet中的網(wǎng)段猬错,分配地址
        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)
        // 將生成的動(dòng)態(tài)生成的地址,以及關(guān)聯(lián)的sunbet中的基本信息茸歧,通過(guò)注解的方式設(shè)置到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))
        }

        //...
}

設(shè)置的部分關(guān)鍵注解對(duì)應(yīng)標(biāo)簽

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"

容器網(wǎng)卡接入

在創(chuàng)建pod創(chuàng)建時(shí)kubelet通過(guò)讀取/etc/cni/net.d下 各個(gè)cni的配置信息。直接調(diào)用cni插件對(duì)應(yīng)的二進(jìn)制程序软瞎。發(fā)起各cni組件的網(wǎng)絡(luò)創(chuàng)建的請(qǐng)求逢唤。

image.png

Kube-ovn-cni 作為daemonset 運(yùn)行在各個(gè)節(jié)點(diǎn)上,當(dāng)服務(wù)啟動(dòng)時(shí)涤浇,將kube-ovn 的cni 配置文件拷貝至/etc/cni/net.d/目錄下鳖藕。

func CmdMain() {
// 拷貝配置文件至 /etc/cni/net.d/01-kube-ovn.conflist(默認(rèn)配置)
if err := mvCNIConf(config.CniConfDir, config.CniConfFile, config.CniConfName); err != nil {
        util.LogFatalAndExit(err, "failed to mv cni config file")
    }
    //...
}

cni配置內(nèi)容

//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
            }
        }
    ]
}

對(duì)應(yīng)cni執(zhí)行文件

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創(chuàng)建容器,在創(chuàng)建網(wǎng)絡(luò)時(shí)將執(zhí)行bin文件并調(diào)用下面的函數(shù)

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 服務(wù)發(fā)起為pod創(chuàng)建ovn網(wǎng)絡(luò)的請(qǐng)求
    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,
    })
    //...
}

最終請(qǐng)求會(huì)調(diào)用至cni常駐服務(wù)接口

pkg/daemon/server.go

在daemon中可見注冊(cè)了容器創(chuàng)建時(shí)調(diào)用的CNI接口

func createHandler(csh *cniServerHandler) http.Handler {
    //...
    // 容器創(chuàng)建時(shí)調(diào)用
    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是容器內(nèi)部tap的接口名只锭,可見默認(rèn)為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 是來(lái)自本用例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接口創(chuàng)建tap網(wǎng)卡對(duì)
        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 的網(wǎng)卡綁定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)
    }
    // ...
    // 獲取容器對(duì)應(yīng)的空間
    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
    }
    // 打開容器空間著恩,設(shè)置 containerNicName 的網(wǎng)卡到該空間使用民逼, 默認(rèn)將別名網(wǎng)卡名為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
}

按照該流程丁寄,調(diào)度到節(jié)點(diǎn)上的容器都將創(chuàng)建并關(guān)聯(lián)該節(jié)點(diǎn)的ovs網(wǎng)絡(luò)端口,從而節(jié)點(diǎn)內(nèi)的容器可以相互通信。

下面分析節(jié)點(diǎn)間網(wǎng)絡(luò)的連接配置铺呵,從而完成節(jié)點(diǎn)間容器的通信。

南北向流量連通

節(jié)點(diǎn)用例

下面是在ovn作為網(wǎng)絡(luò)插件時(shí)的集群節(jié)點(diǎn)隧熙。該集群使用開源項(xiàng)目kubesphere 部署片挂,網(wǎng)絡(luò)插件選擇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"]   

節(jié)點(diǎn)添加處理

pkg/controller/node.go

節(jié)點(diǎn)添加事件將進(jìn)入下列函數(shù)進(jìn)行處理

func (c *Controller) handleAddNode(key string) error {
    //...

    // 從節(jié)點(diǎn)的node.Status.Addresses 中查找internalIP, 即主機(jī)地址
    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
    }

    // 獲取節(jié)點(diǎn)使用的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
    // 拼接出節(jié)點(diǎn)的portname, 規(guī)則為 node-節(jié)點(diǎn)名
    portName := util.NodeLspName(key)
    if node.Annotations[util.AllocatedAnnotation] == "true" && node.Annotations[util.IPAddressAnnotation] != "" && node.Annotations[util.MacAddressAnnotation] != "" {
        macStr := node.Annotations[util.MacAddressAnnotation]
        //為節(jié)點(diǎn)申請(qǐng)靜態(tài)地址
        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 {
        // 為節(jié)點(diǎn)分配動(dòng)態(tài)地址
        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 的所有地址使用逗號(hào)分割連接成字符串
    ipStr := util.GetStringIP(v4IP, v6IP)
    // 在連接節(jié)點(diǎn)的邏輯交換機(jī)上為該節(jié)點(diǎn)創(chuàng)建 端口, 端口名為 node-節(jié)點(diǎn)名
    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
        }
        // 將目標(biāo)為internalIP 的包,即主機(jī)地址的包
        // 下一跳轉(zhuǎn)向該節(jié)點(diǎn)的ovn網(wǎng)卡地址
        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 為 為節(jié)點(diǎn)申請(qǐng)的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
    }

    // 為節(jié)點(diǎn)更新 將配置信息同步更新到節(jié)注解上
    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
    }
    //...
}

配置結(jié)果

進(jìn)入ovn-ovs 組件對(duì)應(yīng)容器躏敢,可使用命令 ovn-nbctl show 查詢到ovn的整體配置拓?fù)湫畔?/p>

  1. 東西向流量: router(ovn-cluster) 連接了swtich(ovn-cluster), 該switch 可能有多個(gè)闷愤,每個(gè)switch對(duì)應(yīng)一個(gè)節(jié)點(diǎn),該交換機(jī)為所屬節(jié)點(diǎn)上的每個(gè)pod提供一個(gè)網(wǎng)絡(luò)端口件余,供pod連接入ovn網(wǎng)絡(luò)讥脐。

  2. 南北向流量: router(ovn-cluster) 連接了swtich(json), 該switch將為每個(gè)加入ovn集群的節(jié)點(diǎn)提供一個(gè)端口。

  3. 東西 南北 向流量連通:router承載跨網(wǎng)段訪問(wèn)的連接啼器,將兩個(gè)方向的流量進(jìn)行連接旬渠。

# 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

節(jié)點(diǎn)網(wǎng)關(guān)連接交換機(jī)

進(jìn)入節(jié)點(diǎn)添加時(shí)調(diào)用的函數(shù) CreateBareLogicalSwitchPort

// CreateBareLogicalSwitchPort create logical switch port with basic configuration
func (c *OVNNbClient) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac string) error {
    // lsname 即交換機(jī)名稱默認(rèn)為 josin
    // lspName 為node-節(jié)點(diǎn)名
    //...
    /* create logical switch port */
    // 為節(jié)點(diǎn)xxx 在join 交換機(jī)上創(chuàng)建了 名為 node-xxx 的端口,該端口需最終與節(jié)點(diǎn)實(shí)體連同則需要
    // 關(guān)聯(lián)節(jié)點(diǎn)上的端口
    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
}

通過(guò)節(jié)點(diǎn)上創(chuàng)建的ovn0 端口作為與 全局節(jié)點(diǎn)邏輯交換機(jī)join的連接端口端壳, 關(guān)注網(wǎng)關(guān)初始函數(shù)

// 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交換機(jī)上為該節(jié)點(diǎn)分配的端口, 該函數(shù)負(fù)責(zé)在節(jié)點(diǎn)創(chuàng)建端口ovn0并連接上
    // 交換機(jī)上為該節(jié)點(diǎn)分配的 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 為節(jié)點(diǎn)上的ovn0
    // external_ids:iface-id=portName 中的portName 即為 join交換機(jī)上為該節(jié)點(diǎn)開的端口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://addozhang.medium.com/source-code-analysis-understanding-cnis-usage-from-kubelet-container-runtime-24d72f29466b

https://atbug.com/how-kubelete-container-runtime-work-with-cni/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末告丢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子损谦,更是在濱河造成了極大的恐慌岖免,老刑警劉巖岳颇,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異觅捆,居然都是意外死亡赦役,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門栅炒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掂摔,“玉大人,你說(shuō)我怎么就攤上這事赢赊∫依欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵释移,是天一觀的道長(zhǎng)叭披。 經(jīng)常有香客問(wèn)我,道長(zhǎng)玩讳,這世上最難降的妖魔是什么涩蜘? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮熏纯,結(jié)果婚禮上同诫,老公的妹妹穿的比我還像新娘。我一直安慰自己樟澜,他們只是感情好误窖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秩贰,像睡著了一般霹俺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毒费,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天丙唧,我揣著相機(jī)與錄音,去河邊找鬼觅玻。 笑死想际,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的串塑。 我是一名探鬼主播沼琉,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼桩匪!你這毒婦竟也來(lái)了打瘪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闺骚,沒(méi)想到半個(gè)月后彩扔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡僻爽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年虫碉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胸梆。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敦捧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碰镜,到底是詐尸還是另有隱情兢卵,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布绪颖,位于F島的核電站秽荤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柠横。R本人自食惡果不足惜窃款,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牍氛。 院中可真熱鬧晨继,春花似錦、人聲如沸糜俗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悠抹。三九已至,卻和暖如春扩淀,著一層夾襖步出監(jiān)牢的瞬間楔敌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工驻谆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卵凑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓胜臊,卻偏偏與公主長(zhǎng)得像勺卢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子象对,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353