diff --git a/api/v1alpha2/linodecluster_webhook.go b/api/v1alpha2/linodecluster_webhook.go index 62452e90..357ca4e8 100644 --- a/api/v1alpha2/linodecluster_webhook.go +++ b/api/v1alpha2/linodecluster_webhook.go @@ -97,6 +97,21 @@ func (r *LinodeCluster) validateLinodeClusterSpec(ctx context.Context, client Li errs = append(errs, err) } + if r.Spec.Network.LoadBalancerType == "dns" { + if r.Spec.Network.DNSRootDomain == "" { + errs = append(errs, &field.Error{ + Field: "dnsRootDomain needs to be set when LoadBalancer Type is DNS", + Type: field.ErrorTypeRequired, + }) + } + if r.Spec.Network.DNSUniqueIdentifier == "" { + errs = append(errs, &field.Error{ + Field: "dnsUniqueIdentifier needs to be set when LoadBalancer Type is DNS", + Type: field.ErrorTypeRequired, + }) + } + } + if len(errs) == 0 { return nil } diff --git a/api/v1alpha2/linodecluster_webhook_test.go b/api/v1alpha2/linodecluster_webhook_test.go index 297e2186..22147fa3 100644 --- a/api/v1alpha2/linodecluster_webhook_test.go +++ b/api/v1alpha2/linodecluster_webhook_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" @@ -110,3 +111,74 @@ func TestValidateCreate(t *testing.T) { ), ) } + +func TestValidateDNSLinodeCluster(t *testing.T) { + t.Parallel() + + var ( + validCluster = LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "example", + }, + Spec: LinodeClusterSpec{ + Region: "us-ord", + Network: NetworkSpec{ + LoadBalancerType: "dns", + DNSRootDomain: "test.net", + DNSUniqueIdentifier: "abc123", + }, + }, + } + noRootDomainCluster = LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "example", + }, + Spec: LinodeClusterSpec{ + Region: "us-ord", + Network: NetworkSpec{ + LoadBalancerType: "dns", + DNSRootDomain: "", + DNSUniqueIdentifier: "abc123", + }, + }, + } + noUniqueIDCluster = LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "example", + }, + Spec: LinodeClusterSpec{ + Region: "us-ord", + Network: NetworkSpec{ + LoadBalancerType: "dns", + DNSRootDomain: "test.net", + DNSUniqueIdentifier: "", + }, + }, + } + ) + + NewSuite(t, mock.MockLinodeClient{}).Run( + OneOf( + Path( + Call("valid", func(ctx context.Context, mck Mock) { + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + }), + Result("success", func(ctx context.Context, mck Mock) { + assert.NoError(t, validCluster.validateLinodeCluster(ctx, mck.LinodeClient)) + }), + ), + ), + OneOf( + Path(Call("no domain and unique id set", func(ctx context.Context, mck Mock) { + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + })), + ), + Result("error", func(ctx context.Context, mck Mock) { + require.ErrorContains(t, noRootDomainCluster.validateLinodeCluster(ctx, mck.LinodeClient), "dnsRootDomain") + require.ErrorContains(t, noUniqueIDCluster.validateLinodeCluster(ctx, mck.LinodeClient), "dnsUniqueIdentifier") + }), + ) +} diff --git a/docs/src/topics/flavors/dns-loadbalancing.md b/docs/src/topics/flavors/dns-loadbalancing.md new file mode 100644 index 00000000..17f82ddd --- /dev/null +++ b/docs/src/topics/flavors/dns-loadbalancing.md @@ -0,0 +1,43 @@ +# DNS based apiserver Load Balancing + +This flavor configures DNS records that resolve to the public (ipv4 and/or IPv6) IPs of the control plane nodes where the apiserver pods are running. No NodeBalancer will be created. +This needs the following to be set in the `LinodeCluster` spec under `network` +```bash +kind: LinodeCluster +spec: + network: + loadBalancerType: dns + dnsRootDomain: test.net + dnsUniqueIdentifier: abc123 +``` +Along with this, the `test.net` domain needs to be registered and also be pre-configured as a [domain on Linode CM](https://cloud.linode.com/domains). +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 + +With these changes, the controlPlaneEndpoint is set to `-.`. This will set as the server in the KUBECONFIG as well. +The controller will create A/AAAA and TXT records under [the Domains tab in the Linode Cloud Manager.](https://cloud.linode.com/domains) + + +## Specification +| Supported Control Plane | CNI | Default OS | Installs ClusterClass | IPv4 | IPv6 | +|-------------------------|--------|--------------|-----------------------|------|------| +| kubeadm | Cilium | Ubuntu 22.04 | No | Yes | Yes | + +## Prerequisites +[Quickstart](../getting-started.md) completed + +## Usage +1. Generate cluster yaml + ```bash + clusterctl generate cluster test-cluster \ + --kubernetes-version v1.29.1 \ + --infrastructure linode-linode \ + --control-plane-machine-count 3 --worker-machine-count 3 \ + --flavor -dns-loadbalancing > test-cluster.yaml + ``` +2. Apply cluster yaml + ```bash + kubectl apply -f test-cluster.yaml + ``` + +## Check +You should in a few moments see the records created and running a nslookup against the server endpoint should return a multianswer dns record