What happened
A federated Service with type=NodePort (or any Service whose spec contains spec.ports[*].nodePort) created in the Karmada control plane gets spec.ports[*].nodePort allocated by the Karmada kube-apiserver. Karmada then dispatches the entire spec — including the control-plane-allocated nodePort — to member clusters unchanged. When a member cluster already has that NodePort taken by another Service, the Work apply fails:
Failed to apply all manifests (0/1): Service "test-svc-0" is invalid:
spec.ports[0].nodePort: Invalid value: 32410: provided port is already allocated
Reproduce
Federated Service (control plane):
kind: Service
apiVersion: v1
metadata:
name: test-svc-0
namespace: svc-test
spec:
type: NodePort
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
selector:
app: test-nginx
Member cluster ResourceBinding status:
{
"aggregatedStatus": [
{
"appliedMessage": "Failed to apply all manifests (0/1): Service \"test-svc-0\" is invalid: spec.ports[0].nodePort: Invalid value: 32410: provided port is already allocated",
"clusterName": "test-cluster-0",
"health": "Unknown"
},
{
"applied": true,
"clusterName": "test-cluster-1",
"health": "Healthy",
}
]
}
A different member cluster applied the same Work fine — proving the spec is valid; the failure is purely a cluster-local port collision.
Root cause
spec.ports[*].nodePort should be allocated by the member-cluster kube-apiserver, not propagated from the control plane. The conflict mechanism: when a member-cluster kube-apiserver receives a create/apply whose Service spec already contains spec.ports[*].nodePort, it honors the requested value and tries to allocate that exact port. If that port is already allocated to another Service in that cluster, the apiserver rejects with provided port is already allocated.
Each member cluster allocates NodePorts from its own --service-node-port-range (default 30000-32767). A port free in the control plane is not guaranteed free in any given member cluster.
removeServiceIrrelevantField (pkg/resourceinterpreter/default/native/prune/prune.go) currently strips only clusterIP / clusterIPs. nodePort is missing.
Proposed fix
Strip spec.ports[*].nodePort in removeServiceIrrelevantField, so the member-cluster kube-apiserver receives a spec without nodePort and allocates one from its own --service-node-port-range — no collision.
Same class of bug as #113 (fixed in #206) for clusterIP / clusterIPs — kube-apiserver-allocated fields should not cross-cluster propagate; the prune function already strips clusterIP/clusterIPs, this issue extends that to nodePort.
What happened
A federated
Servicewithtype=NodePort(or any Service whose spec containsspec.ports[*].nodePort) created in the Karmada control plane getsspec.ports[*].nodePortallocated by the Karmada kube-apiserver. Karmada then dispatches the entire spec — including the control-plane-allocatednodePort— to member clusters unchanged. When a member cluster already has that NodePort taken by anotherService, theWorkapply fails:Reproduce
Federated Service (control plane):
Member cluster
ResourceBindingstatus:{ "aggregatedStatus": [ { "appliedMessage": "Failed to apply all manifests (0/1): Service \"test-svc-0\" is invalid: spec.ports[0].nodePort: Invalid value: 32410: provided port is already allocated", "clusterName": "test-cluster-0", "health": "Unknown" }, { "applied": true, "clusterName": "test-cluster-1", "health": "Healthy", } ] }A different member cluster applied the same Work fine — proving the spec is valid; the failure is purely a cluster-local port collision.
Root cause
spec.ports[*].nodePortshould be allocated by the member-cluster kube-apiserver, not propagated from the control plane. The conflict mechanism: when a member-cluster kube-apiserver receives a create/apply whose Service spec already containsspec.ports[*].nodePort, it honors the requested value and tries to allocate that exact port. If that port is already allocated to anotherServicein that cluster, the apiserver rejects withprovided port is already allocated.Each member cluster allocates NodePorts from its own
--service-node-port-range(default 30000-32767). A port free in the control plane is not guaranteed free in any given member cluster.removeServiceIrrelevantField(pkg/resourceinterpreter/default/native/prune/prune.go) currently strips onlyclusterIP/clusterIPs.nodePortis missing.Proposed fix
Strip
spec.ports[*].nodePortinremoveServiceIrrelevantField, so the member-cluster kube-apiserver receives a spec withoutnodePortand allocates one from its own--service-node-port-range— no collision.Same class of bug as #113 (fixed in #206) for
clusterIP/clusterIPs— kube-apiserver-allocated fields should not cross-cluster propagate; the prune function already stripsclusterIP/clusterIPs, this issue extends that tonodePort.