Repository: metallb/metallb
Pull request #2180 (2023-11-26 10:58:59)

E2E: Enable frr-k8s mode
We adjust the e2e tests to be able to run against an frr-k8s mode deployment, this ensures us we don't regress from what is working on the other modes.


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 49f9e1ed..e4aa6f8d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -218,12 +218,6 @@ jobs:
         deployment: [manifests, helm]
         prometheus: [disabled, enabled]
         exclude:
-        - bgp-type: frr-k8s
-          prometheus: enabled
-        - bgp-type: frr-k8s
-          ip-family: dual
-        - bgp-type: frr-k8s
-          ip-family: ipv6
         - ip-family: ipv6
           prometheus: disabled
         - ip-family: dual
@@ -259,17 +253,17 @@ jobs:
           SKIP="none"
           WITH_VRF="--with-vrf"
           FOCUS=""
-          if [ "${{ matrix.bgp-type }}" == "native" ]; then SKIP="$SKIP|FRR|BFD|VRF|DUALSTACK"; WITH_VRF=""; fi
+          if [ "${{ matrix.bgp-type }}" == "native" ]; then SKIP="$SKIP|FRR|FRR-MODE|BFD|VRF|DUALSTACK"; WITH_VRF=""; fi
           if [ "${{ matrix.ip-family }}" == "ipv4" ]; then SKIP="$SKIP|IPV6|DUALSTACK"; fi
           if [ "${{ matrix.ip-family }}" == "dual" ]; then SKIP="$SKIP|IPV6"; fi
           if [ "${{ matrix.ip-family }}" == "ipv6" ]; then SKIP="$SKIP|IPV4|DUALSTACK"; fi
           if [ "${{ matrix.ip-family }}" == "ipv6" ] && [ ${{ matrix.bgp-type }} == "native" ]; then SKIP="$SKIP|BGP"; fi
           if [ "${{ matrix.prometheus }}" == "disabled" ]; then SKIP="$SKIP|metrics"; fi
-          if [ "${{ matrix.bgp-type }}" == "frr-k8s" ]; then FOCUS="--focus="FRRK8s" "; fi # TODO remove when e2etests are adjusted
+          if [ "${{ matrix.bgp-type }}" == "frr-k8s" ]; then SKIP="$SKIP|FRR-MODE"; fi
 
           echo "Skipping $SKIP"
           echo "With VRF $WITH_VRF"
-          sudo -E env "PATH=$PATH" inv e2etest $FOCUS --skip $SKIP $WITH_VRF -e /tmp/kind_logs
+          sudo -E env "PATH=$PATH" inv e2etest $FOCUS --skip $SKIP --bgp-mode ${{ matrix.bgp-type }} $WITH_VRF -e /tmp/kind_logs
 
       - name: Collect Logs
         if: ${{ failure() }}
@@ -409,7 +403,7 @@ jobs:
           spec:
             logLevel: debug
           EOF
-          sudo -E env "PATH=$PATH" inv e2etest --skip "IPV6|DUALSTACK|metrics|L2-interface selector" -e /tmp/kind_logs
+          sudo -E env "PATH=$PATH" inv e2etest --bgp-mode frr --skip "IPV6|DUALSTACK|metrics|L2-interface selector" -e /tmp/kind_logs
 
       - name: Collect Logs
         if: ${{ failure() }}
@@ -445,7 +439,7 @@ jobs:
       - name: E2E
         run: |
           FOCUS="L2.*should work for ExternalTrafficPolicy=Cluster|BGP.*A service of protocol load balancer should work with.*IPV4 - ExternalTrafficPolicyCluster$|validate FRR running configuration"
-          sudo -E env "PATH=$PATH" inv e2etest --focus "$FOCUS" -e /tmp/kind_logs
+          sudo -E env "PATH=$PATH" inv e2etest --bgp-mode frr --focus "$FOCUS" -e /tmp/kind_logs
 
       - name: Collect Logs
         if: ${{ failure() }}
diff --git a/charts/metallb/templates/speaker.yaml b/charts/metallb/templates/speaker.yaml
index 90ba6e88..635aa0a8 100644
--- a/charts/metallb/templates/speaker.yaml
+++ b/charts/metallb/templates/speaker.yaml
@@ -470,6 +470,7 @@ spec:
             readOnly: true
         {{- end }}
       {{ end }}
+      {{- if .Values.speaker.frr.enabled }}
       {{- if .Values.speaker.frr.secureMetricsPort }}
       - name: kube-rbac-proxy-frr
         image: {{ .Values.prometheus.rbacProxy.repository }}:{{ .Values.prometheus.rbacProxy.tag | default .Chart.AppVersion }}
@@ -485,7 +486,7 @@ spec:
         {{- end }}
         ports:
           - containerPort: {{ .Values.speaker.frr.secureMetricsPort }}
-            name: metricshttps
+            name: frrmetricshttps
         env:
           - name: METALLB_HOST
             valueFrom:
@@ -503,6 +504,7 @@ spec:
             readOnly: true
         {{- end }}
       {{ end }}
+      {{- end }}
       {{- if .Values.speaker.extraContainers }}
       {{- toYaml .Values.speaker.extraContainers | nindent 6 }}
       {{- end }}
