Change:
Why PatchService method is used for deleting finalizers in cloud-controller-manager, This method updates with service/status, It seems to go unnoticed by the API server, resulting in the API server being unaware when the finalizer field on the service is deleted, leading to a delay in the release of service-related NodePorts. service nodeport be released promptly when service is deleted. create a loadblancer service with nodeport seted,then delete it and recreate. cloud-controller-manager add a finalizer on service. delete this service and then cloud-controller remove finalizer. but nodeport is not released when service deleted. ref i change like this, it seems like releasing nodeport when service is deleted : No response <details</details<details</details<details</details<details</details<details</details<details</details
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/sig cloud-provider
Are you sure? that method updates the status and remove the finalizer ... otherwise , who removes the finalizer?
I apologize for any confusion. What I meant was that the update of the status and the removal of the finalizer are performed through updating service/status. Therefore, removing the finalizer does not release resources.It seems like removal of the finalizer by service/status, store.AfterDelete is nil. i think remove the finalizer should by patch servcie and updates the status should by patch service/status.Now we patch both by servcie/status.
no need to apologize, let me see if I can reproduce this locally first, Services api allocation is complex , it overrides the standard AfterDelete and release all allocated resources there /assign
/sig network ack the problem
will look for a possibly-related past cloud provider issue.
this will be fixed by , it is a problem in the apiserver logic
Cool! I forgot that you can patch finalizers thru /status. I repro'ed it on both ClusterIP and NodePort. Saw this in the logs: So the repair loop, yet again, saves our butt but also hides a bug.
we need metrics here about to have visibility of the repairs
I also found this problem in versions 1.23 and 1.27
yep, I started the backports , but we can only backport to the supported versions, 1.23 and 1.24 are EOL

Discussion:
store.BeginCreate = genericStore.beginCreate store.BeginUpdate = genericStore.beginUpdate <ins> // users can patch the status to remove the finalizer, // hence statusStore must participate on the AfterDelete // hook to release the allocated resources statusStore.AfterDelete = genericStore.afterDelete </ins> return genericStore, &StatusREST{store: &statusStore}, &svcreg.ProxyREST{Redirector: genericStore, ProxyTransport: proxyTransport}, nil }

import ( "context" <ins> "encoding/json" "fmt" </ins> "testing" "time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" <ins> "k8s.io/apimachinery/pkg/util/strategicpatch" </ins> "k8s.io/apimachinery/pkg/util/wait" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" "k8s.io/kubernetes/pkg/controlplane"

t.Logf("Created service: %s", svcNodePort.Name) } <ins> func TestServicesFinalizersPatchStatus(t *testing.T) { serviceCIDR := "10.0.0.0/16" clusterIP := "10.0.0.21" nodePort := 30443 _, ctx := ktesting.NewTestContext(t) ctx, cancel := context.WithCancel(ctx) defer cancel() client, _, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{ ModifyServerRunOptions: func(opts *options.ServerRunOptions) { opts.ServiceClusterIPRanges = serviceCIDR }, }) defer tearDownFn() for _, testcase := range []string{"spec", "status"} { t.Run(testcase, func(t *testing.T) { // Create a NodePort service with one finalizer svcNodePort := v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name:       "svc" + testcase, Finalizers: []string{"foo.bar/some-finalizer"}, }, Spec: v1.ServiceSpec{ ClusterIP: clusterIP, Ports: []v1.ServicePort{{ Port:       8443, NodePort:   int32(nodePort), TargetPort: intstr.FromInt32(8443), Protocol:   v1.ProtocolTCP, }}, Type: v1.ServiceTypeNodePort, }, } ns := framework.CreateNamespaceOrDie(client, "test-service-finalizers-"+testcase, t) defer framework.DeleteNamespaceOrDie(client, ns, t) // Create service if _, err := client.CoreV1().Services(ns.Name).Create(ctx, &svcNodePort, metav1.CreateOptions{}); err != nil { t.Fatalf("unexpected error creating service: %v", err) } t.Logf("Created service: %s", svcNodePort.Name) // Check the service has been created correctly svc, err := client.CoreV1().Services(ns.Name).Get(ctx, svcNodePort.Name, metav1.GetOptions{}) if err != nil || svc.Spec.ClusterIP != clusterIP { t.Fatalf("created service is not correct: %v", err) } t.Logf("Service created successfully: %+v", svc) // Delete service if err := client.CoreV1().Services(ns.Name).Delete(ctx, svcNodePort.Name, metav1.DeleteOptions{}); err != nil { t.Fatalf("unexpected error deleting service: %v", err) } t.Logf("Deleted service: %s", svcNodePort.Name) // Check that the service was not deleted and the IP is already allocated svc, err = client.CoreV1().Services(ns.Name).Get(ctx, svcNodePort.Name, metav1.GetOptions{}) if err != nil || svc.Spec.ClusterIP != clusterIP || int(svc.Spec.Ports[0].NodePort) != nodePort || svc.DeletionTimestamp == nil || len(svc.ObjectMeta.Finalizers) != 1 { t.Fatalf("Service expected to be deleting and with the same values: %v", err) } t.Logf("Service after Delete: %+v", svc) // Remove the finalizer updated := svc.DeepCopy() updated.ObjectMeta.Finalizers = []string{} patchBytes, err := getPatchBytes(svc, updated) if err != nil { t.Fatalf("unexpected error getting patch bytes: %v", err) } if testcase == "spec" { if _, err = client.CoreV1().Services(ns.Name).Patch(ctx, svcNodePort.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}); err != nil { t.Fatalf("unexpected error removing finalizer: %v", err) } } else { if _, err = client.CoreV1().Services(ns.Name).Patch(ctx, svcNodePort.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status"); err != nil { t.Fatalf("unexpected error removing finalizer: %v", err) } } t.Logf("Removed service finalizer: %s", svcNodePort.Name) // Check that the service was deleted _, err = client.CoreV1().Services(ns.Name).Get(ctx, svcNodePort.Name, metav1.GetOptions{}) if err == nil { t.Fatalf("service was not delete: %v", err) } // Try to create service again without the finalizer to check the ClusterIP and NodePort are deallocated svc = svcNodePort.DeepCopy() svc.Finalizers = []string{} if _, err := client.CoreV1().Services(ns.Name).Create(ctx, svc, metav1.CreateOptions{}); err != nil { t.Fatalf("unexpected error creating service: %v", err) } // Delete service if err := client.CoreV1().Services(ns.Name).Delete(ctx, svc.Name, metav1.DeleteOptions{}); err != nil { t.Fatalf("unexpected error deleting service: %v", err) } }) } } </ins> // Regresion test for https://issues.k8s.io/115316 func TestServiceCIDR28bits(t *testing.T) { serviceCIDR := "10.0.0.0/28"

t.Fatalf("Error creating test service: %v", err) } } <ins> func getPatchBytes(oldSvc, newSvc *v1.Service) ([]byte, error) { oldData, err := json.Marshal(oldSvc) if err != nil { return nil, fmt.Errorf("failed to Marshal oldData for svc %s/%s: %v", oldSvc.Namespace, oldSvc.Name, err) } newData, err := json.Marshal(newSvc) if err != nil { return nil, fmt.Errorf("failed to Marshal newData for svc %s/%s: %v", newSvc.Namespace, newSvc.Name, err) } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Service{}) if err != nil { return nil, fmt.Errorf("failed to CreateTwoWayMergePatch for svc %s/%s: %v", oldSvc.Namespace, oldSvc.Name, err) } return patchBytes, nil } </ins>