Skip to content
This repository was archived by the owner on Sep 19, 2018. It is now read-only.

Commit fd7e734

Browse files
authored
Merge pull request #10 from utilitywarehouse/multi-target
Enable setting multiple targets and specifying them via a label
2 parents 4e3ed81 + 532cf64 commit fd7e734

File tree

5 files changed

+254
-138
lines changed

5 files changed

+254
-138
lines changed

README.md

+15-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
ingress53 is a service designed to run in kubernetes and maintain DNS records for the cluster's ingress resources in AWS Route53.
44

5-
It will watch the kubernetes API (using the service token) for any Ingress resource changes and try to apply those records to route53 in Amazon, mapping the record to the "target name", which is the dns name of the ingress endpoint for your cluster.
5+
It will watch the kubernetes API (using the service token) for any Ingress resource changes and try to apply those records to route53 in Amazon, mapping the record to the "target", which is the dns name of the ingress endpoint for your cluster.
66

77
# Requirements
88

@@ -42,15 +42,15 @@ The minimum AWS policy you can use:
4242

4343
# Usage
4444

45-
ingress53 is slightly opinionated in that it assumes there are two kinds of ingress endpoints: public and private. A kubernetes selector is used to select public ingresses, while all others default to being private.
45+
A kubernetes selector is used to specify the target (entry point of the cluster).
4646

4747
You will need to create a dns record that points to your ingress endpoint[s]. We will use this to CNAME all ingress resource entries to that "target".
4848

4949
Your set up might look like this:
5050

5151
- A ingress controller (nginx/traefik) kubernetes service running on a nodePort (:8080)
5252
- ELB that serves all worker nodes on :8080
53-
- A CNAME for the elb `private.example.com` > `my-loadbalancer-1234567890.us-west-2.elb.amazonaws.com`
53+
- A CNAME for the elb `private.cluster-entrypoint.com` > `my-loadbalancer-1234567890.us-west-2.elb.amazonaws.com`
5454
- ingress53 service running inside the cluster
5555

