mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 01:56:57 +02:00
Add support for human-friendly TTL values
Supports specifying TTL values in Golang duration format for `external-dns.alpha.kubernetes.io/ttl` annotation.
This commit is contained in:
parent
2ec94f89d2
commit
6e6af8aa0d
17
docs/ttl.md
17
docs/ttl.md
@ -2,6 +2,7 @@ Configure DNS record TTL (Time-To-Live)
|
|||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
An optional annotation `external-dns.alpha.kubernetes.io/ttl` is available to customize the TTL value of a DNS record.
|
An optional annotation `external-dns.alpha.kubernetes.io/ttl` is available to customize the TTL value of a DNS record.
|
||||||
|
TTL is specified as an integer encoded as string representing seconds.
|
||||||
|
|
||||||
To configure it, simply annotate a service/ingress, e.g.:
|
To configure it, simply annotate a service/ingress, e.g.:
|
||||||
|
|
||||||
@ -15,7 +16,21 @@ metadata:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
TTL must be a positive integer encoded as string.
|
TTL can also be specified as a duration value parsable by Golang [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com.
|
||||||
|
external-dns.alpha.kubernetes.io/ttl: "1m"
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Both examples result in the same value of 60 seconds TTL.
|
||||||
|
|
||||||
|
TTL must be a positive value.
|
||||||
|
|
||||||
Providers
|
Providers
|
||||||
=========
|
=========
|
||||||
|
@ -933,6 +933,15 @@ func testGatewayEndpoints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
dnsnames: [][]string{{"example2.org"}},
|
dnsnames: [][]string{{"example2.org"}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fake3",
|
||||||
|
namespace: namespace,
|
||||||
|
annotations: map[string]string{
|
||||||
|
targetAnnotationKey: "gateway-target.com",
|
||||||
|
ttlAnnotationKey: "10s",
|
||||||
|
},
|
||||||
|
dnsnames: [][]string{{"example3.org"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expected: []*endpoint.Endpoint{
|
expected: []*endpoint.Endpoint{
|
||||||
{
|
{
|
||||||
@ -945,6 +954,11 @@ func testGatewayEndpoints(t *testing.T) {
|
|||||||
Targets: endpoint.Targets{"gateway-target.com"},
|
Targets: endpoint.Targets{"gateway-target.com"},
|
||||||
RecordTTL: endpoint.TTL(1),
|
RecordTTL: endpoint.TTL(1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
DNSName: "example3.org",
|
||||||
|
Targets: endpoint.Targets{"gateway-target.com"},
|
||||||
|
RecordTTL: endpoint.TTL(10),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -815,6 +815,16 @@ func testIngressEndpoints(t *testing.T) {
|
|||||||
dnsnames: []string{"example2.org"},
|
dnsnames: []string{"example2.org"},
|
||||||
ips: []string{"8.8.8.8"},
|
ips: []string{"8.8.8.8"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fake3",
|
||||||
|
namespace: namespace,
|
||||||
|
annotations: map[string]string{
|
||||||
|
targetAnnotationKey: "ingress-target.com",
|
||||||
|
ttlAnnotationKey: "10s",
|
||||||
|
},
|
||||||
|
dnsnames: []string{"example3.org"},
|
||||||
|
ips: []string{"8.8.4.4"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expected: []*endpoint.Endpoint{
|
expected: []*endpoint.Endpoint{
|
||||||
{
|
{
|
||||||
@ -827,6 +837,11 @@ func testIngressEndpoints(t *testing.T) {
|
|||||||
Targets: endpoint.Targets{"ingress-target.com"},
|
Targets: endpoint.Targets{"ingress-target.com"},
|
||||||
RecordTTL: endpoint.TTL(1),
|
RecordTTL: endpoint.TTL(1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
DNSName: "example3.org",
|
||||||
|
Targets: endpoint.Targets{"ingress-target.com"},
|
||||||
|
RecordTTL: endpoint.TTL(10),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -824,6 +824,15 @@ func testIngressRouteEndpoints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
host: "example2.org",
|
host: "example2.org",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fake3",
|
||||||
|
namespace: namespace,
|
||||||
|
annotations: map[string]string{
|
||||||
|
targetAnnotationKey: "ingressroute-target.com",
|
||||||
|
ttlAnnotationKey: "10s",
|
||||||
|
},
|
||||||
|
host: "example3.org",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expected: []*endpoint.Endpoint{
|
expected: []*endpoint.Endpoint{
|
||||||
{
|
{
|
||||||
@ -836,6 +845,11 @@ func testIngressRouteEndpoints(t *testing.T) {
|
|||||||
Targets: endpoint.Targets{"ingressroute-target.com"},
|
Targets: endpoint.Targets{"ingressroute-target.com"},
|
||||||
RecordTTL: endpoint.TTL(1),
|
RecordTTL: endpoint.TTL(1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
DNSName: "example3.org",
|
||||||
|
Targets: endpoint.Targets{"ingressroute-target.com"},
|
||||||
|
RecordTTL: endpoint.TTL(10),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -969,6 +969,30 @@ func testServiceSourceEndpoints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ttl annotated (in duration format) and is valid should set Record.TTL",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
v1.ServiceTypeLoadBalancer,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
map[string]string{},
|
||||||
|
map[string]string{
|
||||||
|
hostnameAnnotationKey: "foo.example.org.",
|
||||||
|
ttlAnnotationKey: "1m",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
[]string{"1.2.3.4"},
|
||||||
|
[]string{},
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(60)},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Negative ttl is not valid",
|
"Negative ttl is not valid",
|
||||||
"",
|
"",
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
)
|
)
|
||||||
@ -65,7 +66,7 @@ func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error)
|
|||||||
if !exists {
|
if !exists {
|
||||||
return ttlNotConfigured, nil
|
return ttlNotConfigured, nil
|
||||||
}
|
}
|
||||||
ttlValue, err := strconv.ParseInt(ttlAnnotation, 10, 64)
|
ttlValue, err := parseTTL(ttlAnnotation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ttlNotConfigured, fmt.Errorf("\"%v\" is not a valid TTL value", ttlAnnotation)
|
return ttlNotConfigured, fmt.Errorf("\"%v\" is not a valid TTL value", ttlAnnotation)
|
||||||
}
|
}
|
||||||
@ -75,6 +76,21 @@ func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error)
|
|||||||
return endpoint.TTL(ttlValue), nil
|
return endpoint.TTL(ttlValue), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseTTL parses TTL from string, returning duration in seconds.
|
||||||
|
// parseTTL supports both integers like "600" and durations based
|
||||||
|
// on Go Duration like "10m", hence "600" and "10m" represent the same value.
|
||||||
|
//
|
||||||
|
// Note: for durations like "1.5s" the fraction is omitted (resulting in 1 second
|
||||||
|
// for the example).
|
||||||
|
func parseTTL(s string) (ttlSeconds int64, err error) {
|
||||||
|
ttlDuration, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return strconv.ParseInt(s, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(ttlDuration.Seconds()), nil
|
||||||
|
}
|
||||||
|
|
||||||
func getHostnamesFromAnnotations(annotations map[string]string) []string {
|
func getHostnamesFromAnnotations(annotations map[string]string) []string {
|
||||||
hostnameAnnotation, exists := annotations[hostnameAnnotationKey]
|
hostnameAnnotation, exists := annotations[hostnameAnnotationKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -63,11 +63,23 @@ func TestGetTTLFromAnnotations(t *testing.T) {
|
|||||||
expectedErr: fmt.Errorf("TTL value must be between [%d, %d]", ttlMinimum, ttlMaximum),
|
expectedErr: fmt.Errorf("TTL value must be between [%d, %d]", ttlMinimum, ttlMaximum),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "TTL annotation value is set correctly",
|
title: "TTL annotation value is set correctly using integer",
|
||||||
annotations: map[string]string{ttlAnnotationKey: "60"},
|
annotations: map[string]string{ttlAnnotationKey: "60"},
|
||||||
expectedTTL: endpoint.TTL(60),
|
expectedTTL: endpoint.TTL(60),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "TTL annotation value is set correctly using duration (whole)",
|
||||||
|
annotations: map[string]string{ttlAnnotationKey: "10m"},
|
||||||
|
expectedTTL: endpoint.TTL(600),
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "TTL annotation value is set correcly using duration (fractional)",
|
||||||
|
annotations: map[string]string{ttlAnnotationKey: "20.5s"},
|
||||||
|
expectedTTL: endpoint.TTL(20),
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.title, func(t *testing.T) {
|
t.Run(tc.title, func(t *testing.T) {
|
||||||
ttl, err := getTTLFromAnnotations(tc.annotations)
|
ttl, err := getTTLFromAnnotations(tc.annotations)
|
||||||
|
Loading…
Reference in New Issue
Block a user