feat(coredns): add annotations for coredns groups (#5842)

Signed-off-by: Jan Jansen <jan.jansen@gdata.de>
This commit is contained in:
Jan Jansen 2025-09-19 08:22:12 +02:00 committed by GitHub
parent 413015ea76
commit 8851544c1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 147 additions and 2 deletions

View File

@ -260,3 +260,78 @@ dnstools# dig @10.100.4.143 nginx.example.org +short
10.0.2.15
dnstools#
```
## Specific service annotation options
### Groups
Groups can be used to group set of services together. The main use of this is to limit recursion,
i.e. don't return all records, but only a subset. Let's say we have a configuration like this:
```yaml
apiVersion: v1
kind: Service
metadata:
name: a
annotations:
external-dns.alpha.kubernetes.io/hostname: a.domain.local
external-dns.alpha.kubernetes.io/coredns-group: "g1"
spec:
type: LoadBalancer
...
status:
loadBalancer:
ingress:
- ip: 127.0.0.1
---
apiVersion: v1
kind: Service
metadata:
name: b
annotations:
external-dns.alpha.kubernetes.io/hostname: b.domain.local
external-dns.alpha.kubernetes.io/coredns-group: "g1"
spec:
type: LoadBalancer
...
status:
loadBalancer:
ingress:
- ip: 127.0.0.2
---
apiVersion: v1
kind: Service
metadata:
name: c
annotations:
external-dns.alpha.kubernetes.io/hostname: c.subdom.domain.local
external-dns.alpha.kubernetes.io/coredns-group: "g2"
spec:
type: LoadBalancer
...
status:
loadBalancer:
ingress:
- ip: 127.0.0.3
---
apiVersion: v1
kind: Service
metadata:
name: d
annotations:
external-dns.alpha.kubernetes.io/hostname: d.subdom.domain.local
external-dns.alpha.kubernetes.io/coredns-group: "g2"
spec:
type: LoadBalancer
...
status:
loadBalancer:
ingress:
- ip: 127.0.0.4
```
And we want domain.local to return (127.0.0.1 and 127.0.0.2) and subdom.domain.local to return (127.0.0.3 and 127.0.0.4).
For this the two domains, need to be in different groups. What those groups are does not matter,
as long as a and b belong to the same group which is different from the group c and d belong to.
If a service is found without a group it is always included.

View File

@ -41,7 +41,8 @@ const (
priority = 10 // default priority when nothing is set
etcdTimeout = 5 * time.Second
randomPrefixLabel = "prefix"
randomPrefixLabel = "prefix"
providerSpecificGroup = "coredns/group"
)
// coreDNSClient is an interface to work with CoreDNS service records in etcd
@ -260,6 +261,9 @@ func (p coreDNSProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error
endpoint.TTL(service.TTL),
service.Host,
)
if service.Group != "" {
ep.WithProviderSpecific(providerSpecificGroup, service.Group)
}
log.Debugf("Creating new ep (%s) with new service host (%s)", ep, service.Host)
}
ep.Labels["originalText"] = service.Text
@ -346,12 +350,17 @@ func (p coreDNSProvider) createServicesForEndpoint(dnsName string, ep *endpoint.
prefix = fmt.Sprintf("%08x", rand.Int31())
log.Infof("Generating new prefix: (%s)", prefix)
}
group := ""
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGroup); ok {
group = prop
}
service := Service{
Host: target,
Text: ep.Labels["originalText"],
Key: p.etcdKeyFor(prefix + "." + dnsName),
TargetStrip: strings.Count(prefix, ".") + 1,
TTL: uint32(ep.RecordTTL),
Group: group,
}
services = append(services, &service)
ep.Labels[target] = prefix

View File

@ -460,7 +460,7 @@ func validateServices(services map[string]Service, expectedServices map[string][
}
found := false
for i, expectedServiceEntry := range expectedServiceEntries {
if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text {
if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text && value.Group == expectedServiceEntry.Group {
expectedServiceEntries = append(expectedServiceEntries[:i], expectedServiceEntries[i+1:]...)
found = true
break
@ -863,3 +863,48 @@ func TestCoreDNSProvider_updateTXTRecords_ClearsExtraText(t *testing.T) {
assert.Equal(t, "txt-value", services[0].Text)
assert.Empty(t, services[1].Text)
}
func TestApplyChangesAWithGroupServiceTranslation(t *testing.T) {
client := fakeETCDClient{
map[string]Service{},
}
coredns := coreDNSProvider{
client: client,
coreDNSPrefix: defaultCoreDNSPrefix,
}
changes1 := &plan.Changes{
Create: []*endpoint.Endpoint{
endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "5.5.5.5").WithProviderSpecific(providerSpecificGroup, "test1"),
endpoint.NewEndpoint("domain2.local", endpoint.RecordTypeA, "5.5.5.6").WithProviderSpecific(providerSpecificGroup, "test1"),
endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "5.5.5.7").WithProviderSpecific(providerSpecificGroup, "test2"),
},
}
coredns.ApplyChanges(context.Background(), changes1)
expectedServices1 := map[string][]*Service{
"/skydns/local/domain1": {{Host: "5.5.5.5", Group: "test1"}},
"/skydns/local/domain2": {{Host: "5.5.5.6", Group: "test1"}},
"/skydns/local/domain3": {{Host: "5.5.5.7", Group: "test2"}},
}
validateServices(client.services, expectedServices1, t, 1)
}
func TestRecordsAWithGroupServiceTranslation(t *testing.T) {
client := fakeETCDClient{
map[string]Service{
"/skydns/local/domain1": {Host: "5.5.5.5", Group: "test1"},
},
}
coredns := coreDNSProvider{
client: client,
coreDNSPrefix: defaultCoreDNSPrefix,
}
endpoints, err := coredns.Records(context.Background())
require.NoError(t, err)
if prop, ok := endpoints[0].GetProviderSpecificProperty(providerSpecificGroup); !ok {
t.Error("go no Group name")
} else if prop != "test1" {
t.Errorf("got unexpected Group name: %s != %s", prop, "test1")
}
}

View File

@ -29,6 +29,7 @@ const (
CloudflareRecordCommentKey = AnnotationKeyPrefix + "cloudflare-record-comment"
AWSPrefix = AnnotationKeyPrefix + "aws-"
CoreDNSPrefix = AnnotationKeyPrefix + "coredns-"
SCWPrefix = AnnotationKeyPrefix + "scw-"
WebhookPrefix = AnnotationKeyPrefix + "webhook-"
CloudflarePrefix = AnnotationKeyPrefix + "cloudflare-"

View File

@ -49,6 +49,11 @@ func ProviderSpecificAnnotations(annotations map[string]string) (endpoint.Provid
Name: fmt.Sprintf("webhook/%s", attr),
Value: v,
})
} else if attr, ok := strings.CutPrefix(k, CoreDNSPrefix); ok {
providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{
Name: fmt.Sprintf("coredns/%s", attr),
Value: v,
})
} else if strings.HasPrefix(k, CloudflarePrefix) {
if strings.Contains(k, CloudflareCustomHostnameKey) {
providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{

View File

@ -64,6 +64,16 @@ func TestProviderSpecificAnnotations(t *testing.T) {
},
setIdentifier: "",
},
{
name: "CoreDNS annotation",
annotations: map[string]string{
"external-dns.alpha.kubernetes.io/coredns-group": "g1",
},
expected: endpoint.ProviderSpecific{
{Name: "coredns/group", Value: "g1"},
},
setIdentifier: "",
},
{
name: "Set identifier annotation",
annotations: map[string]string{