總體架構(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 等等废士。
網(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)如下
以上 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ī)器候衍,作為 kubelet
和 kube-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-controller
和 vswitchd
拉馋。 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)求逢唤。
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>
東西向流量: 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ò)讥脐。
南北向流量: router(ovn-cluster) 連接了swtich(json), 該switch將為每個(gè)加入ovn集群的節(jié)點(diǎn)提供一個(gè)端口。
東西 南北 向流量連通: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://atbug.com/how-kubelete-container-runtime-work-with-cni/