Skip to content

Commit 6a0f0af

Browse files
committed
change implementation to parse the iprange clusterconfig early
refactor some code update README.md
1 parent 3ab4087 commit 6a0f0af

File tree

4 files changed

+68
-60
lines changed

4 files changed

+68
-60
lines changed

cluster-autoscaler/cloudprovider/hetzner/README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ The cluster autoscaler for Hetzner Cloud scales worker nodes.
2222
"arm64": "",
2323
"amd64": ""
2424
},
25-
"ipRange": "10.0.0.0/16", // Optional, make sure to match your private network configuration and to use the cidr notation - if not set the hetzner cloud default will be used
25+
"defaultSubnetIPRange": "10.0.0.0/16", // Optional, if not set the hetzner cloud default will be used - make sure this subnet exists within you private network and to use the cidr notation
2626
"nodeConfigs": {
2727
"pool1": { // This equals the pool name. Required for each pool that you have
2828
"cloudInit": "", // HCLOUD_CLOUD_INIT make sure it isn't base64 encoded twice ;]
@@ -36,7 +36,8 @@ The cluster autoscaler for Hetzner Cloud scales worker nodes.
3636
"value": "autoscaler-node",
3737
"effect": "NoExecute"
3838
}
39-
]
39+
],
40+
"subnetIPRange": "10.0.0.0/24", // Optional, if not set the defaultSubnetIPRange will be used - make sure this subnet exists within you private network and to use the cidr notation
4041
}
4142
}
4243
}
@@ -48,18 +49,23 @@ Can be useful when you have many different node pools and run into issues of the
4849

4950
**NOTE**: In contrast to `HCLOUD_CLUSTER_CONFIG`, this file is not base64 encoded.
5051

51-
The `ipRange` configuration can be used to place nodes within a specific IP range. This only applies to private networks. Make sure that the IP range is within the configured private network IP Range. If you do not set this value, the default setting from Hetzner Cloud will be used.
52-
53-
Following global configuration options can be overridden on a per-nodepool basis by adding them to the individual nodepool configurations:
54-
- `imagesForArch`
55-
- `ipRange`
52+
The global `imagesForArch` configuration can be overridden on a per-nodepool basis by adding an `imagesForArch` field to individual nodepool configurations.
5653

5754
The image selection logic works as follows:
5855

5956
1. If a nodepool has its own `imagesForArch` configuration, it will be used for that specific nodepool
6057
1. If a nodepool doesn't have `imagesForArch` configured, the global `imagesForArch` configuration will be used as a fallback
6158
1. If neither is configured, the legacy `HCLOUD_IMAGE` environment variable will be used
6259

60+
61+
The `defaultSubnetIPRange` and `subnetIPRange` configuration can be used to place nodes within a specific IP range.
62+
This only applies to private networks. Make sure that the subnet exists within your private network.
63+
If you do not set this value, the default setting from Hetzner Cloud will be used.
64+
65+
The global `defaultSubnetIPRange` can be overridden on a per-nodepool basis by adding a `subnetIPRange` field to individual nodepool configurations.
66+
67+
68+
6369
`HCLOUD_NETWORK` Default empty , The id or name of the network that is used in the cluster , @see https://docs.hetzner.cloud/#networks
6470

6571
`HCLOUD_FIREWALL` Default empty , The id or name of the firewall that is used in the cluster , @see https://docs.hetzner.cloud/#firewalls

cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provider.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"net"
2324
"regexp"
25+
"slices"
2426
"strconv"
2527
"strings"
2628
"sync"
@@ -198,6 +200,17 @@ func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscove
198200
klog.Fatalf("No cluster config present provider: %v", err)
199201
}
200202

203+
var defaultSubnetIPRange *net.IPNet
204+
if manager.clusterConfig.IsUsingNewFormat && manager.network != nil && manager.clusterConfig.DefaultSubnetIPRange != "" {
205+
_, defaultSubnetIPRange, err = net.ParseCIDR(manager.clusterConfig.DefaultSubnetIPRange)
206+
if err != nil {
207+
klog.Fatalf("failed to parse default subnet ip range %s: %s", manager.clusterConfig.DefaultSubnetIPRange, err)
208+
}
209+
if !isIpRangeInNetwork(defaultSubnetIPRange, manager.network) {
210+
klog.Fatalf("default subnet ip range %s is not part of network %s", manager.clusterConfig.DefaultSubnetIPRange, manager.network.Name)
211+
}
212+
}
213+
201214
validNodePoolName := regexp.MustCompile(`^[a-z0-9A-Z]+[a-z0-9A-Z\-\.\_]*[a-z0-9A-Z]+$|^[a-z0-9A-Z]{1}$`)
202215
clusterUpdateLock := sync.Mutex{}
203216
placementGroupTotals := make(map[string]int)
@@ -214,6 +227,7 @@ func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscove
214227
}
215228

