Skip to content

Commit d2bc261

Browse files
authored
chore(metallb): bump to v0.16.1 and rotate host-network port denylist (#2699)
## What this PR does Bump vendored MetalLB chart from v0.15.2 to v0.16.1 and rebuild the cozystack-built `metallb-controller` / `metallb-speaker` images at the new version. Adopt the upstream-default BGP backend: FRR-K8s replaces the deprecated FRR mode. Rotate the cilium-networkpolicy clusterwide host-network port denylist to match the v0.16 listener set. The chart introduces a new probe contract (`/healthz` and `/readyz` on a dedicated host-local port 17472, distinct from the metrics port), so the cozystack-built controller and speaker images must move in lockstep with the chart. Leaving the digests on v0.15.2 would CrashLoopBackOff every pod on main between merge and the next release-prep commit. Upstream highlights between 0.15.2 and 0.16.1: - 0.16.0: FRR-K8s is the new default BGP backend; legacy FRR is deprecated. - 0.16.0: kube-rbac-proxy replaced by native TLS; metrics are HTTPS-only on the formerly-rbac-proxy port 9120. Default TLS minimum is now 1.3 (was 1.2); operators with TLS 1.2 scrapers override via `metallb.tls.minVersion: VersionTLS12`. - 0.16.0: new `tls.*` and `networkpolicies.*` value blocks (opt-in). - 0.16.1: chart-only patch — liveness/readiness probe port is now a configurable value (default unchanged at 17472), controller probes drop the hardcoded `host: 127.0.0.1`, pods/ServiceMonitor/PodMonitor gain `prometheus.io/scheme: https`, the frr-k8s subchart gains an opt-in ServiceMonitor toggle (default off), and one CRD numeric field widens from int32 to int64. - 0.15.3: `ConfigurationState` CRD exposes reconcile errors per component; chart gained NetworkPolicy support and tightened pod SecurityContext. The `packages/system/metallb/values.yaml` header documents the changes operators see on upgrade, including the partial nature of the legacy-FRR escape hatch (Helm evaluates dep `condition:` against the declaring chart's values context, so a grandparent override from the umbrella cannot suppress the frr-k8s subchart through that condition; the subchart stays installed but idle when the speaker is flipped to FRR mode). New helm-unittest coverage pins the post-bump invariants and would have caught the original chart/binary skew before merge: controller/speaker image tags exactly match the chart `appVersion`, speaker is the single-container DaemonSet with `BGP_TYPE=frr-k8s`, frr-k8s DaemonSet and CRDs are installed, NetworkPolicies stay off by default. The cilium-networkpolicy chart gains its first unittest suite, enumerating every metallb v0.16 host-network port plus the unrelated etcd kube-rbac-proxy port that shares port number 9443 (pod-IP vs host-network). ### Release note ```release-note chore(metallb): bump MetalLB to v0.16.1; adopt upstream-default FRR-K8s BGP backend in place of the deprecated FRR mode. Metrics endpoints move from HTTP on 7472 to HTTPS on 9120 with native TLS 1.3 by default (operators on TLS 1.2 scrapers must set `metallb.tls.minVersion: VersionTLS12`). The cilium-networkpolicy clusterwide host-network port denylist is rotated to cover the new metallb listeners (7946 memberlist, 9120, 9121, 9140, 9141, 19443). The legacy FRR mode remains available via `metallb.frrk8s.enabled=false` + `metallb.speaker.frr.enabled=true`, with the caveat that the frr-k8s subchart stays installed (idle) under that escape hatch. ```
2 parents 94f463d + a9c8f20 commit d2bc261

45 files changed

Lines changed: 3423 additions & 268 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/system/cilium-networkpolicy/Makefile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ export NAMESPACE=cozy-$(NAME)
33

44
include ../../../hack/common-envs.mk
55
include ../../../hack/package.mk
6+
7+
test:
8+
helm unittest .

‎packages/system/cilium-networkpolicy/templates/networkpolicy.yaml‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,27 @@ spec:
1212
- port: "2379" # etcd
1313
- port: "2380" # etcd
1414
- port: "3367" # linstor
15-
- port: "7473" # frr-metrics (metallb)
15+
# metallb speaker memberlist gossip is bound on both TCP and UDP on
16+
# host-network. Cilium PortProtocol matches all transport protocols
17+
# (TCP+UDP+SCTP) when `protocol:` is "ANY", omitted, or empty — so the
18+
# other entries in this list already cover their (single-protocol)
19+
# listeners. The explicit `protocol: ANY` on 7946 is documentation of
20+
# intent for the one multi-protocol listener in the set; it is not a
21+
# behavior change vs. omitting the field. Cluster peers still reach
22+
# 7946 via the ingress fromEntities[cluster,host] clause below
23+
# regardless of the deny.
24+
- port: "7946"
25+
protocol: ANY
1626
- port: "8123" # cozy assets server
17-
- port: "9443" # kube-rbac-proxy
27+
- port: "9120" # metallb speaker metrics (HTTPS, host-network)
28+
- port: "9121" # metallb legacy FRR metrics (HTTPS, host-network; only bound when speaker.frr.enabled=true escape hatch is engaged)
29+
- port: "9140" # metallb frr-k8s controller metrics (HTTPS, host-network)
30+
- port: "9141" # metallb frr-k8s FRR metrics (HTTPS, host-network)
31+
- port: "9443" # etcd kube-rbac-proxy host-network bind (cozy-monitoring DaemonSet); metallb controller also binds 9443 on pod-IP for its webhook — pod-IP is out of scope for this world-deny but the port number collides
1832
- port: "10250" # kubelet
1933
- port: "10257" # kube-controller-manager
2034
- port: "10259" # kube-scheduler
35+
- port: "19443" # metallb frr-k8s statuscleaner webhook (HTTPS, host-network)
2136
ingress:
2237
- fromEntities:
2338
- world
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
suite: cilium-networkpolicy port-deny invariants
2+
release:
3+
name: cilium-networkpolicy
4+
namespace: cozy-cilium-networkpolicy
5+
templates:
6+
- templates/networkpolicy.yaml
7+
tests:
8+
- it: denies world ingress to every metallb v0.16 host-network listener
9+
# These ports are opened on host-network by metallb v0.16:
10+
# 7946 — speaker memberlist gossip (TCP/UDP). Cluster peers still
11+
# reach it via the ingress fromEntities[cluster,host] clause
12+
# on the same policy; the deny only blocks `world`.
13+
# 9120 — speaker metrics (HTTPS).
14+
# 9121 — legacy FRR metrics (HTTPS, only bound when the escape hatch
15+
# speaker.frr.enabled=true is engaged; kept in the denylist
16+
# defensively so the escape hatch stays safe).
17+
# 9140 — frr-k8s controller metrics (HTTPS, DaemonSet).
18+
# 9141 — frr-k8s FRR metrics (HTTPS, DaemonSet).
19+
# 19443 — frr-k8s statuscleaner webhook (HTTPS, Deployment with
20+
# hostNetwork: true and master+control-plane tolerations).
21+
# The deny list MUST cover all six; missing any one leaks the
22+
# corresponding endpoint to world on any node that schedules the pod.
23+
# Port 9443 is bound on host-network by the cozy-monitoring etcd
24+
# kube-rbac-proxy DaemonSet — asserted separately below; metallb
25+
# controller webhook collides on the same port number but on pod-IP,
26+
# out of scope for this world-deny.
27+
asserts:
28+
# 7946 carries memberlist gossip on both TCP and UDP. Cilium's
29+
# PortProtocol matches all transport protocols when "ANY", omitted,
30+
# or empty, so this entry would be a functional no-op without
31+
# `protocol: ANY` too — the assertion pins the explicit-intent
32+
# marker as the contract for the one multi-protocol listener, so a
33+
# future edit that drops `protocol: ANY` (and silently relies on
34+
# the implicit match) fails this test.
35+
- contains:
36+
path: spec.ingressDeny[0].toPorts[0].ports
37+
content:
38+
port: "7946"
39+
protocol: ANY
40+
- contains:
41+
path: spec.ingressDeny[0].toPorts[0].ports
42+
content:
43+
port: "9120"
44+
- contains:
45+
path: spec.ingressDeny[0].toPorts[0].ports
46+
content:
47+
port: "9121"
48+
- contains:
49+
path: spec.ingressDeny[0].toPorts[0].ports
50+
content:
51+
port: "9140"
52+
- contains:
53+
path: spec.ingressDeny[0].toPorts[0].ports
54+
content:
55+
port: "9141"
56+
- contains:
57+
path: spec.ingressDeny[0].toPorts[0].ports
58+
content:
59+
port: "19443"
60+
61+
- it: denies world ingress to the host-network 9443 listener (etcd kube-rbac-proxy)
62+
# The host-network bind on port 9443 belongs to the cozy-monitoring
63+
# kube-rbac-proxy DaemonSet
64+
# (packages/system/monitoring-agents/templates/etcd-proxy-scrape.yaml,
65+
# --secure-listen-address=$(NODE_IP):9443). Port 9443 is ALSO bound by
66+
# the metallb controller Deployment as its webhook server, but on pod-IP
67+
# (non-host-network), which is out of scope for this world-deny. Pin the
68+
# entry here so a future PR that drops the cozy-monitoring stack does
69+
# not silently remove this deny — the port-number collision with the
70+
# metallb controller webhook does not give that webhook any protection
71+
# under this policy regardless.
72+
asserts:
73+
- contains:
74+
path: spec.ingressDeny[0].toPorts[0].ports
75+
content:
76+
port: "9443"
77+
78+
- it: drops the stale frr-mode metrics port now that frrk8s is the default
79+
# Port 7473 was metallb's legacy FRR-mode metrics endpoint. v0.16 disables
80+
# FRR mode by default and migrates BGP to frr-k8s; the port is no longer
81+
# bound by anything cozystack ships, so listing it in the deny rule is
82+
# misleading.
83+
asserts:
84+
- notContains:
85+
path: spec.ingressDeny[0].toPorts[0].ports
86+
content:
87+
port: "7473"

‎packages/system/metallb/Makefile‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ update:
88
rm -rf charts
99
helm repo add metallb https://metallb.github.io/metallb
1010
helm repo update metallb
11-
helm pull metallb/metallb --untar --untardir charts
12-
rm -rf charts/metallb/charts/frr-k8s
11+
helm pull metallb/metallb --version 0.16.1 --untar --untardir charts
12+
13+
test:
14+
helm unittest .
1315

1416
image-controller image-speaker:
1517
$(eval TARGET := $(subst image-,,$@))
1618
$(eval VERSION := $(shell yq '.appVersion' charts/metallb/Chart.yaml))
1719
docker buildx build images/metallb \
20+
--platform=linux/amd64 \
1821
--target $(TARGET) \
1922
--build-arg VERSION=$(VERSION) \
2023
$(call image-tags,metallb-$(TARGET),$(VERSION)) \
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
dependencies:
22
- name: crds
33
repository: ""
4-
version: 0.15.2
4+
version: 0.16.1
55
- name: frr-k8s
66
repository: https://metallb.github.io/frr-k8s
7-
version: 0.0.20
8-
digest: sha256:fcba6d1c8e25efca7a64d2ea30079bfcf041c277605d5881d65d5b3d09a41483
9-
generated: "2025-06-04T12:37:51.709832109+02:00"
7+
version: 0.0.25
8+
digest: sha256:91f6f08e93bcfc87c9388e5b063b814754151fced237f489cc84feaf5cf13584
9+
generated: "2026-05-27T22:48:56.242027047+02:00"
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
apiVersion: v2
2-
appVersion: v0.15.2
2+
appVersion: v0.16.1
33
dependencies:
44
- condition: crds.enabled
55
name: crds
66
repository: ""
7-
version: 0.15.2
7+
version: 0.16.1
88
- condition: frrk8s.enabled
99
name: frr-k8s
1010
repository: https://metallb.github.io/frr-k8s
11-
version: 0.0.20
11+
version: 0.0.25
1212
description: A network load-balancer implementation for Kubernetes using standard
1313
routing protocols
1414
home: https://metallb.universe.tf
@@ -18,4 +18,4 @@ name: metallb
1818
sources:
1919
- https://github.com/metallb/metallb
2020
type: application
21-
version: 0.15.2
21+
version: 0.16.1

‎packages/system/metallb/charts/metallb/README.md‎

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Kubernetes: `>= 1.19.0-0`
1717
| Repository | Name | Version |
1818
|------------|------|---------|
1919
| | crds | 0.0.0 |
20-
| https://metallb.github.io/frr-k8s | frr-k8s | 0.0.20 |
20+
| https://metallb.github.io/frr-k8s | frr-k8s | 0.0.25 |
2121

2222
## Values
2323

@@ -34,6 +34,7 @@ Kubernetes: `>= 1.19.0-0`
3434
| controller.livenessProbe.failureThreshold | int | `3` | |
3535
| controller.livenessProbe.initialDelaySeconds | int | `10` | |
3636
| controller.livenessProbe.periodSeconds | int | `10` | |
37+
| controller.livenessProbe.port | int | `17472` | |
3738
| controller.livenessProbe.successThreshold | int | `1` | |
3839
| controller.livenessProbe.timeoutSeconds | int | `1` | |
3940
| controller.logLevel | string | `"info"` | Controller log level. Must be one of: `all`, `debug`, `info`, `warn`, `error` or `none` |
@@ -44,6 +45,7 @@ Kubernetes: `>= 1.19.0-0`
4445
| controller.readinessProbe.failureThreshold | int | `3` | |
4546
| controller.readinessProbe.initialDelaySeconds | int | `10` | |
4647
| controller.readinessProbe.periodSeconds | int | `10` | |
48+
| controller.readinessProbe.port | int | `17472` | |
4749
| controller.readinessProbe.successThreshold | int | `1` | |
4850
| controller.readinessProbe.timeoutSeconds | int | `1` | |
4951
| controller.resources | object | `{}` | |
@@ -55,20 +57,22 @@ Kubernetes: `>= 1.19.0-0`
5557
| controller.serviceAccount.create | bool | `true` | |
5658
| controller.serviceAccount.name | string | `""` | |
5759
| controller.strategy.type | string | `"RollingUpdate"` | |
58-
| controller.tlsCipherSuites | string | `""` | |
59-
| controller.tlsMinVersion | string | `"VersionTLS12"` | |
6060
| controller.tolerations | list | `[]` | |
61+
| controller.webhookMode | string | `"enabled"` | |
6162
| crds.enabled | bool | `true` | |
6263
| crds.validationFailurePolicy | string | `"Fail"` | |
63-
| frrk8s.enabled | bool | `false` | |
64-
| frrk8s.external | bool | `false` | |
65-
| frrk8s.namespace | string | `""` | |
64+
| frr-k8s.prometheus.serviceMonitor.enabled | bool | `false` | Enable Prometheus ServiceMonitor for frr-k8s metrics. |
65+
| frrk8s.enabled | bool | `true` | If set, enables frrk8s as a backend. This is mutually exclusive to frr mode. |
66+
| frrk8s.external | bool | `false` | If true, uses an external frr-k8s installation instead of the bundled subchart. |
67+
| frrk8s.namespace | string | `""` | Namespace where external frr-k8s is installed (only used when external=true). |
6668
| fullnameOverride | string | `""` | |
6769
| imagePullSecrets | list | `[]` | |
6870
| loadBalancerClass | string | `""` | |
6971
| nameOverride | string | `""` | |
70-
| prometheus.controllerMetricsTLSSecret | string | `""` | |
71-
| prometheus.metricsPort | int | `7472` | |
72+
| networkpolicies.apiPort | int | `6443` | |
73+
| networkpolicies.defaultDeny | bool | `false` | |
74+
| networkpolicies.enabled | bool | `false` | |
75+
| prometheus.metricsPort | int | `9120` | |
7276
| prometheus.namespace | string | `""` | |
7377
| prometheus.podMonitor.additionalLabels | object | `{}` | |
7478
| prometheus.podMonitor.annotations | object | `{}` | |
@@ -99,9 +103,6 @@ Kubernetes: `>= 1.19.0-0`
99103
| prometheus.prometheusRule.staleConfig.enabled | bool | `true` | |
100104
| prometheus.prometheusRule.staleConfig.labels.severity | string | `"warning"` | |
101105
| prometheus.rbacPrometheus | bool | `true` | |
102-
| prometheus.rbacProxy.pullPolicy | string | `nil` | |
103-
| prometheus.rbacProxy.repository | string | `"gcr.io/kubebuilder/kube-rbac-proxy"` | |
104-
| prometheus.rbacProxy.tag | string | `"v0.12.0"` | |
105106
| prometheus.scrapeAnnotations | bool | `false` | |
106107
| prometheus.serviceAccount | string | `""` | |
107108
| prometheus.serviceMonitor.controller.additionalLabels | object | `{}` | |
@@ -115,28 +116,32 @@ Kubernetes: `>= 1.19.0-0`
115116
| prometheus.serviceMonitor.speaker.additionalLabels | object | `{}` | |
116117
| prometheus.serviceMonitor.speaker.annotations | object | `{}` | |
117118
| prometheus.serviceMonitor.speaker.tlsConfig.insecureSkipVerify | bool | `true` | |
118-
| prometheus.speakerMetricsTLSSecret | string | `""` | |
119119
| rbac.create | bool | `true` | |
120120
| speaker.affinity | object | `{}` | |
121+
| speaker.bgpDebounceTimeout | string | `nil` | BGP debounce timeout for FRR configuration reloads, in milliseconds. Only applies when BGP type is frr. Default (when unset) is 3000 ms. This feature is experimental |
121122
| speaker.enabled | bool | `true` | |
122123
| speaker.excludeInterfaces.enabled | bool | `true` | |
123124
| speaker.extraContainers | list | `[]` | |
124-
| speaker.frr.enabled | bool | `true` | |
125+
| speaker.frr.enabled | bool | `false` | |
125126
| speaker.frr.image.pullPolicy | string | `nil` | |
126127
| speaker.frr.image.repository | string | `"quay.io/frrouting/frr"` | |
127-
| speaker.frr.image.tag | string | `"9.1.0"` | |
128-
| speaker.frr.metricsPort | int | `7473` | |
128+
| speaker.frr.image.tag | string | `"10.5.3"` | |
129+
| speaker.frr.metricsPort | int | `9121` | |
129130
| speaker.frr.resources | object | `{}` | |
130131
| speaker.frrMetrics.resources | object | `{}` | |
131132
| speaker.ignoreExcludeLB | bool | `false` | |
132133
| speaker.image.pullPolicy | string | `nil` | |
133134
| speaker.image.repository | string | `"quay.io/metallb/speaker"` | |
134135
| speaker.image.tag | string | `nil` | |
136+
| speaker.initContainers.cpFrrFiles.resources | object | `{}` | |
137+
| speaker.initContainers.cpMetrics.resources | object | `{}` | |
138+
| speaker.initContainers.cpReloader.resources | object | `{}` | |
135139
| speaker.labels | object | `{}` | |
136140
| speaker.livenessProbe.enabled | bool | `true` | |
137141
| speaker.livenessProbe.failureThreshold | int | `3` | |
138142
| speaker.livenessProbe.initialDelaySeconds | int | `10` | |
139143
| speaker.livenessProbe.periodSeconds | int | `10` | |
144+
| speaker.livenessProbe.port | int | `17472` | |
140145
| speaker.livenessProbe.successThreshold | int | `1` | |
141146
| speaker.livenessProbe.timeoutSeconds | int | `1` | |
142147
| speaker.logLevel | string | `"info"` | Speaker log level. Must be one of: `all`, `debug`, `info`, `warn`, `error` or `none` |
@@ -151,6 +156,7 @@ Kubernetes: `>= 1.19.0-0`
151156
| speaker.readinessProbe.failureThreshold | int | `3` | |
152157
| speaker.readinessProbe.initialDelaySeconds | int | `10` | |
153158
| speaker.readinessProbe.periodSeconds | int | `10` | |
159+
| speaker.readinessProbe.port | int | `17472` | |
154160
| speaker.readinessProbe.successThreshold | int | `1` | |
155161
| speaker.readinessProbe.timeoutSeconds | int | `1` | |
156162
| speaker.reloader.resources | object | `{}` | |
@@ -166,6 +172,11 @@ Kubernetes: `>= 1.19.0-0`
166172
| speaker.tolerateMaster | bool | `true` | |
167173
| speaker.tolerations | list | `[]` | |
168174
| speaker.updateStrategy.type | string | `"RollingUpdate"` | |
175+
| tls.cipherSuites | string | `""` | Comma-separated list of TLS cipher suites. If empty, uses Go defaults. Only applies to TLS 1.2. |
176+
| tls.controllerMetricsTLSSecret | string | `""` | The name of the secret to be mounted in the controller pod to provide TLS certificates for metrics endpoints. If not present, a self-signed certificate is auto-generated. |
177+
| tls.curvePreferences | string | `""` | Comma-separated list of numeric CurveID values (e.g. 29,4588). See https://pkg.go.dev/crypto/tls#CurveID. If empty, uses Go defaults. |
178+
| tls.minVersion | string | `""` | Minimum TLS version (VersionTLS12 or VersionTLS13). Defaults to VersionTLS13. |
179+
| tls.speakerMetricsTLSSecret | string | `""` | The name of the secret to be mounted in the speaker pod to provide TLS certificates for metrics endpoints. If not present, a self-signed certificate is auto-generated. |
169180

170181
----------------------------------------------
171182
Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
apiVersion: v2
2-
appVersion: v0.15.2
2+
appVersion: v0.16.1
33
description: MetalLB CRDs
44
home: https://metallb.universe.tf
55
icon: https://metallb.universe.tf/images/logo/metallb-white.png
66
name: crds
77
sources:
88
- https://github.com/metallb/metallb
99
type: application
10-
version: 0.15.2
10+
version: 0.16.1

0 commit comments

Comments
 (0)