[kubernetes] Fix CiliumNetworkPolicy endpointSelector for multi-node RWX volumes

When an NFS-backed RWX volume is published to multiple VMs, the
CiliumNetworkPolicy egress rule only allowed traffic from the first VM.
The endpointSelector.matchLabels was set once on creation and never
broadened, causing NFS mounts to hang on all nodes except the first.

Switch from matchLabels to matchExpressions (operator: In) so the
selector can list multiple VM names. Rebuild the selector whenever
ownerReferences are added or removed.

Signed-off-by: mattia-eleuteri <mattia@hidora.io>
This commit is contained in:
mattia-eleuteri 2026-03-16 14:38:26 +01:00
parent 38d58a77dc
commit cc5ec0b7a3
No known key found for this signature in database

View file

@ -317,11 +317,7 @@ func (w *WrappedControllerService) ControllerPublishVolume(ctx context.Context,
"ownerReferences": []interface{}{vmiOwnerRef},
},
"spec": map[string]interface{}{
"endpointSelector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"kubevirt.io/vm": vmName,
},
},
"endpointSelector": buildEndpointSelector([]string{vmName}),
"egress": []interface{}{
map[string]interface{}{
"toEndpoints": []interface{}{
@ -441,6 +437,13 @@ func (w *WrappedControllerService) addCNPOwnerReference(ctx context.Context, nam
if err := unstructured.SetNestedSlice(existing.Object, ownerRefs, "metadata", "ownerReferences"); err != nil {
return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err)
}
// Rebuild endpointSelector to include all VMs
selector := buildEndpointSelector(vmNamesFromOwnerRefs(ownerRefs))
if err := unstructured.SetNestedField(existing.Object, selector, "spec", "endpointSelector"); err != nil {
return status.Errorf(codes.Internal, "failed to set endpointSelector: %v", err)
}
if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
return err
}
@ -486,6 +489,13 @@ func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context,
if err := unstructured.SetNestedSlice(existing.Object, remaining, "metadata", "ownerReferences"); err != nil {
return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err)
}
// Rebuild endpointSelector from remaining VMs
selector := buildEndpointSelector(vmNamesFromOwnerRefs(remaining))
if err := unstructured.SetNestedField(existing.Object, selector, "spec", "endpointSelector"); err != nil {
return status.Errorf(codes.Internal, "failed to set endpointSelector: %v", err)
}
if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
return err
}
@ -494,6 +504,37 @@ func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context,
})
}
// buildEndpointSelector returns an endpointSelector using matchExpressions
// so that multiple VMs can be listed in a single selector.
func buildEndpointSelector(vmNames []string) map[string]interface{} {
values := make([]interface{}, len(vmNames))
for i, name := range vmNames {
values[i] = name
}
return map[string]interface{}{
"matchExpressions": []interface{}{
map[string]interface{}{
"key": "kubevirt.io/vm",
"operator": "In",
"values": values,
},
},
}
}
// vmNamesFromOwnerRefs extracts VM names from ownerReferences.
func vmNamesFromOwnerRefs(ownerRefs []interface{}) []string {
var names []string
for _, ref := range ownerRefs {
if refMap, ok := ref.(map[string]interface{}); ok {
if name, ok := refMap["name"].(string); ok {
names = append(names, name)
}
}
}
return names
}
func hasRWXAccessMode(pvc *corev1.PersistentVolumeClaim) bool {
for _, mode := range pvc.Spec.AccessModes {
if mode == corev1.ReadWriteMany {