diff --git a/config/manifests/metallb-frr-k8s-prometheus.yaml b/config/manifests/metallb-frr-k8s-prometheus.yaml
new file mode 100644
index 00000000..a1cd7655
--- /dev/null
+++ b/config/manifests/metallb-frr-k8s-prometheus.yaml
@@ -0,0 +1,3487 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  labels:
+    pod-security.kubernetes.io/audit: privileged
+    pod-security.kubernetes.io/enforce: privileged
+    pod-security.kubernetes.io/warn: privileged
+  name: metallb-system
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: addresspools.metallb.io
+spec:
+  conversion:
+    strategy: Webhook
+    webhook:
+      clientConfig:
+        caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
+        service:
+          name: webhook-service
+          namespace: metallb-system
+          path: /convert
+      conversionReviewVersions:
+      - v1alpha1
+      - v1beta1
+  group: metallb.io
+  names:
+    kind: AddressPool
+    listKind: AddressPoolList
+    plural: addresspools
+    singular: addresspool
+  scope: Namespaced
+  versions:
+  - deprecated: true
+    deprecationWarning: metallb.io v1alpha1 AddressPool is deprecated
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: AddressPool is the Schema for the addresspools API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: AddressPoolSpec defines the desired state of AddressPool.
+            properties:
+              addresses:
+                description: A list of IP address ranges over which MetalLB has authority.
+                  You can list multiple ranges in a single pool, they will all share
+                  the same settings. Each range can be either a CIDR prefix, or an
+                  explicit start-end range of IPs.
+                items:
+                  type: string
+                type: array
+              autoAssign:
+                default: true
+                description: AutoAssign flag used to prevent MetallB from automatic
+                  allocation for a pool.
+                type: boolean
+              bgpAdvertisements:
+                description: When an IP is allocated from this pool, how should it
+                  be translated into BGP announcements?
+                items:
+                  properties:
+                    aggregationLength:
+                      default: 32
+                      description: The aggregation-length advertisement option lets
+                        you “roll up” the /32s into a larger prefix.
+                      format: int32
+                      minimum: 1
+                      type: integer
+                    aggregationLengthV6:
+                      default: 128
+                      description: Optional, defaults to 128 (i.e. no aggregation)
+                        if not specified.
+                      format: int32
+                      type: integer
+                    communities:
+                      description: BGP communities
+                      items:
+                        type: string
+                      type: array
+                    localPref:
+                      description: BGP LOCAL_PREF attribute which is used by BGP best
+                        path algorithm, Path with higher localpref is preferred over
+                        one with lower localpref.
+                      format: int32
+                      type: integer
+                  type: object
+                type: array
+              protocol:
+                description: Protocol can be used to select how the announcement is
+                  done.
+                enum:
+                - layer2
+                - bgp
+                type: string
+            required:
+            - addresses
+            - protocol
+            type: object
+          status:
+            description: AddressPoolStatus defines the observed state of AddressPool.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
+  - deprecated: true
+    deprecationWarning: metallb.io v1beta1 AddressPool is deprecated, consider using
+      IPAddressPool
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: AddressPool represents a pool of IP addresses that can be allocated
+          to LoadBalancer services. AddressPool is deprecated and being replaced by
+          IPAddressPool.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: AddressPoolSpec defines the desired state of AddressPool.
+            properties:
+              addresses:
+                description: A list of IP address ranges over which MetalLB has authority.
+                  You can list multiple ranges in a single pool, they will all share
+                  the same settings. Each range can be either a CIDR prefix, or an
+                  explicit start-end range of IPs.
+                items:
+                  type: string
+                type: array
+              autoAssign:
+                default: true
+                description: AutoAssign flag used to prevent MetallB from automatic
+                  allocation for a pool.
+                type: boolean
+              bgpAdvertisements:
+                description: Drives how an IP allocated from this pool should translated
+                  into BGP announcements.
+                items:
+                  properties:
+                    aggregationLength:
+                      default: 32
+                      description: The aggregation-length advertisement option lets
+                        you “roll up” the /32s into a larger prefix.
+                      format: int32
+                      minimum: 1
+                      type: integer
+                    aggregationLengthV6:
+                      default: 128
+                      description: Optional, defaults to 128 (i.e. no aggregation)
+                        if not specified.
+                      format: int32
+                      type: integer
+                    communities:
+                      description: BGP communities to be associated with the given
+                        advertisement.
+                      items:
+                        type: string
+                      type: array
+                    localPref:
+                      description: BGP LOCAL_PREF attribute which is used by BGP best
+                        path algorithm, Path with higher localpref is preferred over
+                        one with lower localpref.
+                      format: int32
+                      type: integer
+                  type: object
+                type: array
+              protocol:
+                description: Protocol can be used to select how the announcement is
+                  done.
+                enum:
+                - layer2
+                - bgp
+                type: string
+            required:
+            - addresses
+            - protocol
+            type: object
+          status:
+            description: AddressPoolStatus defines the observed state of AddressPool.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: bfdprofiles.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: BFDProfile
+    listKind: BFDProfileList
+    plural: bfdprofiles
+    singular: bfdprofile
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.passiveMode
+      name: Passive Mode
+      type: boolean
+    - jsonPath: .spec.transmitInterval
+      name: Transmit Interval
+      type: integer
+    - jsonPath: .spec.receiveInterval
+      name: Receive Interval
+      type: integer
+    - jsonPath: .spec.detectMultiplier
+      name: Multiplier
+      type: integer
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: BFDProfile represents the settings of the bfd session that can
+          be optionally associated with a BGP session.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: BFDProfileSpec defines the desired state of BFDProfile.
+            properties:
+              detectMultiplier:
+                description: Configures the detection multiplier to determine packet
+                  loss. The remote transmission interval will be multiplied by this
+                  value to determine the connection loss detection timer.
+                format: int32
+                maximum: 255
+                minimum: 2
+                type: integer
+              echoInterval:
+                description: Configures the minimal echo receive transmission interval
+                  that this system is capable of handling in milliseconds. Defaults
+                  to 50ms
+                format: int32
+                maximum: 60000
+                minimum: 10
+                type: integer
+              echoMode:
+                description: Enables or disables the echo transmission mode. This
+                  mode is disabled by default, and not supported on multi hops setups.
+                type: boolean
+              minimumTtl:
+                description: 'For multi hop sessions only: configure the minimum expected
+                  TTL for an incoming BFD control packet.'
+                format: int32
+                maximum: 254
+                minimum: 1
+                type: integer
+              passiveMode:
+                description: 'Mark session as passive: a passive session will not
+                  attempt to start the connection and will wait for control packets
+                  from peer before it begins replying.'
+                type: boolean
+              receiveInterval:
+                description: The minimum interval that this system is capable of receiving
+                  control packets in milliseconds. Defaults to 300ms.
+                format: int32
+                maximum: 60000
+                minimum: 10
+                type: integer
+              transmitInterval:
+                description: The minimum transmission interval (less jitter) that
+                  this system wants to use to send BFD control packets in milliseconds.
+                  Defaults to 300ms
+                format: int32
+                maximum: 60000
+                minimum: 10
+                type: integer
+            type: object
+          status:
+            description: BFDProfileStatus defines the observed state of BFDProfile.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: bgpadvertisements.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: BGPAdvertisement
+    listKind: BGPAdvertisementList
+    plural: bgpadvertisements
+    singular: bgpadvertisement
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.ipAddressPools
+      name: IPAddressPools
+      type: string
+    - jsonPath: .spec.ipAddressPoolSelectors
+      name: IPAddressPool Selectors
+      type: string
+    - jsonPath: .spec.peers
+      name: Peers
+      type: string
+    - jsonPath: .spec.nodeSelectors
+      name: Node Selectors
+      priority: 10
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: BGPAdvertisement allows to advertise the IPs coming from the
+          selected IPAddressPools via BGP, setting the parameters of the BGP Advertisement.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: BGPAdvertisementSpec defines the desired state of BGPAdvertisement.
+            properties:
+              aggregationLength:
+                default: 32
+                description: The aggregation-length advertisement option lets you
+                  “roll up” the /32s into a larger prefix. Defaults to 32. Works for
+                  IPv4 addresses.
+                format: int32
+                minimum: 1
+                type: integer
+              aggregationLengthV6:
+                default: 128
+                description: The aggregation-length advertisement option lets you
+                  “roll up” the /128s into a larger prefix. Defaults to 128. Works
+                  for IPv6 addresses.
+                format: int32
+                type: integer
+              communities:
+                description: The BGP communities to be associated with the announcement.
+                  Each item can be a standard community of the form 1234:1234, a large
+                  community of the form large:1234:1234:1234 or the name of an alias
+                  defined in the Community CRD.
+                items:
+                  type: string
+                type: array
+              ipAddressPoolSelectors:
+                description: A selector for the IPAddressPools which would get advertised
+                  via this advertisement. If no IPAddressPool is selected by this
+                  or by the list, the advertisement is applied to all the IPAddressPools.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              ipAddressPools:
+                description: The list of IPAddressPools to advertise via this advertisement,
+                  selected by name.
+                items:
+                  type: string
+                type: array
+              localPref:
+                description: The BGP LOCAL_PREF attribute which is used by BGP best
+                  path algorithm, Path with higher localpref is preferred over one
+                  with lower localpref.
+                format: int32
+                type: integer
+              nodeSelectors:
+                description: NodeSelectors allows to limit the nodes to announce as
+                  next hops for the LoadBalancer IP. When empty, all the nodes having  are
+                  announced as next hops.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              peers:
+                description: Peers limits the bgppeer to advertise the ips of the
+                  selected pools to. When empty, the loadbalancer IP is announced
+                  to all the BGPPeers configured.
+                items:
+                  type: string
+                type: array
+            type: object
+          status:
+            description: BGPAdvertisementStatus defines the observed state of BGPAdvertisement.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: bgppeers.metallb.io
+spec:
+  conversion:
+    strategy: Webhook
+    webhook:
+      clientConfig:
+        caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
+        service:
+          name: webhook-service
+          namespace: metallb-system
+          path: /convert
+      conversionReviewVersions:
+      - v1beta1
+      - v1beta2
+  group: metallb.io
+  names:
+    kind: BGPPeer
+    listKind: BGPPeerList
+    plural: bgppeers
+    singular: bgppeer
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.peerAddress
+      name: Address
+      type: string
+    - jsonPath: .spec.peerASN
+      name: ASN
+      type: string
+    - jsonPath: .spec.bfdProfile
+      name: BFD Profile
+      type: string
+    - jsonPath: .spec.ebgpMultiHop
+      name: Multi Hops
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: BGPPeer is the Schema for the peers API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: BGPPeerSpec defines the desired state of Peer.
+            properties:
+              bfdProfile:
+                type: string
+              ebgpMultiHop:
+                description: EBGP peer is multi-hops away
+                type: boolean
+              holdTime:
+                description: Requested BGP hold time, per RFC4271.
+                type: string
+              keepaliveTime:
+                description: Requested BGP keepalive time, per RFC4271.
+                type: string
+              myASN:
+                description: AS number to use for the local end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              nodeSelectors:
+                description: Only connect to this peer on nodes that match one of
+                  these selectors.
+                items:
+                  properties:
+                    matchExpressions:
+                      items:
+                        properties:
+                          key:
+                            type: string
+                          operator:
+                            type: string
+                          values:
+                            items:
+                              type: string
+                            minItems: 1
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        - values
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      type: object
+                  type: object
+                type: array
+              password:
+                description: Authentication password for routers enforcing TCP MD5
+                  authenticated sessions
+                type: string
+              peerASN:
+                description: AS number to expect from the remote end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              peerAddress:
+                description: Address to dial when establishing the session.
+                type: string
+              peerPort:
+                description: Port to dial when establishing the session.
+                maximum: 16384
+                minimum: 0
+                type: integer
+              routerID:
+                description: BGP router ID to advertise to the peer
+                type: string
+              sourceAddress:
+                description: Source address to use when establishing the session.
+                type: string
+            required:
+            - myASN
+            - peerASN
+            - peerAddress
+            type: object
+          status:
+            description: BGPPeerStatus defines the observed state of Peer.
+            type: object
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
+  - additionalPrinterColumns:
+    - jsonPath: .spec.peerAddress
+      name: Address
+      type: string
+    - jsonPath: .spec.peerASN
+      name: ASN
+      type: string
+    - jsonPath: .spec.bfdProfile
+      name: BFD Profile
+      type: string
+    - jsonPath: .spec.ebgpMultiHop
+      name: Multi Hops
+      type: string
+    name: v1beta2
+    schema:
+      openAPIV3Schema:
+        description: BGPPeer is the Schema for the peers API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: BGPPeerSpec defines the desired state of Peer.
+            properties:
+              bfdProfile:
+                description: The name of the BFD Profile to be used for the BFD session
+                  associated to the BGP session. If not set, the BFD session won't
+                  be set up.
+                type: string
+              ebgpMultiHop:
+                description: To set if the BGPPeer is multi-hops away. Needed for
+                  FRR mode only.
+                type: boolean
+              holdTime:
+                description: Requested BGP hold time, per RFC4271.
+                type: string
+              keepaliveTime:
+                description: Requested BGP keepalive time, per RFC4271.
+                type: string
+              myASN:
+                description: AS number to use for the local end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              nodeSelectors:
+                description: Only connect to this peer on nodes that match one of
+                  these selectors.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              password:
+                description: Authentication password for routers enforcing TCP MD5
+                  authenticated sessions
+                type: string
+              passwordSecret:
+                description: passwordSecret is name of the authentication secret for
+                  BGP Peer. the secret must be of type "kubernetes.io/basic-auth",
+                  and created in the same namespace as the MetalLB deployment. The
+                  password is stored in the secret as the key "password".
+                properties:
+                  name:
+                    description: name is unique within a namespace to reference a
+                      secret resource.
+                    type: string
+                  namespace:
+                    description: namespace defines the space within which the secret
+                      name must be unique.
+                    type: string
+                type: object
+                x-kubernetes-map-type: atomic
+              peerASN:
+                description: AS number to expect from the remote end of the session.
+                format: int32
+                maximum: 4294967295
+                minimum: 0
+                type: integer
+              peerAddress:
+                description: Address to dial when establishing the session.
+                type: string
+              peerPort:
+                default: 179
+                description: Port to dial when establishing the session.
+                maximum: 16384
+                minimum: 0
+                type: integer
+              routerID:
+                description: BGP router ID to advertise to the peer
+                type: string
+              sourceAddress:
+                description: Source address to use when establishing the session.
+                type: string
+              vrf:
+                description: To set if we want to peer with the BGPPeer using an interface
+                  belonging to a host vrf
+                type: string
+            required:
+            - myASN
+            - peerASN
+            - peerAddress
+            type: object
+          status:
+            description: BGPPeerStatus defines the observed state of Peer.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: communities.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: Community
+    listKind: CommunityList
+    plural: communities
+    singular: community
+  scope: Namespaced
+  versions:
+  - name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: Community is a collection of aliases for communities. Users can
+          define named aliases to be used in the BGPPeer CRD.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: CommunitySpec defines the desired state of Community.
+            properties:
+              communities:
+                items:
+                  properties:
+                    name:
+                      description: The name of the alias for the community.
+                      type: string
+                    value:
+                      description: The BGP community value corresponding to the given
+                        name. Can be a standard community of the form 1234:1234 or
+                        a large community of the form large:1234:1234:1234.
+                      type: string
+                  type: object
+                type: array
+            type: object
+          status:
+            description: CommunityStatus defines the observed state of Community.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.3
+  creationTimestamp: null
+  name: frrconfigurations.frrk8s.metallb.io
+spec:
+  group: frrk8s.metallb.io
+  names:
+    kind: FRRConfiguration
+    listKind: FRRConfigurationList
+    plural: frrconfigurations
+    singular: frrconfiguration
+  scope: Namespaced
+  versions:
+  - name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: FRRConfiguration is a piece of FRR configuration.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: FRRConfigurationSpec defines the desired state of FRRConfiguration.
+            properties:
+              bgp:
+                description: BGP is the configuration related to the BGP protocol.
+                properties:
+                  bfdProfiles:
+                    description: BFDProfiles is the list of bfd profiles to be used
+                      when configuring the neighbors.
+                    items:
+                      description: BFDProfile is the configuration related to the
+                        BFD protocol associated to a BGP session.
+                      properties:
+                        detectMultiplier:
+                          description: Configures the detection multiplier to determine
+                            packet loss. The remote transmission interval will be
+                            multiplied by this value to determine the connection loss
+                            detection timer.
+                          format: int32
+                          maximum: 255
+                          minimum: 2
+                          type: integer
+                        echoInterval:
+                          description: Configures the minimal echo receive transmission
+                            interval that this system is capable of handling in milliseconds.
+                            Defaults to 50ms
+                          format: int32
+                          maximum: 60000
+                          minimum: 10
+                          type: integer
+                        echoMode:
+                          description: Enables or disables the echo transmission mode.
+                            This mode is disabled by default, and not supported on
+                            multi hops setups.
+                          type: boolean
+                        minimumTtl:
+                          description: 'For multi hop sessions only: configure the
+                            minimum expected TTL for an incoming BFD control packet.'
+                          format: int32
+                          maximum: 254
+                          minimum: 1
+                          type: integer
+                        name:
+                          description: The name of the BFD Profile to be referenced
+                            in other parts of the configuration.
+                          type: string
+                        passiveMode:
+                          description: 'Mark session as passive: a passive session
+                            will not attempt to start the connection and will wait
+                            for control packets from peer before it begins replying.'
+                          type: boolean
+                        receiveInterval:
+                          description: The minimum interval that this system is capable
+                            of receiving control packets in milliseconds. Defaults
+                            to 300ms.
+                          format: int32
+                          maximum: 60000
+                          minimum: 10
+                          type: integer
+                        transmitInterval:
+                          description: The minimum transmission interval (less jitter)
+                            that this system wants to use to send BFD control packets
+                            in milliseconds. Defaults to 300ms
+                          format: int32
+                          maximum: 60000
+                          minimum: 10
+                          type: integer
+                      required:
+                      - name
+                      type: object
+                    type: array
+                  routers:
+                    description: Routers is the list of routers we want FRR to configure
+                      (one per VRF).
+                    items:
+                      description: Router represent a neighbor router we want FRR
+                        to connect to.
+                      properties:
+                        asn:
+                          description: ASN is the AS number to use for the local end
+                            of the session.
+                          format: int32
+                          maximum: 4294967295
+                          minimum: 0
+                          type: integer
+                        id:
+                          description: ID is the BGP router ID
+                          type: string
+                        neighbors:
+                          description: Neighbors is the list of neighbors we want
+                            to establish BGP sessions with.
+                          items:
+                            description: Neighbor represents a BGP Neighbor we want
+                              FRR to connect to.
+                            properties:
+                              address:
+                                description: Address is the IP address to establish
+                                  the session with.
+                                type: string
+                              asn:
+                                description: ASN is the AS number to use for the local
+                                  end of the session.
+                                format: int32
+                                maximum: 4294967295
+                                minimum: 0
+                                type: integer
+                              bfdProfile:
+                                description: BFDProfile is the name of the BFD Profile
+                                  to be used for the BFD session associated to the
+                                  BGP session. If not set, the BFD session won't be
+                                  set up.
+                                type: string
+                              ebgpMultiHop:
+                                description: EBGPMultiHop indicates if the BGPPeer
+                                  is multi-hops away.
+                                type: boolean
+                              holdTime:
+                                description: HoldTime is the requested BGP hold time,
+                                  per RFC4271. Defaults to 180s.
+                                type: string
+                              keepaliveTime:
+                                description: KeepaliveTime is the requested BGP keepalive
+                                  time, per RFC4271. Defaults to 60s.
+                                type: string
+                              password:
+                                description: Password to be used for establishing
+                                  the BGP session. Password and PasswordSecret are
+                                  mutually exclusive.
+                                type: string
+                              passwordSecret:
+                                description: PasswordSecret is name of the authentication
+                                  secret for the neighbor. the secret must be of type
+                                  "kubernetes.io/basic-auth", and created in the same
+                                  namespace as the frr-k8s daemon. The password is
+                                  stored in the secret as the key "password". Password
+                                  and PasswordSecret are mutually exclusive.
+                                properties:
+                                  name:
+                                    description: name is unique within a namespace
+                                      to reference a secret resource.
+                                    type: string
+                                  namespace:
+                                    description: namespace defines the space within
+                                      which the secret name must be unique.
+                                    type: string
+                                type: object
+                                x-kubernetes-map-type: atomic
+                              port:
+                                description: Port is the port to dial when establishing
+                                  the session. Defaults to 179.
+                                maximum: 16384
+                                minimum: 0
+                                type: integer
+                              toAdvertise:
+                                description: ToAdvertise represents the list of prefixes
+                                  to advertise to the given neighbor and the associated
+                                  properties.
+                                properties:
+                                  allowed:
+                                    description: Allowed is is the list of prefixes
+                                      allowed to be propagated to this neighbor. They
+                                      must match the prefixes defined in the router.
+                                    properties:
+                                      mode:
+                                        default: filtered
+                                        description: Mode is the mode to use when
+                                          handling the prefixes. When set to "filtered",
+                                          only the prefixes in the given list will
+                                          be allowed. When set to "all", all the prefixes
+                                          configured on the router will be allowed.
+                                        enum:
+                                        - all
+                                        - filtered
+                                        type: string
+                                      prefixes:
+                                        items:
+                                          type: string
+                                        type: array
+                                    type: object
+                                  withCommunity:
+                                    description: PrefixesWithCommunity is a list of
+                                      prefixes that are associated to a bgp community
+                                      when being advertised. The prefixes associated
+                                      to a given local pref must be in the prefixes
+                                      allowed to be advertised.
+                                    items:
+                                      description: CommunityPrefixes is a list of
+                                        prefixes associated to a community.
+                                      properties:
+                                        community:
+                                          description: Community is the community
+                                            associated to the prefixes.
+                                          type: string
+                                        prefixes:
+                                          description: Prefixes is the list of prefixes
+                                            associated to the community.
+                                          format: cidr
+                                          items:
+                                            type: string
+                                          minItems: 1
+                                          type: array
+                                      type: object
+                                    type: array
+                                  withLocalPref:
+                                    description: PrefixesWithLocalPref is a list of
+                                      prefixes that are associated to a local preference
+                                      when being advertised. The prefixes associated
+                                      to a given local pref must be in the prefixes
+                                      allowed to be advertised.
+                                    items:
+                                      description: LocalPrefPrefixes is a list of
+                                        prefixes associated to a local preference.
+                                      properties:
+                                        localPref:
+                                          description: LocalPref is the local preference
+                                            associated to the prefixes.
+                                          format: int32
+                                          type: integer
+                                        prefixes:
+                                          description: Prefixes is the list of prefixes
+                                            associated to the local preference.
+                                          format: cidr
+                                          items:
+                                            type: string
+                                          minItems: 1
+                                          type: array
+                                      type: object
+                                    type: array
+                                type: object
+                              toReceive:
+                                description: ToReceive represents the list of prefixes
+                                  to receive from the given neighbor.
+                                properties:
+                                  allowed:
+                                    description: Allowed is the list of prefixes allowed
+                                      to be received from this neighbor.
+                                    properties:
+                                      mode:
+                                        default: filtered
+                                        description: Mode is the mode to use when
+                                          handling the prefixes. When set to "filtered",
+                                          only the prefixes in the given list will
+                                          be allowed. When set to "all", all the prefixes
+                                          configured on the router will be allowed.
+                                        enum:
+                                        - all
+                                        - filtered
+                                        type: string
+                                      prefixes:
+                                        items:
+                                          description: PrefixSelector is a filter
+                                            of prefixes to receive.
+                                          properties:
+                                            ge:
+                                              description: The prefix length modifier.
+                                                This selector accepts any matching
+                                                prefix with length greater or equal
+                                                the given value.
+                                              format: int32
+                                              maximum: 128
+                                              minimum: 1
+                                              type: integer
+                                            le:
+                                              description: The prefix length modifier.
+                                                This selector accepts any matching
+                                                prefix with length less or equal the
+                                                given value.
+                                              format: int32
+                                              maximum: 128
+                                              minimum: 1
+                                              type: integer
+                                            prefix:
+                                              format: cidr
+                                              type: string
+                                          type: object
+                                        type: array
+                                    type: object
+                                type: object
+                            required:
+                            - address
+                            - asn
+                            type: object
+                          type: array
+                        prefixes:
+                          description: Prefixes is the list of prefixes we want to
+                            advertise from this router instance.
+                          items:
+                            type: string
+                          type: array
+                        vrf:
+                          description: VRF is the host vrf used to establish sessions
+                            from this router.
+                          type: string
+                      required:
+                      - asn
+                      type: object
+                    type: array
+                type: object
+              nodeSelector:
+                description: NodeSelector limits the nodes that will attempt to apply
+                  this config. When specified, the configuration will be considered
+                  only on nodes whose labels match the specified selectors. When it
+                  is not specified all nodes will attempt to apply this config.
+                properties:
+                  matchExpressions:
+                    description: matchExpressions is a list of label selector requirements.
+                      The requirements are ANDed.
+                    items:
+                      description: A label selector requirement is a selector that
+                        contains values, a key, and an operator that relates the key
+                        and values.
+                      properties:
+                        key:
+                          description: key is the label key that the selector applies
+                            to.
+                          type: string
+                        operator:
+                          description: operator represents a key's relationship to
+                            a set of values. Valid operators are In, NotIn, Exists
+                            and DoesNotExist.
+                          type: string
+                        values:
+                          description: values is an array of string values. If the
+                            operator is In or NotIn, the values array must be non-empty.
+                            If the operator is Exists or DoesNotExist, the values
+                            array must be empty. This array is replaced during a strategic
+                            merge patch.
+                          items:
+                            type: string
+                          type: array
+                      required:
+                      - key
+                      - operator
+                      type: object
+                    type: array
+                  matchLabels:
+                    additionalProperties:
+                      type: string
+                    description: matchLabels is a map of {key,value} pairs. A single
+                      {key,value} in the matchLabels map is equivalent to an element
+                      of matchExpressions, whose key field is "key", the operator
+                      is "In", and the values array contains only "value". The requirements
+                      are ANDed.
+                    type: object
+                type: object
+                x-kubernetes-map-type: atomic
+              raw:
+                description: Raw is a snippet of raw frr configuration that gets appended
+                  to the one rendered translating the type safe API.
+                properties:
+                  priority:
+                    description: Priority is the order with this configuration is
+                      appended to the bottom of the rendered configuration. A higher
+                      value means the raw config is appended later in the configuration
+                      file.
+                    type: integer
+                  rawConfig:
+                    description: Config is a raw FRR configuration to be appended
+                      to the configuration rendered via the k8s api.
+                    type: string
+                type: object
+            type: object
+          status:
+            description: FRRConfigurationStatus defines the observed state of FRRConfiguration.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.3
+  creationTimestamp: null
+  name: frrnodestates.frrk8s.metallb.io
+spec:
+  group: frrk8s.metallb.io
+  names:
+    kind: FRRNodeState
+    listKind: FRRNodeStateList
+    plural: frrnodestates
+    singular: frrnodestate
+  scope: Cluster
+  versions:
+  - name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: FRRNodeState exposes the status of the FRR instance running on
+          each node.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: FRRNodeStateSpec defines the desired state of FRRNodeState.
+            type: object
+          status:
+            description: FRRNodeStateStatus defines the observed state of FRRNodeState.
+            properties:
+              desiredConfig:
+                description: DesiredConfig represents the desired FRR config, as the
+                  result of translating all of the `FRRConfiguration`s for the given
+                  node.
+                type: string
+              lastConversionResult:
+                description: LastConversionResult is the status of the last translation
+                  between the `FRRConfiguration`s resources and FRR's configuration,
+                  contains "success" or an error.
+                type: string
+              lastReloadResult:
+                description: LastReloadResult represents the status of the last configuration
+                  update operation by FRR, contains "success" or an error.
+                type: string
+              runningConfig:
+                description: RunningConfig represents the current FRR running config,
+                  which is the configuration the FRR instance is currently running
+                  with.
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: ipaddresspools.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: IPAddressPool
+    listKind: IPAddressPoolList
+    plural: ipaddresspools
+    singular: ipaddresspool
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.autoAssign
+      name: Auto Assign
+      type: boolean
+    - jsonPath: .spec.avoidBuggyIPs
+      name: Avoid Buggy IPs
+      type: boolean
+    - jsonPath: .spec.addresses
+      name: Addresses
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: IPAddressPool represents a pool of IP addresses that can be allocated
+          to LoadBalancer services.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: IPAddressPoolSpec defines the desired state of IPAddressPool.
+            properties:
+              addresses:
+                description: A list of IP address ranges over which MetalLB has authority.
+                  You can list multiple ranges in a single pool, they will all share
+                  the same settings. Each range can be either a CIDR prefix, or an
+                  explicit start-end range of IPs.
+                items:
+                  type: string
+                type: array
+              autoAssign:
+                default: true
+                description: AutoAssign flag used to prevent MetallB from automatic
+                  allocation for a pool.
+                type: boolean
+              avoidBuggyIPs:
+                default: false
+                description: AvoidBuggyIPs prevents addresses ending with .0 and .255
+                  to be used by a pool.
+                type: boolean
+              serviceAllocation:
+                description: AllocateTo makes ip pool allocation to specific namespace
+                  and/or service. The controller will use the pool with lowest value
+                  of priority in case of multiple matches. A pool with no priority
+                  set will be used only if the pools with priority can't be used.
+                  If multiple matching IPAddressPools are available it will check
+                  for the availability of IPs sorting the matching IPAddressPools
+                  by priority, starting from the highest to the lowest. If multiple
+                  IPAddressPools have the same priority, choice will be random.
+                properties:
+                  namespaceSelectors:
+                    description: NamespaceSelectors list of label selectors to select
+                      namespace(s) for ip pool, an alternative to using namespace
+                      list.
+                    items:
+                      description: A label selector is a label query over a set of
+                        resources. The result of matchLabels and matchExpressions
+                        are ANDed. An empty label selector matches all objects. A
+                        null label selector matches no objects.
+                      properties:
+                        matchExpressions:
+                          description: matchExpressions is a list of label selector
+                            requirements. The requirements are ANDed.
+                          items:
+                            description: A label selector requirement is a selector
+                              that contains values, a key, and an operator that relates
+                              the key and values.
+                            properties:
+                              key:
+                                description: key is the label key that the selector
+                                  applies to.
+                                type: string
+                              operator:
+                                description: operator represents a key's relationship
+                                  to a set of values. Valid operators are In, NotIn,
+                                  Exists and DoesNotExist.
+                                type: string
+                              values:
+                                description: values is an array of string values.
+                                  If the operator is In or NotIn, the values array
+                                  must be non-empty. If the operator is Exists or
+                                  DoesNotExist, the values array must be empty. This
+                                  array is replaced during a strategic merge patch.
+                                items:
+                                  type: string
+                                type: array
+                            required:
+                            - key
+                            - operator
+                            type: object
+                          type: array
+                        matchLabels:
+                          additionalProperties:
+                            type: string
+                          description: matchLabels is a map of {key,value} pairs.
+                            A single {key,value} in the matchLabels map is equivalent
+                            to an element of matchExpressions, whose key field is
+                            "key", the operator is "In", and the values array contains
+                            only "value". The requirements are ANDed.
+                          type: object
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                  namespaces:
+                    description: Namespaces list of namespace(s) on which ip pool
+                      can be attached.
+                    items:
+                      type: string
+                    type: array
+                  priority:
+                    description: Priority priority given for ip pool while ip allocation
+                      on a service.
+                    type: integer
+                  serviceSelectors:
+                    description: ServiceSelectors list of label selector to select
+                      service(s) for which ip pool can be used for ip allocation.
+                    items:
+                      description: A label selector is a label query over a set of
+                        resources. The result of matchLabels and matchExpressions
+                        are ANDed. An empty label selector matches all objects. A
+                        null label selector matches no objects.
+                      properties:
+                        matchExpressions:
+                          description: matchExpressions is a list of label selector
+                            requirements. The requirements are ANDed.
+                          items:
+                            description: A label selector requirement is a selector
+                              that contains values, a key, and an operator that relates
+                              the key and values.
+                            properties:
+                              key:
+                                description: key is the label key that the selector
+                                  applies to.
+                                type: string
+                              operator:
+                                description: operator represents a key's relationship
+                                  to a set of values. Valid operators are In, NotIn,
+                                  Exists and DoesNotExist.
+                                type: string
+                              values:
+                                description: values is an array of string values.
+                                  If the operator is In or NotIn, the values array
+                                  must be non-empty. If the operator is Exists or
+                                  DoesNotExist, the values array must be empty. This
+                                  array is replaced during a strategic merge patch.
+                                items:
+                                  type: string
+                                type: array
+                            required:
+                            - key
+                            - operator
+                            type: object
+                          type: array
+                        matchLabels:
+                          additionalProperties:
+                            type: string
+                          description: matchLabels is a map of {key,value} pairs.
+                            A single {key,value} in the matchLabels map is equivalent
+                            to an element of matchExpressions, whose key field is
+                            "key", the operator is "In", and the values array contains
+                            only "value". The requirements are ANDed.
+                          type: object
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                type: object
+            required:
+            - addresses
+            type: object
+          status:
+            description: IPAddressPoolStatus defines the observed state of IPAddressPool.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.11.1
+  creationTimestamp: null
+  name: l2advertisements.metallb.io
+spec:
+  group: metallb.io
+  names:
+    kind: L2Advertisement
+    listKind: L2AdvertisementList
+    plural: l2advertisements
+    singular: l2advertisement
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.ipAddressPools
+      name: IPAddressPools
+      type: string
+    - jsonPath: .spec.ipAddressPoolSelectors
+      name: IPAddressPool Selectors
+      type: string
+    - jsonPath: .spec.interfaces
+      name: Interfaces
+      type: string
+    - jsonPath: .spec.nodeSelectors
+      name: Node Selectors
+      priority: 10
+      type: string
+    name: v1beta1
+    schema:
+      openAPIV3Schema:
+        description: L2Advertisement allows to advertise the LoadBalancer IPs provided
+          by the selected pools via L2.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: L2AdvertisementSpec defines the desired state of L2Advertisement.
+            properties:
+              interfaces:
+                description: A list of interfaces to announce from. The LB IP will
+                  be announced only from these interfaces. If the field is not set,
+                  we advertise from all the interfaces on the host.
+                items:
+                  type: string
+                type: array
+              ipAddressPoolSelectors:
+                description: A selector for the IPAddressPools which would get advertised
+                  via this advertisement. If no IPAddressPool is selected by this
+                  or by the list, the advertisement is applied to all the IPAddressPools.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              ipAddressPools:
+                description: The list of IPAddressPools to advertise via this advertisement,
+                  selected by name.
+                items:
+                  type: string
+                type: array
+              nodeSelectors:
+                description: NodeSelectors allows to limit the nodes to announce as
+                  next hops for the LoadBalancer IP. When empty, all the nodes having  are
+                  announced as next hops.
+                items:
+                  description: A label selector is a label query over a set of resources.
+                    The result of matchLabels and matchExpressions are ANDed. An empty
+                    label selector matches all objects. A null label selector matches
+                    no objects.
+                  properties:
+                    matchExpressions:
+                      description: matchExpressions is a list of label selector requirements.
+                        The requirements are ANDed.
+                      items:
+                        description: A label selector requirement is a selector that
+                          contains values, a key, and an operator that relates the
+                          key and values.
+                        properties:
+                          key:
+                            description: key is the label key that the selector applies
+                              to.
+                            type: string
+                          operator:
+                            description: operator represents a key's relationship
+                              to a set of values. Valid operators are In, NotIn, Exists
+                              and DoesNotExist.
+                            type: string
+                          values:
+                            description: values is an array of string values. If the
+                              operator is In or NotIn, the values array must be non-empty.
+                              If the operator is Exists or DoesNotExist, the values
+                              array must be empty. This array is replaced during a
+                              strategic merge patch.
+                            items:
+                              type: string
+                            type: array
+                        required:
+                        - key
+                        - operator
+                        type: object
+                      type: array
+                    matchLabels:
+                      additionalProperties:
+                        type: string
+                      description: matchLabels is a map of {key,value} pairs. A single
+                        {key,value} in the matchLabels map is equivalent to an element
+                        of matchExpressions, whose key field is "key", the operator
+                        is "In", and the values array contains only "value". The requirements
+                        are ANDed.
+                      type: object
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+            type: object
+          status:
+            description: L2AdvertisementStatus defines the observed state of L2Advertisement.
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    app: metallb
+  name: controller
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    app.kubernetes.io/component: rbac
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s-sa
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: serviceaccount
+    app.kubernetes.io/part-of: frr-k8s
+  name: frr-k8s-daemon
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    app: metallb
+  name: speaker
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  labels:
+    app: metallb
+  name: controller
+  namespace: metallb-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resourceNames:
+  - memberlist
+  resources:
+  - secrets
+  verbs:
+  - list
+- apiGroups:
+  - apps
+  resourceNames:
+  - controller
+  resources:
+  - deployments
+  verbs:
+  - get
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgppeers
+  verbs:
+  - get
+  - list
+- apiGroups:
+  - metallb.io
+  resources:
+  - addresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bfdprofiles
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - ipaddresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgpadvertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - l2advertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - communities
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  labels:
+    app: frr-k8s
+  name: frr-k8s-daemon-role
+  namespace: metallb-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+  - update
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  labels:
+    app: metallb
+  name: pod-lister
+  namespace: metallb-system
+rules:
+- apiGroups:
+  - frrk8s.metallb.io
+  resources:
+  - frrconfigurations
+  verbs:
+  - get
+  - list
+  - watch
+  - create
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - list
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - addresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bfdprofiles
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgppeers
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - l2advertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - bgpadvertisements
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - ipaddresspools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metallb.io
+  resources:
+  - communities
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: prometheus-k8s
+  namespace: metallb-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - services
+  - endpoints
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  creationTimestamp: null
+  name: frr-k8s-daemon-role
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - nodes
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - admissionregistration.k8s.io
+  resources:
+  - validatingwebhookconfigurations
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - admissionregistration.k8s.io
+  resourceNames:
+  - frr-k8s-validating-webhook-configuration
+  resources:
+  - validatingwebhookconfigurations
+  verbs:
+  - update
+- apiGroups:
+  - frrk8s.metallb.io
+  resources:
+  - frrconfigurations
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - frrk8s.metallb.io
+  resources:
+  - frrconfigurations/finalizers
+  verbs:
+  - update
+- apiGroups:
+  - frrk8s.metallb.io
+  resources:
+  - frrconfigurations/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - frrk8s.metallb.io
+  resources:
+  - frrnodestates
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - frrk8s.metallb.io
+  resources:
+  - frrnodestates/status
+  verbs:
+  - get
+  - patch
+  - update
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: kube-rbac-proxy
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: metrics-reader
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: clusterrole
+    app.kubernetes.io/part-of: frr-k8s
+  name: frr-k8s-metrics-reader
+rules:
+- nonResourceURLs:
+  - /metrics
+  verbs:
+  - get
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/component: kube-rbac-proxy
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: proxy-role
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: clusterrole
+    app.kubernetes.io/part-of: frr-k8s
+  name: frr-k8s-proxy-role
+rules:
+- apiGroups:
+  - authentication.k8s.io
+  resources:
+  - tokenreviews
+  verbs:
+  - create
+- apiGroups:
+  - authorization.k8s.io
+  resources:
+  - subjectaccessreviews
+  verbs:
+  - create
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:controller
+rules:
+- apiGroups:
+  - authorization.k8s.io
+  resources:
+  - subjectaccessreviews
+  verbs:
+  - create
+- apiGroups:
+  - authentication.k8s.io
+  resources:
+  - tokenreviews
+  verbs:
+  - create
+- apiGroups:
+  - ""
+  resources:
+  - services
+  - namespaces
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - nodes
+  verbs:
+  - list
+- apiGroups:
+  - ""
+  resources:
+  - services/status
+  verbs:
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - events
+  verbs:
+  - create
+  - patch
+- apiGroups:
+  - policy
+  resourceNames:
+  - controller
+  resources:
+  - podsecuritypolicies
+  verbs:
+  - use
+- apiGroups:
+  - admissionregistration.k8s.io
+  resourceNames:
+  - metallb-webhook-configuration
+  resources:
+  - validatingwebhookconfigurations
+  - mutatingwebhookconfigurations
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - admissionregistration.k8s.io
+  resources:
+  - validatingwebhookconfigurations
+  - mutatingwebhookconfigurations
+  verbs:
+  - list
+  - watch
+- apiGroups:
+  - apiextensions.k8s.io
+  resourceNames:
+  - addresspools.metallb.io
+  - bfdprofiles.metallb.io
+  - bgpadvertisements.metallb.io
+  - bgppeers.metallb.io
+  - ipaddresspools.metallb.io
+  - l2advertisements.metallb.io
+  - communities.metallb.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - apiextensions.k8s.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:speaker
+rules:
+- apiGroups:
+  - authorization.k8s.io
+  resources:
+  - subjectaccessreviews
+  verbs:
+  - create
+- apiGroups:
+  - authentication.k8s.io
+  resources:
+  - tokenreviews
+  verbs:
+  - create
+- apiGroups:
+  - ""
+  resources:
+  - services
+  - endpoints
+  - nodes
+  - namespaces
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - discovery.k8s.io
+  resources:
+  - endpointslices
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - events
+  verbs:
+  - create
+  - patch
+- apiGroups:
+  - policy
+  resourceNames:
+  - speaker
+  resources:
+  - podsecuritypolicies
+  verbs:
+  - use
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: controller
+  namespace: metallb-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: controller
+subjects:
+- kind: ServiceAccount
+  name: controller
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: rbac
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s-rolebinding
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: rolebinding
+    app.kubernetes.io/part-of: frr-k8s
+  name: frr-k8s-daemon-rolebinding
+  namespace: metallb-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: frr-k8s-daemon-role
+subjects:
+- kind: ServiceAccount
+  name: frr-k8s-daemon
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: pod-lister
+  namespace: metallb-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: pod-lister
+subjects:
+- kind: ServiceAccount
+  name: speaker
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: prometheus-k8s
+  namespace: metallb-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: prometheus-k8s
+subjects:
+- kind: ServiceAccount
+  name: prometheus-k8s
+  namespace: monitoring
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: rbac
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s-rolebinding
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: clusterrolebinding
+    app.kubernetes.io/part-of: frr-k8s
+  name: frr-k8s-daemon-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: frr-k8s-daemon-role
+subjects:
+- kind: ServiceAccount
+  name: frr-k8s-daemon
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app.kubernetes.io/component: kube-rbac-proxy
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: proxy-rolebinding
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: clusterrolebinding
+    app.kubernetes.io/part-of: frr-k8s
+  name: frr-k8s-proxy-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: frr-k8s-proxy-role
+subjects:
+- kind: ServiceAccount
+  name: frr-k8s-daemon
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:controller
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: metallb-system:controller
+subjects:
+- kind: ServiceAccount
+  name: controller
+  namespace: metallb-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: metallb
+  name: metallb-system:speaker
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: metallb-system:speaker
+subjects:
+- kind: ServiceAccount
+  name: speaker
+  namespace: metallb-system
+---
+apiVersion: v1
+data:
+  daemons: |
+    # This file tells the frr package which daemons to start.
+    #
+    # Sample configurations for these daemons can be found in
+    # /usr/share/doc/frr/examples/.
+    #
+    # ATTENTION:
+    #
+    # When activating a daemon for the first time, a config file, even if it is
+    # empty, has to be present *and* be owned by the user and group "frr", else
+    # the daemon will not be started by /etc/init.d/frr. The permissions should
+    # be u=rw,g=r,o=.
+    # When using "vtysh" such a config file is also needed. It should be owned by
+    # group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too.
+    #
+    # The watchfrr and zebra daemons are always started.
+    #
+    bgpd=yes
+    ospfd=no
+    ospf6d=no
+    ripd=no
+    ripngd=no
+    isisd=no
+    pimd=no
+    ldpd=no
+    nhrpd=no
+    eigrpd=no
+    babeld=no
+    sharpd=no
+    pbrd=no
+    bfdd=yes
+    fabricd=no
+    vrrpd=no
+
+    #
+    # If this option is set the /etc/init.d/frr script automatically loads
+    # the config via "vtysh -b" when the servers are started.
+    # Check /etc/pam.d/frr if you intend to use "vtysh"!
+    #
+    vtysh_enable=yes
+    zebra_options="  -A 127.0.0.1 -s 90000000"
+    bgpd_options="   -A 127.0.0.1 -p 0"
+    ospfd_options="  -A 127.0.0.1"
+    ospf6d_options=" -A ::1"
+    ripd_options="   -A 127.0.0.1"
+    ripngd_options=" -A ::1"
+    isisd_options="  -A 127.0.0.1"
+    pimd_options="   -A 127.0.0.1"
+    ldpd_options="   -A 127.0.0.1"
+    nhrpd_options="  -A 127.0.0.1"
+    eigrpd_options=" -A 127.0.0.1"
+    babeld_options=" -A 127.0.0.1"
+    sharpd_options=" -A 127.0.0.1"
+    pbrd_options="   -A 127.0.0.1"
+    staticd_options="-A 127.0.0.1"
+    bfdd_options="   -A 127.0.0.1"
+    fabricd_options="-A 127.0.0.1"
+    vrrpd_options="  -A 127.0.0.1"
+
+    # configuration profile
+    #
+    #frr_profile="traditional"
+    #frr_profile="datacenter"
+
+    #
+    # This is the maximum number of FD's that will be available.
+    # Upon startup this is read by the control files and ulimit
+    # is called. Uncomment and use a reasonable value for your
+    # setup if you are expecting a large number of peers in
+    # say BGP.
+    #MAX_FDS=1024
+
+    # The list of daemons to watch is automatically generated by the init script.
+    #watchfrr_options=""
+
+    # for debugging purposes, you can specify a "wrap" command to start instead
+    # of starting the daemon directly, e.g. to use valgrind on ospfd:
+    #   ospfd_wrap="/usr/bin/valgrind"
+    # or you can use "all_wrap" for all daemons, e.g. to use perf record:
+    #   all_wrap="/usr/bin/perf record --call-graph -"
+    # the normal daemon command is added to this at the end.
+  frr.conf: |
+    ! This file gets overriden the first time the speaker renders a config.
+    ! So anything configured here is only temporary.
+    frr version 7.5.1
+    frr defaults traditional
+    hostname Router
+    line vty
+    log file /etc/frr/frr.log informational
+  vtysh.conf: |
+    service integrated-vtysh-config
+kind: ConfigMap
+metadata:
+  name: frr-k8s-frr-startup
+  namespace: metallb-system
+---
+apiVersion: v1
+data:
+  excludel2.yaml: |
+    announcedInterfacesToExclude: ["^docker.*", "^cbr.*", "^dummy.*", "^virbr.*", "^lxcbr.*", "^veth.*", "^lo$", "^cali.*", "^tunl.*", "^flannel.*", "^kube-ipvs.*", "^cni.*", "^nodelocaldns.*"]
+kind: ConfigMap
+metadata:
+  name: metallb-excludel2
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: frr-k8s-webhook-server-cert
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: webhook-server-cert
+  namespace: metallb-system
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    prometheus.io/scrape: "true"
+  labels:
+    name: controller-monitor-service
+  name: controller-monitor-service
+  namespace: metallb-system
+spec:
+  clusterIP: None
+  ports:
+  - name: metricshttps
+    port: 9120
+    targetPort: 9120
+  selector:
+    component: controller
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app.kubernetes.io/component: kube-rbac-proxy
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s-metrics-service
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: service
+    app.kubernetes.io/part-of: frr-k8s
+    control-plane: frr-k8s
+  name: frr-k8s-metrics-service
+  namespace: metallb-system
+spec:
+  ports:
+  - name: metricshttps
+    port: 9140
+    targetPort: metricshttps
+  - name: frrmetricshttps
+    port: 9141
+    targetPort: frrmetricshttps
+  selector:
+    control-plane: frr-k8s
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: frr-k8s-webhook-service
+  namespace: metallb-system
+spec:
+  ports:
+  - port: 443
+    targetPort: 9443
+  selector:
+    control-plane: webhook-server
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    prometheus.io/scrape: "true"
+  labels:
+    name: speaker-monitor-service
+  name: speaker-monitor-service
+  namespace: metallb-system
+spec:
+  clusterIP: None
+  ports:
+  - name: metricshttps
+    port: 9120
+    targetPort: 9120
+  selector:
+    component: speaker
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: webhook-service
+  namespace: metallb-system
+spec:
+  ports:
+  - port: 443
+    targetPort: 9443
+  selector:
+    component: controller
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: metallb
+    component: controller
+  name: controller
+  namespace: metallb-system
+spec:
+  revisionHistoryLimit: 3
+  selector:
+    matchLabels:
+      app: metallb
+      component: controller
+  template:
+    metadata:
+      annotations:
+        prometheus.io/port: "7472"
+        prometheus.io/scrape: "true"
+      labels:
+        app: metallb
+        component: controller
+    spec:
+      containers:
+      - args:
+        - --port=7472
+        - --log-level=info
+        - --tls-min-version=VersionTLS12
+        env:
+        - name: METALLB_BGP_TYPE
+          value: frr-k8s
+        - name: METALLB_ML_SECRET_NAME
+          value: memberlist
+        - name: METALLB_DEPLOYMENT
+          value: controller
+        image: quay.io/metallb/controller:main
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: controller
+        ports:
+        - containerPort: 7472
+          name: monitoring
+        - containerPort: 9443
+          name: webhook-server
+          protocol: TCP
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - all
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /tmp/k8s-webhook-server/serving-certs
+          name: cert
+          readOnly: true
+      - args:
+        - --secure-listen-address=0.0.0.0:9120
+        - --upstream=http://127.0.0.1:7472/
+        - --logtostderr=true
+        - --v=0
+        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.12.0
+        name: kube-rbac-proxy
+        ports:
+        - containerPort: 9120
+          name: metricshttps
+          protocol: TCP
+        resources:
+          limits:
+            cpu: 500m
+            memory: 128Mi
+          requests:
+            cpu: 5m
+            memory: 64Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+      nodeSelector:
+        kubernetes.io/os: linux
+      securityContext:
+        fsGroup: 65534
+        runAsNonRoot: true
+        runAsUser: 65534
+      serviceAccountName: controller
+      terminationGracePeriodSeconds: 0
+      volumes:
+      - name: cert
+        secret:
+          defaultMode: 420
+          secretName: webhook-server-cert
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app.kubernetes.io/component: frr-k8s
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: frr-k8s
+    app.kubernetes.io/part-of: frr-k8s
+    control-plane: webhook-server
+  name: frr-k8s-webhook-server
+  namespace: metallb-system
+spec:
+  selector:
+    matchLabels:
+      control-plane: webhook-server
+  template:
+    metadata:
+      annotations:
+        kubectl.kubernetes.io/default-container: frr-k8s-webhook-server
+      labels:
+        app: frr-k8s-webhook-server
+        control-plane: webhook-server
+    spec:
+      containers:
+      - args:
+        - --log-level=info
+        - --webhook-mode=onlywebhook
+        - --namespace=$(NAMESPACE)
+        - --health-probe-bind-address=:8081
+        command:
+        - /frr-k8s
+        env:
+        - name: NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        image: quay.io/metallb/frr-k8s:main
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 8081
+          initialDelaySeconds: 15
+          periodSeconds: 20
+        name: frr-k8s-webhook-server
+        readinessProbe:
+          httpGet:
+            path: /readyz
+            port: 8081
+          initialDelaySeconds: 5
+          periodSeconds: 10
+        resources:
+          limits:
+            cpu: 500m
+            memory: 128Mi
+          requests:
+            cpu: 10m
+            memory: 64Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /tmp/k8s-webhook-server/serving-certs
+          name: cert
+          readOnly: true
+      serviceAccountName: frr-k8s-daemon
+      terminationGracePeriodSeconds: 10
+      tolerations:
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/master
+        operator: Exists
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/control-plane
+        operator: Exists
+      volumes:
+      - name: cert
+        secret:
+          defaultMode: 420
+          secretName: frr-k8s-webhook-server-cert
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  labels:
+    app.kubernetes.io/component: frr-k8s
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: frr-k8s
+    app.kubernetes.io/part-of: frr-k8s
+    control-plane: frr-k8s
+  name: frr-k8s-daemon
+  namespace: metallb-system
+spec:
+  selector:
+    matchLabels:
+      control-plane: frr-k8s
+  template:
+    metadata:
+      annotations:
+        kubectl.kubernetes.io/default-container: frr-k8s
+      labels:
+        app: frr-k8s
+        control-plane: frr-k8s
+    spec:
+      affinity:
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+            - matchExpressions:
+              - key: kubernetes.io/arch
+                operator: In
+                values:
+                - amd64
+                - arm64
+                - ppc64le
+                - s390x
+              - key: kubernetes.io/os
+                operator: In
+                values:
+                - linux
+      containers:
+      - args:
+        - --secure-listen-address=0.0.0.0:9140
+        - --upstream=http://127.0.0.1:7572/
+        - --logtostderr=true
+        - --v=0
+        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
+        name: kube-rbac-proxy
+        ports:
+        - containerPort: 9140
+          name: metricshttps
+          protocol: TCP
+        resources:
+          limits:
+            cpu: 500m
+            memory: 128Mi
+          requests:
+            cpu: 5m
+            memory: 64Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+      - args:
+        - --secure-listen-address=0.0.0.0:9141
+        - --upstream=http://127.0.0.1:7573/
+        - --logtostderr=true
+        - --v=0
+        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
+        name: kube-rbac-proxy-frr
+        ports:
+        - containerPort: 9141
+          name: frrmetricshttps
+          protocol: TCP
+        resources:
+          limits:
+            cpu: 500m
+            memory: 128Mi
+          requests:
+            cpu: 5m
+            memory: 64Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+      - args:
+        - --health-probe-bind-address=127.0.0.1:8081
+        - --metrics-bind-address=127.0.0.1:7572
+        - --node-name=$(NODE_NAME)
+        - --namespace=$(NAMESPACE)
+        - --log-level=info
+        command:
+        - /frr-k8s
+        env:
+        - name: FRR_CONFIG_FILE
+          value: /etc/frr_reloader/frr.conf
+        - name: FRR_RELOADER_PID_FILE
+          value: /etc/frr_reloader/reloader.pid
+        - name: NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        - name: NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        image: quay.io/metallb/frr-k8s:main
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          httpGet:
+            host: 127.0.0.1
+            path: /healthz
+            port: 8081
+          initialDelaySeconds: 15
+          periodSeconds: 20
+        name: frr-k8s
+        ports:
+        - containerPort: 7572
+          name: monitoring
+        readinessProbe:
+          httpGet:
+            host: 127.0.0.1
+            path: /readyz
+            port: 8081
+          initialDelaySeconds: 5
+          periodSeconds: 10
+        resources:
+          limits:
+            cpu: 500m
+            memory: 128Mi
+          requests:
+            cpu: 10m
+            memory: 64Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /etc/frr_reloader
+          name: reloader
+      - command:
+        - /bin/sh
+        - -c
+        - |
+          /sbin/tini -- /usr/lib/frr/docker-start &
+          attempts=0
+          until [[ -f /etc/frr/frr.log || $attempts -eq 60 ]]; do
+            sleep 1
+            attempts=$(( $attempts + 1 ))
+          done
+          tail -f /etc/frr/frr.log
+        env:
+        - name: TINI_SUBREAPER
+          value: "true"
+        image: quay.io/frrouting/frr:8.4.2
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            host: 127.0.0.1
+            path: /livez
+            port: 7573
+          periodSeconds: 5
+        name: frr
+        securityContext:
+          capabilities:
+            add:
+            - NET_ADMIN
+            - NET_RAW
+            - SYS_ADMIN
+            - NET_BIND_SERVICE
+        startupProbe:
+          failureThreshold: 30
+          httpGet:
+            host: 127.0.0.1
+            path: /livez
+            port: 7573
+          periodSeconds: 5
+        volumeMounts:
+        - mountPath: /var/run/frr
+          name: frr-sockets
+        - mountPath: /etc/frr
+          name: frr-conf
+      - args:
+        - --metrics-port=7573
+        - --metrics-bind-address=127.0.0.1
+        command:
+        - /etc/frr_metrics/frr-metrics
+        image: quay.io/frrouting/frr:8.4.2
+        name: frr-metrics
+        ports:
+        - containerPort: 7573
+          name: monitoring
+        volumeMounts:
+        - mountPath: /var/run/frr
+          name: frr-sockets
+        - mountPath: /etc/frr
+          name: frr-conf
+        - mountPath: /etc/frr_metrics
+          name: metrics
+      - command:
+        - /etc/frr_reloader/frr-reloader.sh
+        image: quay.io/frrouting/frr:8.4.2
+        name: reloader
+        volumeMounts:
+        - mountPath: /var/run/frr
+          name: frr-sockets
+        - mountPath: /etc/frr
+          name: frr-conf
+        - mountPath: /etc/frr_reloader
+          name: reloader
+      hostNetwork: true
+      initContainers:
+      - command:
+        - /bin/sh
+        - -c
+        - cp -rLf /tmp/frr/* /etc/frr/
+        image: quay.io/frrouting/frr:8.4.2
+        name: cp-frr-files
+        securityContext:
+          runAsGroup: 101
+          runAsUser: 100
+        volumeMounts:
+        - mountPath: /tmp/frr
+          name: frr-startup
+        - mountPath: /etc/frr
+          name: frr-conf
+      - command:
+        - /bin/sh
+        - -c
+        - cp -f /frr-reloader.sh /etc/frr_reloader/
+        image: quay.io/metallb/frr-k8s:main
+        name: cp-reloader
+        volumeMounts:
+        - mountPath: /etc/frr_reloader
+          name: reloader
+      - command:
+        - /bin/sh
+        - -c
+        - cp -f /frr-metrics /etc/frr_metrics/
+        image: quay.io/metallb/frr-k8s:main
+        name: cp-metrics
+        volumeMounts:
+        - mountPath: /etc/frr_metrics
+          name: metrics
+      serviceAccountName: frr-k8s-daemon
+      shareProcessNamespace: true
+      terminationGracePeriodSeconds: 10
+      tolerations:
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/master
+        operator: Exists
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/control-plane
+        operator: Exists
+      volumes:
+      - emptyDir: {}
+        name: frr-sockets
+      - configMap:
+          name: frr-k8s-frr-startup
+        name: frr-startup
+      - emptyDir: {}
+        name: frr-conf
+      - emptyDir: {}
+        name: reloader
+      - emptyDir: {}
+        name: metrics
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  labels:
+    app: metallb
+    component: speaker
+  name: speaker
+  namespace: metallb-system
+spec:
+  selector:
+    matchLabels:
+      app: metallb
+      component: speaker
+  template:
+    metadata:
+      annotations:
+        prometheus.io/port: "7472"
+        prometheus.io/scrape: "true"
+      labels:
+        app: metallb
+        component: speaker
+    spec:
+      containers:
+      - args:
+        - --port=7472
+        - --log-level=info
+        env:
+        - name: METALLB_BGP_TYPE
+          value: frr-k8s
+        - name: METALLB_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        - name: METALLB_HOST
+          valueFrom:
+            fieldRef:
+              fieldPath: status.hostIP
+        - name: METALLB_ML_BIND_ADDR
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        - name: METALLB_ML_LABELS
+          value: app=metallb,component=speaker
+        - name: METALLB_ML_SECRET_KEY_PATH
+          value: /etc/ml_secret_key
+        image: quay.io/metallb/speaker:main
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: speaker
+        ports:
+        - containerPort: 7472
+          name: monitoring
+        - containerPort: 7946
+          name: memberlist-tcp
+        - containerPort: 7946
+          name: memberlist-udp
+          protocol: UDP
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /metrics
+            port: monitoring
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            add:
+            - NET_RAW
+            drop:
+            - ALL
+          readOnlyRootFilesystem: true
+        volumeMounts:
+        - mountPath: /etc/ml_secret_key
+          name: memberlist
+          readOnly: true
+        - mountPath: /etc/metallb
+          name: metallb-excludel2
+          readOnly: true
+      - args:
+        - --secure-listen-address=0.0.0.0:9120
+        - --upstream=http://$(METALLB_HOST):7472/
+        - --logtostderr=true
+        - --v=0
+        env:
+        - name: METALLB_HOST
+          valueFrom:
+            fieldRef:
+              fieldPath: status.hostIP
+        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.12.0
+        name: kube-rbac-proxy
+        ports:
+        - containerPort: 9120
+          name: metricshttps
+          protocol: TCP
+        resources:
+          limits:
+            cpu: 500m
+            memory: 128Mi
+          requests:
+            cpu: 5m
+            memory: 64Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+      hostNetwork: true
+      nodeSelector:
+        kubernetes.io/os: linux
+      serviceAccountName: speaker
+      terminationGracePeriodSeconds: 2
+      tolerations:
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/master
+        operator: Exists
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/control-plane
+        operator: Exists
+      volumes:
+      - name: memberlist
+        secret:
+          defaultMode: 420
+          secretName: memberlist
+      - configMap:
+          defaultMode: 256
+          name: metallb-excludel2
+        name: metallb-excludel2
+---
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  labels:
+    component: controller
+  name: controller-monitor
+  namespace: metallb-system
+spec:
+  endpoints:
+  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+    honorLabels: true
+    interval: 30s
+    port: metricshttps
+    scheme: https
+    tlsConfig:
+      insecureSkipVerify: true
+  jobLabel: component
+  namespaceSelector:
+    matchNames:
+    - metallb-system
+  selector:
+    matchLabels:
+      name: controller-monitor-service
+---
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  labels:
+    app.kubernetes.io/component: metrics
+    app.kubernetes.io/created-by: frr-k8s
+    app.kubernetes.io/instance: frr-k8s-metrics-monitor
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/name: servicemonitor
+    app.kubernetes.io/part-of: frr-k8s
+    control-plane: frr-k8s
+  name: frr-k8s-metrics-monitor
+  namespace: metallb-system
+spec:
+  endpoints:
+  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+    path: /metrics
+    port: metricshttps
+    scheme: https
+    tlsConfig:
+      insecureSkipVerify: true
+  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+    path: /metrics
+    port: frrmetricshttps
+    scheme: https
+    tlsConfig:
+      insecureSkipVerify: true
+  selector:
+    matchLabels:
+      control-plane: frr-k8s
+---
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  labels:
+    component: speaker
+  name: speaker-monitor
+  namespace: metallb-system
+spec:
+  endpoints:
+  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+    honorLabels: true
+    interval: 30s
+    port: metricshttps
+    scheme: https
+    tlsConfig:
+      insecureSkipVerify: true
+  jobLabel: component
+  namespaceSelector:
+    matchNames:
+    - metallb-system
+  selector:
+    matchLabels:
+      name: speaker-monitor-service
+---
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  creationTimestamp: null
+  name: frr-k8s-validating-webhook-configuration
+webhooks:
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: frr-k8s-webhook-service
+      namespace: metallb-system
+      path: /validate-frrk8s-metallb-io-v1beta1-frrconfiguration
+  failurePolicy: Fail
+  name: frrconfigurationsvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - frrk8s.metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - frrconfigurations
+  sideEffects: None
+---
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  creationTimestamp: null
+  name: metallb-webhook-configuration
+webhooks:
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta2-bgppeer
+  failurePolicy: Fail
+  name: bgppeersvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta2
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - bgppeers
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-addresspool
+  failurePolicy: Fail
+  name: addresspoolvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - addresspools
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-bfdprofile
+  failurePolicy: Fail
+  name: bfdprofilevalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - DELETE
+    resources:
+    - bfdprofiles
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-bgpadvertisement
+  failurePolicy: Fail
+  name: bgpadvertisementvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - bgpadvertisements
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-community
+  failurePolicy: Fail
+  name: communityvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - communities
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-ipaddresspool
+  failurePolicy: Fail
+  name: ipaddresspoolvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - ipaddresspools
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: metallb-system
+      path: /validate-metallb-io-v1beta1-l2advertisement
+  failurePolicy: Fail
+  name: l2advertisementvalidationwebhook.metallb.io
+  rules:
+  - apiGroups:
+    - metallb.io
+    apiVersions:
+    - v1beta1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - l2advertisements
+  sideEffects: None
diff --git a/config/prometheus-frr-k8s/controller-patch.yaml b/config/prometheus-frr-k8s/controller-patch.yaml
new file mode 100644
index 00000000..dac9a27f
--- /dev/null
+++ b/config/prometheus-frr-k8s/controller-patch.yaml
@@ -0,0 +1,13 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  namespace: metallb-system
+  name: controller
+spec:
+  template:
+    spec:
+      containers:
+      - name: controller
+        env:
+        - name: METALLB_BGP_TYPE
+          value: frr-k8s
diff --git a/config/prometheus-frr-k8s/ignore-ns.yaml b/config/prometheus-frr-k8s/ignore-ns.yaml
new file mode 100644
index 00000000..0372521f
--- /dev/null
+++ b/config/prometheus-frr-k8s/ignore-ns.yaml
@@ -0,0 +1,5 @@
+$patch: delete
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: frr-k8s-system
\ No newline at end of file
diff --git a/config/prometheus-frr-k8s/ignore-prom-role.yaml b/config/prometheus-frr-k8s/ignore-prom-role.yaml
new file mode 100644
index 00000000..bee21b05
--- /dev/null
+++ b/config/prometheus-frr-k8s/ignore-prom-role.yaml
@@ -0,0 +1,6 @@
+$patch: delete
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: prometheus-k8s
+  namespace: system
\ No newline at end of file
diff --git a/config/prometheus-frr-k8s/ignore-prom-rolebinding.yaml b/config/prometheus-frr-k8s/ignore-prom-rolebinding.yaml
new file mode 100644
index 00000000..29dec5e3
--- /dev/null
+++ b/config/prometheus-frr-k8s/ignore-prom-rolebinding.yaml
@@ -0,0 +1,6 @@
+$patch: delete
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: prometheus-k8s
+  namespace: system
\ No newline at end of file
diff --git a/config/prometheus-frr-k8s/kustomization.yaml b/config/prometheus-frr-k8s/kustomization.yaml
new file mode 100644
index 00000000..fb654d76
--- /dev/null
+++ b/config/prometheus-frr-k8s/kustomization.yaml
@@ -0,0 +1,24 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- https://github.com/metallb/frr-k8s/config/prometheus/?timeout=120&ref=v0.0.3
+- ../prometheus-native
+patchesStrategicMerge:
+- speaker-patch.yaml
+- controller-patch.yaml
+images:
+- name: quay.io/metallb/frr-k8s
+  newTag: main
+patchesJson6902:
+  - target:
+      group: rbac.authorization.k8s.io
+      version: v1
+      kind: Role
+      name: pod-lister
+      namespace: metallb-system
+    path: role-patch.yaml
+patches:
+  - path: ignore-ns.yaml
+  - path: ignore-prom-role.yaml
+  - path: ignore-prom-rolebinding.yaml
+namespace: metallb-system
diff --git a/config/prometheus-frr-k8s/role-patch.yaml b/config/prometheus-frr-k8s/role-patch.yaml
new file mode 100644
index 00000000..f98c5ce8
--- /dev/null
+++ b/config/prometheus-frr-k8s/role-patch.yaml
@@ -0,0 +1,13 @@
+- op: add
+  path: /rules/0
+  value:
+    apiGroups:
+      - frrk8s.metallb.io
+    resources:
+      - frrconfigurations
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
diff --git a/config/prometheus-frr-k8s/speaker-patch.yaml b/config/prometheus-frr-k8s/speaker-patch.yaml
new file mode 100644
index 00000000..6572864c
--- /dev/null
+++ b/config/prometheus-frr-k8s/speaker-patch.yaml
@@ -0,0 +1,18 @@
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: speaker
+spec:
+  template:
+    metadata:
+      annotations:
+        prometheus.io/scrape: 'true'
+      labels:
+        app: metallb
+        component: speaker
+    spec:
+      containers:
+        - name: speaker
+          env:
+            - name: METALLB_BGP_TYPE
+              value: frr-k8s
diff --git a/internal/bgp/frrk8s/frrk8s.go b/internal/bgp/frrk8s/frrk8s.go
index 665d15b5..e4e1c49a 100644
--- a/internal/bgp/frrk8s/frrk8s.go
+++ b/internal/bgp/frrk8s/frrk8s.go
@@ -16,6 +16,7 @@ import (
 	"github.com/go-kit/log/level"
 	frrv1beta1 "github.com/metallb/frr-k8s/api/v1beta1"
 	"go.universe.tf/metallb/internal/bgp"
+	"go.universe.tf/metallb/internal/bgp/community"
 	"go.universe.tf/metallb/internal/bgp/frr"
 	metallbconfig "go.universe.tf/metallb/internal/config"
 	"go.universe.tf/metallb/internal/logging"
@@ -279,8 +280,11 @@ func (sm *sessionManager) updateConfig() error {
 			rout.prefixes[prefix] = prefix
 
 			for _, c := range adv.Communities {
-				community := c.String()
-				prefixesForCommunity[community] = append(prefixesForCommunity[community], prefix)
+				comm := c.String()
+				if community.IsLarge(c) {
+					comm = fmt.Sprintf("large:%s", c.String())
+				}
+				prefixesForCommunity[comm] = append(prefixesForCommunity[comm], prefix)
 			}
 			if adv.LocalPref != 0 {
 				prefixesForLocalPref[adv.LocalPref] = append(prefixesForLocalPref[adv.LocalPref], prefix)
diff --git a/tasks.py b/tasks.py
index 5799bd0d..b368e20a 100644
--- a/tasks.py
+++ b/tasks.py
@@ -795,11 +795,12 @@ def helmdocs(ctx, env="container"):
     "local_nics": "a list of bridges related node's interfaces separated by comma, default is kind",
     "external_containers": "a comma separated list of external containers names to use for the test. (valid parameters are: ibgp-single-hop / ibgp-multi-hop / ebgp-single-hop / ebgp-multi-hop)",
     "with_vrf": "tells if we want to run the tests against containers reacheable via linux VRFs",
+    "bgp_mode": "tells what bgp mode the cluster is using. valid values are native, frr, frr-k8s.",
     "external_frr_image": "overrides the image used for the external frr containers used in tests",
     "ginkgo_params": "additional ginkgo params to run the e2e tests with",
-    "host_bgp_mode": "tells whether to run the host container in ebgp or ibgp mode"
+    "host_bgp_mode": "tells whether to run the host container in ebgp or ibgp mode",
 })
-def e2etest(ctx, name="kind", export=None, kubeconfig=None, system_namespaces="kube-system,metallb-system", service_pod_port=80, skip_docker=False, focus="", skip="", ipv4_service_range=None, ipv6_service_range=None, prometheus_namespace="", node_nics="kind", local_nics="kind", external_containers="", with_vrf=False,external_frr_image="", ginkgo_params="", host_bgp_mode="ibgp"):
+def e2etest(ctx, name="kind", export=None, kubeconfig=None, system_namespaces="kube-system,metallb-system", service_pod_port=80, skip_docker=False, focus="", skip="", ipv4_service_range=None, ipv6_service_range=None, prometheus_namespace="", node_nics="kind", local_nics="kind", external_containers="", bgp_mode="", with_vrf=False,external_frr_image="", ginkgo_params="", host_bgp_mode="ibgp"):
     """Run E2E tests against development cluster."""
     _fetch_kubectl()
 
@@ -862,7 +863,7 @@ def e2etest(ctx, name="kind", export=None, kubeconfig=None, system_namespaces="k
     if external_frr_image != "":
         external_frr_image = "--frr-image="+(external_frr_image)
     testrun = run("cd `git rev-parse --show-toplevel`/e2etest &&"
-            "KUBECONFIG={} ginkgo {} --timeout=3h {} {} -- --provider=local --kubeconfig={} --service-pod-port={} -ipv4-service-range={} -ipv6-service-range={} {} --report-path {} {} -node-nics {} -local-nics {} {}  -with-vrf={} {} --host-bgp-mode={}".format(kubeconfig, ginkgo_params, ginkgo_focus, ginkgo_skip, kubeconfig, service_pod_port, ipv4_service_range, ipv6_service_range, opt_skip_docker, report_path, prometheus_namespace, node_nics, local_nics, external_containers, with_vrf, external_frr_image, host_bgp_mode), warn="True")
+            "KUBECONFIG={} ginkgo {} --timeout=3h {} {} -- --provider=local --kubeconfig={} --service-pod-port={} -ipv4-service-range={} -ipv6-service-range={} {} --report-path {} {} -node-nics {} -local-nics {} {} -bgp-mode={}  -with-vrf={} {} --host-bgp-mode={}".format(kubeconfig, ginkgo_params, ginkgo_focus, ginkgo_skip, kubeconfig, service_pod_port, ipv4_service_range, ipv6_service_range, opt_skip_docker, report_path, prometheus_namespace, node_nics, local_nics, external_containers, bgp_mode, with_vrf, external_frr_image, host_bgp_mode), warn="True")
 
     if export != None:
         run("kind export logs {}".format(export))
@@ -908,6 +909,7 @@ def generatemanifests(ctx):
     generate_manifest(ctx, bgp_type="frr", with_prometheus=True, output="config/manifests/metallb-frr-prometheus.yaml")
     generate_manifest(ctx, bgp_type="native", with_prometheus=True, output="config/manifests/metallb-native-prometheus.yaml")
     generate_manifest(ctx, bgp_type="frr-k8s", output="config/manifests/metallb-frr-k8s.yaml")
+    generate_manifest(ctx, bgp_type="frr-k8s", with_prometheus=True,output="config/manifests/metallb-frr-k8s-prometheus.yaml")
 
     _align_helm_crds(
         source='config/manifests/metallb-frr.yaml',
diff --git a/website/content/release-notes/_index.md b/website/content/release-notes/_index.md
index 1b989e75..61bc4a26 100644
--- a/website/content/release-notes/_index.md
+++ b/website/content/release-notes/_index.md
@@ -12,6 +12,7 @@ Chores:
 - Dev-env: improve docker build times ([PR 2205](https://github.com/metallb/metallb/pull/2205))
 - Add more frr-k8s related logs under debug loglevel ([PR 2199](https://github.com/metallb/metallb/pull/2199))
 - Move webhooks out of API package ([PR 2193](https://github.com/metallb/metallb/pull/2193))
+- Support running the e2es on frr-k8s deployments ([PR 2180](https://github.com/metallb/metallb/pull/2180))
 
 ## Version 0.13.12
 


Test patch:
diff --git a/e2etest/bgptests/bgp.go b/e2etest/bgptests/bgp.go
index 8abb7aca..48ed0d79 100644
--- a/e2etest/bgptests/bgp.go
+++ b/e2etest/bgptests/bgp.go
@@ -41,6 +41,7 @@ import (
 	"go.universe.tf/e2etest/pkg/frr"
 	frrconfig "go.universe.tf/e2etest/pkg/frr/config"
 	frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
+	frrprovider "go.universe.tf/e2etest/pkg/frr/provider"
 	"go.universe.tf/e2etest/pkg/ipfamily"
 	testservice "go.universe.tf/e2etest/pkg/service"
 	corev1 "k8s.io/api/core/v1"
@@ -62,6 +63,7 @@ const (
 
 var (
 	ConfigUpdater       config.Updater
+	FRRProvider         frrprovider.Provider
 	Reporter            *k8sreporter.KubernetesReporter
 	ReportPath          string
 	PrometheusNamespace string
@@ -1100,7 +1102,7 @@ var _ = ginkgo.Describe("BGP", func() {
 		)
 	})
 
-	ginkgo.Context("MetalLB allows adding extra FRR configuration", func() {
+	ginkgo.Context("FRR-MODE MetalLB allows adding extra FRR configuration", func() {
 		type whenApply string
 		var before whenApply = "before"
 		var after whenApply = "after"
@@ -1212,7 +1214,7 @@ var _ = ginkgo.Describe("BGP", func() {
 		)
 	})
 
-	ginkgo.Context("FRR validate reload feedback", func() {
+	ginkgo.Context("FRR-MODE FRR validate reload feedback", func() {
 		ginkgo.It("should update MetalLB config and log reload-validate success", func() {
 			resources := config.Resources{
 				Pools: []metallbv1beta1.IPAddressPool{
@@ -1308,7 +1310,8 @@ var _ = ginkgo.Describe("BGP", func() {
 			framework.ExpectNoError(err)
 
 			for _, pod := range speakerPods {
-				podExecutor := executor.ForPod(pod.Namespace, pod.Name, "frr")
+				podExecutor, err := FRRProvider.FRRExecutorFor(pod.Namespace, pod.Name)
+				framework.ExpectNoError(err)
 
 				Eventually(func() string {
 					// We need to assert against the output of the command as a bare string, as
@@ -1402,23 +1405,6 @@ var _ = ginkgo.Describe("BGP", func() {
 		ginkgo.Entry("IPV4", ipfamily.IPv4, []string{l2tests.IPV4ServiceRange}),
 		ginkgo.Entry("IPV6", ipfamily.IPv6, []string{l2tests.IPV6ServiceRange}),
 	)
-
-	// TODO: extend all the other tests to make them work with frr-k8s and remove this
-	ginkgo.Context("IPV4 with FRRK8s", func() {
-		ginkgo.It("should work", func() {
-			_, svc := setupBGPService(f, ipfamily.IPv4, []string{v4PoolAddresses}, FRRContainers, func(svc *corev1.Service) {})
-			defer testservice.Delete(cs, svc)
-
-			allNodes, err := cs.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
-			framework.ExpectNoError(err)
-			testservice.ValidateDesiredLB(svc)
-
-			for _, container := range FRRContainers {
-				ginkgo.By(fmt.Sprintf("validating the service from %s", container.Name))
-				validateService(svc, allNodes.Items, container)
-			}
-		})
-	})
 })
 
 // substringCount creates a Gomega transform function that
diff --git a/e2etest/bgptests/dump.go b/e2etest/bgptests/dump.go
index 39e45389..d552c44e 100644
--- a/e2etest/bgptests/dump.go
+++ b/e2etest/bgptests/dump.go
@@ -9,7 +9,6 @@ import (
 	"path"
 	"strings"
 
-	"go.universe.tf/e2etest/pkg/executor"
 	"go.universe.tf/e2etest/pkg/frr"
 	"go.universe.tf/e2etest/pkg/k8s"
 	"go.universe.tf/e2etest/pkg/metallb"
@@ -47,10 +46,12 @@ func dumpBGPInfo(basePath, testName string, cs clientset.Interface, f *framework
 	speakerPods, err := metallb.SpeakerPods(cs)
 	framework.ExpectNoError(err)
 	for _, pod := range speakerPods {
-		if len(pod.Spec.Containers) == 1 { // we dump only in case of frr
+		if FRRProvider == nil { // we dump only in case of frr / frr-k8s
 			break
 		}
-		podExec := executor.ForPod(pod.Namespace, pod.Name, "frr")
+		podExec, err := FRRProvider.FRRExecutorFor(pod.Namespace, pod.Name)
+		framework.ExpectNoError(err)
+
 		dump, err := frr.RawDump(podExec, "/etc/frr/frr.conf", "/etc/frr/frr.log")
 		if err != nil {
 			framework.Logf("External frr dump for pod %s failed %v", pod.Name, err)
diff --git a/e2etest/bgptests/metrics.go b/e2etest/bgptests/metrics.go
index a245c152..c0e2a9f6 100644
--- a/e2etest/bgptests/metrics.go
+++ b/e2etest/bgptests/metrics.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	metallbv1beta1 "go.universe.tf/metallb/api/v1beta1"
 	"go.universe.tf/e2etest/pkg/config"
 	frrconfig "go.universe.tf/e2etest/pkg/frr/config"
 	frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
@@ -19,6 +18,7 @@ import (
 	"go.universe.tf/e2etest/pkg/metrics"
 	"go.universe.tf/e2etest/pkg/pointer"
 	testservice "go.universe.tf/e2etest/pkg/service"
+	metallbv1beta1 "go.universe.tf/metallb/api/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	clientset "k8s.io/client-go/kubernetes"
@@ -132,87 +132,92 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 			for _, speaker := range speakerPods {
 				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
 				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+					metricsPod, metricsPrefix, err := FRRProvider.BGPMetricsPodFor(speaker.Namespace, speaker.Name)
+					if err != nil {
+						return err
+					}
+
+					speakerMetrics, err := metrics.ForPod(promPod, metricsPod, metallb.Namespace)
 					if err != nil {
 						return err
 					}
 					for _, selector := range selectors {
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_opens_sent", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_opens_sent", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_opens_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_opens_sent{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_opens_received", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_opens_received", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_opens_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_opens_received{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bgp_updates_total_received", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bgp_updates_total_received", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_updates_total_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_updates_total_received{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_keepalives_sent", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_keepalives_sent", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_keepalives_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_keepalives_sent{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_keepalives_received", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_keepalives_received", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_keepalives_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_keepalives_received{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_route_refresh_sent", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_route_refresh_sent", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_route_refresh_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_route_refresh_sent{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_total_sent", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_total_sent", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_total_sent{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_total_sent{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), "metallb_bgp_total_received", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bgp_total_received", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_total_received{%s} >= 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_total_received{%s} >= 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
@@ -267,7 +272,7 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 
 			ginkgo.By("checking the metrics when no service is added")
 			Eventually(func() error {
-				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
+				controllerMetrics, err := metrics.ForPod(promPod, controllerPod, metallb.Namespace)
 				if err != nil {
 					return err
 				}
@@ -292,28 +297,42 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 
 			selectors := labelsForPeers(FRRContainers, ipFamily)
 
+			// This is the only test where the same bgp metrics can be from either
+			// the speaker container or frr depending on the deployment's bgp mode.
+			bgpMetricsPodForSpeaker := func(p *corev1.Pod) (*corev1.Pod, string, error) {
+				if FRRProvider == nil {
+					return p, "metallb", nil
+				}
+				return FRRProvider.BGPMetricsPodFor(p.Namespace, p.Name)
+			}
+
 			for _, speaker := range speakerPods {
 				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
 
 				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+					metricsPod, metricsPrefix, err := bgpMetricsPodForSpeaker(speaker)
+					if err != nil {
+						return err
+					}
+
+					speakerMetrics, err := metrics.ForPod(promPod, metricsPod, metallb.Namespace)
 					if err != nil {
 						return err
 					}
 					for _, selector := range selectors {
-						err = metrics.ValidateGaugeValue(1, "metallb_bgp_session_up", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateGaugeValue(1, fmt.Sprintf("%s_bgp_session_up", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_session_up{%s} == 1`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_session_up{%s} == 1`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateGaugeValue(0, "metallb_bgp_announced_prefixes_total", selector.labelsBGP, speakerMetrics)
+						err = metrics.ValidateGaugeValue(0, fmt.Sprintf("%s_bgp_announced_prefixes_total", metricsPrefix), selector.labelsBGP, speakerMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_announced_prefixes_total{%s} == 0`, selector.labelsForQueryBGP), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_announced_prefixes_total{%s} == 0`, metricsPrefix, selector.labelsForQueryBGP), metrics.There)
 						if err != nil {
 							return err
 						}
@@ -328,7 +347,7 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 
 			ginkgo.By("checking the metrics when a service is added")
 			Eventually(func() error {
-				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
+				controllerMetrics, err := metrics.ForPod(promPod, controllerPod, metallb.Namespace)
 				if err != nil {
 					return err
 				}
@@ -347,34 +366,45 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 				ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
 
 				Eventually(func() error {
-					speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+					bgpMetricsPod, metricsPrefix, err := bgpMetricsPodForSpeaker(speaker)
+					if err != nil {
+						return err
+					}
+
+					bgpMetrics, err := metrics.ForPod(promPod, bgpMetricsPod, metallb.Namespace)
+					if err != nil {
+						return err
+					}
+
+					speakerMetrics, err := metrics.ForPod(promPod, speaker, metallb.Namespace)
 					if err != nil {
 						return err
 					}
+
 					for addr := range peerAddrToName {
-						err = metrics.ValidateGaugeValue(1, "metallb_bgp_session_up", map[string]string{"peer": addr}, speakerMetrics)
+						err = metrics.ValidateGaugeValue(1, fmt.Sprintf("%s_bgp_session_up", metricsPrefix), map[string]string{"peer": addr}, bgpMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_session_up{peer="%s"} == 1`, addr), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_session_up{peer="%s"} == 1`, metricsPrefix, addr), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateGaugeValue(1, "metallb_bgp_announced_prefixes_total", map[string]string{"peer": addr}, speakerMetrics)
+						err = metrics.ValidateGaugeValue(1, fmt.Sprintf("%s_bgp_announced_prefixes_total", metricsPrefix), map[string]string{"peer": addr}, bgpMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_announced_prefixes_total{peer="%s"} == 1`, addr), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_announced_prefixes_total{peer="%s"} == 1`, metricsPrefix, addr), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bgp_updates_total", map[string]string{"peer": addr}, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bgp_updates_total", metricsPrefix), map[string]string{"peer": addr}, bgpMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bgp_updates_total{peer="%s"} >= 1`, addr), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bgp_updates_total{peer="%s"} >= 1`, metricsPrefix, addr), metrics.There)
 						if err != nil {
 							return err
 						}
@@ -424,8 +454,6 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 		}
 
 		ginkgo.By("checking metrics")
-		controllerPod, err := metallb.ControllerPod(cs)
-		framework.ExpectNoError(err)
 		speakerPods, err := metallb.SpeakerPods(cs)
 		framework.ExpectNoError(err)
 		promPod, err := metrics.PrometheusPod(cs, PrometheusNamespace)
@@ -437,62 +465,67 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 			ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
 
 			Eventually(func() error {
-				speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+				metricsPod, metricsPrefix, err := FRRProvider.BGPMetricsPodFor(speaker.Namespace, speaker.Name)
+				if err != nil {
+					return err
+				}
+
+				speakerMetrics, err := metrics.ForPod(promPod, metricsPod, metallb.Namespace)
 				if err != nil {
 					return err
 				}
 
 				for _, selector := range selectors {
-					err = metrics.ValidateGaugeValue(1, "metallb_bfd_session_up", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateGaugeValue(1, fmt.Sprintf("%s_bfd_session_up", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up{%s} == 1`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_session_up{%s} == 1`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
 
-					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bfd_control_packet_input", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bfd_control_packet_input", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_control_packet_input{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_control_packet_input{%s} >= 1`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
 
-					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bfd_control_packet_output", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bfd_control_packet_output", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_control_packet_output{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_control_packet_output{%s} >= 1`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
 
-					err = metrics.ValidateGaugeValueCompare(metrics.GreaterOrEqualThan(0), "metallb_bfd_session_down_events", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateGaugeValueCompare(metrics.GreaterOrEqualThan(0), fmt.Sprintf("%s_bfd_session_down_events", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_down_events{%s} >= 0`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_session_down_events{%s} >= 0`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
 
-					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bfd_session_up_events", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bfd_session_up_events", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up_events{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_session_up_events{%s} >= 1`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
 
-					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bfd_zebra_notifications", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bfd_zebra_notifications", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_zebra_notifications{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_zebra_notifications{%s} >= 1`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
@@ -502,20 +535,20 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 						if selector.noEcho {
 							echoVal = 0
 						}
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(echoVal), "metallb_bfd_echo_packet_input", selector.labelsBFD, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(echoVal), fmt.Sprintf("%s_bfd_echo_packet_input", metricsPrefix), selector.labelsBFD, speakerMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_echo_packet_input{%s} >= %d`, selector.labelsForQueryBFD, echoVal), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_echo_packet_input{%s} >= %d`, metricsPrefix, selector.labelsForQueryBFD, echoVal), metrics.There)
 						if err != nil {
 							return err
 						}
 
-						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(echoVal), "metallb_bfd_echo_packet_output", selector.labelsBFD, speakerMetrics)
+						err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(echoVal), fmt.Sprintf("%s_bfd_echo_packet_output", metricsPrefix), selector.labelsBFD, speakerMetrics)
 						if err != nil {
 							return err
 						}
-						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_echo_packet_output{%s} >= %d`, selector.labelsForQueryBFD, echoVal), metrics.There)
+						err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_echo_packet_output{%s} >= %d`, metricsPrefix, selector.labelsForQueryBFD, echoVal), metrics.There)
 						if err != nil {
 							return err
 						}
@@ -538,26 +571,31 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 			ginkgo.By(fmt.Sprintf("checking speaker %s", speaker.Name))
 
 			Eventually(func() error {
-				speakerMetrics, err := metrics.ForPod(controllerPod, speaker, metallb.Namespace)
+				metricsPod, metricsPrefix, err := FRRProvider.BGPMetricsPodFor(speaker.Namespace, speaker.Name)
+				if err != nil {
+					return err
+				}
+
+				speakerMetrics, err := metrics.ForPod(promPod, metricsPod, metallb.Namespace)
 				if err != nil {
 					return err
 				}
 
 				for _, selector := range selectors {
-					err = metrics.ValidateGaugeValue(0, "metallb_bfd_session_up", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateGaugeValue(0, fmt.Sprintf("%s_bfd_session_up", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_up{%s} == 0`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_session_up{%s} == 0`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
 
-					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), "metallb_bfd_session_down_events", selector.labelsBFD, speakerMetrics)
+					err = metrics.ValidateCounterValue(metrics.GreaterOrEqualThan(1), fmt.Sprintf("%s_bfd_session_down_events", metricsPrefix), selector.labelsBFD, speakerMetrics)
 					if err != nil {
 						return err
 					}
-					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`metallb_bfd_session_down_events{%s} >= 1`, selector.labelsForQueryBFD), metrics.There)
+					err = metrics.ValidateOnPrometheus(promPod, fmt.Sprintf(`%s_bfd_session_down_events{%s} >= 1`, metricsPrefix, selector.labelsForQueryBFD), metrics.There)
 					if err != nil {
 						return err
 					}
@@ -648,7 +686,7 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 		for _, pod := range speakers {
 			ginkgo.By(fmt.Sprintf("checking pod %s", pod.Name))
 			Eventually(func() error {
-				podMetrics, err := metrics.ForPod(controllerPod, pod, metallb.Namespace)
+				podMetrics, err := metrics.ForPod(promPod, pod, metallb.Namespace)
 				framework.ExpectNoError(err)
 				err = metrics.ValidateGaugeValue(1, "metallb_k8s_client_config_stale_bool", map[string]string{}, podMetrics)
 				if err != nil {
@@ -668,7 +706,7 @@ var _ = ginkgo.Describe("BGP metrics", func() {
 		for _, pod := range allPods {
 			ginkgo.By(fmt.Sprintf("checking pod %s", pod.Name))
 			Eventually(func() error {
-				podMetrics, err := metrics.ForPod(controllerPod, pod, metallb.Namespace)
+				podMetrics, err := metrics.ForPod(promPod, pod, metallb.Namespace)
 				framework.ExpectNoError(err)
 				err = metrics.ValidateGaugeValue(0, "metallb_k8s_client_config_stale_bool", map[string]string{}, podMetrics)
 				if err != nil {
diff --git a/e2etest/bgptests/validate.go b/e2etest/bgptests/validate.go
index 725770e0..397140df 100644
--- a/e2etest/bgptests/validate.go
+++ b/e2etest/bgptests/validate.go
@@ -12,7 +12,6 @@ import (
 
 	"github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"go.universe.tf/e2etest/pkg/executor"
 	"go.universe.tf/e2etest/pkg/frr"
 	frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
 	"go.universe.tf/e2etest/pkg/ipfamily"
@@ -127,7 +126,8 @@ func validateServiceNoWait(svc *corev1.Service, nodes []corev1.Node, c *frrconta
 func frrIsPairedOnPods(cs clientset.Interface, n *frrcontainer.FRR, ipFamily ipfamily.Family) {
 	pods, err := metallb.SpeakerPods(cs)
 	framework.ExpectNoError(err)
-	podExecutor := executor.ForPod(metallb.Namespace, pods[0].Name, "frr")
+	podExecutor, err := FRRProvider.FRRExecutorFor(pods[0].Namespace, pods[0].Name)
+	framework.ExpectNoError(err)
 
 	Eventually(func() error {
 		addresses := n.AddressesForFamily(ipFamily)
@@ -338,7 +338,9 @@ func validateServiceNotInRoutesForCommunity(c *frrcontainer.FRR, community strin
 // returns the name of the first pod where it is found.
 func isRouteInjected(pods []*corev1.Pod, pairingFamily ipfamily.Family, routeToCheck, vrf string) (bool, string) {
 	for _, pod := range pods {
-		podExec := executor.ForPod(pod.Namespace, pod.Name, "frr")
+		podExec, err := FRRProvider.FRRExecutorFor(pod.Namespace, pod.Name)
+		framework.ExpectNoError(err)
+
 		routes, frrRoutesV6, err := frr.RoutesForVRF(vrf, podExec)
 		framework.ExpectNoError(err)
 
diff --git a/e2etest/e2etest_suite_test.go b/e2etest/e2etest_suite_test.go
index 7d3a4f22..a7fb4fd5 100644
--- a/e2etest/e2etest_suite_test.go
+++ b/e2etest/e2etest_suite_test.go
@@ -30,6 +30,7 @@ import (
 	"go.universe.tf/e2etest/bgptests"
 	"go.universe.tf/e2etest/l2tests"
 	testsconfig "go.universe.tf/e2etest/pkg/config"
+	frrprovider "go.universe.tf/e2etest/pkg/frr/provider"
 	"go.universe.tf/e2etest/pkg/iprange"
 	"go.universe.tf/e2etest/pkg/k8s"
 	"go.universe.tf/e2etest/pkg/metallb"
@@ -56,6 +57,7 @@ var (
 	localNics           string
 	externalContainers  string
 	runOnHost           bool
+	bgpMode             string
 	frrImage            string
 	hostContainerMode   string
 	withVRF             bool
@@ -65,7 +67,15 @@ var (
 func handleFlags() {
 	e2econfig.CopyFlags(e2econfig.Flags, flag.CommandLine)
 	framework.RegisterCommonFlags(flag.CommandLine)
-	framework.RegisterClusterFlags(flag.CommandLine)
+	/*
+		Using framework.RegisterClusterFlags(flag.CommandLine) results in a panic:
+		"flag redefined: kubeconfig".
+		This happens because controller-runtime registers the kubeconfig flag as well.
+		To solve this we set the framework's kubeconfig directly via the KUBECONFIG env var
+		instead of letting it call the flag. Since we also use the provider flag it is handled manually.
+	*/
+	flag.StringVar(&framework.TestContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, skeleton (the fallback if not set), etc.)")
+	framework.TestContext.KubeConfig = os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
 
 	flag.IntVar(&service.TestServicePort, "service-pod-port", 80, "port number that pod opens, default: 80")
 	flag.BoolVar(&skipDockerCmd, "skip-docker", false, "set this to true if the BGP daemon is running on the host instead of in a container")
