Skip to content

Commit efe6689

Browse files
M00nF1shTimothy-Dougherty
authored andcommitted
tolerate missing service error (kubernetes-sigs#1967)
1 parent 3a2b453 commit efe6689

10 files changed

+1168
-93
lines changed

controllers/ingress/group_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func NewGroupReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder
4040

4141
annotationParser := annotations.NewSuffixAnnotationParser(annotations.AnnotationPrefixIngress)
4242
authConfigBuilder := ingress.NewDefaultAuthConfigBuilder(annotationParser)
43-
enhancedBackendBuilder := ingress.NewDefaultEnhancedBackendBuilder(annotationParser)
43+
enhancedBackendBuilder := ingress.NewDefaultEnhancedBackendBuilder(k8sClient, annotationParser, authConfigBuilder)
4444
referenceIndexer := ingress.NewDefaultReferenceIndexer(enhancedBackendBuilder, authConfigBuilder, logger)
4545
modelBuilder := ingress.NewDefaultModelBuilder(k8sClient, eventRecorder,
4646
cloud.EC2(), cloud.ACM(),

pkg/ingress/enhanced_backend_builder.go

Lines changed: 189 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,117 @@ package ingress
33
import (
44
"context"
55
"fmt"
6+
awssdk "github.com/aws/aws-sdk-go/aws"
67
"github.com/pkg/errors"
8+
corev1 "k8s.io/api/core/v1"
79
networking "k8s.io/api/networking/v1beta1"
10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
11+
"k8s.io/apimachinery/pkg/types"
812
"k8s.io/apimachinery/pkg/util/intstr"
13+
"k8s.io/apimachinery/pkg/util/sets"
14+
"sigs.k8s.io/aws-load-balancer-controller/pkg/algorithm"
915
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
1017
)
1118

1219
const (
1320
magicServicePortUseAnnotation = "use-annotation"
21+
22+
// the message body of fixed 503 response used when referencing a non-existent Kubernetes service as backend.
23+
nonExistentBackendServiceMessageBody = "Backend service does not exist"
24+
// the message body of fixed 503 response used when referencing a non-existent annotation Action as backend.
25+
nonExistentBackendActionMessageBody = "Backend action does not exist"
26+
// by default, we tolerate a missing backend service, and use a fixed 503 response instead.
27+
defaultTolerateNonExistentBackendService = true
28+
// by default, we tolerate a missing backend action, and use a fixed 503 response instead.
29+
defaultTolerateNonExistentBackendAction = true
1430
)
1531

16-
// An enhanced version of Ingress backend.
17-
// It contains additional routing conditions we parsed from annotations.
32+
// EnhancedBackend is an enhanced version of Ingress backend.
33+
// It contains additional routing conditions and authentication configurations we parsed from annotations.
1834
// Also, when magic string `use-annotation` is specified as backend, the actions will be parsed from annotations as well.
1935
type EnhancedBackend struct {
2036
Conditions []RuleCondition
2137
Action Action
38+
AuthConfig AuthConfig
39+
}
40+
41+
type EnhancedBackendBuildOptions struct {
42+
// whether to load backend services
43+
LoadBackendServices bool
44+
45+
// BackendServices contains all services referenced in Action, indexed by service's key.
46+
// Note: we support to pass BackendServices during backend build, so that we can use the same service snapshot for same service during entire Ingress build process.
47+
BackendServices map[types.NamespacedName]*corev1.Service
48+
49+
// whether to load auth configuration. when load authConfiguration, LoadBackendServices must be enabled as well.
50+
LoadAuthConfig bool
51+
}
52+
53+
type EnhancedBackendBuildOption func(opts *EnhancedBackendBuildOptions)
54+
55+
func (opts *EnhancedBackendBuildOptions) ApplyOptions(options ...EnhancedBackendBuildOption) {
56+
for _, option := range options {
57+
option(opts)
58+
}
2259
}
2360

24-
// EnhancedBackendBuilder is capable of build EnhancedBackend for Ingress backend.
61+
// WithLoadBackendServices is a option that sets the WithLoadBackendServices and BackendServices.
62+
func WithLoadBackendServices(loadBackendServices bool, backendServices map[types.NamespacedName]*corev1.Service) EnhancedBackendBuildOption {
63+
return func(opts *EnhancedBackendBuildOptions) {
64+
opts.LoadBackendServices = loadBackendServices
65+
opts.BackendServices = backendServices
66+
}
67+
}
68+
69+
// WithLoadAuthConfig is a option that sets the LoadAuthConfig.
70+
func WithLoadAuthConfig(loadAuthConfig bool) EnhancedBackendBuildOption {
71+
return func(opts *EnhancedBackendBuildOptions) {
72+
opts.LoadAuthConfig = loadAuthConfig
73+
}
74+
}
75+
76+
// EnhancedBackendBuilder is capable of build EnhancedBackend for Ingress backend.
2577
type EnhancedBackendBuilder interface {
26-
Build(ctx context.Context, ing *networking.Ingress, backend networking.IngressBackend) (EnhancedBackend, error)
78+
Build(ctx context.Context, ing *networking.Ingress, backend networking.IngressBackend, opts ...EnhancedBackendBuildOption) (EnhancedBackend, error)
2779
}
2880

2981
// NewDefaultEnhancedBackendBuilder constructs new defaultEnhancedBackendBuilder.
30-
func NewDefaultEnhancedBackendBuilder(annotationParser annotations.Parser) *defaultEnhancedBackendBuilder {
82+
func NewDefaultEnhancedBackendBuilder(k8sClient client.Client, annotationParser annotations.Parser, authConfigBuilder AuthConfigBuilder) *defaultEnhancedBackendBuilder {
3183
return &defaultEnhancedBackendBuilder{
32-
annotationParser: annotationParser,
84+
k8sClient: k8sClient,
85+
annotationParser: annotationParser,
86+
authConfigBuilder: authConfigBuilder,
87+
88+
tolerateNonExistentBackendService: defaultTolerateNonExistentBackendAction,
89+
tolerateNonExistentBackendAction: defaultTolerateNonExistentBackendService,
3390
}
3491
}
3592

3693
var _ EnhancedBackendBuilder = &defaultEnhancedBackendBuilder{}
3794

3895
// default implementation for defaultEnhancedBackendBuilder
3996
type defaultEnhancedBackendBuilder struct {
40-
annotationParser annotations.Parser
97+
k8sClient client.Client
98+
annotationParser annotations.Parser
99+
authConfigBuilder AuthConfigBuilder
100+
101+
// whether to tolerate misconfiguration that used a non-existent backend service.
102+
// when tolerate, If a single backend service is used and it's non-existent, a fixed 503 response will be used instead.
103+
tolerateNonExistentBackendService bool
104+
// whether to tolerate misconfiguration that used a non-existent backend action.
105+
// when tolerate, If the backend action annotation is non-existent, a fixed 503 response will be used instead.
106+
tolerateNonExistentBackendAction bool
41107
}
42108

43-
func (b *defaultEnhancedBackendBuilder) Build(ctx context.Context, ing *networking.Ingress, backend networking.IngressBackend) (EnhancedBackend, error) {
109+
func (b *defaultEnhancedBackendBuilder) Build(ctx context.Context, ing *networking.Ingress, backend networking.IngressBackend, opts ...EnhancedBackendBuildOption) (EnhancedBackend, error) {
110+
buildOpts := EnhancedBackendBuildOptions{
111+
LoadBackendServices: true,
112+
LoadAuthConfig: true,
113+
BackendServices: map[types.NamespacedName]*corev1.Service{},
114+
}
115+
buildOpts.ApplyOptions(opts...)
116+
44117
conditions, err := b.buildConditions(ctx, ing.Annotations, backend.ServiceName)
45118
if err != nil {
46119
return EnhancedBackend{}, err
@@ -56,9 +129,24 @@ func (b *defaultEnhancedBackendBuilder) Build(ctx context.Context, ing *networki
56129
action = b.buildActionViaServiceAndServicePort(ctx, backend.ServiceName, backend.ServicePort)
57130
}
58131

132+
var authCfg AuthConfig
133+
if buildOpts.LoadBackendServices {
134+
if err := b.loadBackendServices(ctx, &action, ing.Namespace, buildOpts.BackendServices); err != nil {
135+
return EnhancedBackend{}, err
136+
}
137+
138+
if buildOpts.LoadAuthConfig {
139+
authCfg, err = b.buildAuthConfig(ctx, action, ing.Namespace, ing.Annotations, buildOpts.BackendServices)
140+
if err != nil {
141+
return EnhancedBackend{}, err
142+
}
143+
}
144+
}
145+
59146
return EnhancedBackend{
60147
Conditions: conditions,
61148
Action: action,
149+
AuthConfig: authCfg,
62150
}, nil
63151
}
64152

@@ -77,34 +165,64 @@ func (b *defaultEnhancedBackendBuilder) buildConditions(_ context.Context, ingAn
77165
return conditions, nil
78166
}
79167

80-
func (b *defaultEnhancedBackendBuilder) buildActionViaAnnotation(_ context.Context, ingAnnotation map[string]string, svcName string) (Action, error) {
168+
// buildActionViaAnnotation will build the backend action specified via actions annotation.
169+
func (b *defaultEnhancedBackendBuilder) buildActionViaAnnotation(ctx context.Context, ingAnnotation map[string]string, svcName string) (Action, error) {
81170
action := Action{}
82171
annotationKey := fmt.Sprintf("actions.%v", svcName)
83172
exists, err := b.annotationParser.ParseJSONAnnotation(annotationKey, &action, ingAnnotation)
84173
if err != nil {
85174
return Action{}, err
86175
}
87176
if !exists {
177+
if b.tolerateNonExistentBackendAction {
178+
return b.build503ResponseAction(nonExistentBackendActionMessageBody), nil
179+
}
88180
return Action{}, errors.Errorf("missing %v configuration", annotationKey)
89181
}
90182
if err := action.validate(); err != nil {
91183
return Action{}, err
92184
}
185+
b.normalizeSimplifiedSchemaForwardAction(ctx, &action)
186+
b.normalizeServicePortForBackwardsCompatibility(ctx, &action)
187+
return action, nil
188+
}
93189

94-
// normalize forward action via TargetGroupARN.
95-
if action.Type == ActionTypeForward && action.TargetGroupARN != nil {
96-
action.ForwardConfig = &ForwardActionConfig{
190+
// buildActionViaServiceAndServicePort will build the backend Action that forward to specified Kubernetes Service.
191+
func (b *defaultEnhancedBackendBuilder) buildActionViaServiceAndServicePort(_ context.Context, svcName string, svcPort intstr.IntOrString) Action {
192+
action := Action{
193+
Type: ActionTypeForward,
194+
ForwardConfig: &ForwardActionConfig{
97195
TargetGroups: []TargetGroupTuple{
98196
{
99-
TargetGroupARN: action.TargetGroupARN,
197+
ServiceName: &svcName,
198+
ServicePort: &svcPort,
199+
},
200+
},
201+
},
202+
}
203+
return action
204+
}
205+
206+
// normalizeSimplifiedSchemaForwardAction will normalize to the advanced schema for forward action to share common processing logic.
207+
// we support a simplified schema in action annotation when configure forward to a single TargetGroup.
208+
func (b *defaultEnhancedBackendBuilder) normalizeSimplifiedSchemaForwardAction(_ context.Context, action *Action) {
209+
if action.Type == ActionTypeForward && action.TargetGroupARN != nil {
210+
*action = Action{
211+
Type: ActionTypeForward,
212+
ForwardConfig: &ForwardActionConfig{
213+
TargetGroups: []TargetGroupTuple{
214+
{
215+
TargetGroupARN: action.TargetGroupARN,
216+
},
100217
},
101218
},
102219
}
103-
action.TargetGroupARN = nil
104220
}
221+
}
105222

106-
// normalize servicePort to be int type if possible.
107-
// this is for backwards-compatibility with old AWSALBIngressController, where ServicePort is defined as Type string.
223+
// normalizeServicePortForBackwardsCompatibility will normalize servicePort to be int type if possible.
224+
// this is for backwards-compatibility with old AWSALBIngressController, where ServicePort is defined as Type string.
225+
func (b *defaultEnhancedBackendBuilder) normalizeServicePortForBackwardsCompatibility(_ context.Context, action *Action) {
108226
if action.Type == ActionTypeForward && action.ForwardConfig != nil {
109227
for _, tgt := range action.ForwardConfig.TargetGroups {
110228
if tgt.ServicePort != nil {
@@ -113,20 +231,65 @@ func (b *defaultEnhancedBackendBuilder) buildActionViaAnnotation(_ context.Conte
113231
}
114232
}
115233
}
234+
}
116235

117-
return action, nil
236+
// loadBackendServices will load referenced backend services into backendServices.
237+
// when tolerateNonExistentBackendService==true, and forward to a single non-existent Kubernetes Service, a fixed 503 response instead.
238+
func (b *defaultEnhancedBackendBuilder) loadBackendServices(ctx context.Context, action *Action, namespace string,
239+
backendServices map[types.NamespacedName]*corev1.Service) error {
240+
if action.Type == ActionTypeForward && action.ForwardConfig != nil {
241+
svcNames := sets.NewString()
242+
for _, tgt := range action.ForwardConfig.TargetGroups {
243+
if tgt.ServiceName != nil {
244+
svcNames.Insert(awssdk.StringValue(tgt.ServiceName))
245+
}
246+
}
247+
forwardToSingleSvc := (len(action.ForwardConfig.TargetGroups) == 1) && (svcNames.Len() == 1)
248+
tolerateNonExistentBackendService := b.tolerateNonExistentBackendService && forwardToSingleSvc
249+
for svcName := range svcNames {
250+
svcKey := types.NamespacedName{Namespace: namespace, Name: svcName}
251+
if _, ok := backendServices[svcKey]; ok {
252+
continue
253+
}
254+
255+
svc := &corev1.Service{}
256+
if err := b.k8sClient.Get(ctx, svcKey, svc); err != nil {
257+
if apierrors.IsNotFound(err) && tolerateNonExistentBackendService {
258+
*action = b.build503ResponseAction(nonExistentBackendServiceMessageBody)
259+
return nil
260+
}
261+
return err
262+
}
263+
backendServices[svcKey] = svc
264+
}
265+
}
266+
return nil
118267
}
119268

120-
func (b *defaultEnhancedBackendBuilder) buildActionViaServiceAndServicePort(_ context.Context, svcName string, svcPort intstr.IntOrString) Action {
269+
func (b *defaultEnhancedBackendBuilder) buildAuthConfig(ctx context.Context, action Action, namespace string, ingAnnotation map[string]string, backendServices map[types.NamespacedName]*corev1.Service) (AuthConfig, error) {
270+
svcAndIngAnnotations := ingAnnotation
271+
// when forward to a single Service, the auth annotations on that Service will be merged in.
272+
if action.Type == ActionTypeForward &&
273+
action.ForwardConfig != nil &&
274+
len(action.ForwardConfig.TargetGroups) == 1 &&
275+
action.ForwardConfig.TargetGroups[0].ServiceName != nil {
276+
svcName := awssdk.StringValue(action.ForwardConfig.TargetGroups[0].ServiceName)
277+
svcKey := types.NamespacedName{Namespace: namespace, Name: svcName}
278+
svc := backendServices[svcKey]
279+
svcAndIngAnnotations = algorithm.MergeStringMap(svc.Annotations, svcAndIngAnnotations)
280+
}
281+
282+
return b.authConfigBuilder.Build(ctx, svcAndIngAnnotations)
283+
}
284+
285+
// build503ResponseAction generates a 503 fixed response action when forward to a single non-existent Kubernetes Service.
286+
func (b *defaultEnhancedBackendBuilder) build503ResponseAction(messageBody string) Action {
121287
return Action{
122-
Type: ActionTypeForward,
123-
ForwardConfig: &ForwardActionConfig{
124-
TargetGroups: []TargetGroupTuple{
125-
{
126-
ServiceName: &svcName,
127-
ServicePort: &svcPort,
128-
},
129-
},
288+
Type: ActionTypeFixedResponse,
289+
FixedResponseConfig: &FixedResponseActionConfig{
290+
ContentType: awssdk.String("text/plain"),
291+
StatusCode: "503",
292+
MessageBody: awssdk.String(messageBody),
130293
},
131294
}
132295
}

0 commit comments

Comments
 (0)