Skip to content

Commit 1c80718

Browse files
authored
[feat] DNS based LoadBalancing for the CAPL cluster API Server (#370)
DNS based LoadBalancing for the CAPL cluster API Server
1 parent 967a7d7 commit 1c80718

34 files changed

+2398
-382
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ docs/book
99
release/*
1010
templates/cluster-template*.yaml
1111
infrastructure-*-linode/*
12+
.envrc

.golangci.yml

+11-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ linters-settings:
2020
# [deprecated] comma-separated list of pairs of the form pkg:regex
2121
# the regex is used to ignore names within pkg. (default "fmt:.*").
2222
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
23-
ignore: fmt:.*,io/ioutil:^Read.*
23+
exclude-functions: fmt:.*,io/ioutil:^Read.*
2424

2525
gci:
2626
sections:
@@ -49,7 +49,7 @@ linters-settings:
4949

5050
cyclop:
5151
max-complexity: 15
52-
52+
5353
maligned:
5454
# print struct with more effective memory layout or not, false by default
5555
suggest-new: true
@@ -194,7 +194,6 @@ linters:
194194
- unused
195195
fast: false
196196

197-
198197
issues:
199198
# Excluding configuration per-path and per-linter
200199
exclude-rules:
@@ -208,7 +207,7 @@ issues:
208207
- gosec
209208
- exportloopref
210209
- unparam
211-
210+
212211
# Ease some gocritic warnings on test files.
213212
- path: _test\.go
214213
text: "(unnamedResult|exitAfterDefer)"
@@ -234,31 +233,31 @@ issues:
234233
# rather than using a pointer.
235234
- text: "(hugeParam|rangeValCopy):"
236235
linters:
237-
- gocritic
236+
- gocritic
238237

239238
# This "TestMain should call os.Exit to set exit code" warning is not clever
240239
# enough to notice that we call a helper method that calls os.Exit.
241240
- text: "SA3000:"
242241
linters:
243-
- staticcheck
242+
- staticcheck
244243

245244
- text: "k8s.io/api/core/v1"
246245
linters:
247-
- goimports
246+
- goimports
248247

249248
# This is a "potential hardcoded credentials" warning. It's triggered by
250249
# any variable with 'secret' in the same, and thus hits a lot of false
251250
# positives in Kubernetes land where a Secret is an object type.
252251
- text: "G101:"
253252
linters:
254-
- gosec
255-
- gas
253+
- gosec
254+
- gas
256255

257256
# This is an 'errors unhandled' warning that duplicates errcheck.
258257
- text: "G104:"
259258
linters:
260-
- gosec
261-
- gas
259+
- gosec
260+
- gas
262261

263262
# Independently from option `exclude` we use default exclude patterns,
264263
# it can be disabled by this option. To list all
@@ -281,4 +280,4 @@ issues:
281280
max-same-issues: 0
282281

283282
exclude-files:
284-
- "zz_generated\\..+\\.go$"
283+
- "zz_generated\\..+\\.go$"

Makefile

+1-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ gosec: ## Run gosec against code.
121121

122122
.PHONY: lint
123123
lint: ## Run lint against code.
124-
docker run --rm -w /workdir -v $(PWD):/workdir golangci/golangci-lint:v1.57.2 golangci-lint run -c .golangci.yml --fix
124+
docker run --rm -w /workdir -v $(PWD):/workdir golangci/golangci-lint:v1.59.1 golangci-lint run -c .golangci.yml --fix
125125

126126
.PHONY: nilcheck
127127
nilcheck: nilaway ## Run nil check against code.
@@ -152,7 +152,6 @@ e2etest: generate local-release local-deploy chainsaw
152152
GIT_REF=$(GIT_REF) $(CHAINSAW) test ./e2e --selector $(E2E_SELECTOR) $(E2E_FLAGS)
153153

154154
local-deploy: kind ctlptl tilt kustomize clusterctl
155-
@echo -n "LINODE_TOKEN=$(LINODE_TOKEN)" > config/default/.env.linode
156155
$(CTLPTL) apply -f .tilt/ctlptl-config.yaml
157156
$(TILT) ci -f Tiltfile
158157

@@ -204,7 +203,6 @@ endif
204203

205204
.PHONY: tilt-cluster
206205
tilt-cluster: ctlptl tilt kind clusterctl
207-
@echo -n "LINODE_TOKEN=$(LINODE_TOKEN)" > config/default/.env.linode
208206
$(CTLPTL) apply -f .tilt/ctlptl-config.yaml
209207
$(TILT) up --stream
210208

Tiltfile

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ manager_yaml = decode_yaml_stream(kustomize("config/default"))
128128
for resource in manager_yaml:
129129
if resource["metadata"]["name"] == "capl-manager-credentials":
130130
resource["stringData"]["apiToken"] = os.getenv("LINODE_TOKEN")
131+
resource["stringData"]["dnsToken"] = os.getenv("LINODE_DNS_TOKEN")
131132
if (
132133
resource["kind"] == "CustomResourceDefinition"
133134
and resource["spec"]["group"] == "infrastructure.cluster.x-k8s.io"

api/v1alpha2/linodecluster_types.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,29 @@ func (lm *LinodeCluster) SetConditions(conditions clusterv1.Conditions) {
104104
// NetworkSpec encapsulates Linode networking resources.
105105
type NetworkSpec struct {
106106
// LoadBalancerType is the type of load balancer to use, defaults to NodeBalancer if not otherwise set
107-
// +kubebuilder:validation:Enum=NodeBalancer
107+
// +kubebuilder:validation:Enum=NodeBalancer;dns
108108
// +optional
109109
LoadBalancerType string `json:"loadBalancerType,omitempty"`
110+
// DNSProvider is provider who manages the domain
111+
// Ignored if the LoadBalancerType is set to anything other than dns
112+
// If not set, defaults linode dns
113+
// +optional
114+
DNSProvider int `json:"dnsProvider,omitempty"`
115+
// DNSRootDomain is the root domain used to create a DNS entry for the control-plane endpoint
116+
// Ignored if the LoadBalancerType is set to anything other than dns
117+
// +optional
118+
DNSRootDomain string `json:"dnsRootDomain,omitempty"`
119+
// DNSUniqueIdentifier is the unique identifier for the DNS. This let clusters with the same name have unique
120+
// DNS record
121+
// Ignored if the LoadBalancerType is set to anything other than dns
122+
// If not set, CAPL will create a unique identifier for you
123+
// +optional
124+
DNSUniqueIdentifier string `json:"dnsUniqueIdentifier,omitempty"`
125+
// DNSTTLSec is the TTL for the domain record
126+
// Ignored if the LoadBalancerType is set to anything other than dns
127+
// If not set, defaults to 30
128+
// +optional
129+
DNSTTLSec int `json:"dnsTTLsec,omitempty"`
110130
// apiserverLoadBalancerPort used by the api server. It must be valid ports range (1-65535).
111131
// If omitted, default value is 6443.
112132
// +kubebuilder:validation:Minimum=1

clients/clients.go

+10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type LinodeClient interface {
1515
LinodeInstanceClient
1616
LinodeVPCClient
1717
LinodeObjectStorageClient
18+
LinodeDNSClient
1819
}
1920

2021
// LinodeInstanceClient defines the methods that interact with Linode's Instance service.
@@ -64,6 +65,15 @@ type LinodeObjectStorageClient interface {
6465
DeleteObjectStorageKey(ctx context.Context, keyID int) error
6566
}
6667

68+
// LinodeDNSClient defines the methods that interact with Linode's Domains service.
69+
type LinodeDNSClient interface {
70+
CreateDomainRecord(ctx context.Context, domainID int, recordReq linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error)
71+
UpdateDomainRecord(ctx context.Context, domainID int, domainRecordID int, recordReq linodego.DomainRecordUpdateOptions) (*linodego.DomainRecord, error)
72+
ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]linodego.DomainRecord, error)
73+
ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]linodego.Domain, error)
74+
DeleteDomainRecord(ctx context.Context, domainID int, domainRecordID int) error
75+
}
76+
6777
type K8sClient interface {
6878
client.Client
6979
}

cloud/scope/cluster.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ func NewClusterScope(ctx context.Context, apiKey string, params ClusterScopePara
5757

5858
// Override the controller credentials with ones from the Cluster's Secret reference (if supplied).
5959
if params.LinodeCluster.Spec.CredentialsRef != nil {
60-
data, err := getCredentialDataFromRef(ctx, params.Client, *params.LinodeCluster.Spec.CredentialsRef, params.LinodeCluster.GetNamespace())
60+
// TODO: This key is hard-coded (for now) to match the externally-managed `manager-credentials` Secret.
61+
apiToken, err := getCredentialDataFromRef(ctx, params.Client, *params.LinodeCluster.Spec.CredentialsRef, params.LinodeCluster.GetNamespace(), "apiToken")
6162
if err != nil {
6263
return nil, fmt.Errorf("credentials from secret ref: %w", err)
6364
}
64-
apiKey = string(data)
65+
apiKey = string(apiToken)
6566
}
6667
linodeClient, err := CreateLinodeClient(apiKey, defaultClientTimeout)
6768
if err != nil {

cloud/scope/common.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,14 @@ func CreateLinodeClient(apiKey string, timeout time.Duration, opts ...Option) (L
6363
), nil
6464
}
6565

66-
func getCredentialDataFromRef(ctx context.Context, crClient K8sClient, credentialsRef corev1.SecretReference, defaultNamespace string) ([]byte, error) {
66+
func getCredentialDataFromRef(ctx context.Context, crClient K8sClient, credentialsRef corev1.SecretReference, defaultNamespace, key string) ([]byte, error) {
6767
credSecret, err := getCredentials(ctx, crClient, credentialsRef, defaultNamespace)
6868
if err != nil {
6969
return nil, err
7070
}
71-
72-
// TODO: This key is hard-coded (for now) to match the externally-managed `manager-credentials` Secret.
73-
rawData, ok := credSecret.Data["apiToken"]
71+
rawData, ok := credSecret.Data[key]
7472
if !ok {
75-
return nil, fmt.Errorf("no apiToken key in credentials secret %s/%s", credentialsRef.Namespace, credentialsRef.Name)
73+
return nil, fmt.Errorf("no %s key in credentials secret %s/%s", key, credentialsRef.Namespace, credentialsRef.Name)
7674
}
7775

7876
return rawData, nil

cloud/scope/common_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func TestGetCredentialDataFromRef(t *testing.T) {
174174
mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(testCase.args.funcBehavior)
175175

176176
// Call getCredentialDataFromRef using the mock client
177-
got, err := getCredentialDataFromRef(context.Background(), mockClient, testCase.args.providedCredentialsRef, "default")
177+
got, err := getCredentialDataFromRef(context.Background(), mockClient, testCase.args.providedCredentialsRef, "default", "apiToken")
178178

179179
// Check that the function returned the expected result
180180
if testCase.expectedError != "" {

cloud/scope/machine.go

+33-17
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ type MachineScopeParams struct {
2626
}
2727

2828
type MachineScope struct {
29-
Client K8sClient
30-
PatchHelper *patch.Helper
31-
Cluster *clusterv1.Cluster
32-
Machine *clusterv1.Machine
33-
LinodeClient LinodeClient
34-
LinodeCluster *infrav1alpha2.LinodeCluster
35-
LinodeMachine *infrav1alpha1.LinodeMachine
29+
Client K8sClient
30+
PatchHelper *patch.Helper
31+
Cluster *clusterv1.Cluster
32+
Machine *clusterv1.Machine
33+
LinodeClient LinodeClient
34+
LinodeDomainsClient LinodeClient
35+
LinodeCluster *infrav1alpha2.LinodeCluster
36+
LinodeMachine *infrav1alpha1.LinodeMachine
3637
}
3738

3839
func validateMachineScopeParams(params MachineScopeParams) error {
@@ -52,7 +53,7 @@ func validateMachineScopeParams(params MachineScopeParams) error {
5253
return nil
5354
}
5455

55-
func NewMachineScope(ctx context.Context, apiKey string, params MachineScopeParams) (*MachineScope, error) {
56+
func NewMachineScope(ctx context.Context, apiKey, dnsKey string, params MachineScopeParams) (*MachineScope, error) {
5657
if err := validateMachineScopeParams(params); err != nil {
5758
return nil, err
5859
}
@@ -78,32 +79,47 @@ func NewMachineScope(ctx context.Context, apiKey string, params MachineScopePara
7879
}
7980

8081
if credentialRef != nil {
81-
data, err := getCredentialDataFromRef(ctx, params.Client, *credentialRef, defaultNamespace)
82+
// TODO: This key is hard-coded (for now) to match the externally-managed `manager-credentials` Secret.
83+
apiToken, err := getCredentialDataFromRef(ctx, params.Client, *credentialRef, defaultNamespace, "apiToken")
8284
if err != nil {
8385
return nil, fmt.Errorf("credentials from secret ref: %w", err)
8486
}
85-
apiKey = string(data)
87+
apiKey = string(apiToken)
88+
89+
dnsToken, err := getCredentialDataFromRef(ctx, params.Client, *credentialRef, defaultNamespace, "dnsToken")
90+
if err != nil || len(dnsToken) == 0 {
91+
dnsToken = apiToken
92+
}
93+
dnsKey = string(dnsToken)
8694
}
95+
8796
linodeClient, err := CreateLinodeClient(apiKey, defaultClientTimeout,
8897
WithRetryCount(0),
8998
)
9099
if err != nil {
91100
return nil, fmt.Errorf("failed to create linode client: %w", err)
92101
}
102+
linodeDomainsClient, err := CreateLinodeClient(dnsKey, defaultClientTimeout,
103+
WithRetryCount(0),
104+
)
105+
if err != nil {
106+
return nil, fmt.Errorf("failed to create linode client: %w", err)
107+
}
93108

94109
helper, err := patch.NewHelper(params.LinodeMachine, params.Client)
95110
if err != nil {
96111
return nil, fmt.Errorf("failed to init patch helper: %w", err)
97112
}
98113

99114
return &MachineScope{
100-
Client: params.Client,
101-
PatchHelper: helper,
102-
Cluster: params.Cluster,
103-
Machine: params.Machine,
104-
LinodeClient: linodeClient,
105-
LinodeCluster: params.LinodeCluster,
106-
LinodeMachine: params.LinodeMachine,
115+
Client: params.Client,
116+
PatchHelper: helper,
117+
Cluster: params.Cluster,
118+
Machine: params.Machine,
119+
LinodeClient: linodeClient,
120+
LinodeDomainsClient: linodeDomainsClient,
121+
LinodeCluster: params.LinodeCluster,
122+
LinodeMachine: params.LinodeMachine,
107123
}, nil
108124
}
109125

0 commit comments

Comments
 (0)