Skip to content

Commit de3eb41

Browse files
authored
Merge pull request #423 from kubernetes-sigs/service-annotations
Support for service level annotations
2 parents c524f62 + c6b1e97 commit de3eb41

File tree

11 files changed

+300
-98
lines changed

11 files changed

+300
-98
lines changed

docs/ingress-resources.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ The host field specifies the eventual Route 53-managed domain that will route to
3636
3737
## Annotations
3838
39-
The ALB Ingress Controller is configured by Annotations on the `Ingress` resource object. Some are required and some are optional. All annotations use the namespace `alb.ingress.kubernetes.io/`.
39+
The ALB Ingress Controller is configured by Annotations on the `Ingress` and `Service` resource objects. Some are required and some are optional. All annotations use the namespace `alb.ingress.kubernetes.io/`.
4040

41-
### Required Annotations
41+
### Required Ingress Annotations
4242

4343
```
4444
alb.ingress.kubernetes.io/scheme
@@ -48,7 +48,7 @@ Required annotations are:
4848
4949
- **scheme**: Defines whether an ALB should be `internal` or `internet-facing`. See [Load balancer scheme](http://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html#load-balancer-scheme) in the AWS documentation for more details.
5050
51-
### Optional Annotations
51+
### Optional Ingress Annotations
5252
5353
```
5454
alb.ingress.kubernetes.io/load-balancer-attributes
@@ -120,3 +120,22 @@ Optional annotations are:
120120
- **ip-address-type**: The IP address type thats used to either route IPv4 traffic only or to route both IPv4 and IPv6 traffic. Can be either `dualstack` or `ipv4`. When omitted `ipv4` is used.
121121
122122
- **ssl-policy**: Defines the [Security Policy](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies) that should be assigned to the ALB, allowing you to control the protocol and ciphers.
123+
124+
### Services
125+
126+
A subset of these annotations are supported on Services. This is used to customize the Target Group created for the Service. If a Service has no annotations, the Target Group options will default to the same options configured on the Ingress.
127+
128+
#### Optional Service Annotations
129+
130+
```
131+
alb.ingress.kubernetes.io/backend-protocol
132+
alb.ingress.kubernetes.io/healthcheck-interval-seconds
133+
alb.ingress.kubernetes.io/healthcheck-path
134+
alb.ingress.kubernetes.io/healthcheck-port
135+
alb.ingress.kubernetes.io/healthcheck-protocol
136+
alb.ingress.kubernetes.io/healthcheck-timeout-seconds
137+
alb.ingress.kubernetes.io/healthy-threshold-count
138+
alb.ingress.kubernetes.io/unhealthy-threshold-count
139+
alb.ingress.kubernetes.io/success-codes
140+
alb.ingress.kubernetes.io/target-group-attributes
141+
```

pkg/alb/lb/loadbalancer.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ import (
2626
)
2727

2828
type NewDesiredLoadBalancerOptions struct {
29-
ALBNamePrefix string
30-
Namespace string
31-
IngressName string
32-
ExistingLoadBalancer *LoadBalancer
33-
Logger *log.Logger
34-
Annotations *annotations.Annotations
35-
Tags util.Tags
36-
Attributes []*elbv2.LoadBalancerAttribute
37-
IngressRules []extensions.IngressRule
38-
GetServiceNodePort func(string, int32) (*int64, error)
39-
GetNodes func() util.AWSStringSlice
29+
ALBNamePrefix string
30+
Namespace string
31+
IngressName string
32+
ExistingLoadBalancer *LoadBalancer
33+
Logger *log.Logger
34+
Annotations *annotations.Annotations
35+
AnnotationFactory annotations.AnnotationFactory
36+
IngressAnnotations *map[string]string
37+
Tags util.Tags
38+
Attributes []*elbv2.LoadBalancerAttribute
39+
IngressRules []extensions.IngressRule
40+
GetServiceNodePort func(string, int32) (*int64, error)
41+
GetServiceAnnotations func(string, string) (*map[string]string, error)
42+
GetNodes func() util.AWSStringSlice
4043
}
4144

