Skip to content

Commit 58386d9

Browse files
committed
feat(hetzner): add IP range configuration for private network
1 parent ca53831 commit 58386d9

File tree

3 files changed

+81
-3
lines changed

3 files changed

+81
-3
lines changed

cluster-autoscaler/cloudprovider/hetzner/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +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
2526
"nodeConfigs": {
2627
"pool1": { // This equals the pool name. Required for each pool that you have
2728
"cloudInit": "", // HCLOUD_CLOUD_INIT make sure it isn't base64 encoded twice ;]
@@ -47,7 +48,11 @@ Can be useful when you have many different node pools and run into issues of the
4748

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

50-
The global `imagesForArch` configuration can be overridden on a per-nodepool basis by adding an `imagesForArch` field to individual nodepool configurations.
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 overriden on a per-nodepool basis by adding them to the individual nodepool configurations:
54+
- `imagesForArch`
55+
- `ipRange`
5156

5257
The image selection logic works as follows:
5358

cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type ClusterConfig struct {
6363
NodeConfigs map[string]*NodeConfig
6464
IsUsingNewFormat bool
6565
LegacyConfig LegacyConfig
66+
IPRange string
6667
}
6768

6869
// ImageList holds the image id/names for the different architectures
@@ -78,6 +79,7 @@ type NodeConfig struct {
7879
Taints []apiv1.Taint
7980
Labels map[string]string
8081
ImagesForArch *ImageList
82+
IPRange string
8183
}
8284

8385
// LegacyConfig holds the configuration in the legacy format

cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"maps"
2424
"math/rand"
25+
"net"
2526
"strings"
2627
"sync"
2728

@@ -452,6 +453,39 @@ func instanceTypeArch(manager *hetznerManager, instanceType string) (string, err
452453
}
453454
}
454455

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+
455489
func createServer(n *hetznerNodeGroup) error {
456490
ctx, cancel := context.WithTimeout(n.manager.apiCallContext, n.manager.createTimeout)
457491
defer cancel()
@@ -466,13 +500,22 @@ func createServer(n *hetznerNodeGroup) error {
466500
return err
467501
}
468502

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+
469511
cloudInit := n.manager.clusterConfig.LegacyConfig.CloudInit
470512

471513
if n.manager.clusterConfig.IsUsingNewFormat {
472514
cloudInit = n.manager.clusterConfig.NodeConfigs[n.id].CloudInit
473515
}
474516

475-
StartAfterCreate := true
517+
// dont start the server if we need to attach the server to a private subnet network
518+
StartAfterCreate := ipRange == nil
476519
opts := hcloud.ServerCreateOpts{
477520
Name: newNodeName(n),
478521
UserData: cloudInit,
@@ -492,7 +535,7 @@ func createServer(n *hetznerNodeGroup) error {
492535
if n.manager.sshKey != nil {
493536
opts.SSHKeys = []*hcloud.SSHKey{n.manager.sshKey}
494537
}
495-
if n.manager.network != nil {
538+
if n.manager.network != nil && ipRange == nil {
496539
opts.Networks = []*hcloud.Network{n.manager.network}
497540
}
498541
if n.manager.firewall != nil {
@@ -516,6 +559,34 @@ func createServer(n *hetznerNodeGroup) error {
516559
return fmt.Errorf("failed to start server %s error: %v", server.Name, err)
517560
}
518561

562+
if n.manager.network != nil && ipRange != nil {
563+
// Attach server to private network with ipRange
564+
attachAction, _, err := n.manager.client.Server.AttachToNetwork(ctx, server, hcloud.ServerAttachToNetworkOpts{
565+
Network: n.manager.network,
566+
IPRange: ipRange,
567+
})
568+
if err != nil {
569+
_ = 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)
571+
}
572+
if err = n.manager.client.Action.WaitFor(ctx, attachAction); err != nil {
573+
_ = n.manager.deleteServer(server)
574+
return fmt.Errorf("failed waiting for network action for server %s error: %v", server.Name, err)
575+
}
576+
}
577+
578+
if !StartAfterCreate {
579+
powerOnAction, _, err := n.manager.client.Server.Poweron(ctx, server)
580+
if err != nil {
581+
_ = n.manager.deleteServer(server)
582+
return fmt.Errorf("failed to power on server %s error: %v", server.Name, err)
583+
}
584+
if err = n.manager.client.Action.WaitFor(ctx, powerOnAction); err != nil {
585+
_ = n.manager.deleteServer(server)
586+
return fmt.Errorf("failed waiting for power on action for server %s error: %v", server.Name, err)
587+
}
588+
}
589+
519590
return nil
520591
}
521592

0 commit comments

Comments
 (0)