Merge pull request #1696 from dansimone/dansimone/support-prefer-ingress-annotations

Optional ability to use the host name defined on an ingress's annotations *instead* of its hosts stanza
This commit is contained in:
Kubernetes Prow Robot 2021-06-14 02:13:04 -07:00 committed by GitHub
commit 2f78d09b47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 9 deletions

View File

@ -40,7 +40,9 @@ Services exposed via `type=LoadBalancer`, `type=ExternalName` and for the hostna
There are three sources of information for ExternalDNS to decide on DNS name. ExternalDNS will pick one in order as listed below: 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 loadbalancer IP, it also will look for the annotation `external-dns.alpha.kubernetes.io/internal-hostname` on the service and use the service IP. 1. For ingress objects ExternalDNS will create a DNS record based on the hosts specified for the ingress object, as well as the `external-dns.alpha.kubernetes.io/hostname` annotation. For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the loadbalancer IP, it also will look for the annotation `external-dns.alpha.kubernetes.io/internal-hostname` on the service and use the service IP.
- For ingresses, you can optionally force ExternalDNS to create records based on _either_ the hosts specified or the `external-dns.alpha.kubernetes.io/hostname` annotation. This behavior is controlled by
setting the `external-dns.alpha.kubernetes.io/ingress-hostname-source` annotation on that ingress to either `defined-hosts-only` or `annotation-only`.
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. 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.

View File

@ -42,6 +42,10 @@ const (
ALBDualstackAnnotationKey = "alb.ingress.kubernetes.io/ip-address-type" ALBDualstackAnnotationKey = "alb.ingress.kubernetes.io/ip-address-type"
// ALBDualstackAnnotationValue is the value of the ALB dualstack annotation that indicates it is dualstack // ALBDualstackAnnotationValue is the value of the ALB dualstack annotation that indicates it is dualstack
ALBDualstackAnnotationValue = "dualstack" ALBDualstackAnnotationValue = "dualstack"
// Possible values for the ingress-hostname-source annotation
IngressHostnameSourceAnnotationOnlyValue = "annotation-only"
IngressHostnameSourceDefinedHostsOnlyValue = "defined-hosts-only"
) )
// ingressSource is an implementation of Source for Kubernetes ingress objects. // ingressSource is an implementation of Source for Kubernetes ingress objects.
@ -243,8 +247,6 @@ func (sc *ingressSource) setDualstackLabel(ingress *v1beta1.Ingress, endpoints [
// endpointsFromIngress extracts the endpoints from ingress object // endpointsFromIngress extracts the endpoints from ingress object
func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool) []*endpoint.Endpoint { func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
ttl, err := getTTLFromAnnotations(ing.Annotations) ttl, err := getTTLFromAnnotations(ing.Annotations)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
@ -258,11 +260,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool, i
providerSpecific, setIdentifier := getProviderSpecificAnnotations(ing.Annotations) providerSpecific, setIdentifier := getProviderSpecificAnnotations(ing.Annotations)
// Gather endpoints defined on hosts sections of the ingress
var definedHostsEndpoints []*endpoint.Endpoint
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
if rule.Host == "" { if rule.Host == "" {
continue continue
} }
endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific, setIdentifier)...) definedHostsEndpoints = append(definedHostsEndpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific, setIdentifier)...)
} }
// Skip endpoints if we do not want entries from tls spec section // Skip endpoints if we do not want entries from tls spec section
@ -272,18 +276,33 @@ func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool, i
if host == "" { if host == "" {
continue continue
} }
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier)...) definedHostsEndpoints = append(definedHostsEndpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier)...)
} }
} }
} }
// Skip endpoints if we do not want entries from annotations // Gather endpoints defined on annotations in the ingress
var annotationEndpoints []*endpoint.Endpoint
if !ignoreHostnameAnnotation { if !ignoreHostnameAnnotation {
hostnameList := getHostnamesFromAnnotations(ing.Annotations) for _, hostname := range getHostnamesFromAnnotations(ing.Annotations) {
for _, hostname := range hostnameList { annotationEndpoints = append(annotationEndpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...)
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...)
} }
} }
// Determine which hostnames to consider in our final list
hostnameSourceAnnotation, hostnameSourceAnnotationExists := ing.Annotations[ingressHostnameSourceKey]
if !hostnameSourceAnnotationExists {
return append(definedHostsEndpoints, annotationEndpoints...)
}
// Include endpoints according to the hostname source annotation in our final list
var endpoints []*endpoint.Endpoint
if strings.ToLower(hostnameSourceAnnotation) == IngressHostnameSourceDefinedHostsOnlyValue {
endpoints = append(endpoints, definedHostsEndpoints...)
}
if strings.ToLower(hostnameSourceAnnotation) == IngressHostnameSourceAnnotationOnlyValue {
endpoints = append(endpoints, annotationEndpoints...)
}
return endpoints return endpoints
} }

