mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
headless service already supported mode where DNS entry is created for each pod when `spec.hostName` is present. Not all controllers populate that field, but it is sometimes desireable to create DNS entry per pod nevertheless. This commit adds new annotation `service-pod-endpoints` , which when set on a headless service makes external-dns to create endpoint per pod using pod name as a prefix. diff --git c/docs/sources/service.md i/docs/sources/service.md index ec39aa7f..7f03426e 100644 --- c/docs/sources/service.md +++ i/docs/sources/service.md @@ -33,6 +33,10 @@ a Pod that has a non-empty `spec.hostname` field, additional DNS entries are cre For each domain name created for the Service, the additional DNS entry for the Pod has that domain name prefixed with the value of the Pod's `spec.hostname` field and a `.`. +Another way to create per-pod DNS entries is to annotate headless service with +`external-dns.alpha.kubernetes.io/service-pod-endpoints: true`, this will prefix service domain name with pod name. + + ## Targets If the Service has an `external-dns.alpha.kubernetes.io/target` annotation, uses diff --git c/source/service.go i/source/service.go index 2c33dcea..0186a102 100644 --- c/source/service.go +++ i/source/service.go @@ -279,6 +279,8 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri endpointsType := getEndpointsTypeFromAnnotations(svc.Annotations) + _, perPodDNS := svc.Annotations[servicePodEndpointsKey] + targetsByHeadlessDomainAndType := make(map[endpoint.EndpointKey]endpoint.Targets) for _, subset := range endpointsObject.Subsets { addresses := subset.Addresses @@ -309,6 +311,10 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri headlessDomains = append(headlessDomains, fmt.Sprintf("%s.%s", pod.Spec.Hostname, hostname)) } + if perPodDNS { + headlessDomains = append(headlessDomains, fmt.Sprintf("%s.%s", pod.Name, hostname)) + } + for _, headlessDomain := range headlessDomains { targets := annotations.TargetsFromTargetAnnotation(pod.Annotations) if len(targets) == 0 { diff --git c/source/service_test.go i/source/service_test.go index 5e9948ab..c25e2f29 100644 --- c/source/service_test.go +++ i/source/service_test.go @@ -2892,6 +2892,61 @@ func TestHeadlessServices(t *testing.T) { }, false, }, + { + "annotated Headless services create DNS name for each pod", + "", + "testing", + "foo", + v1.ServiceTypeClusterIP, + "", + "", + false, + map[string]string{"component": "foo"}, + map[string]string{ + servicePodEndpointsKey: "true", + hostnameAnnotationKey: "service.example.org", + endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP, + }, + map[string]string{}, + v1.ClusterIPNone, + []string{"1.1.1.1", "1.1.1.2", "1.1.1.3"}, + []string{"", "", ""}, + map[string]string{ + "component": "foo", + }, + []string{}, + []string{"foo1", "foo2", "foo3"}, + []string{"", "", ""}, + []bool{true, true, true}, + false, + []v1.Node{ + { + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeExternalIP, + Address: "1.2.3.4", + }, + { + Type: v1.NodeInternalIP, + Address: "2001:db8::4", + }, + }, + }, + }, + }, + []*endpoint.Endpoint{ + {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + {DNSName: "foo1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "foo1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + {DNSName: "foo2.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "foo2.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + {DNSName: "foo3.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "foo3.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + }, + false, + }, { "annotated Headless services return dual-stack targets from node external IP if endpoints-type annotation is set", "", diff --git c/source/source.go i/source/source.go index 414ff1ad..0df189bb 100644 --- c/source/source.go +++ i/source/source.go @@ -46,6 +46,7 @@ const ( ingressHostnameSourceKey = annotations.IngressHostnameSourceKey controllerAnnotationValue = annotations.ControllerValue internalHostnameAnnotationKey = annotations.InternalHostnameKey + servicePodEndpointsKey = annotations.ServicePodEndpoints EndpointsTypeNodeExternalIP = "NodeExternalIP" EndpointsTypeHostIP = "HostIP" # Conflicts: # source/service_test.go diff --git c/docs/sources/service.md i/docs/sources/service.md index ec39aa7f..7f03426e 100644 --- c/docs/sources/service.md +++ i/docs/sources/service.md @@ -33,6 +33,10 @@ a Pod that has a non-empty `spec.hostname` field, additional DNS entries are cre For each domain name created for the Service, the additional DNS entry for the Pod has that domain name prefixed with the value of the Pod's `spec.hostname` field and a `.`. +Another way to create per-pod DNS entries is to annotate headless service with +`external-dns.alpha.kubernetes.io/service-pod-endpoints: true`, this will prefix service domain name with pod name. + + ## Targets If the Service has an `external-dns.alpha.kubernetes.io/target` annotation, uses diff --git c/source/annotations/annotations.go i/source/annotations/annotations.go index abfce135..9fd2cd4c 100644 --- c/source/annotations/annotations.go +++ i/source/annotations/annotations.go @@ -55,4 +55,6 @@ const ( ControllerValue = "dns-controller" // The annotation used for defining the desired hostname InternalHostnameKey = AnnotationKeyPrefix + "internal-hostname" + // When set on a service, per-pod DNS entries will be created. + ServicePodEndpoints = AnnotationKeyPrefix + "service-pod-endpoints" ) diff --git c/source/service.go i/source/service.go index a339b192..2bb23e11 100644 --- c/source/service.go +++ i/source/service.go @@ -323,6 +323,8 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri publishPodIPs := endpointsType != EndpointsTypeNodeExternalIP && endpointsType != EndpointsTypeHostIP && !sc.publishHostIP publishNotReadyAddresses := svc.Spec.PublishNotReadyAddresses || sc.alwaysPublishNotReadyAddresses + _, perPodDNS := svc.Annotations[servicePodEndpointsKey] + targetsByHeadlessDomainAndType := make(map[endpoint.EndpointKey]endpoint.Targets) for _, endpointSlice := range endpointSlices { for _, ep := range endpointSlice.Endpoints { @@ -359,6 +361,10 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri headlessDomains = append(headlessDomains, fmt.Sprintf("%s.%s", pod.Spec.Hostname, hostname)) } + if perPodDNS { + headlessDomains = append(headlessDomains, fmt.Sprintf("%s.%s", pod.Name, hostname)) + } + for _, headlessDomain := range headlessDomains { targets := annotations.TargetsFromTargetAnnotation(pod.Annotations) if len(targets) == 0 { diff --git c/source/service_test.go i/source/service_test.go index dd21331e..f8f14f4d 100644 --- c/source/service_test.go +++ i/source/service_test.go @@ -3036,6 +3036,62 @@ func TestHeadlessServices(t *testing.T) { }, false, }, + { + "annotated Headless services create DNS name for each pod", + "", + "testing", + "foo", + v1.ServiceTypeClusterIP, + "", + "", + false, + true, + map[string]string{"component": "foo"}, + map[string]string{ + servicePodEndpointsKey: "true", + hostnameAnnotationKey: "service.example.org", + endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP, + }, + map[string]string{}, + v1.ClusterIPNone, + []string{"1.1.1.1", "1.1.1.2", "1.1.1.3"}, + []string{"", "", ""}, + map[string]string{ + "component": "foo", + }, + []string{}, + []string{"foo1", "foo2", "foo3"}, + []string{"", "", ""}, + []bool{true, true, true}, + false, + []v1.Node{ + { + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeExternalIP, + Address: "1.2.3.4", + }, + { + Type: v1.NodeInternalIP, + Address: "2001:db8::4", + }, + }, + }, + }, + }, + []*endpoint.Endpoint{ + {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + {DNSName: "foo1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "foo1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + {DNSName: "foo2.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "foo2.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + {DNSName: "foo3.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "foo3.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, + }, + false, + }, { "annotated Headless services return dual-stack targets from node external IP if endpoints-type annotation is set and exposeInternalIPv6 flag set", "", diff --git c/source/source.go i/source/source.go index aaa2d1dc..6be7d031 100644 --- c/source/source.go +++ i/source/source.go @@ -38,6 +38,7 @@ const ( ingressHostnameSourceKey = annotations.IngressHostnameSourceKey controllerAnnotationValue = annotations.ControllerValue internalHostnameAnnotationKey = annotations.InternalHostnameKey + servicePodEndpointsKey = annotations.ServicePodEndpoints EndpointsTypeNodeExternalIP = "NodeExternalIP" EndpointsTypeHostIP = "HostIP"
61 lines
2.7 KiB
Go
61 lines
2.7 KiB
Go
/*
|
|
Copyright 2025 The Kubernetes Authors.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package annotations
|
|
|
|
import (
|
|
"math"
|
|
)
|
|
|
|
const (
|
|
// AnnotationKeyPrefix is set on all annotations consumed by external-dns (outside of user templates)
|
|
// to provide easy filtering.
|
|
AnnotationKeyPrefix = "external-dns.alpha.kubernetes.io/"
|
|
|
|
// CloudflareProxiedKey The annotation used for determining if traffic will go through Cloudflare
|
|
CloudflareProxiedKey = AnnotationKeyPrefix + "cloudflare-proxied"
|
|
CloudflareCustomHostnameKey = AnnotationKeyPrefix + "cloudflare-custom-hostname"
|
|
CloudflareRegionKey = AnnotationKeyPrefix + "cloudflare-region-key"
|
|
CloudflareRecordCommentKey = AnnotationKeyPrefix + "cloudflare-record-comment"
|
|
|
|
AWSPrefix = AnnotationKeyPrefix + "aws-"
|
|
SCWPrefix = AnnotationKeyPrefix + "scw-"
|
|
WebhookPrefix = AnnotationKeyPrefix + "webhook-"
|
|
CloudflarePrefix = AnnotationKeyPrefix + "cloudflare-"
|
|
|
|
TtlKey = AnnotationKeyPrefix + "ttl"
|
|
ttlMinimum = 1
|
|
ttlMaximum = math.MaxInt32
|
|
|
|
SetIdentifierKey = AnnotationKeyPrefix + "set-identifier"
|
|
AliasKey = AnnotationKeyPrefix + "alias"
|
|
TargetKey = AnnotationKeyPrefix + "target"
|
|
// The annotation used for figuring out which controller is responsible
|
|
ControllerKey = AnnotationKeyPrefix + "controller"
|
|
// The annotation used for defining the desired hostname
|
|
HostnameKey = AnnotationKeyPrefix + "hostname"
|
|
// The annotation used for specifying whether the public or private interface address is used
|
|
AccessKey = AnnotationKeyPrefix + "access"
|
|
// The annotation used for specifying the type of endpoints to use for headless services
|
|
EndpointsTypeKey = AnnotationKeyPrefix + "endpoints-type"
|
|
// 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 = AnnotationKeyPrefix + "ingress-hostname-source"
|
|
// The value of the controller annotation so that we feel responsible
|
|
ControllerValue = "dns-controller"
|
|
// The annotation used for defining the desired hostname
|
|
InternalHostnameKey = AnnotationKeyPrefix + "internal-hostname"
|
|
// When set on a service, per-pod DNS entries will be created.
|
|
ServicePodEndpoints = AnnotationKeyPrefix + "service-pod-endpoints"
|
|
)
|