Skip to content

Commit d6942c0

Browse files
[improvement] avoid resizing the root disk on instance creation for machines with extra disks defined (#1087)
* avoid resizing the disk but still requires an API call to check the disk size for the plan * update tests * remove outdated warning * oops * fix lint * lose 512 MB to a bogus unused swap disk by default so we can save 90 seconds per machine for cloud-init (ugh) * need to factor in default swap size and subtract that from root disk size if SDB isn't defined when DataDisks are set * linter...
1 parent c494bbe commit d6942c0

8 files changed

Lines changed: 133 additions & 266 deletions

File tree

clients/clients.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ type LinodeInstanceClient interface {
4747
BootInstance(ctx context.Context, linodeID int, configID int) error
4848
ListInstanceConfigs(ctx context.Context, linodeID int, opts *linodego.ListOptions) ([]linodego.InstanceConfig, error)
4949
UpdateInstanceConfig(ctx context.Context, linodeID int, configID int, opts linodego.InstanceConfigUpdateOptions) (*linodego.InstanceConfig, error)
50-
GetInstanceDisk(ctx context.Context, linodeID int, diskID int) (*linodego.InstanceDisk, error)
5150
UpdateInstance(ctx context.Context, linodeId int, opts linodego.InstanceUpdateOptions) (*linodego.Instance, error)
52-
ResizeInstanceDisk(ctx context.Context, linodeID int, diskID int, size int) error
5351
CreateInstanceDisk(ctx context.Context, linodeID int, opts linodego.InstanceDiskCreateOptions) (*linodego.InstanceDisk, error)
5452
GetInstance(ctx context.Context, linodeID int) (*linodego.Instance, error)
5553
DeleteInstance(ctx context.Context, linodeID int) error

docs/src/topics/disks/data-disks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ There are a couple caveats with specifying disks for a linode instance:
99
~~~
1010

1111
```admonish warning
12-
Currently SDB is being used by a swap disk, replacing this disk with a data disk will slow down linode creation by
13-
up to 90 seconds. This will be resolved when the disk creation refactor is finished. (See [#766](https://github.com/linode/cluster-api-provider-linode/issues/766))
12+
By default, /dev/sdb is expected to be a swap disk that is waited for by a start job in cloud-init.
13+
Replacing this disk with a data disk will slow down linode creation by 90 seconds.
1414
```
1515

1616
## Specify a data disk

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/go-logr/logr v1.4.3
1616
github.com/google/go-cmp v0.7.0
1717
github.com/google/uuid v1.6.0
18-
github.com/linode/linodego v1.65.0
18+
github.com/linode/linodego v1.69.1
1919
github.com/onsi/ginkgo/v2 v2.28.1
2020
github.com/onsi/gomega v1.39.1
2121
github.com/stretchr/testify v1.11.1
@@ -138,7 +138,7 @@ require (
138138
go.uber.org/ratelimit v0.3.1 // indirect
139139
go.uber.org/zap v1.27.1 // indirect
140140
golang.org/x/net v0.55.0 // indirect
141-
golang.org/x/oauth2 v0.35.0 // indirect
141+
golang.org/x/oauth2 v0.36.0 // indirect
142142
golang.org/x/sys v0.45.0 // indirect
143143
golang.org/x/term v0.43.0 // indirect
144144
golang.org/x/text v0.37.0 // indirect
@@ -150,7 +150,7 @@ require (
150150
google.golang.org/grpc v1.80.0 // indirect
151151
google.golang.org/protobuf v1.36.11 // indirect
152152
gopkg.in/inf.v0 v0.9.1 // indirect
153-
gopkg.in/ini.v1 v1.67.1 // indirect
153+
gopkg.in/ini.v1 v1.67.2 // indirect
154154
gopkg.in/yaml.v3 v3.0.1 // indirect
155155
k8s.io/apiextensions-apiserver v0.35.0 // indirect
156156
k8s.io/klog/v2 v2.130.1 // indirect

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
167167
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
168168
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
169169
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
170-
github.com/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
171-
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
170+
github.com/linode/linodego v1.69.1 h1:f45N2MHR/oece2/ktTTCYmrlfse4//k3NgwcF5zbGZ0=
171+
github.com/linode/linodego v1.69.1/go.mod h1:Fha0NYsQSx5VZK1HQNJY/z/dIxxkFp+vb5veawbmAUw=
172172
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
173173
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
174174
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
@@ -329,8 +329,8 @@ golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
329329
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
330330
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
331331
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
332-
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
333-
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
332+
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
333+
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
334334
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
335335
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
336336
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
@@ -362,8 +362,8 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnf
362362
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
363363
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
364364
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
365-
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
366-
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
365+
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
366+
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
367367
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
368368
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
369369
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

internal/controller/linodemachine_controller_helpers.go

Lines changed: 44 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ var (
6666
errNoPublicIPv4Addrs = errors.New("no public ipv4 addresses set")
6767
errNoPublicIPv6Addrs = errors.New("no public IPv6 address set")
6868
errNoPublicIPv6SLAACAddrs = errors.New("no public SLAAC address set")
69+
70+
// We have to account for the default swap in Linodes when calculating the root disk size.
71+
// While we don't actually use swap in any of our flavors (we set swapoff), we can't
72+
// explicitly set to swap to 0 for any created Linodes because it adds a 90 second hang on
73+
// cloud-init while it waits for swap regardless of the storage configuration.
74+
// This value only gets used if LinodeMachine.Spec.DataDisks.SDB isn't set
75+
// (swap is by default on /dev/sdb for created Linodes)
76+
defaultSwapDiskSize = int(resource.NewScaledQuantity(512, resource.Mega).ScaledValue(resource.Mega)) //nolint:mnd // already explained
6977
)
7078

7179
func retryIfTransient(err error, logger logr.Logger) (ctrl.Result, error) {
@@ -79,7 +87,7 @@ func retryIfTransient(err error, logger logr.Logger) (ctrl.Result, error) {
7987
return ctrl.Result{RequeueAfter: reconciler.WithJitter(reconciler.DefaultMachineControllerRetryDelay)}, nil
8088
}
8189

82-
func fillCreateConfig(createConfig *linodego.InstanceCreateOptions, machineScope *scope.MachineScope) {
90+
func fillCreateConfig(ctx context.Context, createConfig *linodego.InstanceCreateOptions, machineScope *scope.MachineScope) error {
8391
// This will only be empty if no interfaces or linodeInterfaces were specified in the LinodeMachine spec.
8492
// In that case we default to legacy interfaces.
8593
switch createConfig.InterfaceGeneration {
@@ -115,9 +123,22 @@ func fillCreateConfig(createConfig *linodego.InstanceCreateOptions, machineScope
115123
if createConfig.Image == "" {
116124
createConfig.Image = reconciler.DefaultMachineControllerLinodeImage
117125
}
118-
if createConfig.RootPass == "" {
126+
127+
// You can now omit the root pass entirely during instance creation
128+
// as long as you supply at least one SSH key.
129+
if createConfig.RootPass == "" && len(createConfig.AuthorizedKeys) == 0 {
119130
createConfig.RootPass = uuid.NewString()
120131
}
132+
133+
// dynamically calculate root disk size unless an explicit OS disk is being set
134+
diskSize, err := calculateRootDisk(ctx, machineScope)
135+
if err != nil {
136+
return err
137+
}
138+
if diskSize != nil {
139+
createConfig.BootSize = diskSize
140+
}
141+
return nil
121142
}
122143

123144
func newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, gzipCompressionEnabled bool, logger logr.Logger) (*linodego.InstanceCreateOptions, error) {
@@ -135,7 +156,9 @@ func newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, gzip
135156
return nil, err
136157
}
137158

138-
fillCreateConfig(createConfig, machineScope)
159+
if err := fillCreateConfig(ctx, createConfig, machineScope); err != nil {
160+
return nil, err
161+
}
139162

140163
// Configure VPC interface if needed
141164
if err := configureVPCInterface(ctx, machineScope, createConfig, logger); err != nil {
@@ -989,7 +1012,7 @@ func constructLinodeInterfaceCreateOpts(createOpts []infrav1alpha2.LinodeInterfa
9891012
firewallID = iface.FirewallID
9901013
}
9911014
if firewallID != nil {
992-
ifaceCreateOpts.FirewallID = ptr.To(firewallID)
1015+
ifaceCreateOpts.FirewallID = firewallID
9931016
}
9941017
// createOpts is now fully populated with the interface options
9951018
linodeInterfaces[idx] = ifaceCreateOpts
@@ -1290,9 +1313,6 @@ func configureDisks(ctx context.Context, logger logr.Logger, machineScope *scope
12901313
return nil
12911314
}
12921315

1293-
if err := resizeRootDisk(ctx, logger, machineScope, linodeInstanceID); err != nil {
1294-
return err
1295-
}
12961316
if !reconciler.ConditionTrue(machineScope.LinodeMachine.GetCondition(ConditionPreflightAdditionalDisksCreated)) {
12971317
if err := createDisks(ctx, logger, machineScope, linodeInstanceID); err != nil {
12981318
return err
@@ -1381,94 +1401,23 @@ func configureDisk(ctx context.Context, logger logr.Logger, machineScope *scope.
13811401
return nil
13821402
}
13831403

1384-
func resizeRootDisk(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope, linodeInstanceID int) error {
1385-
if reconciler.ConditionTrue(machineScope.LinodeMachine.GetCondition(ConditionPreflightRootDiskResized)) {
1386-
return nil
1387-
}
1388-
1389-
instanceConfig, err := getDefaultInstanceConfig(ctx, machineScope, linodeInstanceID)
1390-
if err != nil {
1391-
logger.Error(err, "Failed to get default instance configuration")
1392-
1393-
machineScope.LinodeMachine.SetCondition(metav1.Condition{
1394-
Type: ConditionPreflightRootDiskResized,
1395-
Status: metav1.ConditionFalse,
1396-
Reason: util.CreateError,
1397-
Message: err.Error(),
1398-
})
1399-
return err
1400-
}
1401-
1402-
if instanceConfig.Devices.SDA == nil {
1403-
machineScope.LinodeMachine.SetCondition(metav1.Condition{
1404-
Type: ConditionPreflightRootDiskResized,
1405-
Status: metav1.ConditionFalse,
1406-
Reason: util.CreateError,
1407-
Message: "root disk not yet ready",
1408-
})
1409-
1410-
return errors.New("root disk not yet ready")
1411-
}
1412-
1413-
rootDiskID := instanceConfig.Devices.SDA.DiskID
1414-
1415-
// carve out space for the etcd disk
1416-
if !reconciler.ConditionTrue(machineScope.LinodeMachine.GetCondition(ConditionPreflightRootDiskResizing)) {
1417-
rootDisk, err := machineScope.LinodeClient.GetInstanceDisk(ctx, linodeInstanceID, rootDiskID)
1418-
if err != nil {
1419-
logger.Error(err, "Failed to get root disk for instance")
1420-
1421-
machineScope.LinodeMachine.SetCondition(metav1.Condition{
1422-
Type: ConditionPreflightRootDiskResizing,
1423-
Status: metav1.ConditionFalse,
1424-
Reason: util.CreateError,
1425-
Message: err.Error(),
1426-
})
1427-
1428-
return err
1429-
}
1430-
// dynamically calculate root disk size unless an explicit OS disk is being set
1431-
diskSize := calculateRootDisk(machineScope, rootDisk)
1432-
1433-
if err := machineScope.LinodeClient.ResizeInstanceDisk(ctx, linodeInstanceID, rootDiskID, diskSize); err != nil {
1434-
machineScope.LinodeMachine.SetCondition(metav1.Condition{
1435-
Type: ConditionPreflightRootDiskResizing,
1436-
Status: metav1.ConditionFalse,
1437-
Reason: util.CreateError,
1438-
Message: err.Error(),
1439-
})
1440-
return err
1441-
}
1442-
machineScope.LinodeMachine.SetCondition(metav1.Condition{
1443-
Type: ConditionPreflightRootDiskResizing,
1444-
Status: metav1.ConditionTrue,
1445-
Reason: "RootDiskResizing",
1446-
})
1447-
}
1448-
1449-
machineScope.LinodeMachine.DeleteCondition(ConditionPreflightRootDiskResizing)
1450-
machineScope.LinodeMachine.SetCondition(metav1.Condition{
1451-
Type: ConditionPreflightRootDiskResized,
1452-
Status: metav1.ConditionTrue,
1453-
Reason: "RootDiskResized",
1454-
})
1455-
1456-
return nil
1457-
}
1458-
1459-
func calculateRootDisk(machineScope *scope.MachineScope, rootDisk *linodego.InstanceDisk) int {
1404+
func calculateRootDisk(ctx context.Context, machineScope *scope.MachineScope) (*int, error) {
14601405
additionalDiskSize := 0
1461-
// If the user has specified an OS disk, use it's size.
1406+
// If the user has specified an OS disk, use its size.
14621407
if machineScope.LinodeMachine.Spec.OSDisk != nil {
1463-
return int(machineScope.LinodeMachine.Spec.OSDisk.Size.ScaledValue(resource.Mega))
1408+
return ptr.To(int(machineScope.LinodeMachine.Spec.OSDisk.Size.ScaledValue(resource.Mega))), nil
14641409
}
1465-
// If no DataDisks are specified, use the default root disk size.
1410+
// If no DataDisks are specified, omit the size
14661411
if machineScope.LinodeMachine.Spec.DataDisks == nil {
1467-
return rootDisk.Size
1412+
return nil, nil //nolint:nilnil // we want to let the API default the size if there are no other disks to make room for
14681413
}
1414+
14691415
// If DataDisks are specified, calculate the size of the additional disk + root disk for resizing.
14701416
if machineScope.LinodeMachine.Spec.DataDisks.SDB != nil {
14711417
additionalDiskSize += int(machineScope.LinodeMachine.Spec.DataDisks.SDB.Size.ScaledValue(resource.Mega))
1418+
} else {
1419+
// account for the 512 MB default swap disk
1420+
additionalDiskSize += defaultSwapDiskSize
14721421
}
14731422
if machineScope.LinodeMachine.Spec.DataDisks.SDC != nil {
14741423
additionalDiskSize += int(machineScope.LinodeMachine.Spec.DataDisks.SDC.Size.ScaledValue(resource.Mega))
@@ -1488,8 +1437,13 @@ func calculateRootDisk(machineScope *scope.MachineScope, rootDisk *linodego.Inst
14881437
if machineScope.LinodeMachine.Spec.DataDisks.SDH != nil {
14891438
additionalDiskSize += int(machineScope.LinodeMachine.Spec.DataDisks.SDH.Size.ScaledValue(resource.Mega))
14901439
}
1491-
diskSize := rootDisk.Size - additionalDiskSize
1492-
return diskSize
1440+
planType, err := machineScope.LinodeClient.GetType(ctx, machineScope.LinodeMachine.Spec.Type)
1441+
if err != nil {
1442+
return nil, err
1443+
}
1444+
diskSize := planType.Disk - additionalDiskSize
1445+
1446+
return ptr.To(diskSize), nil
14931447
}
14941448

14951449
func updateInstanceConfigProfile(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope, linodeInstanceID int) error {
@@ -1638,7 +1592,7 @@ func configureFirewall(ctx context.Context, machineScope *scope.MachineScope, cr
16381592

16391593
// If using LinodeInterfaces that needs to know about the firewall ID
16401594
for i := range createConfig.LinodeInterfaces {
1641-
createConfig.LinodeInterfaces[i].FirewallID = ptr.To(ptr.To(fwID))
1595+
createConfig.LinodeInterfaces[i].FirewallID = ptr.To(fwID)
16421596
}
16431597

16441598
return nil

0 commit comments

Comments
 (0)