216229
var placementGroup *hcloud.PlacementGroup
230+
var subnetIPRange *net.IPNet
217231
if manager.clusterConfig.IsUsingNewFormat {
218232
_, ok := manager.clusterConfig.NodeConfigs[spec.name]
219233
if !ok {
@@ -232,6 +246,24 @@ func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscove
232246
}
233247
placementGroupTotals[placementGroup.Name] += spec.maxSize
234248
}
249+
if manager.network != nil {
250+
if manager.clusterConfig.NodeConfigs[spec.name].SubnetIPRange != "" {
251+
_, subnetIPRange, err = net.ParseCIDR(manager.clusterConfig.NodeConfigs[spec.name].SubnetIPRange)
252+
if err != nil {
253+
klog.Fatalf("failed to parse subnet ip range %s for node group %s: %s", manager.clusterConfig.NodeConfigs[spec.name].SubnetIPRange, spec.name, err)
254+
}
255+
if !isIpRangeInNetwork(subnetIPRange, manager.network) {
256+
klog.Fatalf("subnet ip range %s for node group %s is not part of network %s", manager.clusterConfig.NodeConfigs[spec.name].SubnetIPRange, spec.name, manager.network.Name)
257+
}
258+
} else {
259+
subnetIPRange = defaultSubnetIPRange
260+
}
261+
klog.Infof("[DEBUG]Using subnet ip range %s for node group %s", subnetIPRange, spec.name)
262+
klog.Infof("[DEBUG]Default SubnetIPRange is %s", defaultSubnetIPRange)
263+
klog.Infof("[DEBUG]Default and SubnetIPRange in Config is: %s + %s", manager.clusterConfig.DefaultSubnetIPRange, manager.clusterConfig.NodeConfigs[spec.name].SubnetIPRange)
264+
265+
}
266+
235267
}
236268

237269
manager.nodeGroups[spec.name] = &hetznerNodeGroup{
@@ -244,6 +276,7 @@ func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscove
244276
targetSize: len(servers),
245277
clusterUpdateMutex: &clusterUpdateLock,
246278
placementGroup: placementGroup,
279+
subnetIPRange: subnetIPRange,
247280
}
248281
}
249282

@@ -262,6 +295,15 @@ func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscove
262295
return provider
263296
}
264297

