mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 23:37:01 +00:00
Merge pull request #7852 from a-robinson/tp
Prevent stranding of partial load balancers resources
This commit is contained in:
commit
6d8eaa924e
@ -66,8 +66,13 @@ type TCPLoadBalancer interface {
|
|||||||
CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (string, error)
|
CreateTCPLoadBalancer(name, region string, externalIP net.IP, ports []int, hosts []string, affinityType api.ServiceAffinity) (string, error)
|
||||||
// UpdateTCPLoadBalancer updates hosts under the specified load balancer.
|
// UpdateTCPLoadBalancer updates hosts under the specified load balancer.
|
||||||
UpdateTCPLoadBalancer(name, region string, hosts []string) error
|
UpdateTCPLoadBalancer(name, region string, hosts []string) error
|
||||||
// DeleteTCPLoadBalancer deletes a specified load balancer.
|
// EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it
|
||||||
DeleteTCPLoadBalancer(name, region string) error
|
// exists, returning nil if the load balancer specified either didn't exist or
|
||||||
|
// was successfully deleted.
|
||||||
|
// This construction is useful because many cloud providers' load balancers
|
||||||
|
// have multiple underlying components, meaning a Get could say that the LB
|
||||||
|
// doesn't exist even if some part of it is still laying around.
|
||||||
|
EnsureTCPLoadBalancerDeleted(name, region string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instances is an abstract, pluggable interface for sets of instances.
|
// Instances is an abstract, pluggable interface for sets of instances.
|
||||||
|
@ -115,9 +115,9 @@ func (f *FakeCloud) UpdateTCPLoadBalancer(name, region string, hosts []string) e
|
|||||||
return f.Err
|
return f.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTCPLoadBalancer is a test-spy implementation of TCPLoadBalancer.DeleteTCPLoadBalancer.
|
// EnsureTCPLoadBalancerDeleted is a test-spy implementation of TCPLoadBalancer.EnsureTCPLoadBalancerDeleted.
|
||||||
// It adds an entry "delete" into the internal method call record.
|
// It adds an entry "delete" into the internal method call record.
|
||||||
func (f *FakeCloud) DeleteTCPLoadBalancer(name, region string) error {
|
func (f *FakeCloud) EnsureTCPLoadBalancerDeleted(name, region string) error {
|
||||||
f.addCall("delete")
|
f.addCall("delete")
|
||||||
return f.Err
|
return f.Err
|
||||||
}
|
}
|
||||||
|
@ -399,8 +399,8 @@ func (gce *GCECloud) UpdateTCPLoadBalancer(name, region string, hosts []string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTCPLoadBalancer is an implementation of TCPLoadBalancer.DeleteTCPLoadBalancer.
|
// EnsureTCPLoadBalancerDeleted is an implementation of TCPLoadBalancer.EnsureTCPLoadBalancerDeleted.
|
||||||
func (gce *GCECloud) DeleteTCPLoadBalancer(name, region string) error {
|
func (gce *GCECloud) EnsureTCPLoadBalancerDeleted(name, region string) error {
|
||||||
op, err := gce.service.ForwardingRules.Delete(gce.projectID, region, name).Do()
|
op, err := gce.service.ForwardingRules.Delete(gce.projectID, region, name).Do()
|
||||||
if err != nil && isHTTPErrorCode(err, http.StatusNotFound) {
|
if err != nil && isHTTPErrorCode(err, http.StatusNotFound) {
|
||||||
glog.Infof("Forwarding rule %s already deleted. Continuing to delete target pool.", name)
|
glog.Infof("Forwarding rule %s already deleted. Continuing to delete target pool.", name)
|
||||||
@ -411,6 +411,7 @@ func (gce *GCECloud) DeleteTCPLoadBalancer(name, region string) error {
|
|||||||
err = gce.waitForRegionOp(op, region)
|
err = gce.waitForRegionOp(op, region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("Failed waiting for Forwarding Rule %s to be deleted: got error %s.", name, err.Error())
|
glog.Warningf("Failed waiting for Forwarding Rule %s to be deleted: got error %s.", name, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
op, err = gce.service.TargetPools.Delete(gce.projectID, region, name).Do()
|
op, err = gce.service.TargetPools.Delete(gce.projectID, region, name).Do()
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -630,31 +631,49 @@ func (lb *LoadBalancer) UpdateTCPLoadBalancer(name, region string, hosts []strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lb *LoadBalancer) DeleteTCPLoadBalancer(name, region string) error {
|
func (lb *LoadBalancer) EnsureTCPLoadBalancerDeleted(name, region string) error {
|
||||||
glog.V(4).Infof("DeleteTCPLoadBalancer(%v, %v)", name, region)
|
glog.V(4).Infof("EnsureTCPLoadBalancerDeleted(%v, %v)", name, region)
|
||||||
|
|
||||||
vip, err := getVipByName(lb.network, name)
|
// TODO(#8352): Because we look up the pool using the VIP object, if the VIP
|
||||||
if err != nil {
|
// is already gone we can't attempt to delete the pool. We should instead
|
||||||
return err
|
// continue even if the VIP doesn't exist and attempt to delete the pool by
|
||||||
|
// name.
|
||||||
|
vip, vipErr := getVipByName(lb.network, name)
|
||||||
|
if vipErr == ErrNotFound {
|
||||||
|
return nil
|
||||||
|
} else if vipErr != nil {
|
||||||
|
return vipErr
|
||||||
}
|
}
|
||||||
|
|
||||||
pool, err := pools.Get(lb.network, vip.PoolID).Extract()
|
// It's ok if the pool doesn't exist, as we may still need to delete the vip
|
||||||
if err != nil {
|
// (although I don't believe the system should ever be in that state).
|
||||||
return err
|
pool, poolErr := pools.Get(lb.network, vip.PoolID).Extract()
|
||||||
|
if poolErr != nil {
|
||||||
|
detailedErr, ok := poolErr.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || detailedErr.Actual != http.StatusNotFound {
|
||||||
|
return poolErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
poolExists := (poolErr == nil)
|
||||||
|
|
||||||
// Have to delete VIP before pool can be deleted
|
// We have to delete the VIP before the pool can be deleted, so we can't
|
||||||
err = vips.Delete(lb.network, vip.ID).ExtractErr()
|
// continue on if this fails.
|
||||||
if err != nil {
|
// TODO(#8352): Only do this if the VIP exists once we can delete pools by
|
||||||
|
// name rather than by ID.
|
||||||
|
err := vips.Delete(lb.network, vip.ID).ExtractErr()
|
||||||
|
if err != nil && err != ErrNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore errors for everything following here
|
// Ignore errors for everything following here
|
||||||
|
|
||||||
for _, monId := range pool.MonitorIDs {
|
if poolExists {
|
||||||
pools.DisassociateMonitor(lb.network, pool.ID, monId)
|
for _, monId := range pool.MonitorIDs {
|
||||||
|
// TODO(#8352): Delete the monitor, don't just disassociate it.
|
||||||
|
pools.DisassociateMonitor(lb.network, pool.ID, monId)
|
||||||
|
}
|
||||||
|
pools.Delete(lb.network, pool.ID)
|
||||||
}
|
}
|
||||||
pools.Delete(lb.network, pool.ID)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ func (s *ServiceController) processDelta(delta *cache.Delta) (error, bool) {
|
|||||||
cachedService.service = service
|
cachedService.service = service
|
||||||
s.cache.set(namespacedName.String(), cachedService)
|
s.cache.set(namespacedName.String(), cachedService)
|
||||||
case cache.Deleted:
|
case cache.Deleted:
|
||||||
err := s.ensureLBDeleted(service)
|
err := s.balancer.EnsureTCPLoadBalancerDeleted(s.loadBalancerName(service), s.zone.Region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, retryable
|
return err, retryable
|
||||||
}
|
}
|
||||||
@ -233,7 +233,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.Name
|
|||||||
// we can recreate it cleanly.
|
// we can recreate it cleanly.
|
||||||
if cachedService.Spec.CreateExternalLoadBalancer {
|
if cachedService.Spec.CreateExternalLoadBalancer {
|
||||||
glog.Infof("Deleting existing load balancer for service %s that needs an updated load balancer.", namespacedName)
|
glog.Infof("Deleting existing load balancer for service %s that needs an updated load balancer.", namespacedName)
|
||||||
if err := s.ensureLBDeleted(cachedService); err != nil {
|
if err := s.balancer.EnsureTCPLoadBalancerDeleted(s.loadBalancerName(cachedService), s.zone.Region); err != nil {
|
||||||
return err, retryable
|
return err, retryable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,7 +254,7 @@ func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.Name
|
|||||||
} else if exists {
|
} else if exists {
|
||||||
glog.Infof("Deleting old LB for previously uncached service %s whose endpoint %s doesn't match the service's desired IPs %v",
|
glog.Infof("Deleting old LB for previously uncached service %s whose endpoint %s doesn't match the service's desired IPs %v",
|
||||||
namespacedName, endpoint, service.Spec.PublicIPs)
|
namespacedName, endpoint, service.Spec.PublicIPs)
|
||||||
if err := s.ensureLBDeleted(service); err != nil {
|
if err := s.balancer.EnsureTCPLoadBalancerDeleted(s.loadBalancerName(service), s.zone.Region); err != nil {
|
||||||
return err, retryable
|
return err, retryable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,23 +347,6 @@ func (s *ServiceController) createExternalLoadBalancer(service *api.Service) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that the load balancer associated with the given service is deleted,
|
|
||||||
// doing the deletion if necessary. Should always be retried upon failure.
|
|
||||||
func (s *ServiceController) ensureLBDeleted(service *api.Service) error {
|
|
||||||
// This is only needed because not all delete load balancer implementations
|
|
||||||
// are currently idempotent to the LB not existing.
|
|
||||||
if _, exists, err := s.balancer.GetTCPLoadBalancer(s.loadBalancerName(service), s.zone.Region); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.balancer.DeleteTCPLoadBalancer(s.loadBalancerName(service), s.zone.Region); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListKeys implements the interface required by DeltaFIFO to list the keys we
|
// ListKeys implements the interface required by DeltaFIFO to list the keys we
|
||||||
// already know about.
|
// already know about.
|
||||||
func (s *serviceCache) ListKeys() []string {
|
func (s *serviceCache) ListKeys() []string {
|
||||||
|
Loading…
Reference in New Issue
Block a user