Skip to content

Commit b42aba6

Browse files
authored
[improvement] add override so users can set full dns name for controlplane endpoint (#442)
* add override so users can set full dns name for controlplane endpoint
1 parent f3bca93 commit b42aba6

File tree

14 files changed

+156
-41
lines changed

14 files changed

+156
-41
lines changed

api/v1alpha1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1alpha2/linodecluster_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ type NetworkSpec struct {
128128
// If not set, defaults to 30
129129
// +optional
130130
DNSTTLSec int `json:"dnsTTLsec,omitempty"`
131+
// DNSSubDomainOverride is used to override CAPL's construction of the controlplane endpoint
132+
// If set, this will override the DNS subdomain from <clustername>-<uniqueid>.<rootdomain> to <overridevalue>.<rootdomain>
133+
// +optional
134+
DNSSubDomainOverride string `json:"dnsSubDomainOverride,omitempty"`
131135
// apiserverLoadBalancerPort used by the api server. It must be valid ports range (1-65535).
132136
// If omitted, default value is 6443.
133137
// +kubebuilder:validation:Minimum=1

api/v1alpha2/linodecluster_webhook.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,6 @@ func (r *LinodeCluster) validateLinodeClusterSpec(ctx context.Context, client Li
104104
Type: field.ErrorTypeRequired,
105105
})
106106
}
107-
if r.Spec.Network.DNSUniqueIdentifier == "" {
108-
errs = append(errs, &field.Error{
109-
Field: "dnsUniqueIdentifier needs to be set when LoadBalancer Type is DNS",
110-
Type: field.ErrorTypeRequired,
111-
})
112-
}
113107
}
114108