298+
func isIpRangeInNetwork(ipRange *net.IPNet, network *hcloud.Network) bool {
299+
return slices.ContainsFunc(network.Subnets, func(subnet hcloud.NetworkSubnet) bool {
300+
if ipRange == nil || subnet.IPRange == nil {
301+
return false
302+
}
303+
return subnet.IPRange.IP.Equal(ipRange.IP) && len(subnet.IPRange.Mask) == len(ipRange.Mask)
304+
})
305+
}
306+
265307
func getPlacementGroup(manager *hetznerManager, placementGroupRef string) (*hcloud.PlacementGroup, error) {
266308
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
267309
defer cancel()

cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ type hetznerManager struct {
5959

6060
// ClusterConfig holds the configuration for all the nodepools
6161
type ClusterConfig struct {
62-
ImagesForArch ImageList
63-
NodeConfigs map[string]*NodeConfig
64-
IsUsingNewFormat bool
65-
LegacyConfig LegacyConfig
66-
IPRange string
62+
ImagesForArch ImageList
63+
NodeConfigs map[string]*NodeConfig
64+
IsUsingNewFormat bool
65+
LegacyConfig LegacyConfig
66+
DefaultSubnetIPRange string
6767
}
6868

6969
// ImageList holds the image id/names for the different architectures
@@ -79,7 +79,7 @@ type NodeConfig struct {
7979
Taints []apiv1.Taint
8080
Labels map[string]string
8181
ImagesForArch *ImageList
82-
IPRange string
82+
SubnetIPRange string
8383
}
8484

8585
// LegacyConfig holds the configuration in the legacy format

cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type hetznerNodeGroup struct {
5050

5151
clusterUpdateMutex *sync.Mutex
5252
placementGroup *hcloud.PlacementGroup
53+
subnetIPRange *net.IPNet
5354
}
5455

5556
type hetznerNodeGroupSpec struct {
@@ -453,39 +454,6 @@ func instanceTypeArch(manager *hetznerManager, instanceType string) (string, err
453454
}
454455
}
455456

456-
func findIpRange(nodeId string, clusterConfig *ClusterConfig) (*net.IPNet, error) {
457-
if !clusterConfig.IsUsingNewFormat {
458-
return nil, nil
459-
}
460-
if nodeConfig, exists := clusterConfig.NodeConfigs[nodeId]; exists && nodeConfig.IPRange != "" {
461-
_, ipNet, err := net.ParseCIDR(nodeConfig.IPRange)
462-
if err != nil {
463-
return nil, fmt.Errorf("failed to parse IP range %s for node %s: %v", nodeConfig.IPRange, nodeId, err)
464-
}
465-
return ipNet, nil
466-
}
467-
if clusterConfig.IPRange != "" {
468-
_, ipNet, err := net.ParseCIDR(clusterConfig.IPRange)
469-
if err != nil {
470-
return nil, fmt.Errorf("failed to parse global IP range %s: %v", clusterConfig.IPRange, err)
471-
}
472-
return ipNet, nil
473-
}
474-
return nil, nil
475-
}
476-
477-
func isIpRangeInNetwork(ipRange *net.IPNet, network *hcloud.Network) bool {
478-
found := false
479-
for _, nSubnet := range network.Subnets {
480-
_, nSubnetRange, _ := net.ParseCIDR(nSubnet.IPRange.String())
481-
if nSubnetRange.String() == ipRange.String() {
482-
found = true
483-
break
484-
}
485-
}
486-
return found
487-
}
488-
489457
func createServer(n *hetznerNodeGroup) error {
490458
ctx, cancel := context.WithTimeout(n.manager.apiCallContext, n.manager.createTimeout)
491459
defer cancel()
@@ -500,22 +468,14 @@ func createServer(n *hetznerNodeGroup) error {
500468
return err
501469
}
502470

503-
ipRange, err := findIpRange(n.Id(), n.manager.clusterConfig)
504-
if err != nil {
505-
return err
506-
}
507-
if ipRange != nil && n.manager.network != nil && !isIpRangeInNetwork(ipRange, n.manager.network) {
508-
return fmt.Errorf("the specified IP range %s for node %s is not part of the network %s", ipRange.String(), n.Id(), n.manager.network.Name)
509-
}
510-
511471
cloudInit := n.manager.clusterConfig.LegacyConfig.CloudInit
512472

513473
if n.manager.clusterConfig.IsUsingNewFormat {
514474
cloudInit = n.manager.clusterConfig.NodeConfigs[n.id].CloudInit
515475
}
516476

517477
// dont start the server if we need to attach the server to a private subnet network
518-
StartAfterCreate := ipRange == nil
478+
StartAfterCreate := n.manager.network != nil && n.subnetIPRange == nil
519479
opts := hcloud.ServerCreateOpts{
520480
Name: newNodeName(n),
521481
UserData: cloudInit,
@@ -535,7 +495,7 @@ func createServer(n *hetznerNodeGroup) error {
535495
if n.manager.sshKey != nil {
536496
opts.SSHKeys = []*hcloud.SSHKey{n.manager.sshKey}
537497
}
538-
if n.manager.network != nil && ipRange == nil {
498+
if n.manager.network != nil && n.subnetIPRange == nil {
539499
opts.Networks = []*hcloud.Network{n.manager.network}
540500
}
541501
if n.manager.firewall != nil {
@@ -559,15 +519,15 @@ func createServer(n *hetznerNodeGroup) error {
559519
return fmt.Errorf("failed to start server %s error: %v", server.Name, err)
560520
}
561521

562-
if n.manager.network != nil && ipRange != nil {
563-
// Attach server to private network with ipRange
522+
if n.manager.network != nil && n.subnetIPRange != nil {
523+
// Attach server to private network with subnetIPRange
564524
attachAction, _, err := n.manager.client.Server.AttachToNetwork(ctx, server, hcloud.ServerAttachToNetworkOpts{
565525
Network: n.manager.network,
566-
IPRange: ipRange,
526+
IPRange: n.subnetIPRange,
567527
})
568528
if err != nil {
569529
_ = n.manager.deleteServer(server)
570-
return fmt.Errorf("failed to attach server %s to network %s with IP range %s error: %v", server.Name, n.manager.network.Name, ipRange.String(), err)
530+
return fmt.Errorf("failed to attach server %s to network %s with IP range %s error: %v", server.Name, n.manager.network.Name, n.subnetIPRange.String(), err)
571531
}
572532
if err = n.manager.client.Action.WaitFor(ctx, attachAction); err != nil {
573533
_ = n.manager.deleteServer(server)

0 commit comments

Comments
 (0)