[kube-ovn] Update and patch Kube-OVN

This patch updates Kube-OVN to 1.14.5 and patches the northd leader
check to test again all northd endpoints instead of just the first one
marked as ready.

```release-note
[kube-ovn, fix] Update Kube-OVN and improve northd leader detection.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
This commit is contained in:
Timofei Larkin
2025-08-28 18:47:56 +03:00
parent 89c80a8178
commit 6b9b700177
16 changed files with 635 additions and 276 deletions

View File

@@ -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) && \

View File

@@ -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"

View File

@@ -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 -}}

View File

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

View File

@@ -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:

View File

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

View File

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

View File

@@ -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:

View File

@@ -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 }}

View File

@@ -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:

View File

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

View File

@@ -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 }}"
enable-vpc-nat-gw: "{{ .Values.func.ENABLE_NAT_GW }}"

View File

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

View File

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

View File

@@ -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
}

View File

@@ -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) {