5656
Now, if you were to create an ingress kubernetes resource:
@@ -60,6 +60,8 @@ apiVersion: extensions/v1beta1
6060
kind: Ingress
6161
metadata:
6262
name: my-app
63+
labels:
64+
ingress53.target: private.cluster-entrypoint.com
6365
spec:
6466
rules:
6567
- host: my-app.example.com
@@ -71,15 +73,17 @@ spec:
7173
servicePort: 80
7274
```
7375
74-
ingress53 will create a CNAME record in route53: `my-app.example.com` > `private.example.com`
76+
ingress53 will create a CNAME record in route53: `my-app.example.com` > `private.cluster-entrypoint.com`
7577

7678
You can test it locally (please refer to the command line help for more options):
7779

7880
```sh
7981
./ingress53 \
8082
-route53-zone-id=XXXXXXXXXXXXXX \
81-
-target-private=private.example.com \
82-
-target-public=public.example.com \
83+
-label-name=ingress53.target \
84+
-target=private.cluster-entrypoint.com \
85+
-target=public.cluster-entrypoint.com \
86+
-default-target=private.cluster-entrypoint.com \
8387
-kubernetes-config=$HOME/.kube/config \
8488
-dry-run
8589
```
@@ -129,10 +133,11 @@ spec:
129133
- name: ingress53
130134
image: utilitywarehouse/ingress53:v1.0.0
131135
args:
132-
- -route53-zone-id=XXXXXX
133-
- -target-private=private.example.com
134-
- -target-public=public.example.com
135-
- -public-ingress-selector=ingress-tag-name:ingress-tag-value
136+
- -route53-zone-id=XXXXXXXXXXXXXX
137+
- -label-name=ingress53.target
138+
- -target=private.cluster-entrypoint.com
139+
- -target=public.cluster-entrypoint.com
140+
- -default-target=private.cluster-entrypoint.com
136141
resources:
137142
requests:
138143
cpu: 10m

ingress_test.go

+65-31
Original file line numberDiff line numberDiff line change
@@ -15,87 +15,121 @@ import (
1515
)
1616

1717
var (
18-
testIngressA = &v1beta1.Ingress{
18+
privateIngressHostsAB = &v1beta1.Ingress{
1919
ObjectMeta: v1.ObjectMeta{
20-
Name: "exampleA",
20+
Name: "privateIngressHostsAB",
2121
Namespace: api.NamespaceDefault,
22-
Labels: map[string]string{},
22+
Labels: map[string]string{
23+
LabelName: PrivateTarget,
24+
},
2325
},
2426
Spec: v1beta1.IngressSpec{
2527
Rules: []v1beta1.IngressRule{
26-
{Host: "foo1.example.com"},
27-
{Host: "foo2.example.com"},
28+
{Host: "a.example.com"},
29+
{Host: "b.example.com"},
2830
},
2931
},
3032
}
3133

32-
testIngressB = &v1beta1.Ingress{
34+
publicIngressHostC = &v1beta1.Ingress{
3335
ObjectMeta: v1.ObjectMeta{
34-
Name: "exampleB",
36+
Name: "publicIngressHostCD",
3537
Namespace: api.NamespaceDefault,
3638
Labels: map[string]string{
37-
"public": "true",
39+
LabelName: PublicTarget,
3840
},
3941
},
4042
Spec: v1beta1.IngressSpec{
4143
Rules: []v1beta1.IngressRule{
42-
{Host: "bar.example.com"},
44+
{Host: "c.example.com"},
4345
},
4446
},
4547
}
4648

47-
testIngressB2 = &v1beta1.Ingress{
49+
publicIngressHostD = &v1beta1.Ingress{
4850
ObjectMeta: v1.ObjectMeta{
49-
Name: "exampleB",
51+
Name: "publicIngressHostCD",
5052
Namespace: api.NamespaceDefault,
5153
Labels: map[string]string{
52-
"public": "true",
54+
LabelName: PublicTarget,
5355
},
5456
},
5557
Spec: v1beta1.IngressSpec{
5658
Rules: []v1beta1.IngressRule{
57-
{Host: "bar2.example.com"},
59+
{Host: "d.example.com"},
5860
},
5961
},
6062
}
6163

62-
testIngressC1 = &v1beta1.Ingress{
64+
privateIngressHostE = &v1beta1.Ingress{
6365
ObjectMeta: v1.ObjectMeta{
64-
Name: "exampleC1",
66+
Name: "ingressHostE",
6567
Namespace: api.NamespaceDefault,
66-
Labels: map[string]string{},
68+
Labels: map[string]string{
69+
LabelName: PrivateTarget,
70+
},
71+
},
72+
Spec: v1beta1.IngressSpec{
73+
Rules: []v1beta1.IngressRule{
74+
{Host: "e.example.com"},
75+
},
76+
},
77+
}
78+
79+
privateIngressHostEDup = &v1beta1.Ingress{
80+
ObjectMeta: v1.ObjectMeta{
81+
Name: "ingressHostE",
82+
Namespace: api.NamespaceDefault,
83+
Labels: map[string]string{
84+
LabelName: PrivateTarget,
85+
},
86+
},
87+
Spec: v1beta1.IngressSpec{
88+
Rules: []v1beta1.IngressRule{
89+
{Host: "e.example.com"},
90+
},
91+
},
92+
}
93+
94+
publicIngressHostEDup = &v1beta1.Ingress{
95+
ObjectMeta: v1.ObjectMeta{
96+
Name: "ingressHostE",
97+
Namespace: api.NamespaceDefault,
98+
Labels: map[string]string{
99+
LabelName: PublicTarget,
100+
},
67101
},
68102
Spec: v1beta1.IngressSpec{
69103
Rules: []v1beta1.IngressRule{
70-
{Host: "baz.example.com"},
104+
{Host: "e.example.com"},
71105
},
72106
},
73107
}
74108

75-
testIngressC2 = &v1beta1.Ingress{
109+
ingressNoLabels = &v1beta1.Ingress{
76110
ObjectMeta: v1.ObjectMeta{
77-
Name: "exampleC2",
111+
Name: "ingressNoLabels",
78112
Namespace: api.NamespaceDefault,
79113
Labels: map[string]string{},
80114
},
81115
Spec: v1beta1.IngressSpec{
82116
Rules: []v1beta1.IngressRule{
83-
{Host: "baz.example.com"},
117+
{Host: "no-labels.example.com"},
84118
},
85119
},
86120
}
87121

88-
testIngressC3 = &v1beta1.Ingress{
122+
nonRegisteredIngress = &v1beta1.Ingress{
89123
ObjectMeta: v1.ObjectMeta{
90-
Name: "exampleC3",
124+
Name: "nonRegisteredIngress",
91125
Namespace: api.NamespaceDefault,
92126
Labels: map[string]string{
93-
"public": "true",
127+
LabelName: "non-registered-target.aws.com",
94128
},
95129
},
96130
Spec: v1beta1.IngressSpec{
97131
Rules: []v1beta1.IngressRule{
98-
{Host: "baz.example.com"},
132+
{Host: "non-registered-target.example.com"},
99133
},
100134
},
101135
}
@@ -180,13 +214,13 @@ func waitForTrue(test func() bool, timeout time.Duration) error {
180214

181215
func TestIngressWatcher(t *testing.T) {
182216
expected := []testIngressEvent{
183-
{watch.Added, nil, testIngressA},
184-
{watch.Added, nil, testIngressB},
185-
{watch.Deleted, testIngressA, nil},
186-
{watch.Modified, testIngressB, testIngressB2},
217+
{watch.Added, nil, privateIngressHostsAB},
218+
{watch.Added, nil, publicIngressHostC},
219+
{watch.Deleted, privateIngressHostsAB, nil},
220+
{watch.Modified, publicIngressHostC, publicIngressHostD},
187221
}
188222

189-
client, watcher := newTestIngressWatcherClient(*testIngressA, *testIngressB)
223+
client, watcher := newTestIngressWatcherClient(*privateIngressHostsAB, *publicIngressHostC)
190224

191225
pM := &sync.Mutex{}
192226
processed := []testIngressEvent{}
@@ -219,11 +253,11 @@ func TestIngressWatcher(t *testing.T) {
219253
if err := waitForTrue(pLenIs(2), 10*time.Second); err != nil {
220254
t.Fatalf("timed out waiting for ingressWatcher to process events")
221255
}
222-
watcher.Delete(testIngressA)
256+
watcher.Delete(privateIngressHostsAB)
223257
if err := waitForTrue(pLenIs(3), 10*time.Second); err != nil {
224258
t.Fatalf("timed out waiting for ingressWatcher to process events")
225259
}
226-
watcher.Modify(testIngressB2)
260+
watcher.Modify(publicIngressHostD)
227261
if err := waitForTrue(pLenIs(4), 10*time.Second); err != nil {
228262
t.Fatalf("timed out waiting for ingressWatcher to process events")
229263
}

main.go

+44-21
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"log"
67
"net/http"
78
"os"
89
"os/signal"
10+
"strings"
911

1012
"k8s.io/client-go/1.5/tools/clientcmd"
1113

@@ -15,20 +17,45 @@ import (
1517
"github.com/utilitywarehouse/go-operational/op"
1618
)
1719

20+
// Define a type named "strslice" as a slice of strings
21+
type strslice []string
22+
23+
// Now, for our new type, implement the two methods of
24+
// the flag.Value interface...
25+
// The first method is String() string
26+
func (s *strslice) String() string {
27+
return fmt.Sprint(*s)
28+
}
29+
30+
// Set is the method to set the flag value, part of the flag.Value interface.
31+
// Set's argument is a string to be parsed to set the flag.
32+
// It's a comma-separated list, so we split it.
33+
func (s *strslice) Set(value string) error {
34+
for _, target := range strings.Split(value, ",") {
35+
*s = append(*s, target)
36+
}
37+
return nil
38+
}
39+
1840
var (
1941
appGitHash = "master"
2042

21-
kubeConfig = flag.String("kubernetes-config", "", "path to the kubeconfig file, if unspecified then in-cluster config will be used")
22-
publicSelectorString = flag.String("public-ingress-selector", "", "selector for ingresses that should point to the public target")
23-
targetPublic = flag.String("target-public", "", "target hostname for public ingresses")
24-
targetPrivate = flag.String("target-private", "", "target hostnam for private ingresses")
25-
r53ZoneID = flag.String("route53-zone-id", "", "route53 hosted DNS zone id")
26-
debugLogs = flag.Bool("debug", false, "enables debug logs")
27-
dryRun = flag.Bool("dry-run", false, "if set, ingress53 will not make any Route53 changes")
43+
// Define a flag to accumulate durations. Because it has a special type,
44+
// we need to use the Var function and therefore create the flag during
45+
// init.
46+
targets strslice
47+
48+
kubeConfig = flag.String("kubernetes-config", "", "path to the kubeconfig file, if unspecified then in-cluster config will be used")
49+
labelName = flag.String("label-name", "", "Kubernetes key of the label that specifies the target type")
50+
defaultTarget = flag.String("default-target", "", "Default target to use in the absense of matching labels")
51+
r53ZoneID = flag.String("route53-zone-id", "", "route53 hosted DNS zone id")
52+
debugLogs = flag.Bool("debug", false, "enables debug logs")
53+
dryRun = flag.Bool("dry-run", false, "if set, ingress53 will not make any Route53 changes")
2854

2955
metricUpdatesApplied = prometheus.NewCounterVec(
3056
prometheus.CounterOpts{
3157
Namespace: "ingress53",
58+
3259
Subsystem: "route53",
3360
Name: "updates_applied",
3461
Help: "number of route53 updates",
@@ -56,34 +83,30 @@ var (
5683
)
5784
)
5885

59-
func init() {
86+
func main() {
87+
prometheus.MustRegister(metricUpdatesApplied)
88+
prometheus.MustRegister(metricUpdatesReceived)
89+
prometheus.MustRegister(metricUpdatesRejected)
90+
91+
flag.Var(&targets, "target", "List of endpoints (ELB) targets to map ingress records to")
6092
flag.Parse()
6193

6294
luf := &logutils.LevelFilter{
6395
Levels: []logutils.LogLevel{"DEBUG", "INFO", "ERROR"},
6496
MinLevel: logutils.LogLevel("INFO"),
6597
Writer: os.Stdout,
6698
}
67-
6899
if *debugLogs {
69100
luf.MinLevel = logutils.LogLevel("DEBUG")
70101
}
71-
72102
log.SetOutput(luf)
73103

74-
prometheus.MustRegister(metricUpdatesApplied)
75-
prometheus.MustRegister(metricUpdatesReceived)
76-
prometheus.MustRegister(metricUpdatesRejected)
77-
}
78-
79-
func main() {
80104
ro := registratorOptions{
81-
PrivateHostname: *targetPrivate,
82-
PublicHostname: *targetPublic,
83-
PublicResourceSelector: *publicSelectorString,
84-
Route53ZoneID: *r53ZoneID,
105+
Targets: targets,
106+
LabelName: *labelName,
107+
DefaultTarget: *defaultTarget,
108+
Route53ZoneID: *r53ZoneID,
85109
}
86-
87110
if *kubeConfig != "" {
88111
config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig)
89112
if err != nil {

0 commit comments

Comments
 (0)