@@ -80,6 +90,7 @@ func handleFlags() {
 	flag.StringVar(&frrImage, "frr-image", "quay.io/frrouting/frr:8.5.2", "the image to use for the external frr containers")
 	flag.StringVar(&hostContainerMode, "host-bgp-mode", string(bgptests.IBGPMode), "tells whether to run the host container in ebgp or ibgp mode")
 	flag.BoolVar(&withVRF, "with-vrf", false, "runs the tests against containers reacheable via linux vrfs. More coverage, but might not work depending on the OS")
+	flag.StringVar(&bgpMode, "bgp-mode", "", "says which bgp mode we are testing against. valid options are: native, frr, frr-k8s")
 
 	flag.Parse()
 
@@ -167,6 +178,19 @@ var _ = ginkgo.BeforeSuite(func() {
 	updaterOtherNS, err = testsconfig.UpdaterForCRs(clientconfig, otherNamespace)
 	framework.ExpectNoError(err)
 
+	switch bgpMode {
+	case "native":
+		bgptests.FRRProvider = nil
+	case "frr":
+		bgptests.FRRProvider, err = frrprovider.NewFRRMode(clientconfig)
+		framework.ExpectNoError(err)
+	case "frr-k8s":
+		bgptests.FRRProvider, err = frrprovider.NewFRRK8SMode(clientconfig)
+		framework.ExpectNoError(err)
+	default:
+		framework.Fail(fmt.Sprintf("unsupported --bgp-mode %s - supported options are: native, frr, frr-k8s", bgpMode))
+	}
+
 	reporter := k8s.InitReporter(framework.TestContext.KubeConfig, reportPath, metallb.Namespace)
 
 	bgptests.ConfigUpdater = updater
diff --git a/e2etest/go.mod b/e2etest/go.mod
index 581b6f5a..c2c16c36 100644
--- a/e2etest/go.mod
+++ b/e2etest/go.mod
@@ -35,22 +35,23 @@ replace (
 )
 
 require (
-	github.com/google/go-cmp v0.5.9
+	github.com/google/go-cmp v0.6.0
+	github.com/metallb/frr-k8s v0.0.4
 	github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721
-	github.com/onsi/ginkgo/v2 v2.11.0
-	github.com/onsi/gomega v1.27.10
+	github.com/onsi/ginkgo/v2 v2.13.0
+	github.com/onsi/gomega v1.28.1
 	github.com/openshift-kni/k8sreporter v1.0.3
 	github.com/pkg/errors v0.9.1
-	github.com/prometheus/client_model v0.4.0
+	github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16
 	github.com/prometheus/common v0.44.0
 	go.universe.tf/metallb v0.13.9
 	golang.org/x/sync v0.3.0
-	k8s.io/api v0.28.2
-	k8s.io/apimachinery v0.28.2
+	k8s.io/api v0.28.3
+	k8s.io/apimachinery v0.28.3
 	k8s.io/client-go v1.5.2
 	k8s.io/kubernetes v1.28.2
 	k8s.io/pod-security-admission v0.0.0
-	sigs.k8s.io/controller-runtime v0.16.2
+	sigs.k8s.io/controller-runtime v0.16.3
 )
 
 require (
@@ -65,7 +66,7 @@ require (
 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/docker/distribution v2.8.2+incompatible // indirect
-	github.com/emicklei/go-restful/v3 v3.10.1 // indirect
+	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
 	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
@@ -74,6 +75,7 @@ require (
 	github.com/go-logfmt/logfmt v0.5.1 // indirect
 	github.com/go-logr/logr v1.2.4 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-logr/zapr v1.2.4 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
@@ -100,8 +102,8 @@ require (
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.16.0 // indirect
-	github.com/prometheus/procfs v0.10.1 // indirect
+	github.com/prometheus/client_golang v1.17.0 // indirect
+	github.com/prometheus/procfs v0.11.1 // indirect
 	github.com/spf13/cobra v1.7.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/stoewer/go-strcase v1.2.0 // indirect
@@ -135,23 +137,23 @@ require (
 	google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
 	google.golang.org/grpc v1.56.3 // indirect
-	google.golang.org/protobuf v1.30.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/apiextensions-apiserver v0.28.2 // indirect
-	k8s.io/apiserver v0.28.2 // indirect
+	k8s.io/apiextensions-apiserver v0.28.3 // indirect
+	k8s.io/apiserver v0.28.3 // indirect
 	k8s.io/cloud-provider v0.0.0 // indirect
-	k8s.io/component-base v0.28.2 // indirect
+	k8s.io/component-base v0.28.3 // indirect
 	k8s.io/component-helpers v0.28.2 // indirect
 	k8s.io/controller-manager v0.28.2 // indirect
 	k8s.io/klog/v2 v2.100.1 // indirect
-	k8s.io/kms v0.28.2 // indirect
+	k8s.io/kms v0.28.3 // indirect
 	k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
 	k8s.io/kubectl v0.0.0 // indirect
 	k8s.io/kubelet v0.0.0 // indirect
-	k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
+	k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
 	sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
diff --git a/e2etest/go.sum b/e2etest/go.sum
index 52278cbf..c8f558a1 100644
--- a/e2etest/go.sum
+++ b/e2etest/go.sum
@@ -45,6 +45,7 @@ github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/g
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -81,8 +82,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
-github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
+github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -114,6 +115,7 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
+github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
 github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
 github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
@@ -176,8 +178,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -236,6 +238,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/metallb/frr-k8s v0.0.4 h1:tz7x2JLDNj57dqZvnC1tsE92tjGLKyw7BgcsTNzEMIY=
+github.com/metallb/frr-k8s v0.0.4/go.mod h1:4sXI3rKEwYdLc0g1lXKQDK0ovQc1crkBOjHDhPa1H14=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
 github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
@@ -249,8 +253,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
 github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
-github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
-github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
+github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/openshift-kni/k8sreporter v1.0.3 h1:PR7wf6lEEZeh27dnXxrDuGEtKpBPYuh+WxQJDWohrOU=
@@ -260,15 +264,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
-github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
-github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
+github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
 github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
 github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
-github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
-github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
+github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -300,6 +304,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
 go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
 go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
@@ -337,9 +342,13 @@ go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/A
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
 go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
 go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
 go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
 go.universe.tf/metallb v0.13.9 h1:x5OZ0bUxdKc/uKrbr14kAs9QVA/l9dKs9CNI0bu/lnk=
@@ -383,6 +392,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -431,6 +441,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -523,6 +534,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
 golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -623,8 +635,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -682,15 +694,15 @@ k8s.io/kubernetes v1.28.2 h1:GhcnYeNTukeaC0dD5BC+UWBvzQsFEpWj7XBVMQptfYc=
 k8s.io/kubernetes v1.28.2/go.mod h1:FmB1Mlp9ua0ezuwQCTGs/y6wj/fVisN2sVxhzjj0WDk=
 k8s.io/pod-security-admission v0.28.2 h1:3kiOL+gc6auNTGHuQ0hVsGxYu2YO/7DZb0xYR84GxiQ=
 k8s.io/pod-security-admission v0.28.2/go.mod h1:gReea39xbhIzf4Ry0FDuiTi8uj1N5R9YXOh8zQSuTxs=
-k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
-k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0=
-sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQVuIPU=
-sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU=
+sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
+sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
diff --git a/e2etest/l2tests/l2.go b/e2etest/l2tests/l2.go
index 7249d0c7..17319173 100644
--- a/e2etest/l2tests/l2.go
+++ b/e2etest/l2tests/l2.go
@@ -510,7 +510,7 @@ var _ = ginkgo.Describe("L2", func() {
 
 			ginkgo.By("checking the metrics when no service is added")
 			gomega.Eventually(func() error {
-				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
+				controllerMetrics, err := metrics.ForPod(promPod, controllerPod, metallb.Namespace)
 				if err != nil {
 					return err
 				}
@@ -544,7 +544,7 @@ var _ = ginkgo.Describe("L2", func() {
 
 			ginkgo.By("checking the metrics when a service is added")
 			gomega.Eventually(func() error {
-				controllerMetrics, err := metrics.ForPod(controllerPod, controllerPod, metallb.Namespace)
+				controllerMetrics, err := metrics.ForPod(promPod, controllerPod, metallb.Namespace)
 				if err != nil {
 					return err
 				}
@@ -590,7 +590,7 @@ var _ = ginkgo.Describe("L2", func() {
 					return fmt.Errorf("could not find speaker pod on announcing node %s", advNode.Name)
 				}
 
-				speakerMetrics, err := metrics.ForPod(controllerPod, advSpeaker, metallb.Namespace)
+				speakerMetrics, err := metrics.ForPod(promPod, advSpeaker, metallb.Namespace)
 				if err != nil {
 					return err
 				}
@@ -644,7 +644,7 @@ var _ = ginkgo.Describe("L2", func() {
 			delete(speakerPods, advSpeaker.Spec.NodeName)
 
 			for _, p := range speakerPods {
-				speakerMetrics, err := metrics.ForPod(controllerPod, p, metallb.Namespace)
+				speakerMetrics, err := metrics.ForPod(promPod, p, metallb.Namespace)
 				framework.ExpectNoError(err)
 
 				err = metrics.ValidateGaugeValue(1, "metallb_speaker_announced", map[string]string{"node": p.Spec.NodeName, "protocol": "layer2", "service": fmt.Sprintf("%s/%s", f.Namespace.Name, svc.Name)}, speakerMetrics)
@@ -660,7 +660,7 @@ var _ = ginkgo.Describe("L2", func() {
 			framework.ExpectNoError(err)
 
 			gomega.Eventually(func() error {
-				speakerMetrics, err := metrics.ForPod(controllerPod, advSpeaker, metallb.Namespace)
+				speakerMetrics, err := metrics.ForPod(promPod, advSpeaker, metallb.Namespace)
 				if err != nil {
 					return err
 				}
diff --git a/e2etest/pkg/frr/provider/provider.go b/e2etest/pkg/frr/provider/provider.go
new file mode 100644
index 00000000..42dbbd3d
--- /dev/null
+++ b/e2etest/pkg/frr/provider/provider.go
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier:Apache-2.0
+
+package provider
+
+import (
+	"fmt"
+
+	"go.universe.tf/e2etest/pkg/executor"
+	"go.universe.tf/e2etest/pkg/metallb"
+	corev1 "k8s.io/api/core/v1"
+	clientset "k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+)
+
+type Provider interface {
+	// FRRExecutorFor returns an executor for the frr instance corresponding to the given speaker.
+	FRRExecutorFor(speakerNamespace, speakerName string) (executor.Executor, error)
+
+	// BGPMetricsPodFor returns the pod object to be scraped for FRR BGP/BFD metrics corresponding
+	// to the given speaker. It also returns the metric prefix to expect when scraping the pod directly.
+	BGPMetricsPodFor(speakerNamespace, speakerName string) (pod *corev1.Pod, metricsPrefix string, err error)
+}
+
+type frrModeProvider struct {
+	speakers map[string]*corev1.Pod
+}
+
+// NewFRRMode returns a provider for a deployment using "frr" as its BGP mode.
+// In this mode the frr instance for a node is a sidecar container of the speaker pod.
+func NewFRRMode(r *rest.Config) (Provider, error) {
+	cl, err := clientset.NewForConfig(r)
+	if err != nil {
+		return nil, err
+	}
+
+	speakerPods, err := metallb.SpeakerPods(cl)
+	if err != nil {
+		return nil, err
+	}
+
+	speakers := map[string]*corev1.Pod{}
+	for _, p := range speakerPods {
+		speakers[p.Name] = p
+	}
+
+	return frrModeProvider{speakers: speakers}, nil
+}
+
+func (f frrModeProvider) FRRExecutorFor(ns, name string) (executor.Executor, error) {
+	_, ok := f.speakers[name]
+	if !ok {
+		return nil, fmt.Errorf("speakers %s/%s not found in known speakers %v", ns, name, f.speakers)
+	}
+
+	return executor.ForPod(ns, name, "frr"), nil
+}
+
+func (f frrModeProvider) BGPMetricsPodFor(ns, name string) (*corev1.Pod, string, error) {
+	p, ok := f.speakers[name]
+	if !ok {
+		return nil, "", fmt.Errorf("speakers %s/%s not found in in known speakers %v", ns, name, f.speakers)
+	}
+
+	return p, "metallb", nil
+}
+
+type frrk8sModeProvider struct {
+	frrk8sPodForSpeakerPod map[string]*corev1.Pod
+}
+
+// NewFRRK8SMode returns a provider for a deployment using "frr-k8s" as its BGP mode.
+// In this mode the frr instance for a node is a sidecar container of the frr-k8s pod.
+func NewFRRK8SMode(r *rest.Config) (Provider, error) {
+	cl, err := clientset.NewForConfig(r)
+	if err != nil {
+		return nil, err
+	}
+
+	speakerPods, err := metallb.SpeakerPods(cl)
+	if err != nil {
+		return nil, err
+	}
+
+	frrk8sPods, err := metallb.FRRK8SPods(cl)
+	if err != nil {
+		return nil, err
+	}
+
+	frrK8SForSpeaker := map[string]*corev1.Pod{}
+	for _, s := range speakerPods {
+		found := false
+		for _, f := range frrk8sPods {
+			if s.Spec.NodeName == f.Spec.NodeName {
+				frrK8SForSpeaker[s.Name] = f
+				found = true
+			}
+		}
+		if !found {
+			return nil, fmt.Errorf("speaker %s/%s on node %s does not have a matching frr-k8s", s.Namespace, s.Name, s.Spec.NodeName)
+		}
+	}
+
+	res := frrk8sModeProvider{
+		frrk8sPodForSpeakerPod: frrK8SForSpeaker,
+	}
+
+	return res, nil
+}
+
+func (f frrk8sModeProvider) FRRExecutorFor(ns, name string) (executor.Executor, error) {
+	frrk8s, ok := f.frrk8sPodForSpeakerPod[name]
+	if !ok {
+		return nil, fmt.Errorf("speaker %s/%s does not have a matching frr-k8s", ns, name)
+	}
+
+	return executor.ForPod(ns, frrk8s.Name, "frr"), nil
+}
+
+func (f frrk8sModeProvider) BGPMetricsPodFor(ns, name string) (*corev1.Pod, string, error) {
+	p, ok := f.frrk8sPodForSpeakerPod[name]
+	if !ok {
+		return nil, "", fmt.Errorf("speakers %s/%s not found in map %v", ns, name, f.frrk8sPodForSpeakerPod)
+	}
+
+	return p, "frrk8s", nil
+}
diff --git a/e2etest/pkg/k8s/reporter.go b/e2etest/pkg/k8s/reporter.go
index 14ffd7f9..5bd28dda 100644
--- a/e2etest/pkg/k8s/reporter.go
+++ b/e2etest/pkg/k8s/reporter.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 	"time"
 
+	frrk8sv1beta1 "github.com/metallb/frr-k8s/api/v1beta1"
 	"github.com/onsi/ginkgo/v2"
 	"github.com/openshift-kni/k8sreporter"
 	metallbv1beta1 "go.universe.tf/metallb/api/v1beta1"
@@ -26,6 +27,11 @@ func InitReporter(kubeconfig, path, namespace string) *k8sreporter.KubernetesRep
 		if err != nil {
 			return err
 		}
+		err = frrk8sv1beta1.AddToScheme(s)
+		if err != nil {
+			return err
+		}
+
 		return nil
 	}
 
@@ -52,6 +58,8 @@ func InitReporter(kubeconfig, path, namespace string) *k8sreporter.KubernetesRep
 		{Cr: &metallbv1beta1.BFDProfileList{}},
 		{Cr: &metallbv1beta1.CommunityList{}},
 		{Cr: &corev1.ServiceList{}},
+		{Cr: &frrk8sv1beta1.FRRConfigurationList{}},
+		{Cr: &frrk8sv1beta1.FRRNodeStateList{}},
 	}
 
 	reporter, err := k8sreporter.New(kubeconfig, addToScheme, dumpNamespace, path, crds...)
diff --git a/e2etest/pkg/metallb/config.go b/e2etest/pkg/metallb/config.go
index 6828e128..81e691ac 100644
--- a/e2etest/pkg/metallb/config.go
+++ b/e2etest/pkg/metallb/config.go
@@ -7,9 +7,9 @@ import (
 	"os"
 	"time"
 
-	metallbv1beta2 "go.universe.tf/metallb/api/v1beta2"
 	frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
 	"go.universe.tf/e2etest/pkg/ipfamily"
+	metallbv1beta2 "go.universe.tf/metallb/api/v1beta2"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
diff --git a/e2etest/pkg/metallb/metallb.go b/e2etest/pkg/metallb/metallb.go
index 846b0d3d..58541b4f 100644
--- a/e2etest/pkg/metallb/metallb.go
+++ b/e2etest/pkg/metallb/metallb.go
@@ -101,3 +101,22 @@ func RestartController(cs clientset.Interface) {
 	})
 	framework.ExpectNoError(err)
 }
+
+// FRRK8SPods returns the set of pods related to FRR-K8s.
+func FRRK8SPods(cs clientset.Interface) ([]*corev1.Pod, error) {
+	pods, err := cs.CoreV1().Pods(Namespace).List(context.Background(), metav1.ListOptions{
+		LabelSelector: "app=frr-k8s",
+	})
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to fetch frr-k8s pods")
+	}
+	if len(pods.Items) == 0 {
+		return nil, errors.New("No frr-k8s pods found")
+	}
+	res := make([]*corev1.Pod, 0)
+	for _, item := range pods.Items {
+		i := item
+		res = append(res, &i)
+	}
+	return res, nil
+}
diff --git a/e2etest/pkg/metrics/metrics.go b/e2etest/pkg/metrics/metrics.go
index c38556e3..d3e19a81 100644
--- a/e2etest/pkg/metrics/metrics.go
+++ b/e2etest/pkg/metrics/metrics.go
@@ -31,21 +31,31 @@ type prometheusResponseData struct {
 
 // MetricsForPod returns the parsed metrics for the given pod, scraping them
 // from the source pod.
-func ForPod(controller, target *corev1.Pod, namespace string) ([]map[string]*dto.MetricFamily, error) {
+func ForPod(promPod, target *corev1.Pod, namespace string) ([]map[string]*dto.MetricFamily, error) {
 	ports := make([]int, 0)
 	allMetrics := make([]map[string]*dto.MetricFamily, 0)
 	for _, c := range target.Spec.Containers {
 		for _, p := range c.Ports {
-			if p.Name == "monitoring" {
+			if p.Name == "metricshttps" || p.Name == "frrmetricshttps" {
 				ports = append(ports, int(p.ContainerPort))
 			}
 		}
 	}
 
-	podExecutor := executor.ForPod(namespace, controller.Name, "controller")
+	podExecutor := executor.ForPod(promPod.Namespace, promPod.Name, "prometheus")
+
+	// We add a token header to the requests, without it kube-rbac-proxy returns Unauthorized.
+	token, err := podExecutor.Exec("cat", "/var/run/secrets/kubernetes.io/serviceaccount/token")
+	if err != nil {
+		return nil, err
+	}
+
 	for _, p := range ports {
-		metricsURL := path.Join(net.JoinHostPort(target.Status.PodIP, strconv.Itoa(p)), "metrics")
-		metrics, err := podExecutor.Exec("wget", "-qO-", metricsURL)
+		metricsPath := path.Join(net.JoinHostPort(target.Status.PodIP, strconv.Itoa(p)), "metrics")
+		metricsURL := fmt.Sprintf("https://%s", metricsPath)
+		metrics, err := podExecutor.Exec("wget",
+			"--no-check-certificate", "-qO-", metricsURL,
+			"--header", fmt.Sprintf("Authorization: Bearer %s", token))
 		if err != nil {
 			return nil, errors.Wrapf(err, "failed to scrape metrics for %s", target.Name)
 		}
diff --git a/internal/bgp/frrk8s/testdata/TestLargeCommunities.golden b/internal/bgp/frrk8s/testdata/TestLargeCommunities.golden
index 042ecc21..0bd94459 100644
--- a/internal/bgp/frrk8s/testdata/TestLargeCommunities.golden
+++ b/internal/bgp/frrk8s/testdata/TestLargeCommunities.golden
@@ -39,19 +39,19 @@
                                         "prefixes": [
                                             "172.16.1.10/24"
                                         ],
-                                        "community": "1111:2222:3333"
+                                        "community": "3333:4444"
                                     },
                                     {
                                         "prefixes": [
                                             "172.16.1.10/24"
                                         ],
-                                        "community": "2222:3333:4444"
+                                        "community": "large:1111:2222:3333"
                                     },
                                     {
                                         "prefixes": [
                                             "172.16.1.10/24"
                                         ],
-                                        "community": "3333:4444"
+                                        "community": "large:2222:3333:4444"
                                     }
                                 ]
                             },