115109
if len(errs) == 0 {

api/v1alpha2/linodecluster_webhook_test.go

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -130,31 +130,15 @@ func TestValidateDNSLinodeCluster(t *testing.T) {
130130
},
131131
},
132132
}
133-
noRootDomainCluster = LinodeCluster{
133+
inValidCluster = LinodeCluster{
134134
ObjectMeta: metav1.ObjectMeta{
135135
Name: "example",
136136
Namespace: "example",
137137
},
138138
Spec: LinodeClusterSpec{
139139
Region: "us-ord",
140140
Network: NetworkSpec{
141-
LoadBalancerType: "dns",
142-
DNSRootDomain: "",
143-
DNSUniqueIdentifier: "abc123",
144-
},
145-
},
146-
}
147-
noUniqueIDCluster = LinodeCluster{
148-
ObjectMeta: metav1.ObjectMeta{
149-
Name: "example",
150-
Namespace: "example",
151-
},
152-
Spec: LinodeClusterSpec{
153-
Region: "us-ord",
154-
Network: NetworkSpec{
155-
LoadBalancerType: "dns",
156-
DNSRootDomain: "test.net",
157-
DNSUniqueIdentifier: "",
141+
LoadBalancerType: "dns",
158142
},
159143
},
160144
}
@@ -172,13 +156,12 @@ func TestValidateDNSLinodeCluster(t *testing.T) {
172156
),
173157
),
174158
OneOf(
175-
Path(Call("no domain and unique id set", func(ctx context.Context, mck Mock) {
159+
Path(Call("no root domain set", func(ctx context.Context, mck Mock) {
176160
mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
177161
})),
178162
),
179163
Result("error", func(ctx context.Context, mck Mock) {
180-
require.ErrorContains(t, noRootDomainCluster.validateLinodeCluster(ctx, mck.LinodeClient), "dnsRootDomain")
181-
require.ErrorContains(t, noUniqueIDCluster.validateLinodeCluster(ctx, mck.LinodeClient), "dnsUniqueIdentifier")
164+
require.ErrorContains(t, inValidCluster.validateLinodeCluster(ctx, mck.LinodeClient), "dnsRootDomain")
182165
}),
183166
)
184167
}

cloud/services/domains.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ func EnsureAkamaiDNSEntries(ctx context.Context, mscope *scope.MachineScope, ope
7979
linodeCluster := mscope.LinodeCluster
8080
linodeClusterNetworkSpec := linodeCluster.Spec.Network
8181
rootDomain := linodeClusterNetworkSpec.DNSRootDomain
82-
fqdn := linodeCluster.Name + "-" + linodeClusterNetworkSpec.DNSUniqueIdentifier + "." + rootDomain
8382
akaDNSClient := mscope.AkamaiDomainsClient
83+
fqdn := getSubDomain(mscope) + "." + rootDomain
8484

8585
for _, dnsEntry := range dnsEntries {
8686
recordBody, err := akaDNSClient.GetRecord(ctx, rootDomain, fqdn, string(dnsEntry.DNSRecordType))
@@ -150,7 +150,7 @@ func (d *DNSEntries) getDNSEntriesToEnsure(mscope *scope.MachineScope) ([]DNSOpt
150150
if mscope.LinodeMachine.Status.Addresses == nil {
151151
return nil, fmt.Errorf("no addresses available on the LinodeMachine resource")
152152
}
153-
domainHostname := mscope.LinodeCluster.ObjectMeta.Name + "-" + mscope.LinodeCluster.Spec.Network.DNSUniqueIdentifier
153+
subDomain := getSubDomain(mscope)
154154

155155
for _, IPs := range mscope.LinodeMachine.Status.Addresses {
156156
recordType := linodego.RecordTypeA
@@ -164,9 +164,9 @@ func (d *DNSEntries) getDNSEntriesToEnsure(mscope *scope.MachineScope) ([]DNSOpt
164164
if !addr.Is4() {
165165
recordType = linodego.RecordTypeAAAA
166166
}
167-
d.options = append(d.options, DNSOptions{domainHostname, IPs.Address, recordType, dnsTTLSec})
167+
d.options = append(d.options, DNSOptions{subDomain, IPs.Address, recordType, dnsTTLSec})
168168
}
169-
d.options = append(d.options, DNSOptions{domainHostname, mscope.LinodeMachine.Name, linodego.RecordTypeTXT, dnsTTLSec})
169+
d.options = append(d.options, DNSOptions{subDomain, mscope.LinodeMachine.Name, linodego.RecordTypeTXT, dnsTTLSec})
170170

171171
return d.options, nil
172172
}
@@ -273,3 +273,16 @@ func IsDomainRecordOwner(ctx context.Context, mscope *scope.MachineScope, hostna
273273

274274
return true, nil
275275
}
276+
277+
func getSubDomain(mscope *scope.MachineScope) (subDomain string) {
278+
if mscope.LinodeCluster.Spec.Network.DNSSubDomainOverride != "" {
279+
subDomain = mscope.LinodeCluster.Spec.Network.DNSSubDomainOverride
280+
} else {
281+
uniqueID := ""
282+
if mscope.LinodeCluster.Spec.Network.DNSUniqueIdentifier != "" {
283+
uniqueID = "-" + mscope.LinodeCluster.Spec.Network.DNSUniqueIdentifier
284+
}
285+
subDomain = mscope.LinodeCluster.Name + uniqueID
286+
}
287+
return subDomain
288+
}

config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ spec:
373373
DNSRootDomain is the root domain used to create a DNS entry for the control-plane endpoint
374374
Ignored if the LoadBalancerType is set to anything other than dns
375375
type: string
376+
dnsSubDomainOverride:
377+
description: |-
378+
DNSSubDomainOverride is used to override CAPL's construction of the controlplane endpoint
379+
If set, this will override the DNS subdomain from <clustername>-<uniqueid>.<rootdomain> to <overridevalue>.<rootdomain>
380+
type: string
376381
dnsTTLsec:
377382
description: |-
378383
DNSTTLSec is the TTL for the domain record

config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclustertemplates.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,11 @@ spec:
301301
DNSRootDomain is the root domain used to create a DNS entry for the control-plane endpoint
302302
Ignored if the LoadBalancerType is set to anything other than dns
303303
type: string
304+
dnsSubDomainOverride:
305+
description: |-
306+
DNSSubDomainOverride is used to override CAPL's construction of the controlplane endpoint
307+
If set, this will override the DNS subdomain from <clustername>-<uniqueid>.<rootdomain> to <overridevalue>.<rootdomain>
308+
type: string
304309
dnsTTLsec:
305310
description: |-
306311
DNSTTLSec is the TTL for the domain record

controller/linodecluster_controller.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,24 @@ func (r *LinodeClusterReconciler) handleNBCreate(ctx context.Context, logger log
232232
}
233233

234234
func (r *LinodeClusterReconciler) handleDNS(clusterScope *scope.ClusterScope) {
235-
domainName := clusterScope.LinodeCluster.ObjectMeta.Name + "-" + clusterScope.LinodeCluster.Spec.Network.DNSUniqueIdentifier + "." + clusterScope.LinodeCluster.Spec.Network.DNSRootDomain
235+
clusterSpec := clusterScope.LinodeCluster.Spec
236+
clusterMetadata := clusterScope.LinodeCluster.ObjectMeta
237+
uniqueID := ""
238+
if clusterSpec.Network.DNSUniqueIdentifier != "" {
239+
uniqueID = "-" + clusterSpec.Network.DNSUniqueIdentifier
240+
}
241+
subDomain := clusterMetadata.Name + uniqueID
242+
243+
if clusterScope.LinodeCluster.Spec.Network.DNSSubDomainOverride != "" {
244+
subDomain = clusterScope.LinodeCluster.Spec.Network.DNSSubDomainOverride
245+
}
246+
dnsHost := subDomain + "." + clusterSpec.Network.DNSRootDomain
236247
apiLBPort := services.DefaultApiserverLBPort
237248
if clusterScope.LinodeCluster.Spec.Network.ApiserverLoadBalancerPort != 0 {
238249
apiLBPort = clusterScope.LinodeCluster.Spec.Network.ApiserverLoadBalancerPort
239250
}
240251
clusterScope.LinodeCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
241-
Host: domainName,
252+
Host: dnsHost,
242253
Port: int32(apiLBPort),
243254
}
244255
}

controller/linodecluster_controller_test.go

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ var _ = Describe("cluster-lifecycle", Ordered, Label("cluster", "cluster-lifecyc
4747
controlPlaneEndpointHost := "10.0.0.1"
4848
controlPlaneEndpointPort := 6443
4949
clusterName := "cluster-lifecycle"
50-
clusterNameSpace := "default"
5150
ownerRef := metav1.OwnerReference{
5251
Name: clusterName,
5352
APIVersion: "cluster.x-k8s.io/v1beta1",
@@ -57,7 +56,7 @@ var _ = Describe("cluster-lifecycle", Ordered, Label("cluster", "cluster-lifecyc
5756
ownerRefs := []metav1.OwnerReference{ownerRef}
5857
metadata := metav1.ObjectMeta{
5958
Name: clusterName,
60-
Namespace: clusterNameSpace,
59+
Namespace: defaultNamespace,
6160
OwnerReferences: ownerRefs,
6261
}
6362
linodeCluster := infrav1alpha2.LinodeCluster{
@@ -258,7 +257,6 @@ var _ = Describe("cluster-lifecycle-dns", Ordered, Label("cluster", "cluster-lif
258257
controlPlaneEndpointHost := "cluster-lifecycle-dns-abc123.lkedevs.net"
259258
controlPlaneEndpointPort := 1000
260259
clusterName := "cluster-lifecycle-dns"
261-
clusterNameSpace := "default"
262260
ownerRef := metav1.OwnerReference{
263261
Name: clusterName,
264262
APIVersion: "cluster.x-k8s.io/v1beta1",
@@ -268,7 +266,7 @@ var _ = Describe("cluster-lifecycle-dns", Ordered, Label("cluster", "cluster-lif
268266
ownerRefs := []metav1.OwnerReference{ownerRef}
269267
metadata := metav1.ObjectMeta{
270268
Name: clusterName,
271-
Namespace: clusterNameSpace,
269+
Namespace: defaultNamespace,
272270
OwnerReferences: ownerRefs,
273271
}
274272

@@ -338,7 +336,6 @@ var _ = Describe("cluster-lifecycle-dns", Ordered, Label("cluster", "cluster-lif
338336
var _ = Describe("cluster-delete", Ordered, Label("cluster", "cluster-delete"), func() {
339337
nodebalancerID := 1
340338
clusterName := "cluster-delete"
341-
clusterNameSpace := "default"
342339
ownerRef := metav1.OwnerReference{
343340
Name: clusterName,
344341
APIVersion: "cluster.x-k8s.io/v1beta1",
@@ -348,7 +345,7 @@ var _ = Describe("cluster-delete", Ordered, Label("cluster", "cluster-delete"),
348345
ownerRefs := []metav1.OwnerReference{ownerRef}
349346
metadata := metav1.ObjectMeta{
350347
Name: clusterName,
351-
Namespace: clusterNameSpace,
348+
Namespace: defaultNamespace,
352349
OwnerReferences: ownerRefs,
353350
}
354351

@@ -421,3 +418,83 @@ var _ = Describe("cluster-delete", Ordered, Label("cluster", "cluster-delete"),
421418
}),
422419
)
423420
})
421+
422+
var _ = Describe("dns-override-endpoint", Ordered, Label("cluster", "dns-override-endpoint"), func() {
423+
subDomainOverRide := "dns-override-endpoint"
424+
controlPlaneEndpointHost := "dns-override-endpoint.lkedevs.net"
425+
controlPlaneEndpointPort := 1000
426+
clusterName := "dns-override-endpoint"
427+
ownerRef := metav1.OwnerReference{
428+
Name: clusterName,
429+
APIVersion: "cluster.x-k8s.io/v1beta1",
430+
Kind: "Cluster",
431+
UID: "00000000-000-0000-0000-000000000000",
432+
}
433+
ownerRefs := []metav1.OwnerReference{ownerRef}
434+
metadata := metav1.ObjectMeta{
435+
Name: clusterName,
436+
Namespace: defaultNamespace,
437+
OwnerReferences: ownerRefs,
438+
}
439+
440+
linodeCluster := infrav1alpha2.LinodeCluster{
441+
ObjectMeta: metadata,
442+
Spec: infrav1alpha2.LinodeClusterSpec{
443+
Region: "us-ord",
444+
Network: infrav1alpha2.NetworkSpec{
445+
ApiserverLoadBalancerPort: controlPlaneEndpointPort,
446+
LoadBalancerType: "dns",
447+
DNSSubDomainOverride: subDomainOverRide,
448+
DNSRootDomain: "lkedevs.net",
449+
},
450+
},
451+
}
452+
453+
ctlrSuite := NewControllerSuite(GinkgoT(), mock.MockLinodeClient{})
454+
reconciler := LinodeClusterReconciler{}
455+
cScope := &scope.ClusterScope{}
456+
clusterKey := client.ObjectKeyFromObject(&linodeCluster)
457+
458+
BeforeAll(func(ctx SpecContext) {
459+
cScope.Client = k8sClient
460+
Expect(k8sClient.Create(ctx, &linodeCluster)).To(Succeed())
461+
})
462+
463+
ctlrSuite.BeforeEach(func(ctx context.Context, mck Mock) {
464+
reconciler.Recorder = mck.Recorder()
465+
466+
Expect(k8sClient.Get(ctx, clusterKey, &linodeCluster)).To(Succeed())
467+
cScope.LinodeCluster = &linodeCluster
468+
469+
// Create patch helper with latest state of resource.
470+
// This is only needed when relying on envtest's k8sClient.
471+
patchHelper, err := patch.NewHelper(&linodeCluster, k8sClient)
472+
Expect(err).NotTo(HaveOccurred())
473+
cScope.PatchHelper = patchHelper
474+
})
475+
476+
ctlrSuite.Run(
477+
OneOf(
478+
Path(
479+
Call("cluster with dns loadbalancing is created", func(ctx context.Context, mck Mock) {
480+
cScope.LinodeClient = mck.LinodeClient
481+
}),
482+
Result("cluster created", func(ctx context.Context, mck Mock) {
483+
_, err := reconciler.reconcile(ctx, cScope, logr.Logger{})
484+
Expect(err).NotTo(HaveOccurred())
485+
486+
By("checking ready conditions")
487+
clusterKey := client.ObjectKeyFromObject(&linodeCluster)
488+
Expect(k8sClient.Get(ctx, clusterKey, &linodeCluster)).To(Succeed())
489+
Expect(linodeCluster.Status.Ready).To(BeTrue())
490+
Expect(linodeCluster.Status.Conditions).To(HaveLen(1))
491+
Expect(linodeCluster.Status.Conditions[0].Type).To(Equal(clusterv1.ReadyCondition))
492+
493+
By("checking controlPlaneEndpoint/NB host and port")
494+
Expect(linodeCluster.Spec.ControlPlaneEndpoint.Host).To(Equal(controlPlaneEndpointHost))
495+
Expect(linodeCluster.Spec.ControlPlaneEndpoint.Port).To(Equal(int32(controlPlaneEndpointPort)))
496+
}),
497+
),
498+
),
499+
)
500+
})

docs/src/topics/flavors/dns-loadbalancing.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This flavor configures DNS records that resolve to the public (ipv4 and/or IPv6)
44
The following need to be set in the `LinodeCluster` spec under `network`
55
```bash
66
kind: LinodeCluster
7+
metadata:
8+
name: test-cluster
79
spec:
810
network:
911
loadBalancerType: dns
@@ -14,6 +16,8 @@ We support DNS management with both, [Linode Cloud Manager](https://cloud.linode
1416
We default to the linode provider but to use akamai, you'll need
1517
```bash
1618
kind: LinodeCluster
19+
metadata:
20+
name: test-cluster
1721
spec:
1822
network:
1923
loadBalancerType: dns
@@ -22,10 +26,24 @@ spec:
2226
dnsProvider: akamai
2327
```
2428
Along with this, the `test.net` domain needs to be registered and also be pre-configured as a domain on Linode or zone on Akamai.
25-
With these changes, the controlPlaneEndpoint is set to `<domain-name>-<uniqueid>.<root-domain>`. This will set as the server in the KUBECONFIG as well.
29+
With these changes, the controlPlaneEndpoint is set to `test-cluster-abc123.test.net`. This will be set as the server in the KUBECONFIG as well.
30+
If users wish to override the subdomain format with something custom, they can pass in the override using the env var `DNS_SUBDOMAIN_OVERRIDE`.
31+
```bash
32+
kind: LinodeCluster
33+
metadata:
34+
name: test-cluster
35+
spec:
36+
network:
37+
loadBalancerType: dns
38+
dnsRootDomain: test.net
39+
dnsProvider: akamai
40+
dnsSubDomainOverride: my-special-overide
41+
```
42+
This will replace the subdomain creation from `test-cluster-abc123.test.net` to make the url `my-special-overide.test.net`.
43+
2644
The controller will create A/AAAA and TXT records under [the Domains tab in the Linode Cloud Manager.](https://cloud.linode.com/domains) or Akamai Edge DNS depending on the provider.
2745

28-
### Linode Domains:
46+
### Linode Domains:
2947
Using the `LINODE_DNS_TOKEN` env var, you can pass the [API token of a different account](https://cloud.linode.com/profile/tokens) if the Domain has been created in another acount under Linode CM:
3048

3149
```bash

0 commit comments

Comments
 (0)