4245
// NewDesiredLoadBalancer returns a new loadbalancer.LoadBalancer based on the opts provided.
@@ -102,13 +105,16 @@ func NewDesiredLoadBalancer(o *NewDesiredLoadBalancerOptions) (newLoadBalancer *
102105
IngressRules: o.IngressRules,
103106
LoadBalancerID: newLoadBalancer.id,
104107
ExistingTargetGroups: existingtgs,
105-
Annotations: o.Annotations,
106-
ALBNamePrefix: o.ALBNamePrefix,
107-
Namespace: o.Namespace,
108-
Tags: o.Tags,
109-
Logger: o.Logger,
110-
GetServiceNodePort: o.GetServiceNodePort,
111-
GetNodes: o.GetNodes,
108+
// Annotations: o.Annotations,
109+
IngressAnnotations: o.IngressAnnotations,
110+
ALBNamePrefix: o.ALBNamePrefix,
111+
Namespace: o.Namespace,
112+
Tags: o.Tags,
113+
Logger: o.Logger,
114+
GetServiceNodePort: o.GetServiceNodePort,
115+
GetServiceAnnotations: o.GetServiceAnnotations,
116+
AnnotationFactory: o.AnnotationFactory,
117+
GetNodes: o.GetNodes,
112118
})
113119

