Skip to content

Commit 8087153

Browse files
authored
Merge pull request kosmos-io#774 from qiuwei68/feature_unittests
feat: add external eps support dualstack
2 parents 849a934 + 657fd36 commit 8087153

File tree

13 files changed

+766
-165
lines changed

13 files changed

+766
-165
lines changed

cmd/kubenest/operator/app/operator.go

+10
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ func createKubeConfig(opts *options.Options) (*restclient.Config, error) {
181181
}
182182

183183
func startEndPointsControllers(mgr manager.Manager) error {
184+
restConfig := mgr.GetConfig()
185+
186+
kubeClient, err := kubernetes.NewForConfig(restConfig)
187+
if err != nil {
188+
return err
189+
}
190+
184191
coreEndPointsController := endpointscontroller.CoreDNSController{
185192
Client: mgr.GetClient(),
186193
EventRecorder: mgr.GetEventRecorderFor(constants.GlobalNodeControllerName),
@@ -199,9 +206,12 @@ func startEndPointsControllers(mgr manager.Manager) error {
199206
return fmt.Errorf("error starting %s: %v", endpointscontroller.KonnectivitySyncControllerName, err)
200207
}
201208

209+
nodeGetter := &endpointscontroller.RealNodeGetter{}
202210
APIServerExternalSyncController := endpointscontroller.APIServerExternalSyncController{
203211
Client: mgr.GetClient(),
204212
EventRecorder: mgr.GetEventRecorderFor(constants.GlobalNodeControllerName),
213+
KubeClient: kubeClient,
214+
NodeGetter: nodeGetter,
205215
}
206216

207217
if err := APIServerExternalSyncController.SetupWithManager(mgr); err != nil {

pkg/kubenest/common/resource.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package common
2+
3+
import (
4+
"k8s.io/client-go/kubernetes"
5+
6+
"github.com/kosmos.io/kosmos/pkg/apis/kosmos/v1alpha1"
7+
)
8+
9+
type APIServerExternalResource struct {
10+
Namespace string
11+
Name string
12+
Vc *v1alpha1.VirtualCluster
13+
RootClientSet kubernetes.Interface
14+
}

pkg/kubenest/constants/constant.go

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
ControllerFinalizerName = "operator.virtualcluster.io/finalizer"
2828
DefaultKubeconfigPath = "/etc/cluster-tree/cert"
2929
Label = "virtualCluster-app"
30+
LabelValue = "apiserver"
3031
ComponentBeReadyTimeout = 300 * time.Second
3132
ComponentBeDeletedTimeout = 300 * time.Second
3233

pkg/kubenest/controller/endpoints.sync.controller/apiserver_external_sync_controller.go

+119-100
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,20 @@ package endpointcontroller
33
import (
44
"context"
55
"fmt"
6-
"strings"
6+
"reflect"
77

88
v1 "k8s.io/api/core/v1"
99
apierrors "k8s.io/apimachinery/pkg/api/errors"
1010
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11-
"k8s.io/apimachinery/pkg/types"
1211
"k8s.io/client-go/kubernetes"
1312
"k8s.io/client-go/tools/record"
1413
"k8s.io/client-go/util/retry"
1514
"k8s.io/klog/v2"
1615
controllerruntime "sigs.k8s.io/controller-runtime"
17-
"sigs.k8s.io/controller-runtime/pkg/builder"
1816
"sigs.k8s.io/controller-runtime/pkg/client"
1917
"sigs.k8s.io/controller-runtime/pkg/controller"
20-
"sigs.k8s.io/controller-runtime/pkg/event"
2118
"sigs.k8s.io/controller-runtime/pkg/handler"
2219
"sigs.k8s.io/controller-runtime/pkg/manager"
23-
"sigs.k8s.io/controller-runtime/pkg/predicate"
2420
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2521
"sigs.k8s.io/controller-runtime/pkg/source"
2622

@@ -30,153 +26,176 @@ import (
3026
"github.com/kosmos.io/kosmos/pkg/utils"
3127
)
3228

29+
type NodeGetter interface {
30+
GetAPIServerNodes(client kubernetes.Interface, namespace string) (*v1.NodeList, error)
31+
}
32+
33+
type RealNodeGetter struct{}
34+
35+
func (r *RealNodeGetter) GetAPIServerNodes(client kubernetes.Interface, namespace string) (*v1.NodeList, error) {
36+
return util.GetAPIServerNodes(client, namespace)
37+
}
38+
3339
type APIServerExternalSyncController struct {
3440
client.Client
3541
EventRecorder record.EventRecorder
42+
KubeClient kubernetes.Interface
43+
NodeGetter NodeGetter
3644
}
3745

3846
const APIServerExternalSyncControllerName string = "api-server-external-service-sync-controller"
3947

4048
func (e *APIServerExternalSyncController) SetupWithManager(mgr manager.Manager) error {
41-
skipEvent := func(obj client.Object) bool {
42-
return strings.Contains(obj.GetName(), "apiserver") && obj.GetNamespace() != ""
43-
}
44-
4549
return controllerruntime.NewControllerManagedBy(mgr).
4650
Named(APIServerExternalSyncControllerName).
4751
WithOptions(controller.Options{MaxConcurrentReconciles: 5}).
48-
For(&v1.Endpoints{},
49-
builder.WithPredicates(predicate.Funcs{
50-
CreateFunc: func(createEvent event.CreateEvent) bool {
51-
return skipEvent(createEvent.Object)
52-
},
53-
UpdateFunc: func(updateEvent event.UpdateEvent) bool { return skipEvent(updateEvent.ObjectNew) },
54-
DeleteFunc: func(deleteEvent event.DeleteEvent) bool { return false },
55-
})).
56-
Watches(&source.Kind{Type: &v1alpha1.VirtualCluster{}}, handler.EnqueueRequestsFromMapFunc(e.newVirtualClusterMapFunc())).
52+
Watches(&source.Kind{Type: &v1.Pod{}}, handler.EnqueueRequestsFromMapFunc(e.newPodMapFunc())).
5753
Complete(e)
5854
}
5955

60-
func (e *APIServerExternalSyncController) newVirtualClusterMapFunc() handler.MapFunc {
61-
return func(a client.Object) []reconcile.Request {
62-
var requests []reconcile.Request
63-
vcluster := a.(*v1alpha1.VirtualCluster)
64-
65-
// Join the Reconcile queue only if the status of the vcluster is Completed
66-
if vcluster.Status.Phase == v1alpha1.Completed {
67-
klog.V(4).Infof("api-server-external-sync-controller: virtualcluster change to completed: %s", vcluster.Name)
68-
// Add the vcluster to the Reconcile queue
69-
requests = append(requests, reconcile.Request{
70-
NamespacedName: types.NamespacedName{
71-
Name: vcluster.Name,
72-
Namespace: vcluster.Namespace,
56+
func (e *APIServerExternalSyncController) newPodMapFunc() handler.MapFunc {
57+
return func(obj client.Object) []reconcile.Request {
58+
pod, ok := obj.(*v1.Pod)
59+
60+
if !ok {
61+
klog.Warningf("Object is not a Pod, skipping: %v", obj)
62+
return nil
63+
}
64+
65+
// If the pod contains the specified label virtualCluster-app=apiserver,it indicates that it belongs to vc.
66+
if val, exists := pod.Labels[constants.Label]; exists && val == constants.LabelValue {
67+
return []reconcile.Request{
68+
{
69+
NamespacedName: client.ObjectKey{
70+
Name: pod.Name,
71+
Namespace: pod.Namespace,
72+
},
7373
},
74-
})
74+
}
7575
}
76-
return requests
76+
77+
return nil
7778
}
7879
}
7980

80-
func (e *APIServerExternalSyncController) SyncAPIServerExternalEPS(ctx context.Context, k8sClient kubernetes.Interface) error {
81-
kubeEndpoints, err := k8sClient.CoreV1().Endpoints(constants.DefaultNs).Get(ctx, "kubernetes", metav1.GetOptions{})
82-
if err != nil {
83-
klog.Errorf("Error getting endpoints: %v", err)
84-
return err
85-
}
86-
klog.V(4).Infof("Endpoints for service 'kubernetes': %v", kubeEndpoints)
87-
for _, subset := range kubeEndpoints.Subsets {
88-
for _, address := range subset.Addresses {
89-
klog.V(4).Infof("IP: %s", address.IP)
90-
}
81+
func (e *APIServerExternalSyncController) SyncAPIServerExternalEndpoints(ctx context.Context, k8sClient kubernetes.Interface, vc *v1alpha1.VirtualCluster) error {
82+
if e.NodeGetter == nil {
83+
return fmt.Errorf("NodeGetter is nil")
9184
}
9285

93-
if len(kubeEndpoints.Subsets) != 1 {
94-
return fmt.Errorf("eps %s Subsets length is not 1", "kubernetes")
86+
nodes, err := e.NodeGetter.GetAPIServerNodes(e.KubeClient, vc.Namespace)
87+
if err != nil {
88+
return fmt.Errorf("failed to get API server nodes: %w", err)
9589
}
9690

97-
if kubeEndpoints.Subsets[0].Addresses == nil || len(kubeEndpoints.Subsets[0].Addresses) == 0 {
98-
klog.Errorf("eps %s Addresses length is nil", "kubernetes")
99-
return err
91+
if len(nodes.Items) == 0 {
92+
return fmt.Errorf("no API server nodes found in the cluster")
10093
}
10194

102-
apiServerExternalEndpoints, err := k8sClient.CoreV1().Endpoints(constants.DefaultNs).Get(ctx, constants.APIServerExternalService, metav1.GetOptions{})
103-
if err != nil && !apierrors.IsNotFound(err) {
104-
klog.Errorf("failed to get endpoints for %s : %v", constants.APIServerExternalService, err)
105-
return err
95+
var addresses []v1.EndpointAddress
96+
for _, node := range nodes.Items {
97+
for _, address := range node.Status.Addresses {
98+
if address.Type == v1.NodeInternalIP {
99+
addresses = append(addresses, v1.EndpointAddress{
100+
IP: address.Address,
101+
})
102+
}
103+
}
106104
}
107105

108-
updateEPS := apiServerExternalEndpoints.DeepCopy()
109-
110-
if apiServerExternalEndpoints != nil {
111-
klog.V(4).Infof("apiServerExternalEndpoints: %v", apiServerExternalEndpoints)
112-
} else {
113-
klog.V(4).Info("apiServerExternalEndpoints is nil")
106+
if len(addresses) == 0 {
107+
return fmt.Errorf("no internal IP addresses found for the API server nodes")
114108
}
115109

116-
if updateEPS != nil {
117-
klog.V(4).Infof("updateEPS: %v", updateEPS)
118-
} else {
119-
klog.V(4).Info("updateEPS is nil")
110+
apiServerPort, ok := vc.Status.PortMap[constants.APIServerPortKey]
111+
if !ok {
112+
return fmt.Errorf("failed to get API server port from VirtualCluster status")
120113
}
114+
klog.V(4).Infof("API server port: %d", apiServerPort)
121115

122-
if len(updateEPS.Subsets) == 1 && len(updateEPS.Subsets[0].Addresses) == 1 {
123-
ip := kubeEndpoints.Subsets[0].Addresses[0].IP
124-
klog.V(4).Infof("IP address: %s", ip)
125-
updateEPS.Subsets[0].Addresses[0].IP = ip
116+
newEndpoint := &v1.Endpoints{
117+
ObjectMeta: metav1.ObjectMeta{
118+
Name: constants.APIServerExternalService,
119+
Namespace: constants.KosmosNs,
120+
},
121+
Subsets: []v1.EndpointSubset{
122+
{
123+
Addresses: addresses,
124+
Ports: []v1.EndpointPort{
125+
{
126+
Name: "https",
127+
Port: apiServerPort,
128+
Protocol: v1.ProtocolTCP,
129+
},
130+
},
131+
},
132+
},
133+
}
126134

127-
if _, err := k8sClient.CoreV1().Endpoints(constants.DefaultNs).Update(ctx, updateEPS, metav1.UpdateOptions{}); err != nil {
128-
klog.Errorf("failed to update endpoints for api-server-external-service: %v", err)
129-
return err
135+
//avoid unnecessary updates
136+
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
137+
currentEndpoint, err := k8sClient.CoreV1().Endpoints(constants.KosmosNs).Get(ctx, constants.APIServerExternalService, metav1.GetOptions{})
138+
if apierrors.IsNotFound(err) {
139+
_, err := k8sClient.CoreV1().Endpoints(constants.KosmosNs).Create(ctx, newEndpoint, metav1.CreateOptions{})
140+
if err != nil {
141+
return fmt.Errorf("failed to create api-server-external-service endpoint: %w", err)
142+
}
143+
klog.V(4).Info("Created api-server-external-service Endpoint")
144+
return nil
145+
} else if err != nil {
146+
return fmt.Errorf("failed to get existing api-server-external-service endpoint: %w", err)
130147
}
131-
} else {
132-
klog.ErrorS(err, "Unexpected format of endpoints for api-server-external-service", "endpoint_data", updateEPS)
133-
return err
134-
}
135148

136-
return nil
149+
// determine if an update is needed
150+
if !reflect.DeepEqual(currentEndpoint.Subsets, newEndpoint.Subsets) {
151+
_, err := k8sClient.CoreV1().Endpoints(constants.KosmosNs).Update(ctx, newEndpoint, metav1.UpdateOptions{})
152+
if err != nil {
153+
return fmt.Errorf("failed to update api-server-external-service endpoint: %w", err)
154+
}
155+
klog.V(4).Info("Updated api-server-external-service Endpoint")
156+
} else {
157+
klog.V(4).Info("No changes detected in Endpoint, skipping update")
158+
}
159+
return nil
160+
})
137161
}
138162

139163
func (e *APIServerExternalSyncController) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
140164
klog.V(4).Infof("============ %s start to reconcile %s ============", APIServerExternalSyncControllerName, request.NamespacedName)
141165
defer klog.V(4).Infof("============ %s finish to reconcile %s ============", APIServerExternalSyncControllerName, request.NamespacedName)
142166

143-
var virtualClusterList v1alpha1.VirtualClusterList
144-
if err := e.List(ctx, &virtualClusterList); err != nil {
145-
if apierrors.IsNotFound(err) {
146-
return reconcile.Result{}, nil
147-
}
148-
klog.V(4).Infof("query virtualcluster failed: %v", err)
167+
var vcList v1alpha1.VirtualClusterList
168+
if err := e.List(ctx, &vcList, client.InNamespace(request.NamespacedName.Namespace)); err != nil {
169+
klog.Errorf("Failed to list VirtualClusters in namespace %s: %v", request.NamespacedName.Namespace, err)
149170
return reconcile.Result{RequeueAfter: utils.DefaultRequeueTime}, nil
150171
}
151-
var targetVirtualCluster v1alpha1.VirtualCluster
152-
hasVirtualCluster := false
153-
for _, vc := range virtualClusterList.Items {
154-
if vc.Namespace == request.Namespace {
155-
targetVirtualCluster = vc
156-
klog.V(4).Infof("virtualcluster %s found", targetVirtualCluster.Name)
157-
hasVirtualCluster = true
158-
break
159-
}
160-
}
161-
if !hasVirtualCluster {
162-
klog.V(4).Infof("virtualcluster %s not found", request.Namespace)
172+
173+
if len(vcList.Items) == 0 {
174+
klog.V(4).Infof("No VirtualCluster found in namespace %s", request.NamespacedName.Namespace)
163175
return reconcile.Result{}, nil
164176
}
165177

166-
if targetVirtualCluster.Status.Phase != v1alpha1.Completed {
178+
// A namespace should correspond to only one virtual cluster (vc). If it corresponds to multiple vcs, it indicates an error.
179+
if len(vcList.Items) > 1 {
180+
klog.Errorf("Multiple VirtualClusters found in namespace %s, expected only one", request.NamespacedName.Namespace)
181+
return reconcile.Result{RequeueAfter: utils.DefaultRequeueTime}, nil
182+
}
183+
184+
vc := vcList.Items[0]
185+
186+
if vc.Status.Phase != v1alpha1.Completed {
187+
klog.V(4).Infof("VirtualCluster %s is not in Completed phase", vc.Name)
167188
return reconcile.Result{}, nil
168189
}
169190

170-
k8sClient, err := util.GenerateKubeclient(&targetVirtualCluster)
191+
k8sClient, err := util.GenerateKubeclient(&vc)
171192
if err != nil {
172-
klog.Errorf("virtualcluster %s crd kubernetes client failed: %v", targetVirtualCluster.Name, err)
193+
klog.Errorf("Failed to generate Kubernetes client for VirtualCluster %s: %v", vc.Name, err)
173194
return reconcile.Result{}, nil
174195
}
175196

176-
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
177-
return e.SyncAPIServerExternalEPS(ctx, k8sClient)
178-
}); err != nil {
179-
klog.Errorf("virtualcluster %s sync apiserver external endpoints failed: %v", targetVirtualCluster.Name, err)
197+
if err := e.SyncAPIServerExternalEndpoints(ctx, k8sClient, &vc); err != nil {
198+
klog.Errorf("Failed to sync apiserver external Endpoints: %v", err)
180199
return reconcile.Result{RequeueAfter: utils.DefaultRequeueTime}, nil
181200
}
182201

0 commit comments

Comments
 (0)