Skip to content

Commit 49b21ad

Browse files
kishorjTimothy-Dougherty
authored andcommitted
add support for loadBalancerClass (kubernetes-sigs#2489)
* add support for loadBalancerClass * throw error in case of unallocated NodePort * centralize IsServiceSupported logic * handle lb type annotation change for type LoadBalancer * refactor IsServiceSupported
1 parent 8732c8c commit 49b21ad

14 files changed

+784
-91
lines changed

apis/elbv2/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/elbv2/v1beta1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controllers/service/eventhandlers/service_events.go

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,28 @@ import (
77
"k8s.io/apimachinery/pkg/types"
88
"k8s.io/client-go/tools/record"
99
"k8s.io/client-go/util/workqueue"
10-
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
1110
svcpkg "sigs.k8s.io/aws-load-balancer-controller/pkg/service"
1211
"sigs.k8s.io/controller-runtime/pkg/event"
1312
"sigs.k8s.io/controller-runtime/pkg/handler"
1413
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1514
)
1615

1716
// NewEnqueueRequestForServiceEvent constructs new enqueueRequestsForServiceEvent.
18-
func NewEnqueueRequestForServiceEvent(eventRecorder record.EventRecorder, annotationParser annotations.Parser, logger logr.Logger) *enqueueRequestsForServiceEvent {
17+
func NewEnqueueRequestForServiceEvent(eventRecorder record.EventRecorder,
18+
serviceUtils svcpkg.ServiceUtils, logger logr.Logger) *enqueueRequestsForServiceEvent {
1919
return &enqueueRequestsForServiceEvent{
20-
eventRecorder: eventRecorder,
21-
annotationParser: annotationParser,
22-
logger: logger,
20+
eventRecorder: eventRecorder,
21+
serviceUtils: serviceUtils,
22+
logger: logger,
2323
}
2424
}
2525

2626
var _ handler.EventHandler = (*enqueueRequestsForServiceEvent)(nil)
2727

2828
type enqueueRequestsForServiceEvent struct {
29-
eventRecorder record.EventRecorder
30-
annotationParser annotations.Parser
31-
logger logr.Logger
29+
eventRecorder record.EventRecorder
30+
serviceUtils svcpkg.ServiceUtils
31+
logger logr.Logger
3232
}
3333

3434
func (h *enqueueRequestsForServiceEvent) Create(e event.CreateEvent, queue workqueue.RateLimitingInterface) {
@@ -57,24 +57,9 @@ func (h *enqueueRequestsForServiceEvent) Delete(e event.DeleteEvent, queue workq
5757
func (h *enqueueRequestsForServiceEvent) Generic(e event.GenericEvent, queue workqueue.RateLimitingInterface) {
5858
}
5959

60-
func (h *enqueueRequestsForServiceEvent) isServiceSupported(service *corev1.Service) bool {
61-
lbType := ""
62-
_ = h.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixLoadBalancerType, &lbType, service.Annotations)
63-
if lbType == svcpkg.LoadBalancerTypeNLBIP {
64-
return true
65-
}
66-
var lbTargetType string
67-
_ = h.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixTargetType, &lbTargetType, service.Annotations)
68-
if lbType == svcpkg.LoadBalancerTypeExternal && (lbTargetType == svcpkg.LoadBalancerTargetTypeIP ||
69-
lbTargetType == svcpkg.LoadBalancerTargetTypeInstance) {
70-
return true
71-
}
72-
return false
73-
}
74-
7560
func (h *enqueueRequestsForServiceEvent) enqueueManagedService(queue workqueue.RateLimitingInterface, service *corev1.Service) {
7661
// Check if the svc needs to be handled
77-
if !h.isServiceSupported(service) {
62+
if !h.serviceUtils.IsServicePendingFinalization(service) && !h.serviceUtils.IsServiceSupported(service) {
7863
return
7964
}
8065
queue.Add(reconcile.Request{

controllers/service/service_controller.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,18 @@ func NewServiceReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorde
4141
annotationParser := annotations.NewSuffixAnnotationParser(serviceAnnotationPrefix)
4242
trackingProvider := tracking.NewDefaultProvider(serviceTagPrefix, config.ClusterName)
4343
elbv2TaggingManager := elbv2.NewDefaultTaggingManager(cloud.ELBV2(), cloud.VpcID(), config.FeatureGates, logger)
44+
serviceUtils := service.NewServiceUtils(annotationParser, serviceFinalizer, config.ServiceConfig.LoadBalancerClass, config.FeatureGates)
4445
modelBuilder := service.NewDefaultModelBuilder(annotationParser, subnetsResolver, vpcInfoProvider, cloud.VpcID(), trackingProvider,
45-
elbv2TaggingManager, config.ClusterName, config.DefaultTags, config.ExternalManagedTags, config.DefaultSSLPolicy)
46+
elbv2TaggingManager, config.ClusterName, config.DefaultTags, config.ExternalManagedTags, config.DefaultSSLPolicy, serviceUtils)
4647
stackMarshaller := deploy.NewDefaultStackMarshaller()
4748
stackDeployer := deploy.NewDefaultStackDeployer(cloud, k8sClient, networkingSGManager, networkingSGReconciler, config, serviceTagPrefix, logger)
4849
return &serviceReconciler{
49-
k8sClient: k8sClient,
50-
eventRecorder: eventRecorder,
51-
finalizerManager: finalizerManager,
52-
annotationParser: annotationParser,
50+
k8sClient: k8sClient,
51+
eventRecorder: eventRecorder,
52+
finalizerManager: finalizerManager,
53+
annotationParser: annotationParser,
54+
loadBalancerClass: config.ServiceConfig.LoadBalancerClass,
55+
serviceUtils: serviceUtils,
5356

5457
modelBuilder: modelBuilder,
5558
stackMarshaller: stackMarshaller,
@@ -61,10 +64,12 @@ func NewServiceReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorde
6164
}
6265

6366
type serviceReconciler struct {
64-
k8sClient client.Client
65-
eventRecorder record.EventRecorder
66-
finalizerManager k8s.FinalizerManager
67-
annotationParser annotations.Parser
67+
k8sClient client.Client
68+
eventRecorder record.EventRecorder
69+
finalizerManager k8s.FinalizerManager
70+
annotationParser annotations.Parser
71+
loadBalancerClass string
72+
serviceUtils service.ServiceUtils
6873

6974
modelBuilder service.ModelBuilder
7075
stackMarshaller deploy.StackMarshaller
@@ -87,13 +92,17 @@ func (r *serviceReconciler) reconcile(ctx context.Context, req ctrl.Request) err
8792
if err := r.k8sClient.Get(ctx, req.NamespacedName, svc); err != nil {
8893
return client.IgnoreNotFound(err)
8994
}
90-
if !svc.DeletionTimestamp.IsZero() {
91-
return r.cleanupLoadBalancerResources(ctx, svc)
95+
stack, lb, err := r.buildModel(ctx, svc)
96+
if err != nil {
97+
return err
9298
}
93-
return r.reconcileLoadBalancerResources(ctx, svc)
99+
if lb == nil {
100+
return r.cleanupLoadBalancerResources(ctx, svc, stack)
101+
}
102+
return r.reconcileLoadBalancerResources(ctx, svc, stack, lb)
94103
}
95104

96-
func (r *serviceReconciler) buildAndDeployModel(ctx context.Context, svc *corev1.Service) (core.Stack, *elbv2model.LoadBalancer, error) {
105+
func (r *serviceReconciler) buildModel(ctx context.Context, svc *corev1.Service) (core.Stack, *elbv2model.LoadBalancer, error) {
97106
stack, lb, err := r.modelBuilder.Build(ctx, svc)
98107
if err != nil {
99108
r.eventRecorder.Event(svc, corev1.EventTypeWarning, k8s.ServiceEventReasonFailedBuildModel, fmt.Sprintf("Failed build model due to %v", err))
@@ -105,22 +114,25 @@ func (r *serviceReconciler) buildAndDeployModel(ctx context.Context, svc *corev1
105114
return nil, nil, err
106115
}
107116
r.logger.Info("successfully built model", "model", stackJSON)
117+
return stack, lb, nil
118+
}
108119

109-
if err = r.stackDeployer.Deploy(ctx, stack); err != nil {
120+
func (r *serviceReconciler) deployModel(ctx context.Context, svc *corev1.Service, stack core.Stack) error {
121+
if err := r.stackDeployer.Deploy(ctx, stack); err != nil {
110122
r.eventRecorder.Event(svc, corev1.EventTypeWarning, k8s.ServiceEventReasonFailedDeployModel, fmt.Sprintf("Failed deploy model due to %v", err))
111-
return nil, nil, err
123+
return err
112124
}
113125
r.logger.Info("successfully deployed model", "service", k8s.NamespacedName(svc))
114126

115-
return stack, lb, nil
127+
return nil
116128
}
117129

118-
func (r *serviceReconciler) reconcileLoadBalancerResources(ctx context.Context, svc *corev1.Service) error {
130+
func (r *serviceReconciler) reconcileLoadBalancerResources(ctx context.Context, svc *corev1.Service, stack core.Stack, lb *elbv2model.LoadBalancer) error {
119131
if err := r.finalizerManager.AddFinalizers(ctx, svc, serviceFinalizer); err != nil {
120132
r.eventRecorder.Event(svc, corev1.EventTypeWarning, k8s.ServiceEventReasonFailedAddFinalizer, fmt.Sprintf("Failed add finalizer due to %v", err))
121133
return err
122134
}
123-
_, lb, err := r.buildAndDeployModel(ctx, svc)
135+
err := r.deployModel(ctx, svc, stack)
124136
if err != nil {
125137
return err
126138
}
@@ -137,12 +149,16 @@ func (r *serviceReconciler) reconcileLoadBalancerResources(ctx context.Context,
137149
return nil
138150
}
139151

140-
func (r *serviceReconciler) cleanupLoadBalancerResources(ctx context.Context, svc *corev1.Service) error {
152+
func (r *serviceReconciler) cleanupLoadBalancerResources(ctx context.Context, svc *corev1.Service, stack core.Stack) error {
141153
if k8s.HasFinalizer(svc, serviceFinalizer) {
142-
_, _, err := r.buildAndDeployModel(ctx, svc)
154+
err := r.deployModel(ctx, svc, stack)
143155
if err != nil {
144156
return err
145157
}
158+
if err = r.cleanupServiceStatus(ctx, svc); err != nil {
159+
r.eventRecorder.Event(svc, corev1.EventTypeWarning, k8s.ServiceEventReasonFailedCleanupStatus, fmt.Sprintf("Failed update status due to %v", err))
160+
return err
161+
}
146162
if err := r.finalizerManager.RemoveFinalizers(ctx, svc, serviceFinalizer); err != nil {
147163
r.eventRecorder.Event(svc, corev1.EventTypeWarning, k8s.ServiceEventReasonFailedRemoveFinalizer, fmt.Sprintf("Failed remove finalizer due to %v", err))
148164
return err
@@ -168,6 +184,15 @@ func (r *serviceReconciler) updateServiceStatus(ctx context.Context, lbDNS strin
168184
return nil
169185
}
170186

187+
func (r *serviceReconciler) cleanupServiceStatus(ctx context.Context, svc *corev1.Service) error {
188+
svcOld := svc.DeepCopy()
189+
svc.Status.LoadBalancer = corev1.LoadBalancerStatus{}
190+
if err := r.k8sClient.Status().Patch(ctx, svc, client.MergeFrom(svcOld)); err != nil {
191+
return errors.Wrapf(err, "failed to cleanup service status: %v", k8s.NamespacedName(svc))
192+
}
193+
return nil
194+
}
195+
171196
func (r *serviceReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
172197
c, err := controller.New(controllerName, mgr, controller.Options{
173198
MaxConcurrentReconciles: r.maxConcurrentReconciles,
@@ -183,8 +208,8 @@ func (r *serviceReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag
183208
}
184209

185210
func (r *serviceReconciler) setupWatches(_ context.Context, c controller.Controller) error {
186-
svcEventHandler := eventhandlers.NewEnqueueRequestForServiceEvent(r.eventRecorder, r.annotationParser,
187-
r.logger.WithName("eventHandlers").WithName("service"))
211+
svcEventHandler := eventhandlers.NewEnqueueRequestForServiceEvent(r.eventRecorder,
212+
r.serviceUtils, r.logger.WithName("eventHandlers").WithName("service"))
188213
if err := c.Watch(&source.Kind{Type: &corev1.Service{}}, svcEventHandler); err != nil {
189214
return err
190215
}

pkg/config/controller_config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ type ControllerConfig struct {
6060
IngressConfig IngressConfig
6161
// Configurations for Addons feature
6262
AddonsConfig AddonsConfig
63+
// Configurations for the Service controller
64+
ServiceConfig ServiceConfig
6365

6466
// Default AWS Tags that will be applied to all AWS resources managed by this controller.
6567
DefaultTags map[string]string
@@ -127,6 +129,7 @@ func (cfg *ControllerConfig) BindFlags(fs *pflag.FlagSet) {
127129
cfg.PodWebhookConfig.BindFlags(fs)
128130
cfg.IngressConfig.BindFlags(fs)
129131
cfg.AddonsConfig.BindFlags(fs)
132+
cfg.ServiceConfig.BindFlags(fs)
130133
}
131134

132135
// Validate the controller configuration

pkg/config/feature_gates.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010
type Feature string
1111

1212
const (
13-
ListenerRulesTagging Feature = "ListenerRulesTagging"
14-
WeightedTargetGroups Feature = "WeightedTargetGroups"
13+
ListenerRulesTagging Feature = "ListenerRulesTagging"
14+
WeightedTargetGroups Feature = "WeightedTargetGroups"
15+
ServiceTypeLoadBalancerOnly Feature = "ServiceTypeLoadBalancerOnly"
1516
)
1617

1718
type FeatureGates interface {
@@ -39,8 +40,9 @@ type defaultFeatureGates struct {
3940
func NewFeatureGates() FeatureGates {
4041
return &defaultFeatureGates{
4142
featureState: map[Feature]bool{
42-
ListenerRulesTagging: true,
43-
WeightedTargetGroups: true,
43+
ListenerRulesTagging: true,
44+
WeightedTargetGroups: true,
45+
ServiceTypeLoadBalancerOnly: false,
4446
},
4547
}
4648
}

pkg/config/service_config.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package config
2+
3+
import "github.com/spf13/pflag"
4+
5+
const (
6+
flagLoadBalancerClass = "load-balancer-class"
7+
defaultLoadBalancerClass = "service.k8s.aws/nlb"
8+
)
9+
10+
// ServiceConfig contains the configurations for the Service controller
11+
type ServiceConfig struct {
12+
// LoadBalancerClass is the name of the load balancer class reconciled by this controller
13+
LoadBalancerClass string
14+
}
15+
16+
// BindFlags binds the command line flags to the fields in the config object
17+
func (cfg *ServiceConfig) BindFlags(fs *pflag.FlagSet) {
18+
fs.StringVar(&cfg.LoadBalancerClass, flagLoadBalancerClass, defaultLoadBalancerClass,
19+
"Name of the load balancer class reconciled by this controller")
20+
}

pkg/k8s/events.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const (
1515
ServiceEventReasonFailedAddFinalizer = "FailedAddFinalizer"
1616
ServiceEventReasonFailedRemoveFinalizer = "FailedRemoveFinalizer"
1717
ServiceEventReasonFailedUpdateStatus = "FailedUpdateStatus"
18+
ServiceEventReasonFailedCleanupStatus = "FailedCleanupStatus"
1819
ServiceEventReasonFailedBuildModel = "FailedBuildModel"
1920
ServiceEventReasonFailedDeployModel = "FailedDeployModel"
2021
ServiceEventReasonSuccessfullyReconciled = "SuccessfullyReconciled"

pkg/service/model_build_target_group.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func (t *defaultModelBuildTask) buildTargetGroup(ctx context.Context, port corev
3535
if targetGroup, exists := t.tgByResID[tgResourceID]; exists {
3636
return targetGroup, nil
3737
}
38-
targetType, err := t.buildTargetType(ctx)
38+
targetType, err := t.buildTargetType(ctx, port)
3939
if err != nil {
4040
return nil, err
4141
}
@@ -332,22 +332,23 @@ func (t *defaultModelBuildTask) buildTargetGroupHealthCheckUnhealthyThresholdCou
332332
return unhealthyThresholdCount, nil
333333
}
334334

335-
func (t *defaultModelBuildTask) buildTargetType(_ context.Context) (elbv2model.TargetType, error) {
335+
func (t *defaultModelBuildTask) buildTargetType(_ context.Context, port corev1.ServicePort) (elbv2model.TargetType, error) {
336336
svcType := t.service.Spec.Type
337337
var lbType string
338338
_ = t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixLoadBalancerType, &lbType, t.service.Annotations)
339339
var lbTargetType string
340+
lbTargetType = string(t.defaultTargetType)
340341
_ = t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixTargetType, &lbTargetType, t.service.Annotations)
341-
if lbType == LoadBalancerTypeNLBIP || (lbType == LoadBalancerTypeExternal && lbTargetType == LoadBalancerTargetTypeIP) {
342+
if lbType == LoadBalancerTypeNLBIP || lbTargetType == LoadBalancerTargetTypeIP {
342343
return elbv2model.TargetTypeIP, nil
343344
}
344-
if lbType == LoadBalancerTypeExternal && lbTargetType == LoadBalancerTargetTypeInstance {
345-
if svcType == corev1.ServiceTypeClusterIP {
346-
return "", errors.Errorf("unsupported service type \"%v\" for load balancer target type \"%v\"", svcType, lbTargetType)
347-
}
348-
return elbv2model.TargetTypeInstance, nil
345+
if svcType == corev1.ServiceTypeClusterIP {
346+
return "", errors.Errorf("unsupported service type \"%v\" for load balancer target type \"%v\"", svcType, lbTargetType)
347+
}
348+
if port.NodePort == 0 && t.service.Spec.AllocateLoadBalancerNodePorts != nil && !*t.service.Spec.AllocateLoadBalancerNodePorts {
349+
return "", errors.New("unable to support instance target type with an unallocated NodePort")
349350
}
350-
return "", errors.Errorf("unsupported target type \"%v\" for load balancer type \"%v\"", lbTargetType, lbType)
351+
return elbv2model.TargetTypeInstance, nil
351352
}
352353

353354
func (t *defaultModelBuildTask) buildTargetGroupResourceID(svcKey types.NamespacedName, port intstr.IntOrString) string {

0 commit comments

Comments
 (0)