diff --git a/packages/system/kubeovn/Makefile b/packages/system/kubeovn/Makefile index 1a21f6db..9bbd8578 100644 --- a/packages/system/kubeovn/Makefile +++ b/packages/system/kubeovn/Makefile @@ -8,9 +8,9 @@ include ../../../scripts/package.mk update: rm -rf charts && mkdir -p charts/kube-ovn - tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/kubeovn/kube-ovn | awk -F'[/^]' '{print $$3}' | grep '^v1\.13\.' | tail -n1 ) && \ + tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/kubeovn/kube-ovn | awk -F'[/^]' '{print $$3}' | grep '^v1\.14\.' | tail -n1 ) && \ curl -sSL https://github.com/kubeovn/kube-ovn/archive/refs/tags/$${tag}.tar.gz | \ - tar xzvf - --strip 1 kube-ovn-$${tag#*v}/charts + tar xzvf - --strip 1 kube-ovn-$${tag#*v}/charts/kube-ovn patch --no-backup-if-mismatch -p4 < patches/cozyconfig.diff patch --no-backup-if-mismatch -p4 < patches/mtu.diff version=$$(awk '$$1 == "version:" {print $$2}' charts/kube-ovn/Chart.yaml) && \ diff --git a/packages/system/kubeovn/charts/kube-ovn/Chart.yaml b/packages/system/kubeovn/charts/kube-ovn/Chart.yaml index 7c779d4f..44744459 100644 --- a/packages/system/kubeovn/charts/kube-ovn/Chart.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/Chart.yaml @@ -15,12 +15,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: v1.13.14 +version: v1.14.5 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.13.14" +appVersion: "1.14.5" -kubeVersion: ">= 1.23.0-0" +kubeVersion: ">= 1.29.0-0" diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl b/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl index 1b9a0575..e6697c6e 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl +++ b/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl @@ -20,6 +20,9 @@ Get IP-addresses of master nodes {{- end -}} {{- end -}} {{- end -}} +{{- if and (eq (len $ips) 0) (not $.Values.MASTER_NODES) -}} + {{- fail (printf "No nodes found with label '%s'. Please check your MASTER_NODES_LABEL configuration or ensure master nodes are properly labeled." $.Values.MASTER_NODES_LABEL) -}} +{{- end -}} {{ join "," $ips }} {{- end -}} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml index 2f5ef406..5c4587f9 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml @@ -115,6 +115,9 @@ spec: - --enable-anp={{- .Values.func.ENABLE_ANP }} - --ovsdb-con-timeout={{- .Values.func.OVSDB_CON_TIMEOUT }} - --ovsdb-inactivity-timeout={{- .Values.func.OVSDB_INACTIVITY_TIMEOUT }} + - --enable-live-migration-optimize={{- .Values.func.ENABLE_LIVE_MIGRATION_OPTIMIZE }} + - --enable-ovn-lb-prefer-local={{- .Values.func.ENABLE_OVN_LB_PREFER_LOCAL }} + - --image={{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} securityContext: runAsUser: {{ include "kubeovn.runAsUser" . }} privileged: false diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml index 4dec76ee..ee3e1461 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml @@ -3,7 +3,7 @@ kind: Deployment apiVersion: apps/v1 metadata: name: ovn-ic-controller - namespace: kube-system + namespace: {{ .Values.namespace }} annotations: kubernetes.io/description: | OVN IC Client @@ -109,7 +109,9 @@ spec: name: kube-ovn-log nodeSelector: kubernetes.io/os: "linux" - kube-ovn/role: "master" + {{- with splitList "=" .Values.MASTER_NODES_LABEL }} + {{ index . 0 }}: "{{ if eq (len .) 2 }}{{ index . 1 }}{{ end }}" + {{- end }} volumes: - name: host-run-ovn hostPath: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml index 1c858734..1f45c50c 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml @@ -823,6 +823,378 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + name: vpc-egress-gateways.kubeovn.io +spec: + group: kubeovn.io + names: + plural: vpc-egress-gateways + singular: vpc-egress-gateway + shortNames: + - vpc-egress-gw + - veg + kind: VpcEgressGateway + listKind: VpcEgressGatewayList + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.vpc + name: VPC + type: string + - jsonPath: .spec.replicas + name: REPLICAS + type: integer + - jsonPath: .spec.bfd.enabled + name: BFD ENABLED + type: boolean + - jsonPath: .spec.externalSubnet + name: EXTERNAL SUBNET + type: string + - jsonPath: .status.phase + name: PHASE + type: string + - jsonPath: .status.ready + name: READY + type: boolean + - jsonPath: .status.internalIPs + name: INTERNAL IPS + priority: 1 + type: string + - jsonPath: .status.externalIPs + name: EXTERNAL IPS + priority: 1 + type: string + - jsonPath: .status.workload.nodes + name: WORKING NODES + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1 + served: true + storage: true + subresources: + status: {} + scale: + # specReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Spec.Replicas. + specReplicasPath: .spec.replicas + # statusReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Replicas. + statusReplicasPath: .status.replicas + # labelSelectorPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Selector. + labelSelectorPath: .status.labelSelector + schema: + openAPIV3Schema: + type: object + properties: + status: + properties: + replicas: + type: integer + format: int32 + labelSelector: + type: string + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + lastUpdateTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - lastUpdateTime + - observedGeneration + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + internalIPs: + items: + type: string + type: array + externalIPs: + items: + type: string + type: array + phase: + type: string + default: Pending + enum: + - Pending + - Processing + - Completed + ready: + type: boolean + default: false + workload: + type: object + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + nodes: + type: array + items: + type: string + required: + - conditions + - phase + type: object + spec: + type: object + required: + - externalSubnet + x-kubernetes-validations: + - rule: "!has(self.internalIPs) || size(self.internalIPs) == 0 || size(self.internalIPs) >= self.replicas" + message: 'Size of Internal IPs MUST be equal to or greater than Replicas' + fieldPath: ".internalIPs" + - rule: "!has(self.externalIPs) || size(self.externalIPs) == 0 || size(self.externalIPs) >= self.replicas" + message: 'Size of External IPs MUST be equal to or greater than Replicas' + fieldPath: ".externalIPs" + - rule: "size(self.policies) != 0 || size(self.selectors) != 0" + message: 'Each VPC Egress Gateway MUST have at least one policy or selector' + properties: + replicas: + type: integer + format: int32 + default: 1 + minimum: 0 + maximum: 10 + prefix: + type: string + anyOf: + - pattern: ^$ + - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*[-\.]?$ + x-kubernetes-validations: + - rule: "self == oldSelf" + message: "This field is immutable." + vpc: + type: string + internalSubnet: + type: string + externalSubnet: + type: string + internalIPs: + items: + type: string + oneOf: + - format: ipv4 + - format: ipv6 + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5]),((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + type: array + x-kubernetes-list-type: set + externalIPs: + items: + type: string + oneOf: + - format: ipv4 + - format: ipv6 + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5]),((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + type: array + x-kubernetes-list-type: set + image: + type: string + bfd: + type: object + properties: + enabled: + type: boolean + default: false + minRX: + type: integer + format: int32 + default: 1000 + minTX: + type: integer + format: int32 + default: 1000 + multiplier: + type: integer + format: int32 + default: 3 + selectors: + type: array + items: + type: object + properties: + namespaceSelector: + type: object + properties: + matchLabels: + additionalProperties: + type: string + type: object + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + x-kubernetes-validations: + - rule: "size(self.matchLabels) != 0 || size(self.matchExpressions) != 0" + message: 'Each namespace selector MUST have at least one matchLabels or matchExpressions' + podSelector: + type: object + properties: + matchLabels: + additionalProperties: + type: string + type: object + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + x-kubernetes-validations: + - rule: "size(self.matchLabels) != 0 || size(self.matchExpressions) != 0" + message: 'Each pod selector MUST have at least one matchLabels or matchExpressions' + policies: + type: array + items: + type: object + properties: + snat: + type: boolean + default: false + ipBlocks: + type: array + x-kubernetes-list-type: set + items: + type: string + anyOf: + - format: ipv4 + - format: ipv6 + - format: cidr + subnets: + type: array + x-kubernetes-list-type: set + items: + type: string + minLength: 1 + x-kubernetes-validations: + - rule: "size(self.ipBlocks) != 0 || size(self.subnets) != 0" + message: 'Each policy MUST have at least one ipBlock or subnet' + trafficPolicy: + type: string + enum: + - Local + - Cluster + default: Cluster + nodeSelector: + type: array + items: + type: object + properties: + matchLabels: + additionalProperties: + type: string + type: object + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist + - Gt + - Lt + values: + type: array + x-kubernetes-list-type: set + items: + type: string + required: + - key + - operator + matchFields: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist + - Gt + - Lt + values: + type: array + x-kubernetes-list-type: set + items: + type: string + required: + - key + - operator +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: name: iptables-eips.kubeovn.io spec: @@ -1293,6 +1665,9 @@ spec: - jsonPath: .status.vpc name: Vpc type: string + - jsonPath: .spec.type + name: Type + type: string - jsonPath: .status.v4Eip name: V4Eip type: string @@ -1357,6 +1732,8 @@ spec: type: string ipType: type: string + type: + type: string ipName: type: string vpc: @@ -1665,6 +2042,51 @@ spec: type: string type: object type: array + bfdPort: + properties: + enabled: + type: boolean + default: false + ip: + type: string + anyOf: + - pattern: ^$ + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5]),((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + nodeSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + x-kubernetes-validations: + - rule: "self.enabled == false || self.ip != ''" + message: 'Port IP must be set when BFD Port is enabled' type: object status: properties: @@ -1721,6 +2143,17 @@ spec: type: string sctpSessionLoadBalancer: type: string + bfdPort: + type: object + properties: + ip: + type: string + name: + type: string + nodes: + type: array + items: + type: string type: object type: object served: true @@ -1837,18 +2270,12 @@ spec: - name: Mac type: string jsonPath: .status.mac - - name: PMac - type: string - jsonPath: .spec.parentMac - name: Subnet type: string jsonPath: .spec.subnet - - jsonPath: .status.ready - name: Ready - type: boolean - - jsonPath: .status.type - name: Type + - name: Type type: string + jsonPath: .status.type schema: openAPIV3Schema: type: object @@ -1866,12 +2293,6 @@ spec: type: string mac: type: string - pv4ip: - type: string - pv6ip: - type: string - pmac: - type: string selector: type: array items: @@ -1912,12 +2333,6 @@ spec: type: string v6ip: type: string - parentV4ip: - type: string - parentMac: - type: string - parentV6ip: - type: string selector: type: array items: @@ -2196,6 +2611,8 @@ spec: type: boolean enableMulticastSnoop: type: boolean + enableExternalLBAddress: + type: boolean routeTable: type: string namespaceSelectors: @@ -2375,6 +2792,8 @@ spec: type: array items: type: string + conflict: + type: boolean additionalPrinterColumns: - name: ID type: string @@ -2382,6 +2801,9 @@ spec: - name: Provider type: string jsonPath: .spec.provider + - name: conflict + type: boolean + jsonPath: .status.conflict scope: Cluster names: plural: vlans diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml index a0f7a26a..e89d8d46 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml @@ -13,6 +13,8 @@ rules: - vpcs/status - vpc-nat-gateways - vpc-nat-gateways/status + - vpc-egress-gateways + - vpc-egress-gateways/status - subnets - subnets/status - ippools @@ -98,6 +100,18 @@ rules: - daemonsets verbs: - get + - apiGroups: + - apps + resources: + - deployments + - deployments/scale + verbs: + - get + - list + - watch + - create + - update + - delete - apiGroups: - "" resources: @@ -121,12 +135,18 @@ rules: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch - apiGroups: - apps resources: - statefulsets - - deployments - - deployments/scale verbs: - get - list @@ -155,6 +175,7 @@ rules: verbs: - get - list + - watch - apiGroups: - "policy.networking.k8s.io" resources: @@ -240,9 +261,14 @@ rules: - "" resources: - services - - endpoints verbs: - get + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list - apiGroups: - apps resources: @@ -278,6 +304,7 @@ rules: - nodes - nodes/status - pods + - services verbs: - get - list diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml index c46e3389..9a1d591f 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml @@ -31,7 +31,7 @@ spec: hostPID: true containers: - name: openvswitch - image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }}-dpdk + image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.DPDK_IMAGE_TAG }} imagePullPolicy: {{ .Values.image.pullPolicy }} command: ["/kube-ovn/start-ovs-dpdk-v2.sh"] securityContext: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml index 003d0c71..17743d5f 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml @@ -38,11 +38,7 @@ spec: hostPID: true initContainers: - name: hostpath-init - {{- if .Values.DPDK }} - image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.dpdkRepository }}:{{ .Values.DPDK_VERSION }}-{{ .Values.global.images.kubeovn.tag }} - {{- else }} image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} - {{- end }} imagePullPolicy: {{ .Values.image.pullPolicy }} command: - sh @@ -82,17 +78,9 @@ spec: name: host-log-ovs containers: - name: openvswitch - {{- if .Values.DPDK }} - image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.dpdkRepository }}:{{ .Values.DPDK_VERSION }}-{{ .Values.global.images.kubeovn.tag }} - {{- else }} image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} - {{- end }} imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- if .Values.DPDK }} - command: ["/kube-ovn/start-ovs-dpdk.sh"] - {{- else }} command: ["/kube-ovn/start-ovs.sh"] - {{- end }} securityContext: runAsUser: {{ include "kubeovn.runAsUser" . }} privileged: false @@ -156,59 +144,30 @@ spec: - mountPath: /var/run/containerd name: cruntime readOnly: true - {{- if .Values.DPDK }} - - mountPath: /opt/ovs-config - name: host-config-ovs - - mountPath: /dev/hugepages - name: hugepage - {{- end }} readinessProbe: exec: - {{- if .Values.DPDK }} - command: - - bash - - /kube-ovn/ovs-dpdk-healthcheck.sh - {{- else }} command: - bash - /kube-ovn/ovs-healthcheck.sh - {{- end }} initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 45 livenessProbe: exec: - {{- if .Values.DPDK }} - command: - - bash - - /kube-ovn/ovs-dpdk-healthcheck.sh - {{- else }} command: - bash - /kube-ovn/ovs-healthcheck.sh - {{- end }} initialDelaySeconds: 60 periodSeconds: 5 failureThreshold: 5 timeoutSeconds: 45 resources: requests: - {{- if .Values.DPDK }} - cpu: {{ .Values.DPDK_CPU }} - memory: {{ .Values.DPDK_MEMORY }} - {{- else }} cpu: {{ index .Values "ovs-ovn" "requests" "cpu" }} memory: {{ index .Values "ovs-ovn" "requests" "memory" }} - {{- end }} limits: - {{- if .Values.DPDK }} - cpu: {{ .Values.DPDK_CPU }} - memory: {{ .Values.DPDK_MEMORY }} - hugepages-1Gi: 1Gi - {{- else }} cpu: {{ index .Values "ovs-ovn" "limits" "cpu" }} memory: {{ index .Values "ovs-ovn" "limits" "memory" }} - {{- end }} nodeSelector: kubernetes.io/os: "linux" volumes: @@ -242,12 +201,3 @@ spec: - hostPath: path: /var/run/containerd name: cruntime - {{- if .Values.DPDK }} - - name: host-config-ovs - hostPath: - path: /opt/ovs-config - type: DirectoryOrCreate - - name: hugepage - emptyDir: - medium: HugePages - {{- end }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml index a69a13ff..66a34853 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml @@ -129,6 +129,18 @@ spec: limits: cpu: {{ index .Values "kube-ovn-pinger" "limits" "cpu" }} memory: {{ index .Values "kube-ovn-pinger" "limits" "memory" }} + livenessProbe: + httpGet: + path: /metrics + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /metrics + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 nodeSelector: kubernetes.io/os: "linux" volumes: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/pre-delete-hook.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml similarity index 83% rename from packages/system/kubeovn/charts/kube-ovn/templates/pre-delete-hook.yaml rename to packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml index d81c5ca2..a4c0d618 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/pre-delete-hook.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml @@ -1,12 +1,12 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: kube-ovn-pre-delete-hook + name: kube-ovn-post-delete-hook namespace: {{ .Values.namespace }} annotations: # This is what defines this resource as a hook. Without this line, the # job is considered part of the release. - "helm.sh/hook": pre-delete + "helm.sh/hook": post-delete "helm.sh/hook-weight": "1" "helm.sh/hook-delete-policy": hook-succeeded --- @@ -17,15 +17,17 @@ metadata: rbac.authorization.k8s.io/system-only: "true" # This is what defines this resource as a hook. Without this line, the # job is considered part of the release. - "helm.sh/hook": pre-delete + "helm.sh/hook": post-delete "helm.sh/hook-weight": "2" "helm.sh/hook-delete-policy": hook-succeeded - name: system:kube-ovn-pre-delete-hook + name: system:kube-ovn-post-delete-hook rules: - apiGroups: - kubeovn.io resources: - subnets + - vpcs + - ips verbs: - get - list @@ -34,26 +36,26 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: kube-ovn-pre-delete-hook + name: kube-ovn-post-delete-hook annotations: # This is what defines this resource as a hook. Without this line, the # job is considered part of the release. - "helm.sh/hook": pre-delete + "helm.sh/hook": post-delete "helm.sh/hook-weight": "3" "helm.sh/hook-delete-policy": hook-succeeded roleRef: - name: system:kube-ovn-pre-delete-hook + name: system:kube-ovn-post-delete-hook kind: ClusterRole apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount - name: kube-ovn-pre-delete-hook + name: kube-ovn-post-delete-hook namespace: {{ .Values.namespace }} --- apiVersion: batch/v1 kind: Job metadata: - name: "{{ .Chart.Name }}-pre-delete-hook" + name: "{{ .Chart.Name }}-post-delete-hook" namespace: {{ .Values.namespace }} labels: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} @@ -63,7 +65,7 @@ metadata: annotations: # This is what defines this resource as a hook. Without this line, the # job is considered part of the release. - "helm.sh/hook": pre-delete + "helm.sh/hook": post-delete "helm.sh/hook-weight": "4" "helm.sh/hook-delete-policy": hook-succeeded spec: @@ -75,7 +77,7 @@ spec: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - app: kube-ovn-pre-delete-hook + app: kube-ovn-post-delete-hook component: job spec: tolerations: @@ -91,7 +93,7 @@ spec: - key: app operator: In values: - - kube-ovn-pre-delete-hook + - kube-ovn-post-delete-hook - key: component operator: In values: @@ -100,8 +102,8 @@ spec: hostNetwork: true nodeSelector: kubernetes.io/os: "linux" - serviceAccount: kube-ovn-pre-delete-hook - serviceAccountName: kube-ovn-pre-delete-hook + serviceAccount: kube-ovn-post-delete-hook + serviceAccountName: kube-ovn-post-delete-hook containers: - name: remove-subnet-finalizer image: "{{ .Values.global.registry.address}}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }}" @@ -113,7 +115,7 @@ spec: command: - sh - -c - - /kube-ovn/remove-subnet-finalizer.sh 2>&1 | tee -a /var/log/kube-ovn/remove-subnet-finalizer.log + - /kube-ovn/remove-finalizer.sh 2>&1 | tee -a /var/log/kube-ovn/remove-finalizer.log volumeMounts: - mountPath: /var/log/kube-ovn name: kube-ovn-log diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml index c005bec1..ae9a0ce8 100755 --- a/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml @@ -14,6 +14,6 @@ kind: ConfigMap apiVersion: v1 metadata: name: ovn-vpc-nat-gw-config - namespace: kube-system + namespace: {{ .Values.namespace }} data: - enable-vpc-nat-gw: "{{ .Values.func.ENABLE_NAT_GW }}" \ No newline at end of file + enable-vpc-nat-gw: "{{ .Values.func.ENABLE_NAT_GW }}" diff --git a/packages/system/kubeovn/charts/kube-ovn/values.yaml b/packages/system/kubeovn/charts/kube-ovn/values.yaml index d5395006..74a69133 100644 --- a/packages/system/kubeovn/charts/kube-ovn/values.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/values.yaml @@ -8,9 +8,8 @@ global: images: kubeovn: repository: kube-ovn - dpdkRepository: kube-ovn-dpdk vpcRepository: vpc-nat-gateway - tag: v1.13.14 + tag: v1.14.5 support_arm: true thirdparty: true @@ -58,7 +57,7 @@ networking: func: ENABLE_LB: true ENABLE_NP: true - ENABLE_EXTERNAL_VPC: true + ENABLE_EXTERNAL_VPC: false HW_OFFLOAD: false ENABLE_LB_SVC: false ENABLE_KEEP_VM_IP: true @@ -78,6 +77,7 @@ func: OVSDB_CON_TIMEOUT: 3 OVSDB_INACTIVITY_TIMEOUT: 10 ENABLE_LIVE_MIGRATION_OPTIMIZE: true + ENABLE_OVN_LB_PREFER_LOCAL: false ipv4: PINGER_EXTERNAL_ADDRESS: "1.1.1.1" @@ -133,10 +133,7 @@ fullnameOverride: "" HYBRID_DPDK: false HUGEPAGE_SIZE_TYPE: hugepages-2Mi # Default HUGEPAGES: 1Gi - -# DPDK -DPDK: false -DPDK_VERSION: "19.11" +DPDK_IMAGE_TAG: "v1.14.0-dpdk" DPDK_CPU: "1000m" # Default CPU configuration DPDK_MEMORY: "2Gi" # Default Memory configuration diff --git a/packages/system/kubeovn/images/kubeovn/Dockerfile b/packages/system/kubeovn/images/kubeovn/Dockerfile index d8468355..f4d1267c 100644 --- a/packages/system/kubeovn/images/kubeovn/Dockerfile +++ b/packages/system/kubeovn/images/kubeovn/Dockerfile @@ -1,10 +1,10 @@ # syntax = docker/dockerfile:experimental -ARG VERSION=v1.13.14 +ARG VERSION=v1.14.5 ARG BASE_TAG=$VERSION -FROM golang:1.23-bookworm as builder +FROM golang:1.24-bookworm as builder -ARG TAG=v1.13.14 +ARG TAG=v1.14.5 RUN git clone --branch ${TAG} --depth 1 https://github.com/kubeovn/kube-ovn /source WORKDIR /source diff --git a/packages/system/kubeovn/images/kubeovn/patches/5294-db-healthcheck.diff b/packages/system/kubeovn/images/kubeovn/patches/5294-db-healthcheck.diff deleted file mode 100644 index b65073ee..00000000 --- a/packages/system/kubeovn/images/kubeovn/patches/5294-db-healthcheck.diff +++ /dev/null @@ -1,168 +0,0 @@ -diff --git a/mocks/pkg/ovs/interface.go b/mocks/pkg/ovs/interface.go -index e9c472bee..59076f9ed 100644 ---- a/mocks/pkg/ovs/interface.go -+++ b/mocks/pkg/ovs/interface.go -@@ -10,6 +10,7 @@ - package ovs - - import ( -+ context "context" - reflect "reflect" - - v1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" -@@ -3322,6 +3323,20 @@ func (mr *MockNbClientMockRecorder) DeleteSecurityGroup(sgName any) *gomock.Call - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSecurityGroup", reflect.TypeOf((*MockNbClient)(nil).DeleteSecurityGroup), sgName) - } - -+// Echo mocks base method. -+func (m *MockNbClient) Echo(arg0 context.Context) error { -+ m.ctrl.T.Helper() -+ ret := m.ctrl.Call(m, "Echo", arg0) -+ ret0, _ := ret[0].(error) -+ return ret0 -+} -+ -+// Echo indicates an expected call of Echo. -+func (mr *MockNbClientMockRecorder) Echo(arg0 any) *gomock.Call { -+ mr.mock.ctrl.T.Helper() -+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Echo", reflect.TypeOf((*MockNbClient)(nil).Echo), arg0) -+} -+ - // EnablePortLayer2forward mocks base method. - func (m *MockNbClient) EnablePortLayer2forward(lspName string) error { - m.ctrl.T.Helper() -@@ -4770,6 +4785,20 @@ func (mr *MockSbClientMockRecorder) GetAllChassisByHost(nodeName any) *gomock.Ca - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllChassisByHost", reflect.TypeOf((*MockSbClient)(nil).GetAllChassisByHost), nodeName) - } - -+// Echo mocks base method. -+func (m *MockSbClient) Echo(arg0 context.Context) error { -+ m.ctrl.T.Helper() -+ ret := m.ctrl.Call(m, "Echo", arg0) -+ ret0, _ := ret[0].(error) -+ return ret0 -+} -+ -+// Echo indicates an expected call of Echo. -+func (mr *MockSbClientMockRecorder) Echo(arg0 any) *gomock.Call { -+ mr.mock.ctrl.T.Helper() -+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Echo", reflect.TypeOf((*MockSbClient)(nil).Echo), arg0) -+} -+ - // GetChassis mocks base method. - func (m *MockSbClient) GetChassis(chassisName string, ignoreNotFound bool) (*ovnsb.Chassis, error) { - m.ctrl.T.Helper() -@@ -4915,6 +4944,20 @@ func (m *MockCommon) EXPECT() *MockCommonMockRecorder { - return m.recorder - } - -+// Echo mocks base method. -+func (m *MockCommon) Echo(arg0 context.Context) error { -+ m.ctrl.T.Helper() -+ ret := m.ctrl.Call(m, "Echo", arg0) -+ ret0, _ := ret[0].(error) -+ return ret0 -+} -+ -+// Echo indicates an expected call of Echo. -+func (mr *MockCommonMockRecorder) Echo(arg0 any) *gomock.Call { -+ mr.mock.ctrl.T.Helper() -+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Echo", reflect.TypeOf((*MockCommon)(nil).Echo), arg0) -+} -+ - // GetEntityInfo mocks base method. - func (m *MockCommon) GetEntityInfo(entity any) error { - m.ctrl.T.Helper() -diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go -index cb594a4c8..a2a88eb06 100644 ---- a/pkg/controller/controller.go -+++ b/pkg/controller/controller.go -@@ -268,6 +268,9 @@ type Controller struct { - cmInformerFactory kubeinformers.SharedInformerFactory - kubeovnInformerFactory kubeovninformer.SharedInformerFactory - anpInformerFactory anpinformer.SharedInformerFactory -+ -+ // Database health check -+ dbFailureCount int - } - - func newTypedRateLimitingQueue[T comparable](name string, rateLimiter workqueue.TypedRateLimiter[T]) workqueue.TypedRateLimitingInterface[T] { -@@ -944,6 +947,48 @@ func (c *Controller) Run(ctx context.Context) { - klog.Info("Shutting down workers") - } - -+func (c *Controller) dbStatus() { -+ const maxFailures = 5 -+ -+ done := make(chan error, 2) -+ go func() { -+ done <- c.OVNNbClient.Echo(context.Background()) -+ }() -+ go func() { -+ done <- c.OVNSbClient.Echo(context.Background()) -+ }() -+ -+ resultsReceived := 0 -+ timeout := time.After(time.Duration(c.config.OvnTimeout) * time.Second) -+ -+ for resultsReceived < 2 { -+ select { -+ case err := <-done: -+ resultsReceived++ -+ if err != nil { -+ c.dbFailureCount++ -+ klog.Errorf("OVN database echo failed (%d/%d): %v", c.dbFailureCount, maxFailures, err) -+ if c.dbFailureCount >= maxFailures { -+ util.LogFatalAndExit(err, "OVN database connection failed after %d attempts", maxFailures) -+ } -+ return -+ } -+ case <-timeout: -+ c.dbFailureCount++ -+ klog.Errorf("OVN database echo timeout (%d/%d) after %ds", c.dbFailureCount, maxFailures, c.config.OvnTimeout) -+ if c.dbFailureCount >= maxFailures { -+ util.LogFatalAndExit(nil, "OVN database connection timeout after %d attempts", maxFailures) -+ } -+ return -+ } -+ } -+ -+ if c.dbFailureCount > 0 { -+ klog.Infof("OVN database connection recovered after %d failures", c.dbFailureCount) -+ c.dbFailureCount = 0 -+ } -+} -+ - func (c *Controller) shutdown() { - utilruntime.HandleCrash() - -@@ -1277,6 +1322,8 @@ func (c *Controller) startWorkers(ctx context.Context) { - if c.config.EnableLiveMigrationOptimize { - go wait.Until(runWorker("add/update vmiMigration ", c.addOrUpdateVMIMigrationQueue, c.handleAddOrUpdateVMIMigration), 50*time.Millisecond, ctx.Done()) - } -+ -+ go wait.Until(c.dbStatus, 15*time.Second, ctx.Done()) - } - - func (c *Controller) allSubnetReady(subnets ...string) (bool, error) { -diff --git a/pkg/ovs/interface.go b/pkg/ovs/interface.go -index df6907c4d..5e70dd6c7 100644 ---- a/pkg/ovs/interface.go -+++ b/pkg/ovs/interface.go -@@ -1,6 +1,8 @@ - package ovs - - import ( -+ "context" -+ - netv1 "k8s.io/api/networking/v1" - - "github.com/ovn-org/libovsdb/ovsdb" -@@ -235,6 +237,7 @@ type SbClient interface { - } - - type Common interface { -+ Echo(context.Context) error - Transact(method string, operations []ovsdb.Operation) error - GetEntityInfo(entity interface{}) error - } diff --git a/packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff b/packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff new file mode 100644 index 00000000..93edd879 --- /dev/null +++ b/packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff @@ -0,0 +1,109 @@ +diff --git a/pkg/ovn_leader_checker/ovn.go b/pkg/ovn_leader_checker/ovn.go +index 0f86a371d..8ddf7bca6 100755 +--- a/pkg/ovn_leader_checker/ovn.go ++++ b/pkg/ovn_leader_checker/ovn.go +@@ -10,6 +10,7 @@ import ( + "os/exec" + "strconv" + "strings" ++ "sync" + "syscall" + "time" + +@@ -271,19 +272,56 @@ func checkNorthdSvcExist(cfg *Configuration, namespace, svcName string) bool { + return true + } + +-func checkNorthdEpAvailable(ip string) bool { +- address := net.JoinHostPort(ip, OvnNorthdPort) +- conn, err := net.DialTimeout("tcp", address, 3*time.Second) +- if err != nil { +- klog.Errorf("failed to connect to northd leader %s, err: %v", ip, err) +- failCount++ +- if failCount >= MaxFailCount { +- return false +- } +- } else { ++func checkNorthdEpAvailable(ips ...string) bool { ++ var wg sync.WaitGroup ++ success := make(chan struct{}, 1) ++ failure := make(chan struct{}) ++ ctx, cancel := context.WithCancel(context.Background()) ++ defer cancel() ++ d := net.Dialer{Timeout: 3 * time.Second} ++ for _, ip := range ips { ++ address := net.JoinHostPort(ip, OvnNorthdPort) ++ wg.Add(1) ++ go func() { ++ defer wg.Done() ++ conn, err := d.DialContext(ctx, "tcp", address) ++ if err != nil { ++ klog.Errorf("failed to connect to northd leader %s, err: %v", ip, err) ++ return ++ } else { ++ defer conn.Close() ++ select { ++ case success <- struct{}{}: ++ klog.V(5).Infof("succeed to connect to northd leader %s", ip) ++ cancel() ++ default: ++ // someone else already succeeded ++ } ++ } ++ }() ++ } ++ go func() { ++ wg.Wait() ++ close(failure) ++ }() ++ select { ++ case <-success: + failCount = 0 +- klog.V(5).Infof("succeed to connect to northd leader %s", ip) +- _ = conn.Close() ++ return true ++ case <-failure: ++ // if the last groroutine is the one to succeed, ++ // there's a small chance that failure is selected, ++ // this is a guard for this ++ select { ++ case <-success: ++ failCount = 0 ++ return true ++ default: ++ failCount++ ++ if failCount >= MaxFailCount { ++ return false ++ } ++ } + } + return true + } +@@ -295,6 +333,8 @@ func checkNorthdEpAlive(cfg *Configuration, namespace, service string) bool { + return false + } + ++ addresses := []string{} ++ + for _, eps := range epsList.Items { + for _, ep := range eps.Endpoints { + if (ep.Conditions.Ready != nil && !*ep.Conditions.Ready) || len(ep.Addresses) == 0 { +@@ -303,12 +343,15 @@ func checkNorthdEpAlive(cfg *Configuration, namespace, service string) bool { + + // Found an address, check its availability. We only need one. + klog.V(5).Infof("found address %s in endpoint slice %s/%s for service %s, checking availability", ep.Addresses[0], eps.Namespace, eps.Name, service) +- return checkNorthdEpAvailable(ep.Addresses[0]) ++ addresses = append(addresses, ep.Addresses[0]) // Addresses are fungible by k8s API design + } + } + +- klog.V(5).Infof("no address found in any endpoint slices for service %s/%s", namespace, service) +- return false ++ if len(addresses) == 0 { ++ klog.V(5).Infof("no address found in any endpoint slices for service %s/%s", namespace, service) ++ return false ++ } ++ return checkNorthdEpAvailable(addresses...) + } + + func compactOvnDatabase(db string) {