Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Headless service: allow to specify target as NodeExternalIP or by annotation #2115

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/tutorials/hostport.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
36 changes: 26 additions & 10 deletions source/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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...)
}
}
}
Expand Down
145 changes: 141 additions & 4 deletions source/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}{
Expand All @@ -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",
},
Expand All @@ -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"}},
Expand All @@ -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",
},
Expand All @@ -2073,6 +2081,7 @@ func TestHeadlessServices(t *testing.T) {
[]string{"foo-0", "foo-1"},
[]bool{true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{},
false,
},
Expand All @@ -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",
},
Expand All @@ -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)},
Expand All @@ -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",
},
Expand All @@ -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"}},
Expand All @@ -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",
},
Expand All @@ -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"}},
Expand All @@ -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",
},
Expand All @@ -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"}},
},
Expand All @@ -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",
},
Expand All @@ -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) {
Expand All @@ -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{},
}
Expand All @@ -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],
},
}

Expand Down Expand Up @@ -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(
Expand Down
Loading