diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 435225206..0103c5d9a 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - go: ["1.25"] + go: ["1.26"] name: Go ${{ matrix.go }} steps: - name: Checkout Metal LB Operator diff --git a/Dockerfile b/Dockerfile index d5dfab102..60a56ceac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.2 -FROM --platform=$BUILDPLATFORM docker.io/golang:1.25.7 AS builder +FROM --platform=$BUILDPLATFORM docker.io/golang:1.26.8 AS builder ARG GIT_COMMIT=dev ARG GIT_BRANCH=dev diff --git a/api/v1beta1/metallb_types.go b/api/v1beta1/metallb_types.go index a67867f61..0de8466aa 100644 --- a/api/v1beta1/metallb_types.go +++ b/api/v1beta1/metallb_types.go @@ -100,6 +100,17 @@ type MetalLBSpec struct { // The specific frr-k8s configuration FRRK8SConfig *FRRK8SConfig `json:"frrk8sConfig,omitempty"` + + // SpeakerGratuitousARPInterval is the interval at which the MetalLB speaker + // sends periodic gratuitous ARP / unsolicited NDP announcements for + // L2-advertised services. When unset or set to 0, periodic announcements + // are disabled and only the announcements triggered by service events are + // sent. The value must be a valid Go duration string (e.g. "30s", "1m") + // and, when non-zero, must be at least 1s to avoid flooding the network + // with announcements. + // +optional + // +kubebuilder:validation:XValidation:rule="duration(self) == duration('0s') || duration(self) >= duration('1s')",message="speakerGratuitousARPInterval must be 0 or at least 1s" + SpeakerGratuitousARPInterval *metav1.Duration `json:"speakerGratuitousARPInterval,omitempty"` } type FRRK8SConfig struct { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index eeb86fda7..b104072b9 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -23,7 +23,7 @@ package v1beta1 import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -183,6 +183,11 @@ func (in *MetalLBSpec) DeepCopyInto(out *MetalLBSpec) { *out = new(FRRK8SConfig) (*in).DeepCopyInto(*out) } + if in.SpeakerGratuitousARPInterval != nil { + in, out := &in.SpeakerGratuitousARPInterval, &out.SpeakerGratuitousARPInterval + *out = new(metav1.Duration) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetalLBSpec. diff --git a/bin/metallb-operator.yaml b/bin/metallb-operator.yaml index 1a312d8fa..a11883644 100644 --- a/bin/metallb-operator.yaml +++ b/bin/metallb-operator.yaml @@ -9,7 +9,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: bfdprofiles.metallb.io spec: group: metallb.io @@ -130,7 +130,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: bgpadvertisements.metallb.io spec: group: metallb.io @@ -411,7 +411,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: bgppeers.metallb.io spec: conversion: @@ -874,7 +874,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: communities.metallb.io spec: group: metallb.io @@ -939,7 +939,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: configurationstates.metallb.io spec: group: metallb.io @@ -1703,7 +1703,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: ipaddresspools.metallb.io spec: group: metallb.io @@ -1941,7 +1941,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: l2advertisements.metallb.io spec: group: metallb.io @@ -4403,6 +4403,19 @@ spec: description: Define container runtime configuration class type: string type: object + speakerGratuitousARPInterval: + description: |- + SpeakerGratuitousARPInterval is the interval at which the MetalLB speaker + sends periodic gratuitous ARP / unsolicited NDP announcements for + L2-advertised services. When unset or set to 0, periodic announcements + are disabled and only the announcements triggered by service events are + sent. The value must be a valid Go duration string (e.g. "30s", "1m") + and, when non-zero, must be at least 1s to avoid flooding the network + with announcements. + type: string + x-kubernetes-validations: + - message: speakerGratuitousARPInterval must be 0 or at least 1s + rule: duration(self) == duration('0s') || duration(self) >= duration('1s') speakerTolerations: description: |- tolerations is a list of tolerations applied to MetalLB speaker @@ -4517,7 +4530,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: servicebgpstatuses.metallb.io spec: group: metallb.io @@ -4603,7 +4616,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: servicel2statuses.metallb.io spec: group: metallb.io diff --git a/bindata/deployment/helm/metallb/README.md b/bindata/deployment/helm/metallb/README.md index 15985b089..0aabac02d 100644 --- a/bindata/deployment/helm/metallb/README.md +++ b/bindata/deployment/helm/metallb/README.md @@ -49,6 +49,7 @@ Kubernetes: `>= 1.19.0-0` | controller.readinessProbe.successThreshold | int | `1` | | | controller.readinessProbe.timeoutSeconds | int | `1` | | | controller.resources | object | `{}` | | +| controller.revisionHistoryLimit | int | `10` | | | controller.runtimeClassName | string | `""` | | | controller.securityContext.fsGroup | int | `65534` | | | controller.securityContext.runAsNonRoot | bool | `true` | | @@ -124,13 +125,17 @@ Kubernetes: `>= 1.19.0-0` | speaker.enabled | bool | `true` | | | speaker.excludeInterfaces.enabled | bool | `true` | | | speaker.extraContainers | list | `[]` | | +| speaker.frr.dockerStartPath | string | `"/usr/lib/frr/docker-start"` | Path to the docker-start script inside the FRR container. Override this when using an FRR image (e.g. Docker Hardened Images) that places docker-start at a different location. | | speaker.frr.enabled | bool | `false` | | | speaker.frr.image.pullPolicy | string | `nil` | | | speaker.frr.image.repository | string | `"quay.io/frrouting/frr"` | | | speaker.frr.image.tag | string | `"10.5.3"` | | | speaker.frr.metricsPort | int | `9121` | | | speaker.frr.resources | object | `{}` | | +| speaker.frr.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"add":["NET_ADMIN","NET_RAW","SYS_ADMIN","NET_BIND_SERVICE"]},"readOnlyRootFilesystem":true}` | Security context for the FRR container. | +| speaker.frr.tiniPath | string | `"/sbin/tini"` | Path to the tini binary inside the FRR container. Override this when using an FRR image (e.g. Docker Hardened Images) that places tini at a different location. | | speaker.frrMetrics.resources | object | `{}` | | +| speaker.gratuitousARPInterval | string | `nil` | Interval in seconds for periodic gratuitous ARP/NDP announcements. Unset or 0 (default) disables periodic announcements. | | speaker.ignoreExcludeLB | bool | `false` | | | speaker.image.pullPolicy | string | `nil` | | | speaker.image.repository | string | `"quay.io/metallb/speaker"` | | @@ -163,6 +168,7 @@ Kubernetes: `>= 1.19.0-0` | speaker.readinessProbe.timeoutSeconds | int | `1` | | | speaker.reloader.resources | object | `{}` | | | speaker.resources | object | `{}` | | +| speaker.revisionHistoryLimit | int | `10` | | | speaker.runtimeClassName | string | `""` | | | speaker.securityContext | object | `{}` | | | speaker.serviceAccount.annotations | object | `{}` | | @@ -181,4 +187,4 @@ Kubernetes: `>= 1.19.0-0` | tls.speakerMetricsTLSSecret | string | `""` | The name of the secret to be mounted in the speaker pod to provide TLS certificates for metrics endpoints. If not present, a self-signed certificate is auto-generated. | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0) +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/bindata/deployment/helm/metallb/templates/controller.yaml b/bindata/deployment/helm/metallb/templates/controller.yaml index 10053c777..fada472b4 100644 --- a/bindata/deployment/helm/metallb/templates/controller.yaml +++ b/bindata/deployment/helm/metallb/templates/controller.yaml @@ -14,6 +14,7 @@ spec: {{- if .Values.controller.strategy }} strategy: {{- toYaml .Values.controller.strategy | nindent 4 }} {{- end }} + revisionHistoryLimit: {{ .Values.controller.revisionHistoryLimit }} selector: matchLabels: {{- include "metallb.selectorLabels" . | nindent 6 }} diff --git a/bindata/deployment/helm/metallb/templates/servicemonitor.yaml b/bindata/deployment/helm/metallb/templates/servicemonitor.yaml index 60be10e76..9fdab0b25 100644 --- a/bindata/deployment/helm/metallb/templates/servicemonitor.yaml +++ b/bindata/deployment/helm/metallb/templates/servicemonitor.yaml @@ -64,11 +64,15 @@ spec: apiVersion: v1 kind: Service metadata: + {{- if or .Values.prometheus.scrapeAnnotations .Values.prometheus.serviceMonitor.speaker.annotations }} annotations: + {{- if .Values.prometheus.scrapeAnnotations }} prometheus.io/scrape: "true" prometheus.io/scheme: "https" - {{- if .Values.prometheus.serviceMonitor.speaker.annotations }} -{{ toYaml .Values.prometheus.serviceMonitor.speaker.annotations | indent 4 }} + {{- end }} + {{- if .Values.prometheus.serviceMonitor.speaker.annotations }} + {{- toYaml .Values.prometheus.serviceMonitor.speaker.annotations | nindent 4 }} + {{- end }} {{- end }} labels: name: speaker-monitor-service @@ -139,11 +143,15 @@ spec: apiVersion: v1 kind: Service metadata: + {{- if or .Values.prometheus.scrapeAnnotations .Values.prometheus.serviceMonitor.controller.annotations }} annotations: + {{- if .Values.prometheus.scrapeAnnotations }} prometheus.io/scrape: "true" prometheus.io/scheme: "https" - {{- if .Values.prometheus.serviceMonitor.controller.annotations }} -{{ toYaml .Values.prometheus.serviceMonitor.controller.annotations | indent 4 }} + {{- end }} + {{- if .Values.prometheus.serviceMonitor.controller.annotations }} + {{- toYaml .Values.prometheus.serviceMonitor.controller.annotations | nindent 4 }} + {{- end }} {{- end }} labels: name: controller-monitor-service diff --git a/bindata/deployment/helm/metallb/templates/speaker.yaml b/bindata/deployment/helm/metallb/templates/speaker.yaml index f0df774d4..b1b95fe50 100644 --- a/bindata/deployment/helm/metallb/templates/speaker.yaml +++ b/bindata/deployment/helm/metallb/templates/speaker.yaml @@ -138,6 +138,7 @@ spec: {{- if .Values.speaker.updateStrategy }} updateStrategy: {{- toYaml .Values.speaker.updateStrategy | nindent 4 }} {{- end }} + revisionHistoryLimit: {{ .Values.speaker.revisionHistoryLimit }} selector: matchLabels: {{- include "metallb.selectorLabels" . | nindent 6 }} @@ -287,6 +288,9 @@ spec: {{- if .Values.speaker.bgpDebounceTimeout }} - --bgp-debounce-timeout={{ .Values.speaker.bgpDebounceTimeout }} {{- end }} + {{- if .Values.speaker.gratuitousARPInterval }} + - --gratuitous-arp-interval={{ .Values.speaker.gratuitousARPInterval }} + {{- end }} {{- if .Values.frrk8s.external }} - --frrk8s-namespace={{ required "namespace is required when frrk8s is external" .Values.frrk8s.namespace }} {{- if .Values.frrk8s.secretPassthrough }} @@ -419,15 +423,10 @@ spec: {{- end }} {{- if .Values.speaker.frr.enabled }} - name: frr + {{- if .Values.speaker.frr.securityContext }} securityContext: - readOnlyRootFilesystem: true - allowPrivilegeEscalation: false - capabilities: - add: - - NET_ADMIN - - NET_RAW - - SYS_ADMIN - - NET_BIND_SERVICE + {{- toYaml .Values.speaker.frr.securityContext | nindent 10 }} + {{- end }} image: {{ .Values.speaker.frr.image.repository }}:{{ .Values.speaker.frr.image.tag | default .Chart.AppVersion }} {{- if .Values.speaker.frr.image.pullPolicy }} imagePullPolicy: {{ .Values.speaker.frr.image.pullPolicy }} @@ -445,9 +444,9 @@ spec: - name: frr-lib mountPath: /var/lib/frr command: - - /bin/sh - - -c - - /sbin/tini -- /usr/lib/frr/docker-start + - {{ .Values.speaker.frr.tiniPath | quote }} + - -- + - {{ .Values.speaker.frr.dockerStartPath | quote }} {{- with .Values.speaker.frr.resources }} resources: {{- toYaml . | nindent 12 }} diff --git a/bindata/deployment/helm/metallb/values.schema.json b/bindata/deployment/helm/metallb/values.schema.json index 617045732..743212cba 100644 --- a/bindata/deployment/helm/metallb/values.schema.json +++ b/bindata/deployment/helm/metallb/values.schema.json @@ -307,6 +307,10 @@ }, "required": [ "type" ] }, + "revisionHistoryLimit": { + "type": "integer", + "minimum": 0 + }, "command" : { "type": "string" }, @@ -375,6 +379,10 @@ }, "required": [ "type" ] }, + "revisionHistoryLimit": { + "type": "integer", + "minimum": 0 + }, "runtimeClassName": { "type": "string" }, @@ -394,7 +402,10 @@ "image": { "$ref": "#/definitions/component/properties/image" }, "metricsPort": { "type": "integer" }, "secureMetricsPort": { "type": "integer" }, - "resources:": { "type": "object" } + "resources:": { "type": "object" }, + "tiniPath": { "type": "string" }, + "dockerStartPath": { "type": "string" }, + "securityContext": { "type": "object" } }, "required": [ "enabled" ] }, diff --git a/bindata/deployment/helm/metallb/values.yaml b/bindata/deployment/helm/metallb/values.yaml index 00d8c7002..d62c3d66a 100644 --- a/bindata/deployment/helm/metallb/values.yaml +++ b/bindata/deployment/helm/metallb/values.yaml @@ -80,7 +80,7 @@ prometheus: # - sourceLabels: [__meta_kubernetes_pod_node_name] # separator: ; # regex: ^(.*)$ - # target_label: nodename + # targetLabel: nodename # replacement: $1 # action: replace @@ -131,7 +131,7 @@ prometheus: # - sourceLabels: [__meta_kubernetes_pod_node_name] # separator: ; # regex: ^(.*)$ - # target_label: nodename + # targetLabel: nodename # replacement: $1 # action: replace @@ -210,6 +210,7 @@ controller: ## maxSurge: 25% ## maxUnavailable: 25% ## + revisionHistoryLimit: 10 strategy: type: RollingUpdate serviceAccount: @@ -274,6 +275,8 @@ speaker: ignoreExcludeLB: false # -- BGP debounce timeout for FRR configuration reloads, in milliseconds. Only applies when BGP type is frr. Default (when unset) is 3000 ms. This feature is experimental bgpDebounceTimeout: null + # -- Interval in seconds for periodic gratuitous ARP/NDP announcements. Unset or 0 (default) disables periodic announcements. + gratuitousARPInterval: null image: repository: quay.io/metallb/speaker @@ -282,6 +285,7 @@ speaker: ## @param speaker.updateStrategy.type Speaker daemonset strategy type ## ref: https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/ ## + revisionHistoryLimit: 10 updateStrategy: ## StrategyType ## Can be set to RollingUpdate or OnDelete @@ -344,6 +348,24 @@ speaker: pullPolicy: metricsPort: 9121 resources: {} + # -- Path to the tini binary inside the FRR container. Override this + # when using an FRR image (e.g. Docker Hardened Images) that places + # tini at a different location. + tiniPath: /sbin/tini + # -- Path to the docker-start script inside the FRR container. Override + # this when using an FRR image (e.g. Docker Hardened Images) that places + # docker-start at a different location. + dockerStartPath: /usr/lib/frr/docker-start + # -- Security context for the FRR container. + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + add: + - NET_ADMIN + - NET_RAW + - SYS_ADMIN + - NET_BIND_SERVICE reloader: resources: {} diff --git a/bundle/manifests/metallb-operator.clusterserviceversion.yaml b/bundle/manifests/metallb-operator.clusterserviceversion.yaml index 565daea5d..836b43ee7 100644 --- a/bundle/manifests/metallb-operator.clusterserviceversion.yaml +++ b/bundle/manifests/metallb-operator.clusterserviceversion.yaml @@ -433,7 +433,7 @@ metadata: categories: Networking certified: "false" containerImage: quay.io/metallb/metallb-operator - createdAt: "2026-07-28T06:35:36Z" + createdAt: "2026-09-11T08:44:27Z" description: An operator for deploying MetalLB on a kubernetes cluster. operators.operatorframework.io/builder: operator-sdk-v1.40.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 diff --git a/bundle/manifests/metallb.io_bfdprofiles.yaml b/bundle/manifests/metallb.io_bfdprofiles.yaml index 57d20e62c..88d96be40 100644 --- a/bundle/manifests/metallb.io_bfdprofiles.yaml +++ b/bundle/manifests/metallb.io_bfdprofiles.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: bfdprofiles.metallb.io spec: diff --git a/bundle/manifests/metallb.io_bgpadvertisements.yaml b/bundle/manifests/metallb.io_bgpadvertisements.yaml index 4ca4036e8..0b242a8eb 100644 --- a/bundle/manifests/metallb.io_bgpadvertisements.yaml +++ b/bundle/manifests/metallb.io_bgpadvertisements.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: bgpadvertisements.metallb.io spec: diff --git a/bundle/manifests/metallb.io_bgppeers.yaml b/bundle/manifests/metallb.io_bgppeers.yaml index 7d02a240d..d9d6acf6f 100644 --- a/bundle/manifests/metallb.io_bgppeers.yaml +++ b/bundle/manifests/metallb.io_bgppeers.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: bgppeers.metallb.io spec: diff --git a/bundle/manifests/metallb.io_communities.yaml b/bundle/manifests/metallb.io_communities.yaml index a797d80af..b8821fe5a 100644 --- a/bundle/manifests/metallb.io_communities.yaml +++ b/bundle/manifests/metallb.io_communities.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: communities.metallb.io spec: diff --git a/bundle/manifests/metallb.io_configurationstates.yaml b/bundle/manifests/metallb.io_configurationstates.yaml index 3d2edbe9e..97665bf17 100644 --- a/bundle/manifests/metallb.io_configurationstates.yaml +++ b/bundle/manifests/metallb.io_configurationstates.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: configurationstates.metallb.io spec: diff --git a/bundle/manifests/metallb.io_ipaddresspools.yaml b/bundle/manifests/metallb.io_ipaddresspools.yaml index 286673a31..8b0ddfdc1 100644 --- a/bundle/manifests/metallb.io_ipaddresspools.yaml +++ b/bundle/manifests/metallb.io_ipaddresspools.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: ipaddresspools.metallb.io spec: diff --git a/bundle/manifests/metallb.io_l2advertisements.yaml b/bundle/manifests/metallb.io_l2advertisements.yaml index 2fb9181b8..6c3765bb3 100644 --- a/bundle/manifests/metallb.io_l2advertisements.yaml +++ b/bundle/manifests/metallb.io_l2advertisements.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: l2advertisements.metallb.io spec: diff --git a/bundle/manifests/metallb.io_metallbs.yaml b/bundle/manifests/metallb.io_metallbs.yaml index 2d0183542..b58d02d5c 100644 --- a/bundle/manifests/metallb.io_metallbs.yaml +++ b/bundle/manifests/metallb.io_metallbs.yaml @@ -2146,6 +2146,19 @@ spec: description: Define container runtime configuration class type: string type: object + speakerGratuitousARPInterval: + description: |- + SpeakerGratuitousARPInterval is the interval at which the MetalLB speaker + sends periodic gratuitous ARP / unsolicited NDP announcements for + L2-advertised services. When unset or set to 0, periodic announcements + are disabled and only the announcements triggered by service events are + sent. The value must be a valid Go duration string (e.g. "30s", "1m") + and, when non-zero, must be at least 1s to avoid flooding the network + with announcements. + type: string + x-kubernetes-validations: + - message: speakerGratuitousARPInterval must be 0 or at least 1s + rule: duration(self) == duration('0s') || duration(self) >= duration('1s') speakerTolerations: description: |- tolerations is a list of tolerations applied to MetalLB speaker diff --git a/bundle/manifests/metallb.io_servicebgpstatuses.yaml b/bundle/manifests/metallb.io_servicebgpstatuses.yaml index af1618763..b200cdf0c 100644 --- a/bundle/manifests/metallb.io_servicebgpstatuses.yaml +++ b/bundle/manifests/metallb.io_servicebgpstatuses.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: servicebgpstatuses.metallb.io spec: diff --git a/bundle/manifests/metallb.io_servicel2statuses.yaml b/bundle/manifests/metallb.io_servicel2statuses.yaml index bebacbcea..f64e6e10b 100644 --- a/bundle/manifests/metallb.io_servicel2statuses.yaml +++ b/bundle/manifests/metallb.io_servicel2statuses.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 creationTimestamp: null name: servicel2statuses.metallb.io spec: diff --git a/config/crd/bases/metallb.io_bfdprofiles.yaml b/config/crd/bases/metallb.io_bfdprofiles.yaml index 4e5945125..7565018ba 100644 --- a/config/crd/bases/metallb.io_bfdprofiles.yaml +++ b/config/crd/bases/metallb.io_bfdprofiles.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: bfdprofiles.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_bgpadvertisements.yaml b/config/crd/bases/metallb.io_bgpadvertisements.yaml index 62cb8595a..5286bce70 100644 --- a/config/crd/bases/metallb.io_bgpadvertisements.yaml +++ b/config/crd/bases/metallb.io_bgpadvertisements.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: bgpadvertisements.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_bgppeers.yaml b/config/crd/bases/metallb.io_bgppeers.yaml index 9fb7ed353..e2553d493 100644 --- a/config/crd/bases/metallb.io_bgppeers.yaml +++ b/config/crd/bases/metallb.io_bgppeers.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: bgppeers.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_communities.yaml b/config/crd/bases/metallb.io_communities.yaml index 00d7df11e..af1509e55 100644 --- a/config/crd/bases/metallb.io_communities.yaml +++ b/config/crd/bases/metallb.io_communities.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: communities.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_configurationstates.yaml b/config/crd/bases/metallb.io_configurationstates.yaml index d60e86dc1..62f06b6d8 100644 --- a/config/crd/bases/metallb.io_configurationstates.yaml +++ b/config/crd/bases/metallb.io_configurationstates.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: configurationstates.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_ipaddresspools.yaml b/config/crd/bases/metallb.io_ipaddresspools.yaml index 5182f8ccd..7cf9be19b 100644 --- a/config/crd/bases/metallb.io_ipaddresspools.yaml +++ b/config/crd/bases/metallb.io_ipaddresspools.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: ipaddresspools.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_l2advertisements.yaml b/config/crd/bases/metallb.io_l2advertisements.yaml index e8ba5b12c..8f66fa406 100644 --- a/config/crd/bases/metallb.io_l2advertisements.yaml +++ b/config/crd/bases/metallb.io_l2advertisements.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: l2advertisements.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_metallbs.yaml b/config/crd/bases/metallb.io_metallbs.yaml index b6c7146c7..5d9be3871 100644 --- a/config/crd/bases/metallb.io_metallbs.yaml +++ b/config/crd/bases/metallb.io_metallbs.yaml @@ -2146,6 +2146,19 @@ spec: description: Define container runtime configuration class type: string type: object + speakerGratuitousARPInterval: + description: |- + SpeakerGratuitousARPInterval is the interval at which the MetalLB speaker + sends periodic gratuitous ARP / unsolicited NDP announcements for + L2-advertised services. When unset or set to 0, periodic announcements + are disabled and only the announcements triggered by service events are + sent. The value must be a valid Go duration string (e.g. "30s", "1m") + and, when non-zero, must be at least 1s to avoid flooding the network + with announcements. + type: string + x-kubernetes-validations: + - message: speakerGratuitousARPInterval must be 0 or at least 1s + rule: duration(self) == duration('0s') || duration(self) >= duration('1s') speakerTolerations: description: |- tolerations is a list of tolerations applied to MetalLB speaker diff --git a/config/crd/bases/metallb.io_servicebgpstatuses.yaml b/config/crd/bases/metallb.io_servicebgpstatuses.yaml index 8f20dcbb1..6b2b4d870 100644 --- a/config/crd/bases/metallb.io_servicebgpstatuses.yaml +++ b/config/crd/bases/metallb.io_servicebgpstatuses.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: servicebgpstatuses.metallb.io spec: group: metallb.io diff --git a/config/crd/bases/metallb.io_servicel2statuses.yaml b/config/crd/bases/metallb.io_servicel2statuses.yaml index 4def283e0..2ba2c1d89 100644 --- a/config/crd/bases/metallb.io_servicel2statuses.yaml +++ b/config/crd/bases/metallb.io_servicel2statuses.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.22.0 name: servicel2statuses.metallb.io spec: group: metallb.io diff --git a/go.mod b/go.mod index 5ca850860..b01c98661 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/metallb/metallb-operator -go 1.25.0 +go 1.26.0 -toolchain go1.25.7 +toolchain go1.26.8 require ( github.com/Masterminds/semver v1.5.0 diff --git a/hack/metallb_ref.txt b/hack/metallb_ref.txt index 7a0b099a1..0aade7ec9 100644 --- a/hack/metallb_ref.txt +++ b/hack/metallb_ref.txt @@ -1 +1 @@ -44ee0904c4ed2ea6a7637efdafec598797e2ae78 +7dff5b4c1094fbe3802a964256bf23df6d102d96 diff --git a/pkg/helm/metallb.go b/pkg/helm/metallb.go index 2435eee9e..eb0de7ecc 100644 --- a/pkg/helm/metallb.go +++ b/pkg/helm/metallb.go @@ -17,6 +17,8 @@ limitations under the License. package helm import ( + "time" + metallbv1beta1 "github.com/metallb/metallb-operator/api/v1beta1" "github.com/metallb/metallb-operator/pkg/openshift" "github.com/metallb/metallb-operator/pkg/params" @@ -362,6 +364,9 @@ func speakerValues(envConfig params.EnvConfig, crdConfig *metallbv1beta1.MetalLB speakerValueMap["podAnnotations"] = toInterfaceMap(otherConfigs.Annotations) } } + if crdConfig.Spec.SpeakerGratuitousARPInterval != nil && crdConfig.Spec.SpeakerGratuitousARPInterval.Duration >= time.Second { + speakerValueMap["gratuitousARPInterval"] = crdConfig.Spec.SpeakerGratuitousARPInterval.Duration.String() + } return speakerValueMap } diff --git a/pkg/helm/metallb_test.go b/pkg/helm/metallb_test.go index 1a8a51752..b08c96dd4 100644 --- a/pkg/helm/metallb_test.go +++ b/pkg/helm/metallb_test.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/google/go-cmp/cmp" metallbv1beta1 "github.com/metallb/metallb-operator/api/v1beta1" @@ -280,6 +281,59 @@ func TestSecretPassthrough(t *testing.T) { } } +func TestSpeakerGratuitousARPInterval(t *testing.T) { + tests := []struct { + name string + interval *metav1.Duration + expectArg string + expectSet bool + }{ + {"unset", nil, "", false}, + {"set", &metav1.Duration{Duration: 5 * time.Second}, "--gratuitous-arp-interval=5s", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGomegaWithT(t) + chart, err := NewMetalLBChart(metalLBChartPath, metalLBChartName, MetalLBTestNameSpace, nil) + g.Expect(err).To(BeNil()) + + metallb := &metallbv1beta1.MetalLB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "metallb", + Namespace: MetalLBTestNameSpace, + }, + Spec: metallbv1beta1.MetalLBSpec{ + SpeakerGratuitousARPInterval: tt.interval, + }, + } + + objs, err := chart.Objects(defaultEnvConfig, metallb) + g.Expect(err).To(BeNil()) + var speakerFound bool + for _, obj := range objs { + if obj.GetKind() == "DaemonSet" && obj.GetName() == speakerDaemonSet { + speaker := appsv1.DaemonSet{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &speaker) + g.Expect(err).To(BeNil()) + for _, container := range speaker.Spec.Template.Spec.Containers { + if container.Name == "speaker" { + if tt.expectSet { + g.Expect(container.Args).To(ContainElement(tt.expectArg)) + } else { + for _, a := range container.Args { + g.Expect(a).NotTo(HavePrefix("--gratuitous-arp-interval")) + } + } + speakerFound = true + } + } + } + } + g.Expect(speakerFound).To(BeTrue()) + }) + } +} + func TestParseOCPSecureMetrics(t *testing.T) { g := NewGomegaWithT(t) diff --git a/pkg/helm/testdata/ocp-metrics-controller.golden b/pkg/helm/testdata/ocp-metrics-controller.golden index 7bb721b37..878b4cd49 100644 --- a/pkg/helm/testdata/ocp-metrics-controller.golden +++ b/pkg/helm/testdata/ocp-metrics-controller.golden @@ -13,6 +13,7 @@ "namespace": "metallb-test-namespace" }, "spec": { + "revisionHistoryLimit": 10, "selector": { "matchLabels": { "app": "metallb", diff --git a/pkg/helm/testdata/ocp-metrics-speaker.golden b/pkg/helm/testdata/ocp-metrics-speaker.golden index 66e25a7eb..afcd84caa 100644 --- a/pkg/helm/testdata/ocp-metrics-speaker.golden +++ b/pkg/helm/testdata/ocp-metrics-speaker.golden @@ -13,6 +13,7 @@ "namespace": "metallb-test-namespace" }, "spec": { + "revisionHistoryLimit": 10, "selector": { "matchLabels": { "app": "metallb", @@ -180,9 +181,9 @@ }, { "command": [ - "/bin/sh", - "-c", - "/sbin/tini -- /usr/lib/frr/docker-start" + "/sbin/tini", + "--", + "/usr/lib/frr/docker-start" ], "env": [ { diff --git a/pkg/helm/testdata/vanilla-metrics-speaker.golden b/pkg/helm/testdata/vanilla-metrics-speaker.golden index bf1d846b6..073fce017 100644 --- a/pkg/helm/testdata/vanilla-metrics-speaker.golden +++ b/pkg/helm/testdata/vanilla-metrics-speaker.golden @@ -13,6 +13,7 @@ "namespace": "metallb-test-namespace" }, "spec": { + "revisionHistoryLimit": 10, "selector": { "matchLabels": { "app": "metallb", @@ -171,9 +172,9 @@ }, { "command": [ - "/bin/sh", - "-c", - "/sbin/tini -- /usr/lib/frr/docker-start" + "/sbin/tini", + "--", + "/usr/lib/frr/docker-start" ], "env": [ {