-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(source): optional exclusion of unschedulable nodes #5045
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
Changes from all commits
6494085
1f80e31
7fcb9f4
e95f9f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,9 @@ The node source adds an `A` record per each node `externalIP` (if not found, any | |
It also adds an `AAAA` record per each node IPv6 `internalIP`. Refer to the [IPv6 Behavior](#ipv6-behavior) section for more details. | ||
The TTL of the records can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation. | ||
|
||
Nodes marked as **Unschedulable** as per [core/v1/NodeSpec](https://pkg.go.dev/k8s.io/[email protected]/core/v1#NodeSpec) are excluded. | ||
This avoid exposing Unhealthy, NotReady or SchedulingDisabled (cordon) nodes. | ||
Nodes marked as **Unschedulable** as per [core/v1/NodeSpec](https://pkg.go.dev/k8s.io/[email protected]/core/v1#NodeSpec) are excluded by default. | ||
As such, no DNS records are created for Unhealthy, NotReady or SchedulingDisabled (cordon) nodes (and existing ones are removed). | ||
In case you want to override the default, for example if you manage per-host DNS records via ExternalDNS, you can specify `--no-exclude-unschedulable` to always expose nodes no matter their status. | ||
|
||
## IPv6 Behavior | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,7 @@ func testNodeSourceNewNodeSource(t *testing.T) { | |
ti.fqdnTemplate, | ||
labels.Everything(), | ||
true, | ||
true, | ||
) | ||
|
||
if ti.expectError { | ||
|
@@ -99,18 +100,21 @@ func testNodeSourceEndpoints(t *testing.T) { | |
t.Parallel() | ||
|
||
for _, tc := range []struct { | ||
title string | ||
annotationFilter string | ||
labelSelector string | ||
fqdnTemplate string | ||
nodeName string | ||
nodeAddresses []v1.NodeAddress | ||
labels map[string]string | ||
annotations map[string]string | ||
exposeInternalIPv6 bool // default to true for this version. Change later when the next minor version is released. | ||
unschedulable bool // default to false | ||
expected []*endpoint.Endpoint | ||
expectError bool | ||
title string | ||
annotationFilter string | ||
labelSelector string | ||
fqdnTemplate string | ||
nodeName string | ||
nodeAddresses []v1.NodeAddress | ||
labels map[string]string | ||
annotations map[string]string | ||
excludeUnschedulable bool // default to false | ||
exposeInternalIPv6 bool // default to true for this version. Change later when the next minor version is released. | ||
unschedulable bool // default to false | ||
expected []*endpoint.Endpoint | ||
expectError bool | ||
expectedLogs []string | ||
expectedAbsentLogs []string | ||
}{ | ||
{ | ||
title: "node with short hostname returns one endpoint", | ||
|
@@ -363,16 +367,40 @@ func testNodeSourceEndpoints(t *testing.T) { | |
}, | ||
}, | ||
{ | ||
title: "unschedulable node return nothing", | ||
nodeName: "node1", | ||
exposeInternalIPv6: true, | ||
nodeAddresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}}, | ||
unschedulable: true, | ||
expected: []*endpoint.Endpoint{}, | ||
title: "unschedulable node return nothing with excludeUnschedulable=true", | ||
nodeName: "node1", | ||
exposeInternalIPv6: true, | ||
nodeAddresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}}, | ||
unschedulable: true, | ||
excludeUnschedulable: true, | ||
expected: []*endpoint.Endpoint{}, | ||
expectedLogs: []string{ | ||
"Skipping node node1 because it is unschedulable", | ||
}, | ||
}, | ||
{ | ||
title: "unschedulable node returns node with excludeUnschedulable=false", | ||
nodeName: "node1", | ||
nodeAddresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}}, | ||
unschedulable: true, | ||
excludeUnschedulable: false, | ||
expected: []*endpoint.Endpoint{ | ||
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}}, | ||
}, | ||
expectedAbsentLogs: []string{ | ||
"Skipping node node1 because it is unschedulable", | ||
}, | ||
}, | ||
} { | ||
tc := tc | ||
t.Run(tc.title, func(t *testing.T) { | ||
var buf *bytes.Buffer | ||
if len(tc.expectedLogs) == 0 && len(tc.expectedAbsentLogs) == 0 { | ||
t.Parallel() | ||
} else { | ||
buf = testutils.LogsToBuffer(log.DebugLevel, t) | ||
} | ||
Comment on lines
+397
to
+402
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a random data race since this PR has been merged, which can be seen on other PRs. @ivankatliarchuk Do you think this section can be a potential cause ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I opened a small PR which should fix the CI, see #5268 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll review the whole |
||
|
||
labelSelector := labels.Everything() | ||
if tc.labelSelector != "" { | ||
var err error | ||
|
@@ -408,6 +436,7 @@ func testNodeSourceEndpoints(t *testing.T) { | |
tc.fqdnTemplate, | ||
labelSelector, | ||
tc.exposeInternalIPv6, | ||
tc.excludeUnschedulable, | ||
) | ||
require.NoError(t, err) | ||
|
||
|
@@ -420,24 +449,33 @@ func testNodeSourceEndpoints(t *testing.T) { | |
|
||
// Validate returned endpoints against desired endpoints. | ||
validateEndpoints(t, endpoints, tc.expected) | ||
|
||
for _, entry := range tc.expectedLogs { | ||
assert.Contains(t, buf.String(), entry) | ||
} | ||
|
||
for _, entry := range tc.expectedAbsentLogs { | ||
assert.NotContains(t, buf.String(), entry) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func testNodeEndpointsWithIPv6(t *testing.T) { | ||
for _, tc := range []struct { | ||
title string | ||
annotationFilter string | ||
labelSelector string | ||
fqdnTemplate string | ||
nodeName string | ||
nodeAddresses []v1.NodeAddress | ||
labels map[string]string | ||
annotations map[string]string | ||
exposeInternalIPv6 bool // default to true for this version. Change later when the next minor version is released. | ||
unschedulable bool // default to false | ||
expected []*endpoint.Endpoint | ||
expectError bool | ||
title string | ||
annotationFilter string | ||
labelSelector string | ||
fqdnTemplate string | ||
nodeName string | ||
nodeAddresses []v1.NodeAddress | ||
labels map[string]string | ||
annotations map[string]string | ||
excludeUnschedulable bool // defaults to false | ||
exposeInternalIPv6 bool // default to true for this version. Change later when the next minor version is released. | ||
unschedulable bool // default to false | ||
expected []*endpoint.Endpoint | ||
expectError bool | ||
}{ | ||
{ | ||
title: "node with only internal IPs should return internal IPvs irrespective of exposeInternalIPv6", | ||
|
@@ -516,6 +554,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) { | |
tc.fqdnTemplate, | ||
labelSelector, | ||
tc.exposeInternalIPv6, | ||
tc.excludeUnschedulable, | ||
) | ||
require.NoError(t, err) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add tests that capture debug logging?
external-dns/internal/testutils/log.go
Line 36 in e64e536
You could search for example usages in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late response, but I've been pretty busy the last couple weeks.
I've now added an example of how this could be implemented in the existing test method (which would allow the other testcases to check for logs, as well. Not quite ideal, as this requires disabling parallelism for these testcases, but the alternative would be having to duplicate more of the test logic, which I'm not really a fan of, either.