Commit d54cd72e authored by Henning Jacobs's avatar Henning Jacobs Committed by GitHub
Browse files

Support for annotation based targets for Ingress (#312)

* Support for annotation based targets for Ingress

* stay DRY: extract into addEndpointsFromTargetAnnotation
parent 5d3926bd
Showing with 111 additions and 4 deletions
+111 -4
......@@ -42,11 +42,11 @@ Services exposed via `type=LoadBalancer` and for the hostnames defined in Ingres
There are three sources of information for ExternalDNS to decide on DNS name. ExternalDNS will pick one in order as listed below:
1. For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object. For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.
1. For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object. For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.
2. If compatibility mode is enabled (e.g. `--compatibility={mate,molecule}` flag), External DNS will parse annotations used by Zalando/Mate, wearemolecule/route53-kubernetes. Compatibility mode with Kops DNS Controller is planned to be added in the future.
3. If `--fqdn-template` flag is specified, e.g. `--fqdn-template={{.Name}}.my-org.com`, ExternalDNS will use service/ingress specifications for the provided template to generate DNS name.
3. If `--fqdn-template` flag is specified, e.g. `--fqdn-template={{.Name}}.my-org.com`, ExternalDNS will use service/ingress specifications for the provided template to generate DNS name.
### Which Service and Ingress controllers are supported?
......@@ -57,7 +57,13 @@ Regarding Ingress, we'll support:
* nginx-ingress-controller v0.9.x with a fronting Service
* Zalando's [AWS Ingress controller](https://github.com/zalando-incubator/kube-ingress-aws-controller), based on AWS ALBs and [Skipper](https://github.com/zalando/skipper)
### What about those other implementations?
### Are other Ingress Controllers supported?
For Ingress objects, ExternalDNS will attempt to discover the target hostname of the relevant Ingress Controller automatically. If you are using an Ingress Controller that is not listed above you may have issues with ExternalDNS not discovering Endpoints and consequently not creating any DNS records. As a workaround, it is possible to force create an Endpoint by manually specifying a target host/IP for the records to be created by setting the annotation `external-dns.alpha.kubernetes.io/target` in the Ingress object.
Note that the hostname specified in the Ingress object's annotation must already exist. (i.e.: You have a Service resource for your Ingress Controller with the `external-dns.alpha.kubernetes.io/hostname` annotation set to the same value.)
### What about those other projects?
ExternalDNS is a joint effort to unify different projects accomplishing the same goals, namely:
......
......@@ -33,7 +33,7 @@ import (
// ingressSource is an implementation of Source for Kubernetes ingress objects.
// Ingress implementation will use the spec.rules.host value for the hostname
// Ingress annotations are ignored
// Use targetAnnotationKey to add an additional Endpoint. (useful if the ingress controller does not update)
type ingressSource struct {
client kubernetes.Interface
namespace string
......@@ -103,6 +103,21 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
return endpoints, nil
}
// append endpoints from optional "target" annotation
func addEndpointsFromTargetAnnotation(ing *v1beta1.Ingress, hostname string, endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
// Get the desired hostname of the ingress from the annotation.
targetAnnotation, exists := ing.Annotations[targetAnnotationKey]
if exists {
// splits the hostname annotation and removes the trailing periods
targetsList := strings.Split(strings.Replace(targetAnnotation, " ", "", -1), ",")
for _, targetHostname := range targetsList {
targetHostname = strings.TrimSuffix(targetHostname, ".")
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, targetHostname, ""))
}
}
return endpoints
}
func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoint.Endpoint, error) {
var endpoints []*endpoint.Endpoint
......@@ -113,6 +128,9 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin
}
hostname := buf.String()
endpoints = addEndpointsFromTargetAnnotation(ing, hostname, endpoints)
for _, lb := range ing.Status.LoadBalancer.Ingress {
if lb.IP != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.IP, ""))
......@@ -133,6 +151,9 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
if rule.Host == "" {
continue
}
endpoints = addEndpointsFromTargetAnnotation(ing, rule.Host, endpoints)
for _, lb := range ing.Status.LoadBalancer.Ingress {
if lb.IP != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(rule.Host, lb.IP, ""))
......
......@@ -338,6 +338,84 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{},
fqdnTemplate: "{{.Name}}.ext-dns.test.com",
},
{
title: "ingress rules with annotation",
targetNamespace: "",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
targetAnnotationKey: "ingress-target.com",
},
dnsnames: []string{"example.org"},
ips: []string{},
},
{
name: "fake2",
namespace: namespace,
annotations: map[string]string{
targetAnnotationKey: "ingress-target.com",
},
dnsnames: []string{"example2.org"},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "ingress-target.com",
},
{
DNSName: "example2.org",
Target: "ingress-target.com",
},
{
DNSName: "example2.org",
Target: "8.8.8.8",
},
},
},
{
title: "template for ingress with annotation",
targetNamespace: "",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
targetAnnotationKey: "ingress-target.com",
},
dnsnames: []string{},
ips: []string{},
hostnames: []string{},
},
{
name: "fake2",
namespace: namespace,
annotations: map[string]string{
targetAnnotationKey: "ingress-target.com",
},
dnsnames: []string{},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "fake1.ext-dns.test.com",
Target: "ingress-target.com",
},
{
DNSName: "fake2.ext-dns.test.com",
Target: "ingress-target.com",
},
{
DNSName: "fake2.ext-dns.test.com",
Target: "8.8.8.8",
},
},
fqdnTemplate: "{{.Name}}.ext-dns.test.com",
},
} {
t.Run(ti.title, func(t *testing.T) {
ingresses := make([]*v1beta1.Ingress, 0)
......
......@@ -23,6 +23,8 @@ const (
controllerAnnotationKey = "external-dns.alpha.kubernetes.io/controller"
// The annotation used for defining the desired hostname
hostnameAnnotationKey = "external-dns.alpha.kubernetes.io/hostname"
// The annotation used for defining the desired ingress target
targetAnnotationKey = "external-dns.alpha.kubernetes.io/target"
// The value of the controller annotation so that we feel resposible
controllerAnnotationValue = "dns-controller"
)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment