mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
Headless service: allow to specify target as NodeExternalIP or by annotation
If external-dns.alpha.kubernetes.io/target annotation is present on a pod, it's value will be used as the target for the headless service. If annotation external-dns.alpha.kubernetes.io/access=public is present, NodeExternalIP of the node running the pod is used as the target for the headless service.
This commit is contained in:
parent
2d08e66611
commit
ea45b03972
@ -198,3 +198,35 @@ kafka-1.ksvc.example.org
|
||||
kafka-2.ksvc.example.org
|
||||
```
|
||||
|
||||
#### Using pods' HostIPs as targets
|
||||
|
||||
Add the following annotation to your `Service`:
|
||||
|
||||
```yaml
|
||||
external-dns.alpha.kubernetes.io/endpoints-type: HostIP
|
||||
```
|
||||
|
||||
external-dns will now publish the value of the `.status.hostIP` field of the pods backing your `Service`.
|
||||
```
|
||||
|
||||
#### Using node external IPs as targets
|
||||
|
||||
Add the following annotation to your `Service`:
|
||||
|
||||
```yaml
|
||||
external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP
|
||||
```
|
||||
|
||||
external-dns will now publish the node external IP (`.status.addresses` entries of with `type: NodeExternalIP`) of the nodes on which the pods backing your `Service` are running.
|
||||
|
||||
#### Using pod annotations to specify target IPs
|
||||
|
||||
Add the following annotation to the **pods** backing your `Service`:
|
||||
|
||||
```yaml
|
||||
external-dns.alpha.kubernetes.io/target: "1.2.3.4"
|
||||
```
|
||||
|
||||
external-dns will publish the IP specified in the annotation of each pod instead of using the podIP advertised by Kubernetes.
|
||||
|
||||
This can be useful e.g. if you are NATing public IPs onto your pod IPs and want to publish these in DNS.
|
||||
|
@ -259,11 +259,13 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
|
||||
|
||||
pods, err := sc.podInformer.Lister().Pods(svc.Namespace).List(selector)
|
||||
if err != nil {
|
||||
log.Errorf("List Pods of service[%s] error:%v", svc.GetName(), err)
|
||||
log.Errorf("List pods of service[%s] error: %v", svc.GetName(), err)
|
||||
return endpoints
|
||||
}
|
||||
|
||||
targetsByHeadlessDomain := make(map[string][]string)
|
||||
endpointsType := getEndpointsTypeFromAnnotations(svc.Annotations)
|
||||
|
||||
targetsByHeadlessDomain := make(map[string]endpoint.Targets)
|
||||
for _, subset := range endpointsObject.Subsets {
|
||||
addresses := subset.Addresses
|
||||
if svc.Spec.PublishNotReadyAddresses || sc.alwaysPublishNotReadyAddresses {
|
||||
@ -294,15 +296,29 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
|
||||
}
|
||||
|
||||
for _, headlessDomain := range headlessDomains {
|
||||
var ep string
|
||||
if sc.publishHostIP {
|
||||
ep = pod.Status.HostIP
|
||||
log.Debugf("Generating matching endpoint %s with HostIP %s", headlessDomain, ep)
|
||||
} else {
|
||||
ep = address.IP
|
||||
log.Debugf("Generating matching endpoint %s with EndpointAddress IP %s", headlessDomain, ep)
|
||||
targets := getTargetsFromTargetAnnotation(pod.Annotations)
|
||||
if len(targets) == 0 {
|
||||
if endpointsType == EndpointsTypeNodeExternalIP {
|
||||
node, err := sc.nodeInformer.Lister().Get(pod.Spec.NodeName)
|
||||
if err != nil {
|
||||
log.Errorf("Get node[%s] of pod[%s] error: %v; not adding any NodeExternalIP endpoints", pod.Spec.NodeName, pod.GetName(), err)
|
||||
return endpoints
|
||||
}
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == v1.NodeExternalIP {
|
||||
targets = endpoint.Targets{address.Address}
|
||||
log.Debugf("Generating matching endpoint %s with NodeExternalIP %s", headlessDomain, address.Address)
|
||||
}
|
||||
}
|
||||
} else if endpointsType == EndpointsTypeHostIP || sc.publishHostIP {
|
||||
targets = endpoint.Targets{pod.Status.HostIP}
|
||||
log.Debugf("Generating matching endpoint %s with HostIP %s", headlessDomain, pod.Status.HostIP)
|
||||
} else {
|
||||
targets = endpoint.Targets{address.IP}
|
||||
log.Debugf("Generating matching endpoint %s with EndpointAddress IP %s", headlessDomain, address.IP)
|
||||
}
|
||||
}
|
||||
targetsByHeadlessDomain[headlessDomain] = append(targetsByHeadlessDomain[headlessDomain], ep)
|
||||
targetsByHeadlessDomain[headlessDomain] = append(targetsByHeadlessDomain[headlessDomain], targets...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2008,15 +2008,18 @@ func TestHeadlessServices(t *testing.T) {
|
||||
fqdnTemplate string
|
||||
ignoreHostnameAnnotation bool
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
svcAnnotations map[string]string
|
||||
podAnnotations map[string]string
|
||||
clusterIP string
|
||||
podIPs []string
|
||||
hostIPs []string
|
||||
selector map[string]string
|
||||
lbs []string
|
||||
podnames []string
|
||||
hostnames []string
|
||||
podsReady []bool
|
||||
publishNotReadyAddresses bool
|
||||
nodes []v1.Node
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
}{
|
||||
@ -2033,8 +2036,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2043,6 +2048,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}},
|
||||
@ -2063,8 +2069,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2073,6 +2081,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{},
|
||||
false,
|
||||
},
|
||||
@ -2090,8 +2099,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
ttlAnnotationKey: "1",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2100,6 +2111,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
|
||||
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
|
||||
@ -2120,8 +2132,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2130,6 +2144,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]bool{true, false},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
@ -2149,8 +2164,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2159,6 +2176,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]bool{true, false},
|
||||
true,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}},
|
||||
@ -2179,8 +2197,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2189,6 +2209,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"", ""},
|
||||
[]bool{true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
|
||||
},
|
||||
@ -2207,8 +2228,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1", "1.1.1.1", "1.1.1.2"},
|
||||
[]string{"", "", ""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
@ -2217,11 +2240,120 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"", "", ""},
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"annotated Headless services return targets from pod annotation",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeClusterIP,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{"component": "foo"},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
},
|
||||
map[string]string{
|
||||
targetAnnotationKey: "1.2.3.4",
|
||||
},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1"},
|
||||
[]string{""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
[]string{},
|
||||
[]string{"foo"},
|
||||
[]string{"", "", ""},
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"annotated Headless services return targets from node external IP if endpoints-type annotation is set",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeClusterIP,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{"component": "foo"},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP,
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1"},
|
||||
[]string{""},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
[]string{},
|
||||
[]string{"foo"},
|
||||
[]string{"", "", ""},
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{
|
||||
{
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{
|
||||
Type: v1.NodeExternalIP,
|
||||
Address: "1.2.3.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"annotated Headless services return targets from hostIP if endpoints-type annotation is set",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeClusterIP,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{"component": "foo"},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
endpointsTypeAnnotationKey: EndpointsTypeHostIP,
|
||||
},
|
||||
map[string]string{},
|
||||
v1.ClusterIPNone,
|
||||
[]string{"1.1.1.1"},
|
||||
[]string{"1.2.3.4"},
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
[]string{},
|
||||
[]string{"foo"},
|
||||
[]string{"", "", ""},
|
||||
[]bool{true, true, true},
|
||||
false,
|
||||
[]v1.Node{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
@ -2241,7 +2373,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
Namespace: tc.svcNamespace,
|
||||
Name: tc.svcName,
|
||||
Labels: tc.labels,
|
||||
Annotations: tc.annotations,
|
||||
Annotations: tc.svcAnnotations,
|
||||
},
|
||||
Status: v1.ServiceStatus{},
|
||||
}
|
||||
@ -2259,10 +2391,11 @@ func TestHeadlessServices(t *testing.T) {
|
||||
Namespace: tc.svcNamespace,
|
||||
Name: podname,
|
||||
Labels: tc.labels,
|
||||
Annotations: tc.annotations,
|
||||
Annotations: tc.podAnnotations,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
PodIP: tc.podIPs[i],
|
||||
PodIP: tc.podIPs[i],
|
||||
HostIP: tc.hostIPs[i],
|
||||
},
|
||||
}
|
||||
|
||||
@ -2298,6 +2431,10 @@ func TestHeadlessServices(t *testing.T) {
|
||||
}
|
||||
_, err = kubernetes.CoreV1().Endpoints(tc.svcNamespace).Create(context.Background(), endpointsObject, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
for _, node := range tc.nodes {
|
||||
_, err = kubernetes.CoreV1().Nodes().Create(context.Background(), &node, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, _ := NewServiceSource(
|
||||
|
@ -44,6 +44,8 @@ const (
|
||||
hostnameAnnotationKey = "external-dns.alpha.kubernetes.io/hostname"
|
||||
// The annotation used for specifying whether the public or private interface address is used
|
||||
accessAnnotationKey = "external-dns.alpha.kubernetes.io/access"
|
||||
// The annotation used for specifying the type of endpoints to use for headless services
|
||||
endpointsTypeAnnotationKey = "external-dns.alpha.kubernetes.io/endpoints-type"
|
||||
// The annotation used for defining the desired ingress target
|
||||
targetAnnotationKey = "external-dns.alpha.kubernetes.io/target"
|
||||
// The annotation used for defining the desired DNS record TTL
|
||||
@ -59,6 +61,11 @@ const (
|
||||
internalHostnameAnnotationKey = "external-dns.alpha.kubernetes.io/internal-hostname"
|
||||
)
|
||||
|
||||
const (
|
||||
EndpointsTypeNodeExternalIP = "NodeExternalIP"
|
||||
EndpointsTypeHostIP = "HostIP"
|
||||
)
|
||||
|
||||
// Provider-specific annotations
|
||||
const (
|
||||
// The annotation used for determining if traffic will go through Cloudflare
|
||||
@ -151,6 +158,10 @@ func getAccessFromAnnotations(annotations map[string]string) string {
|
||||
return annotations[accessAnnotationKey]
|
||||
}
|
||||
|
||||
func getEndpointsTypeFromAnnotations(annotations map[string]string) string {
|
||||
return annotations[endpointsTypeAnnotationKey]
|
||||
}
|
||||
|
||||
func getInternalHostnamesFromAnnotations(annotations map[string]string) []string {
|
||||
internalHostnameAnnotation, exists := annotations[internalHostnameAnnotationKey]
|
||||
if !exists {
|
||||
|
Loading…
Reference in New Issue
Block a user