114120
if err != nil {

pkg/alb/tg/targetgroups.go

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,18 @@ func NewCurrentTargetGroups(o *NewCurrentTargetGroupsOptions) (TargetGroups, err
121121
}
122122

123123
type NewDesiredTargetGroupsOptions struct {
124-
IngressRules []extensions.IngressRule
125-
LoadBalancerID string
126-
ExistingTargetGroups TargetGroups
127-
Annotations *annotations.Annotations
128-
ALBNamePrefix string
129-
Namespace string
130-
Tags util.Tags
131-
Logger *log.Logger
132-
GetServiceNodePort func(string, int32) (*int64, error)
133-
GetNodes func() util.AWSStringSlice
124+
IngressRules []extensions.IngressRule
125+
LoadBalancerID string
126+
ExistingTargetGroups TargetGroups
127+
AnnotationFactory annotations.AnnotationFactory
128+
IngressAnnotations *map[string]string
129+
ALBNamePrefix string
130+
Namespace string
131+
Tags util.Tags
132+
Logger *log.Logger
133+
GetServiceNodePort func(string, int32) (*int64, error)
134+
GetServiceAnnotations func(string, string) (*map[string]string, error)
135+
GetNodes func() util.AWSStringSlice
134136
}
135137

136138
// NewDesiredTargetGroups returns a new targetgroups.TargetGroups based on an extensions.Ingress.
@@ -146,9 +148,20 @@ func NewDesiredTargetGroups(o *NewDesiredTargetGroupsOptions) (TargetGroups, err
146148
return nil, err
147149
}
148150

151+
tgAnnotations, err := mergeAnnotations(&mergeAnnotationsOptions{
152+
AnnotationFactory: o.AnnotationFactory,
153+
IngressAnnotations: o.IngressAnnotations,
154+
Namespace: o.Namespace,
155+
ServiceName: path.Backend.ServiceName,
156+
GetServiceAnnotations: o.GetServiceAnnotations,
157+
})
158+
if err != nil {
159+
return output, err
160+
}
161+
149162
// Start with a new target group with a new Desired state.
150163
targetGroup := NewDesiredTargetGroup(&NewDesiredTargetGroupOptions{
151-
Annotations: o.Annotations,
164+
Annotations: tgAnnotations,
152165
Tags: o.Tags,
153166
ALBNamePrefix: o.ALBNamePrefix,
154167
LoadBalancerID: o.LoadBalancerID,
@@ -187,3 +200,39 @@ func NewDesiredTargetGroups(o *NewDesiredTargetGroupsOptions) (TargetGroups, err
187200
}
188201
return output, nil
189202
}
203+
204+
type mergeAnnotationsOptions struct {
205+
AnnotationFactory annotations.AnnotationFactory
206+
IngressAnnotations *map[string]string
207+
Namespace string
208+
ServiceName string
209+
GetServiceAnnotations func(string, string) (*map[string]string, error)
210+
}
211+
212+
func mergeAnnotations(o *mergeAnnotationsOptions) (*annotations.Annotations, error) {
213+
serviceAnnotations, err := o.GetServiceAnnotations(o.Namespace, o.ServiceName)
214+
if err != nil {
215+
return nil, err
216+
}
217+
218+
mergedAnnotations := make(map[string]string)
219+
for k, v := range *o.IngressAnnotations {
220+
mergedAnnotations[k] = v
221+
}
222+
223+
for k, v := range *serviceAnnotations {
224+
mergedAnnotations[k] = v
225+
}
226+
227+
tgAnnotations, err := o.AnnotationFactory.ParseAnnotations(&annotations.ParseAnnotationsOptions{
228+
Annotations: mergedAnnotations,
229+
Namespace: o.Namespace,
230+
ServiceName: o.ServiceName,
231+
})
232+
233+
if err != nil {
234+
msg := fmt.Errorf("Error parsing service annotations: %s", err.Error())
235+
return nil, msg
236+
}
237+
return tgAnnotations, nil
238+
}

pkg/alb/tg/targetgroups_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package tg
2+
3+
import (
4+
"testing"
5+
6+
"github.com/aws/aws-sdk-go/aws"
7+
"github.com/kubernetes-sigs/aws-alb-ingress-controller/pkg/annotations"
8+
)
9+
10+
func TestMergeAnnotations(t *testing.T) {
11+
var tests = []struct {
12+
ingressAnnotations map[string]string
13+
serviceAnnotations map[string]string
14+
expected annotations.Annotations
15+
pass bool
16+
}{
17+
{
18+
map[string]string{
19+
"alb.ingress.kubernetes.io/subnets": "subnet-abcdfg",
20+
"alb.ingress.kubernetes.io/scheme": "internal",
21+
"alb.ingress.kubernetes.io/healthcheck-path": "/ingressPath",
22+
},
23+
map[string]string{
24+
"alb.ingress.kubernetes.io/healthcheck-path": "/servicePath",
25+
},
26+
annotations.Annotations{
27+
HealthcheckPath: aws.String("/servicePath"),
28+
},
29+
true,
30+
},
31+
{
32+
map[string]string{
33+
"alb.ingress.kubernetes.io/subnets": "subnet-abcdfg",
34+
"alb.ingress.kubernetes.io/scheme": "internal",
35+
"alb.ingress.kubernetes.io/healthcheck-path": "/ingressPath",
36+
},
37+
map[string]string{},
38+
annotations.Annotations{
39+
HealthcheckPath: aws.String("/ingressPath"),
40+
},
41+
true,
42+
},
43+
{
44+
map[string]string{
45+
"alb.ingress.kubernetes.io/subnets": "subnet-abcdfg",
46+
"alb.ingress.kubernetes.io/scheme": "internal",
47+
"alb.ingress.kubernetes.io/healthcheck-path": "/ingressPath",
48+
},
49+
map[string]string{},
50+
annotations.Annotations{
51+
HealthcheckPath: aws.String("/"),
52+
},
53+
false,
54+
},
55+
}
56+
vf := annotations.NewValidatingAnnotationFactory(&annotations.NewValidatingAnnotationFactoryOptions{
57+
Validator: annotations.FakeValidator{VpcId: "vpc-1"},
58+
ClusterName: "clusterName"})
59+
60+
for i, tt := range tests {
61+
a, err := mergeAnnotations(&mergeAnnotationsOptions{
62+
AnnotationFactory: vf,
63+
IngressAnnotations: &tt.ingressAnnotations,
64+
GetServiceAnnotations: func(string, string) (*map[string]string, error) {
65+
return &tt.serviceAnnotations, nil
66+
},
67+
})
68+
69+
if err != nil && tt.pass {
70+
t.Errorf("mergeAnnotations(%v): got %v expected %v, errored: %v", i, *a.HealthcheckPath, *tt.expected.HealthcheckPath, err)
71+
}
72+
73+
if err == nil && tt.pass && *tt.expected.HealthcheckPath != *a.HealthcheckPath {
74+
t.Errorf("mergeAnnotations(%v): expected %v, actual %v", i, *tt.expected.HealthcheckPath, *a.HealthcheckPath)
75+
}
76+
77+
if err == nil && !tt.pass && *tt.expected.HealthcheckPath == *a.HealthcheckPath {
78+
t.Errorf("mergeAnnotations(%v): not expected %v, actual %v", i, *tt.expected.HealthcheckPath, *a.HealthcheckPath)
79+
}
80+
}
81+
}

pkg/albingress/albingress.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ type NewALBIngressFromIngressOptions struct {
5353
ClusterName string
5454
ALBNamePrefix string
5555
GetServiceNodePort func(string, int32) (*int64, error)
56+
GetServiceAnnotations func(string, string) (*map[string]string, error)
5657
GetNodes func() util.AWSStringSlice
5758
Recorder record.EventRecorder
5859
ConnectionIdleTimeout *int64
60+
AnnotationFactory annotations.AnnotationFactory
5961
}
6062

6163
// NewALBIngressFromIngress builds ALBIngress's based off of an Ingress object
6264
// https://godoc.org/k8s.io/kubernetes/pkg/apis/extensions#Ingress. Creates a new ingress object,
6365
// and looks up to see if a previous ingress object with the same id is known to the ALBController.
6466
// If there is an issue and the ingress is invalid, nil is returned.
65-
func NewALBIngressFromIngress(o *NewALBIngressFromIngressOptions, annotationFactory annotations.AnnotationFactory) *ALBIngress {
67+
func NewALBIngressFromIngress(o *NewALBIngressFromIngressOptions) *ALBIngress {
6668
var err error
6769

6870
// Create newIngress ALBIngress object holding the resource details and some cluster information.
@@ -90,7 +92,11 @@ func NewALBIngressFromIngress(o *NewALBIngressFromIngressOptions, annotationFact
9092
}
9193

9294
// Load up the ingress with our current annotations.
93-
newIngress.annotations, err = annotationFactory.ParseAnnotations(o.Ingress)
95+
newIngress.annotations, err = o.AnnotationFactory.ParseAnnotations(&annotations.ParseAnnotationsOptions{
96+
Annotations: o.Ingress.Annotations,
97+
Namespace: o.Ingress.Namespace,
98+
IngressName: o.Ingress.Name,
99+
})
94100
if err != nil {
95101
msg := fmt.Sprintf("Error parsing annotations: %s", err.Error())
96102
newIngress.reconciled = false
@@ -110,16 +116,19 @@ func NewALBIngressFromIngress(o *NewALBIngressFromIngressOptions, annotationFact
110116

111117
// Assemble the load balancer
112118
newIngress.loadBalancer, err = lb.NewDesiredLoadBalancer(&lb.NewDesiredLoadBalancerOptions{
113-
ALBNamePrefix: o.ALBNamePrefix,
114-
Namespace: o.Ingress.GetNamespace(),
115-
ExistingLoadBalancer: newIngress.loadBalancer,
116-
IngressName: o.Ingress.Name,
117-
IngressRules: o.Ingress.Spec.Rules,
118-
Logger: newIngress.logger,
119-
Annotations: newIngress.annotations,
120-
Tags: newIngress.Tags(o.ClusterName),
121-
GetServiceNodePort: o.GetServiceNodePort,
122-
GetNodes: o.GetNodes,
119+
ALBNamePrefix: o.ALBNamePrefix,
120+
Namespace: o.Ingress.GetNamespace(),
121+
ExistingLoadBalancer: newIngress.loadBalancer,
122+
IngressName: o.Ingress.Name,
123+
IngressRules: o.Ingress.Spec.Rules,
124+
Logger: newIngress.logger,
125+
Annotations: newIngress.annotations,
126+
IngressAnnotations: &o.Ingress.Annotations,
127+
Tags: newIngress.Tags(o.ClusterName),
128+
GetServiceNodePort: o.GetServiceNodePort,
129+
GetServiceAnnotations: o.GetServiceAnnotations,
130+
GetNodes: o.GetNodes,
131+
AnnotationFactory: o.AnnotationFactory,
123132
})
124133

125134
if err != nil {
@@ -294,10 +303,6 @@ func (a *ALBIngress) Tags(clusterName string) []*elbv2.Tag {
294303
return tags
295304
}
296305

297-
type ReconcileOptions struct {
298-
Eventf func(string, string, string, ...interface{})
299-
}
300-
301306
// id returns an ingress id based off of a namespace and name
302307
func GenerateID(namespace, name string) string {
303308
return fmt.Sprintf("%s/%s", namespace, name)

pkg/albingress/albingress_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,24 @@ func TestNewALBIngressFromIngress(t *testing.T) {
6666
nodePort := int64(8000)
6767
return &nodePort, nil
6868
},
69+
GetServiceAnnotations: func(namespace string, serviceName string) (*map[string]string, error) {
70+
a := make(map[string]string)
71+
return &a, nil
72+
},
6973
GetNodes: func() types.AWSStringSlice {
7074
instance1 := "i-1"
7175
instance2 := "i-2"
7276
return types.AWSStringSlice{&instance1, &instance2}
7377
},
7478
ClusterName: "testCluster",
7579
ALBNamePrefix: "albNamePrefix",
80+
AnnotationFactory: annotations.NewValidatingAnnotationFactory(&annotations.NewValidatingAnnotationFactoryOptions{
81+
Validator: annotations.FakeValidator{VpcId: "vpc-1"},
82+
ClusterName: "testCluster",
83+
},
84+
),
7685
}
77-
ingress := NewALBIngressFromIngress(
78-
options,
79-
annotations.NewValidatingAnnotationFactory(annotations.FakeValidator{VpcId: "vpc-1"}, "testCluster"),
80-
)
86+
ingress := NewALBIngressFromIngress(options)
8187
if ingress == nil {
8288
t.Errorf("NewALBIngressFromIngress returned nil")
8389
}

0 commit comments

Comments
 (0)