View File

@ -85,6 +85,7 @@ func (suite *IngressSuite) TestDualstackLabelIsSet() {
func TestIngress(t *testing.T) { func TestIngress(t *testing.T) {
suite.Run(t, new(IngressSuite)) suite.Run(t, new(IngressSuite))
t.Run("endpointsFromIngress", testEndpointsFromIngress) t.Run("endpointsFromIngress", testEndpointsFromIngress)
t.Run("endpointsFromIngressHostnameSourceAnnotation", testEndpointsFromIngressHostnameSourceAnnotation)
t.Run("Endpoints", testIngressEndpoints) t.Run("Endpoints", testIngressEndpoints)
} }
@ -228,6 +229,98 @@ func testEndpointsFromIngress(t *testing.T) {
} }
} }
func testEndpointsFromIngressHostnameSourceAnnotation(t *testing.T) {
// Host names and host name annotation provided, with various values of the ingress-hostname-source annotation
for _, ti := range []struct {
title string
ingress fakeIngress
expected []*endpoint.Endpoint
}{
{
title: "No ingress-hostname-source annotation, one rule.host, one annotation host",
ingress: fakeIngress{
dnsnames: []string{"foo.bar"},
annotations: map[string]string{hostnameAnnotationKey: "foo.baz"},
hostnames: []string{"lb.com"},
},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Targets: endpoint.Targets{"lb.com"},
},
{
DNSName: "foo.baz",
Targets: endpoint.Targets{"lb.com"},
},
},
},
{
title: "No ingress-hostname-source annotation, one rule.host",
ingress: fakeIngress{
dnsnames: []string{"foo.bar"},
hostnames: []string{"lb.com"},
},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Targets: endpoint.Targets{"lb.com"},
},
},
},
{
title: "No ingress-hostname-source annotation, one rule.host, one annotation host",
ingress: fakeIngress{
dnsnames: []string{"foo.bar"},
annotations: map[string]string{hostnameAnnotationKey: "foo.baz"},
hostnames: []string{"lb.com"},
},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Targets: endpoint.Targets{"lb.com"},
},
{
DNSName: "foo.baz",
Targets: endpoint.Targets{"lb.com"},
},
},
},
{
title: "Ingress-hostname-source=defined-hosts-only, one rule.host, one annotation host",
ingress: fakeIngress{
dnsnames: []string{"foo.bar"},
annotations: map[string]string{hostnameAnnotationKey: "foo.baz", ingressHostnameSourceKey: "defined-hosts-only"},
hostnames: []string{"lb.com"},
},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Targets: endpoint.Targets{"lb.com"},
},
},
},
{
title: "Ingress-hostname-source=annotation-only, one rule.host, one annotation host",
ingress: fakeIngress{
dnsnames: []string{"foo.bar"},
annotations: map[string]string{hostnameAnnotationKey: "foo.baz", ingressHostnameSourceKey: "annotation-only"},
hostnames: []string{"lb.com"},
},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.baz",
Targets: endpoint.Targets{"lb.com"},
},
},
},
} {
t.Run(ti.title, func(t *testing.T) {
realIngress := ti.ingress.Ingress()
validateEndpoints(t, endpointsFromIngress(realIngress, false, false), ti.expected)
})
}
}
func testIngressEndpoints(t *testing.T) { func testIngressEndpoints(t *testing.T) {
namespace := "testing" namespace := "testing"
for _, ti := range []struct { for _, ti := range []struct {

View File

@ -46,6 +46,9 @@ const (
ttlAnnotationKey = "external-dns.alpha.kubernetes.io/ttl" ttlAnnotationKey = "external-dns.alpha.kubernetes.io/ttl"
// The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME // The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME
aliasAnnotationKey = "external-dns.alpha.kubernetes.io/alias" aliasAnnotationKey = "external-dns.alpha.kubernetes.io/alias"
// The annotation used to determine the source of hostnames for ingresses. This is an optional field - all
// available hostname sources are used if not specified.
ingressHostnameSourceKey = "external-dns.alpha.kubernetes.io/ingress-hostname-source"
// The value of the controller annotation so that we feel responsible // The value of the controller annotation so that we feel responsible
controllerAnnotationValue = "dns-controller" controllerAnnotationValue = "dns-controller"
// The annotation used for defining the desired hostname // The annotation used for defining the desired hostname