mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #107631 from lzhecheng/fix-retry-svc-to-update
Avoid updating Services with stale specs
This commit is contained in:
commit
68d26c868f
@ -75,7 +75,7 @@ type serviceCache struct {
|
|||||||
type Controller struct {
|
type Controller struct {
|
||||||
cloud cloudprovider.Interface
|
cloud cloudprovider.Interface
|
||||||
knownHosts []*v1.Node
|
knownHosts []*v1.Node
|
||||||
servicesToUpdate []*v1.Service
|
servicesToUpdate sets.String
|
||||||
kubeClient clientset.Interface
|
kubeClient clientset.Interface
|
||||||
clusterName string
|
clusterName string
|
||||||
balancer cloudprovider.LoadBalancer
|
balancer cloudprovider.LoadBalancer
|
||||||
@ -735,16 +735,28 @@ func (s *Controller) nodeSyncInternal(ctx context.Context, workers int) {
|
|||||||
if !s.needFullSyncAndUnmark() {
|
if !s.needFullSyncAndUnmark() {
|
||||||
// The set of nodes in the cluster hasn't changed, but we can retry
|
// The set of nodes in the cluster hasn't changed, but we can retry
|
||||||
// updating any services that we failed to update last time around.
|
// updating any services that we failed to update last time around.
|
||||||
s.servicesToUpdate = s.updateLoadBalancerHosts(ctx, s.servicesToUpdate, workers)
|
// It is required to call `s.cache.get()` on each Service in case there was
|
||||||
|
// an update event that occurred between retries.
|
||||||
|
var servicesToUpdate []*v1.Service
|
||||||
|
for key := range s.servicesToUpdate {
|
||||||
|
cachedService, exist := s.cache.get(key)
|
||||||
|
if !exist {
|
||||||
|
klog.Errorf("Service %q should be in the cache but not", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
servicesToUpdate = append(servicesToUpdate, cachedService.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.servicesToUpdate = s.updateLoadBalancerHosts(ctx, servicesToUpdate, workers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
klog.V(2).Infof("Syncing backends for all LB services.")
|
klog.V(2).Infof("Syncing backends for all LB services.")
|
||||||
|
|
||||||
// Try updating all services, and save the ones that fail to try again next
|
// Try updating all services, and save the failed ones to try again next
|
||||||
// round.
|
// round.
|
||||||
s.servicesToUpdate = s.cache.allServices()
|
servicesToUpdate := s.cache.allServices()
|
||||||
numServices := len(s.servicesToUpdate)
|
numServices := len(servicesToUpdate)
|
||||||
s.servicesToUpdate = s.updateLoadBalancerHosts(ctx, s.servicesToUpdate, workers)
|
s.servicesToUpdate = s.updateLoadBalancerHosts(ctx, servicesToUpdate, workers)
|
||||||
klog.V(2).Infof("Successfully updated %d out of %d load balancers to direct traffic to the updated set of nodes",
|
klog.V(2).Infof("Successfully updated %d out of %d load balancers to direct traffic to the updated set of nodes",
|
||||||
numServices-len(s.servicesToUpdate), numServices)
|
numServices-len(s.servicesToUpdate), numServices)
|
||||||
}
|
}
|
||||||
@ -772,10 +784,11 @@ func (s *Controller) nodeSyncService(svc *v1.Service) bool {
|
|||||||
// updateLoadBalancerHosts updates all existing load balancers so that
|
// updateLoadBalancerHosts updates all existing load balancers so that
|
||||||
// they will match the latest list of nodes with input number of workers.
|
// they will match the latest list of nodes with input number of workers.
|
||||||
// Returns the list of services that couldn't be updated.
|
// Returns the list of services that couldn't be updated.
|
||||||
func (s *Controller) updateLoadBalancerHosts(ctx context.Context, services []*v1.Service, workers int) (servicesToRetry []*v1.Service) {
|
func (s *Controller) updateLoadBalancerHosts(ctx context.Context, services []*v1.Service, workers int) (servicesToRetry sets.String) {
|
||||||
klog.V(4).Infof("Running updateLoadBalancerHosts(len(services)==%d, workers==%d)", len(services), workers)
|
klog.V(4).Infof("Running updateLoadBalancerHosts(len(services)==%d, workers==%d)", len(services), workers)
|
||||||
|
|
||||||
// lock for servicesToRetry
|
// lock for servicesToRetry
|
||||||
|
servicesToRetry = sets.NewString()
|
||||||
lock := sync.Mutex{}
|
lock := sync.Mutex{}
|
||||||
doWork := func(piece int) {
|
doWork := func(piece int) {
|
||||||
if shouldRetry := s.nodeSyncService(services[piece]); !shouldRetry {
|
if shouldRetry := s.nodeSyncService(services[piece]); !shouldRetry {
|
||||||
@ -783,7 +796,8 @@ func (s *Controller) updateLoadBalancerHosts(ctx context.Context, services []*v1
|
|||||||
}
|
}
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
servicesToRetry = append(servicesToRetry, services[piece])
|
key := fmt.Sprintf("%s/%s", services[piece].Namespace, services[piece].Name)
|
||||||
|
servicesToRetry.Insert(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
workqueue.ParallelizeUntil(ctx, workers, len(services), doWork)
|
workqueue.ParallelizeUntil(ctx, workers, len(services), doWork)
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
@ -44,6 +45,8 @@ import (
|
|||||||
fakecloud "k8s.io/cloud-provider/fake"
|
fakecloud "k8s.io/cloud-provider/fake"
|
||||||
servicehelper "k8s.io/cloud-provider/service/helpers"
|
servicehelper "k8s.io/cloud-provider/service/helpers"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const region = "us-central"
|
const region = "us-central"
|
||||||
@ -548,7 +551,7 @@ func TestUpdateNodesInExternalLoadBalancer(t *testing.T) {
|
|||||||
controller, cloud, _ := newController()
|
controller, cloud, _ := newController()
|
||||||
controller.nodeLister = newFakeNodeLister(nil, nodes...)
|
controller.nodeLister = newFakeNodeLister(nil, nodes...)
|
||||||
|
|
||||||
if servicesToRetry := controller.updateLoadBalancerHosts(ctx, item.services, item.workers); servicesToRetry != nil {
|
if servicesToRetry := controller.updateLoadBalancerHosts(ctx, item.services, item.workers); len(servicesToRetry) != 0 {
|
||||||
t.Errorf("for case %q, unexpected servicesToRetry: %v", item.desc, servicesToRetry)
|
t.Errorf("for case %q, unexpected servicesToRetry: %v", item.desc, servicesToRetry)
|
||||||
}
|
}
|
||||||
compareUpdateCalls(t, item.expectedUpdateCalls, cloud.UpdateCalls)
|
compareUpdateCalls(t, item.expectedUpdateCalls, cloud.UpdateCalls)
|
||||||
@ -569,6 +572,11 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
newService("s4", "123", v1.ServiceTypeLoadBalancer),
|
newService("s4", "123", v1.ServiceTypeLoadBalancer),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serviceNames := sets.NewString()
|
||||||
|
for _, svc := range services {
|
||||||
|
serviceNames.Insert(fmt.Sprintf("%s/%s", svc.GetObjectMeta().GetNamespace(), svc.GetObjectMeta().GetName()))
|
||||||
|
}
|
||||||
|
|
||||||
controller, cloud, _ := newController()
|
controller, cloud, _ := newController()
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
desc string
|
desc string
|
||||||
@ -576,7 +584,7 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
expectedUpdateCalls []fakecloud.UpdateBalancerCall
|
expectedUpdateCalls []fakecloud.UpdateBalancerCall
|
||||||
worker int
|
worker int
|
||||||
nodeListerErr error
|
nodeListerErr error
|
||||||
expectedRetryServices []*v1.Service
|
expectedRetryServices sets.String
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "only 1 node",
|
desc: "only 1 node",
|
||||||
@ -589,7 +597,7 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
worker: 3,
|
worker: 3,
|
||||||
nodeListerErr: nil,
|
nodeListerErr: nil,
|
||||||
expectedRetryServices: []*v1.Service{},
|
expectedRetryServices: sets.NewString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "2 nodes",
|
desc: "2 nodes",
|
||||||
@ -602,7 +610,7 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
worker: 1,
|
worker: 1,
|
||||||
nodeListerErr: nil,
|
nodeListerErr: nil,
|
||||||
expectedRetryServices: []*v1.Service{},
|
expectedRetryServices: sets.NewString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "4 nodes",
|
desc: "4 nodes",
|
||||||
@ -615,7 +623,7 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
worker: 3,
|
worker: 3,
|
||||||
nodeListerErr: nil,
|
nodeListerErr: nil,
|
||||||
expectedRetryServices: []*v1.Service{},
|
expectedRetryServices: sets.NewString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "error occur during sync",
|
desc: "error occur during sync",
|
||||||
@ -623,7 +631,7 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
expectedUpdateCalls: []fakecloud.UpdateBalancerCall{},
|
expectedUpdateCalls: []fakecloud.UpdateBalancerCall{},
|
||||||
worker: 3,
|
worker: 3,
|
||||||
nodeListerErr: fmt.Errorf("random error"),
|
nodeListerErr: fmt.Errorf("random error"),
|
||||||
expectedRetryServices: services,
|
expectedRetryServices: serviceNames,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "error occur during sync with 1 workers",
|
desc: "error occur during sync with 1 workers",
|
||||||
@ -631,7 +639,7 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
expectedUpdateCalls: []fakecloud.UpdateBalancerCall{},
|
expectedUpdateCalls: []fakecloud.UpdateBalancerCall{},
|
||||||
worker: 1,
|
worker: 1,
|
||||||
nodeListerErr: fmt.Errorf("random error"),
|
nodeListerErr: fmt.Errorf("random error"),
|
||||||
expectedRetryServices: services,
|
expectedRetryServices: serviceNames,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
@ -639,37 +647,13 @@ func TestNodeChangesInExternalLoadBalancer(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
controller.nodeLister = newFakeNodeLister(tc.nodeListerErr, tc.nodes...)
|
controller.nodeLister = newFakeNodeLister(tc.nodeListerErr, tc.nodes...)
|
||||||
servicesToRetry := controller.updateLoadBalancerHosts(ctx, services, tc.worker)
|
servicesToRetry := controller.updateLoadBalancerHosts(ctx, services, tc.worker)
|
||||||
compareServiceList(t, tc.expectedRetryServices, servicesToRetry)
|
assert.Truef(t, tc.expectedRetryServices.Equal(servicesToRetry), "Services to retry are not expected")
|
||||||
compareUpdateCalls(t, tc.expectedUpdateCalls, cloud.UpdateCalls)
|
compareUpdateCalls(t, tc.expectedUpdateCalls, cloud.UpdateCalls)
|
||||||
cloud.UpdateCalls = []fakecloud.UpdateBalancerCall{}
|
cloud.UpdateCalls = []fakecloud.UpdateBalancerCall{}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compareServiceList compares if both left and right inputs contains the same service list despite the order.
|
|
||||||
func compareServiceList(t *testing.T, left, right []*v1.Service) {
|
|
||||||
if len(left) != len(right) {
|
|
||||||
t.Errorf("expect len(left) == len(right), but got %v != %v", len(left), len(right))
|
|
||||||
}
|
|
||||||
|
|
||||||
mismatch := false
|
|
||||||
for _, l := range left {
|
|
||||||
found := false
|
|
||||||
for _, r := range right {
|
|
||||||
if reflect.DeepEqual(l, r) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
mismatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mismatch {
|
|
||||||
t.Errorf("expected service list to match, expected %+v, got %+v", left, right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compareUpdateCalls compares if the same update calls were made in both left and right inputs despite the order.
|
// compareUpdateCalls compares if the same update calls were made in both left and right inputs despite the order.
|
||||||
func compareUpdateCalls(t *testing.T, left, right []fakecloud.UpdateBalancerCall) {
|
func compareUpdateCalls(t *testing.T, left, right []fakecloud.UpdateBalancerCall) {
|
||||||
if len(left) != len(right) {
|
if len(left) != len(right) {
|
||||||
|
Loading…
Reference in New Issue
Block a user