Skip to content

Commit abb6776

Browse files
authored
Merge pull request #449 from kubernetes-sigs/pod-targets
Support routing directly to pods
2 parents 47faafa + 83418f9 commit abb6776

23 files changed

+437
-261
lines changed

docs/ingress-resources.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ alb.ingress.kubernetes.io/healthcheck-timeout-seconds
6262
alb.ingress.kubernetes.io/healthy-threshold-count
6363
alb.ingress.kubernetes.io/unhealthy-threshold-count
6464
alb.ingress.kubernetes.io/listen-ports
65+
alb.ingress.kubernetes.io/target-type
6566
alb.ingress.kubernetes.io/security-groups
6667
alb.ingress.kubernetes.io/subnets
6768
alb.ingress.kubernetes.io/success-codes
@@ -96,6 +97,8 @@ Optional annotations are:
9697
9798
- **listen-ports**: Defines the ports the ALB will expose. It defaults to `[{"HTTP": 80}]` unless a certificate ARN is defined, then it is `[{"HTTPS": 443}]`. Uses a format as follows '[{"HTTP":8080,"HTTPS": 443}]'.
9899
100+
- **target-type**: Defines if the EC2 instance ID or the pod IP are used in the managed Target Groups. Defaults to `instance`. Valid options are `instance` and `pod`. With `instance` the Target Group targets are `<ec2 instance id>:<node port>`, for `pod` the targets are `<pod ip>:<pod port>`. When using the pod IP, it will route from all availabilty zones. `pod` is to be used when the pod network is routable and can be reached by the ALB.
101+
99102
- **security-groups**: [Security groups](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_SecurityGroups.html) that should be applied to the ALB instance. These can be referenced by security group IDs or the name tag associated with each security group. Example ID values are `sg-723a380a,sg-a6181ede,sg-a5181edd`. Example tag values are `appSG, webSG`. When the annotation is not present, the controller will create a security group with appropriate ports allowing access to `0.0.0.0/0` and attached to the ALB. It will also create a security group for instances that allows all TCP traffic when the source is the security group created for the ALB.
100103
101104
- **subnets**: The subnets where the ALB instance should be deployed. Must include 2 subnets, each in a different [availability zone](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html). These can be referenced by subnet IDs or the name tag associated with the subnet. Example values for subnet IDs are `subnet-a4f0098e,subnet-457ed533,subnet-95c904cd`. Example values for name tags are: `webSubnet,appSubnet`. If subnets are not specified the ALB controller will attempt to detect qualified subnets. This qualification is done by locating subnets that match the following criteria.
@@ -133,6 +136,7 @@ alb.ingress.kubernetes.io/healthcheck-protocol
133136
alb.ingress.kubernetes.io/healthcheck-timeout-seconds
134137
alb.ingress.kubernetes.io/healthy-threshold-count
135138
alb.ingress.kubernetes.io/unhealthy-threshold-count
139+
alb.ingress.kubernetes.io/target-type
136140
alb.ingress.kubernetes.io/success-codes
137141
alb.ingress.kubernetes.io/target-group-attributes
138142
```

examples/2048/2048-ingress.yaml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
apiVersion: extensions/v1beta1
22
kind: Ingress
33
metadata:
4-
name: "nginx-ingress"
4+
name: "2048-ingress"
55
namespace: "2048-game"
66
annotations:
77
kubernetes.io/ingress.class: alb
88
alb.ingress.kubernetes.io/scheme: internal
9-
alb.ingress.kubernetes.io/subnets: subnet-1234
10-
alb.ingress.kubernetes.io/security-groups: sg-1234
9+
alb.ingress.kubernetes.io/target-type: pod
1110
labels:
12-
app: 2048-nginx-ingress
11+
app: 2048-ingress
1312
spec:
1413
rules:
15-
- host: 2048.example.com
16-
http:
14+
- http:
1715
paths:
1816
- path: /
1917
backend:
2018
serviceName: "service-2048"
21-
servicePort: 80
19+
servicePort: 80

examples/alb-ingress-controller.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,5 @@ spec:
9494
restartPolicy: Always
9595
securityContext: {}
9696
terminationGracePeriodSeconds: 30
97+
serviceAccountName: alb-ingress
98+
serviceAccount: alb-ingress

pkg/alb/lb/loadbalancer.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type NewDesiredLoadBalancerOptions struct {
4040
IngressRules []extensions.IngressRule
4141
GetServiceNodePort func(string, int32) (*int64, error)
4242
GetServiceAnnotations func(string, string) (*map[string]string, error)
43-
GetNodes func() util.AWSStringSlice
43+
TargetsFunc func(*string, string, string, *int64) albelbv2.TargetDescriptions
4444
}
4545

4646
// NewDesiredLoadBalancer returns a new loadbalancer.LoadBalancer based on the opts provided.
@@ -125,7 +125,7 @@ func NewDesiredLoadBalancer(o *NewDesiredLoadBalancerOptions) (newLoadBalancer *
125125
GetServiceNodePort: o.GetServiceNodePort,
126126
GetServiceAnnotations: o.GetServiceAnnotations,
127127
AnnotationFactory: o.AnnotationFactory,
128-
GetNodes: o.GetNodes,
128+
TargetsFunc: o.TargetsFunc,
129129
})
130130

131131
if err != nil {
@@ -176,15 +176,6 @@ type NewCurrentLoadBalancerOptions struct {
176176

177177
// NewCurrentLoadBalancer returns a new loadbalancer.LoadBalancer based on an elbv2.LoadBalancer.
178178
func NewCurrentLoadBalancer(o *NewCurrentLoadBalancerOptions) (newLoadBalancer *LoadBalancer, err error) {
179-
lbTags := o.ResourceTags.LoadBalancers[*o.LoadBalancer.LoadBalancerArn]
180-
181-
namespace, ingressName, err := tagsFromLB(lbTags)
182-
if err != nil {
183-
return nil, fmt.Errorf("The LoadBalancer %s does not have the proper tags, can't import: %s", *o.LoadBalancer.LoadBalancerName, err.Error())
184-
}
185-
186-
name := createLBName(namespace, ingressName, o.ALBNamePrefix)
187-
188179
attrs, err := albelbv2.ELBV2svc.DescribeLoadBalancerAttributesFiltered(o.LoadBalancer.LoadBalancerArn)
189180
if err != nil {
190181
return newLoadBalancer, fmt.Errorf("Failed to retrieve attributes from ELBV2 in AWS. Error: %s", err.Error())
@@ -239,8 +230,8 @@ func NewCurrentLoadBalancer(o *NewCurrentLoadBalancerOptions) (newLoadBalancer *
239230
}
240231

241232
newLoadBalancer = &LoadBalancer{
242-
id: name,
243-
tags: tags{current: lbTags},
233+
id: *o.LoadBalancer.LoadBalancerName,
234+
tags: tags{current: o.ResourceTags.LoadBalancers[*o.LoadBalancer.LoadBalancerArn]},
244235
lb: lb{current: o.LoadBalancer},
245236
logger: o.Logger,
246237
attributes: attributes{current: attrs},
@@ -335,6 +326,7 @@ func (l *LoadBalancer) Reconcile(rOpts *ReconcileOptions) []error {
335326
IgnoreDeletes: true,
336327
}
337328

329+
// Creates target groups
338330
tgs, err := l.targetgroups.Reconcile(tgsOpts)
339331
if err != nil {
340332
errors = append(errors, err)
@@ -353,12 +345,13 @@ func (l *LoadBalancer) Reconcile(rOpts *ReconcileOptions) []error {
353345
l.listeners = ltnrs
354346
}
355347

356-
// Decide: Is this still needed?
348+
// Does not consider TG used for listener default action
357349
for _, listener := range l.listeners {
358350
unusedTGs := listener.GetRules().FindUnusedTGs(l.targetgroups)
359351
unusedTGs.StripDesiredState()
360352
}
361353

354+
// removes target groups
362355
tgsOpts.IgnoreDeletes = false
363356
tgs, err = l.targetgroups.Reconcile(tgsOpts)
364357
if err != nil {

pkg/alb/ls/listener.go

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ func NewCurrentListener(o *NewCurrentListenerOptions) (*Listener, error) {
110110
// results in no action, the creation, the deletion, or the modification of an AWS listener to
111111
// satisfy the ingress's current state.
112112
func (l *Listener) Reconcile(rOpts *ReconcileOptions) error {
113+
// If there is a desired listener, set some of the ARNs which are not available when we assemble the desired state
114+
if l.ls.desired != nil {
115+
l.ls.desired.LoadBalancerArn = rOpts.LoadBalancerArn
116+
117+
// Set the listener default action to the targetgroup from the default rule.
118+
// Not good
119+
if rOpts != nil {
120+
defaultRule := l.rules.DefaultRule()
121+
if defaultRule != nil {
122+
l.ls.desired.DefaultActions[0].TargetGroupArn = defaultRule.TargetGroupArn(rOpts.TargetGroups)
123+
}
124+
}
125+
}
126+
113127
switch {
114128
case l.ls.desired == nil: // listener should be deleted
115129
if l.ls.current == nil {
@@ -156,14 +170,6 @@ func (l *Listener) Reconcile(rOpts *ReconcileOptions) error {
156170

157171
// Adds a Listener to an existing ALB in AWS. This Listener maps the ALB to an existing TargetGroup.
158172
func (l *Listener) create(rOpts *ReconcileOptions) error {
159-
l.ls.desired.LoadBalancerArn = rOpts.LoadBalancerArn
160-
161-
// Set the listener default action to the targetgroup from the default rule.
162-
defaultRule := l.rules.DefaultRule()
163-
if defaultRule != nil {
164-
l.ls.desired.DefaultActions[0].TargetGroupArn = defaultRule.TargetGroupArn(rOpts.TargetGroups)
165-
}
166-
167173
// Attempt listener creation.
168174
desired := l.ls.desired
169175
in := &elbv2.CreateListenerInput{
@@ -172,12 +178,7 @@ func (l *Listener) create(rOpts *ReconcileOptions) error {
172178
Protocol: desired.Protocol,
173179
Port: desired.Port,
174180
SslPolicy: desired.SslPolicy,
175-
DefaultActions: []*elbv2.Action{
176-
{
177-
Type: desired.DefaultActions[0].Type,
178-
TargetGroupArn: desired.DefaultActions[0].TargetGroupArn,
179-
},
180-
},
181+
DefaultActions: desired.DefaultActions,
181182
}
182183
o, err := albelbv2.ELBV2svc.CreateListener(in)
183184
if err != nil {
@@ -191,11 +192,6 @@ func (l *Listener) create(rOpts *ReconcileOptions) error {
191192

192193
// Modifies a listener
193194
func (l *Listener) modify(rOpts *ReconcileOptions) error {
194-
if l.ls.current == nil {
195-
// not a modify, a create
196-
return l.create(rOpts)
197-
}
198-
199195
desired := l.ls.desired
200196
in := &elbv2.ModifyListenerInput{
201197
ListenerArn: l.ls.current.ListenerArn,
@@ -233,14 +229,6 @@ func (l *Listener) needsModification(rOpts *ReconcileOptions) bool {
233229
lsc := l.ls.current
234230
lsd := l.ls.desired
235231

236-
// Set the listener default action to the targetgroup from the default rule.
237-
if rOpts != nil {
238-
defaultRule := l.rules.DefaultRule()
239-
if defaultRule != nil {
240-
lsd.DefaultActions[0].TargetGroupArn = defaultRule.TargetGroupArn(rOpts.TargetGroups)
241-
}
242-
}
243-
244232
switch {
245233
case lsc == nil && lsd == nil:
246234
return false
@@ -281,3 +269,7 @@ func (l *Listener) stripCurrentState() {
281269
func (l *Listener) GetRules() rs.Rules {
282270
return l.rules
283271
}
272+
273+
func (l *Listener) DefaultActionArn() *string {
274+
return l.ls.current.DefaultActions[0].TargetGroupArn
275+
}

pkg/alb/rs/rule.go

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type NewDesiredRuleOptions struct {
2121
IgnoreHostHeader bool
2222
Path string
2323
SvcName string
24+
SvcPort int32
2425
Logger *log.Logger
2526
}
2627

@@ -60,31 +61,39 @@ func NewDesiredRule(o *NewDesiredRuleOptions) *Rule {
6061
}
6162

6263
return &Rule{
63-
svcname: svcname{desired: o.SvcName},
64-
rs: rs{desired: r},
65-
logger: o.Logger,
64+
svc: svc{desired: service{name: o.SvcName, port: o.SvcPort}},
65+
rs: rs{desired: r},
66+
logger: o.Logger,
6667
}
6768
}
6869

6970
type NewCurrentRuleOptions struct {
7071
SvcName string
72+
SvcPort int32
7173
Rule *elbv2.Rule
7274
Logger *log.Logger
7375
}
7476

7577
// NewCurrentRule creates a Rule from an elbv2.Rule
7678
func NewCurrentRule(o *NewCurrentRuleOptions) *Rule {
7779
return &Rule{
78-
svcname: svcname{current: o.SvcName},
79-
rs: rs{current: o.Rule},
80-
logger: o.Logger,
80+
svc: svc{current: service{name: o.SvcName, port: o.SvcPort}},
81+
rs: rs{current: o.Rule},
82+
logger: o.Logger,
8183
}
8284
}
8385

8486
// Reconcile compares the current and desired state of this Rule instance. Comparison
8587
// results in no action, the creation, the deletion, or the modification of an AWS Rule to
8688
// satisfy the ingress's current state.
8789
func (r *Rule) Reconcile(rOpts *ReconcileOptions) error {
90+
// If there is a desired rule, set some of the ARNs which are not available when we assemble the desired state
91+
if r.rs.desired != nil {
92+
for i := range r.rs.desired.Actions {
93+
r.rs.desired.Actions[i].TargetGroupArn = r.TargetGroupArn(rOpts.TargetGroups)
94+
}
95+
}
96+
8897
switch {
8998
case r.rs.desired == nil: // rule should be deleted
9099
if r.rs.current == nil {
@@ -102,8 +111,10 @@ func (r *Rule) Reconcile(rOpts *ReconcileOptions) error {
102111
log.Prettify(r.rs.current.Priority),
103112
log.Prettify(r.rs.current.Conditions))
104113

105-
case *r.rs.desired.IsDefault: // rule is default (attached to listener), do nothing
106-
// r.logger.Debugf("Found desired rule that is a default and is already created with its respective listener. Rule: %s", log.Prettify(r.rs.desired))
114+
case *r.rs.desired.IsDefault:
115+
// rule is default (attached to listener), do nothing
116+
// Seems to happen automatically, if we try to change it we get an error:
117+
// OperationNotPermitted: Default rule '<arn>' cannot be modified
107118
r.rs.current = r.rs.desired
108119

109120
case r.rs.current == nil: // rule doesn't exist and should be created
@@ -121,23 +132,20 @@ func (r *Rule) Reconcile(rOpts *ReconcileOptions) error {
121132
return err
122133
}
123134
rOpts.Eventf(api.EventTypeNormal, "MODIFY", "%s rule modified", *r.rs.current.Priority)
124-
125-
default:
126-
// r.logger.Debugf("No rule modification required.")
127135
}
128136

129137
return nil
130138
}
131139

132140
func (r *Rule) TargetGroupArn(tgs tg.TargetGroups) *string {
133-
i := tgs.LookupBySvc(r.svcname.desired)
141+
i := tgs.LookupBySvc(r.svc.desired.name, r.svc.desired.port)
134142
if i < 0 {
135-
r.logger.Errorf("Failed to locate TargetGroup related to this service: %s", r.svcname.desired)
143+
r.logger.Errorf("Failed to locate TargetGroup related to this service: %s:%d", r.svc.desired.name, r.svc.desired.port)
136144
return nil
137145
}
138146
arn := tgs[i].CurrentARN()
139147
if arn == nil {
140-
r.logger.Errorf("Located TargetGroup but no known (current) state found: %s", r.svcname.desired)
148+
r.logger.Errorf("Located TargetGroup but no known (current) state found: %s:%d", r.svc.desired.name, r.svc.desired.port)
141149
}
142150
return arn
143151
}
@@ -150,15 +158,13 @@ func (r *Rule) create(rOpts *ReconcileOptions) error {
150158
Priority: priority(r.rs.desired.Priority),
151159
}
152160

153-
in.Actions[0].TargetGroupArn = r.TargetGroupArn(rOpts.TargetGroups)
154-
155161
o, err := albelbv2.ELBV2svc.CreateRule(in)
156162
if err != nil {
157163
rOpts.Eventf(api.EventTypeWarning, "ERROR", "Error creating %v rule: %s", *in.Priority, err.Error())
158164
return fmt.Errorf("Failed Rule creation. Rule: %s | Error: %s", log.Prettify(r.rs.desired), err.Error())
159165
}
160166
r.rs.current = o.Rules[0]
161-
r.svcname.current = r.svcname.desired
167+
r.svc.current = r.svc.desired
162168

163169
return nil
164170
}
@@ -169,7 +175,6 @@ func (r *Rule) modify(rOpts *ReconcileOptions) error {
169175
Conditions: r.rs.desired.Conditions,
170176
RuleArn: r.rs.current.RuleArn,
171177
}
172-
in.Actions[0].TargetGroupArn = r.TargetGroupArn(rOpts.TargetGroups)
173178

174179
o, err := albelbv2.ELBV2svc.ModifyRule(in)
175180
if err != nil {
@@ -180,7 +185,7 @@ func (r *Rule) modify(rOpts *ReconcileOptions) error {
180185
if len(o.Rules) > 0 {
181186
r.rs.current = o.Rules[0]
182187
}
183-
r.svcname.current = r.svcname.desired
188+
r.svc.current = r.svc.desired
184189

185190
return nil
186191
}
@@ -217,12 +222,17 @@ func (r *Rule) needsModification() bool {
217222
case crs == nil:
218223
r.logger.Debugf("Current is nil")
219224
return true
220-
// TODO: We need to sort these because they're causing false positives
225+
case !util.DeepEqual(crs.Actions, drs.Actions):
226+
r.logger.Debugf("Actions needs to be changed (%v != %v)", log.Prettify(crs.Actions), log.Prettify(drs.Actions))
227+
return true
221228
case !conditionsEqual(crs.Conditions, drs.Conditions):
222229
r.logger.Debugf("Conditions needs to be changed (%v != %v)", log.Prettify(crs.Conditions), log.Prettify(drs.Conditions))
223230
return true
224-
case r.svcname.current != r.svcname.desired:
225-
r.logger.Debugf("SvcName needs to be changed (%v != %v)", r.svcname.current, r.svcname.desired)
231+
case r.svc.current.name != r.svc.desired.name:
232+
r.logger.Debugf("SvcName needs to be changed (%v != %v)", r.svc.current.name, r.svc.desired.name)
233+
return true
234+
case r.svc.current.port != r.svc.desired.port && r.svc.current.port != 0: // Check against 0 because that is the default for legacy tags
235+
r.logger.Debugf("SvcPort needs to be changed (%v != %v)", r.svc.current.port, r.svc.desired.port)
226236
return true
227237
}
228238

0 commit comments

Comments
 (0)