mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-05 23:47:50 +00:00
fix service allocation concurrency issues
The service allocator is used to allocate ip addresses for the Service IP allocator and NodePorts for the Service NodePort allocator. It uses a bitmap backed by etcd to store the allocation and tries to allocate the resources directly from the local memory instead from etcd, that can cause issues in environment with high concurrency. It may happen, in deployments with multiple apiservers, that the resource allocation information is out of sync, this is more sensible with NodePorts, per example: 1. apiserver A create a service with NodePort X 2. apiserver B deletes the service 3. apiserver A creates the service again If the allocation data of apiserver A wasn't refreshed with the deletion of apiserver B, apiserver A fails the allocation because the data is out of sync. The Repair loops solve the problem later, but there are some use cases that require to improve the concurrency in the allocation logic. We can try to not do the Allocation and Release operations locally, and try instead to check if the local data is up to date with etcd, and operate over the most recent version of the data.
This commit is contained in:
@@ -40,6 +40,7 @@ go_test(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -494,3 +495,84 @@ func TestReconcilerMasterLeaseMultiMoreMasters(t *testing.T) {
|
||||
func TestReconcilerMasterLeaseMultiCombined(t *testing.T) {
|
||||
testReconcilersMasterLease(t, 3, 3)
|
||||
}
|
||||
|
||||
func TestMultiMasterNodePortAllocation(t *testing.T) {
|
||||
var kubeAPIServers []*kubeapiservertesting.TestServer
|
||||
var clientAPIServers []*kubernetes.Clientset
|
||||
etcd := framework.SharedEtcd()
|
||||
|
||||
instanceOptions := &kubeapiservertesting.TestServerInstanceOptions{
|
||||
DisableStorageCleanup: true,
|
||||
}
|
||||
|
||||
// cleanup the registry storage
|
||||
defer registry.CleanupStorage()
|
||||
|
||||
// create 2 api servers and 2 clients
|
||||
for i := 0; i < 2; i++ {
|
||||
// start master count api server
|
||||
t.Logf("starting api server: %d", i)
|
||||
server := kubeapiservertesting.StartTestServerOrDie(t, instanceOptions, []string{
|
||||
"--advertise-address", fmt.Sprintf("10.0.1.%v", i+1),
|
||||
}, etcd)
|
||||
kubeAPIServers = append(kubeAPIServers, server)
|
||||
|
||||
// verify kube API servers have registered and create a client
|
||||
if err := wait.PollImmediate(3*time.Second, 2*time.Minute, func() (bool, error) {
|
||||
client, err := kubernetes.NewForConfig(kubeAPIServers[i].ClientConfig)
|
||||
if err != nil {
|
||||
t.Logf("create client error: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
clientAPIServers = append(clientAPIServers, client)
|
||||
endpoints, err := client.CoreV1().Endpoints("default").Get(context.TODO(), "kubernetes", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Logf("error fetching endpoints: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
return verifyEndpointsWithIPs(kubeAPIServers, getEndpointIPs(endpoints)), nil
|
||||
}); err != nil {
|
||||
t.Fatalf("did not find only lease endpoints: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
serviceObject := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
Name: "test-node-port",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "nodeport-test",
|
||||
Port: 443,
|
||||
TargetPort: intstr.IntOrString{IntVal: 443},
|
||||
NodePort: 32080,
|
||||
Protocol: "TCP",
|
||||
},
|
||||
},
|
||||
Type: "NodePort",
|
||||
Selector: map[string]string{"foo": "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
// create and delete the same nodePortservice using different APIservers
|
||||
// to check that API servers are using the same port allocation bitmap
|
||||
for i := 0; i < 2; i++ {
|
||||
// Create the service using the first API server
|
||||
_, err := clientAPIServers[0].CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), serviceObject, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create service: %v", err)
|
||||
}
|
||||
// Delete the service using the second API server
|
||||
if err := clientAPIServers[1].CoreV1().Services(metav1.NamespaceDefault).Delete(context.TODO(), serviceObject.ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
|
||||
t.Fatalf("got unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown the api servers
|
||||
for _, server := range kubeAPIServers {
|
||||
server.TearDownFn()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user