Skip to content

Commit 9b5db57

Browse files
authored
Merge pull request #727 from privatecoder/add-private-ip-connection
add option to create and set-up a cluster through their private-IPs
2 parents 381b661 + f53b25d commit 9b5db57

17 files changed

Lines changed: 94 additions & 35 deletions

File tree

docs/Creating_a_cluster.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ networking:
1919
ssh:
2020
port: 22
2121
use_agent: false # set to true if your key has a passphrase
22+
use_private_ip: false # set to true to connect to nodes via their private IPs
2223
public_key_path: "~/.ssh/id_ed25519.pub"
2324
private_key_path: "~/.ssh/id_ed25519"
2425
allowed_networks:
@@ -243,6 +244,8 @@ To create the cluster run:
243244
hetzner-k3s create --config cluster_config.yaml | tee create.log
244245
```
245246

247+
If you need to bypass the validation that your current IP is included in the allowed SSH/API networks, add `--skip-current-ip-validation`.
248+
246249
This process will take a few minutes, depending on how many master and worker nodes you have.
247250

248251
### Disabling public IPs (IPv4 or IPv6 or both) on nodes
@@ -371,4 +374,3 @@ The `create` command can be run multiple times with the same configuration witho
371374
eval "$(ssh-agent -s)"
372375
ssh-add --apple-use-keychain ~/.ssh/<private key>
373376
```
374-

docs/Setting_up_a_cluster.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ networking:
3232
ssh:
3333
port: 22
3434
use_agent: false
35+
use_private_ip: false
3536
public_key_path: "~/.ssh/id_rsa.pub"
3637
private_key_path: "~/.ssh/id_rsa"
3738
allowed_networks:

docs/Troubleshooting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ This will provide more detailed output, which can help you identify the root of
6464

6565
**Symptoms**: Cluster creation hangs after nodes are created. SSH connection times out when trying to connect to a private IP address.
6666

67-
**Note**: The tool currently does not support IPv6-only public network configuration. When you disable IPv4 (`public_network.ipv4: false`), you must run `hetzner-k3s` from a machine that has access to the same private network, either directly or through a VPN. Otherwise, the tool will attempt to use the private IP addresses for SSH connections and fail.
67+
**Note**: The tool currently does not support IPv6-only public network configuration. When you disable IPv4 (`public_network.ipv4: false`) or enable `networking.ssh.use_private_ip: true`, you must run `hetzner-k3s` from a machine that has access to the same private network, either directly or through a VPN. Otherwise, the tool will attempt to use private IP addresses for SSH/API connections and fail.
6868

6969
### Load Balancer Issues
7070

src/cluster/create.cr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ class Cluster::Create
1212
private getter hetzner_client : Hetzner::Client { configuration.hetzner_client }
1313
private getter settings : Configuration::Main { configuration.settings }
1414
private getter autoscaling_worker_node_pools : Array(Configuration::Models::WorkerNodePool) { settings.worker_node_pools.select(&.autoscaling_enabled) }
15-
private getter ssh_client : Util::SSH { Util::SSH.new(settings.networking.ssh.private_key_path, settings.networking.ssh.public_key_path) }
15+
private getter ssh_client : Util::SSH do
16+
Util::SSH.new(
17+
settings.networking.ssh.private_key_path,
18+
settings.networking.ssh.public_key_path,
19+
settings.networking.ssh.use_private_ip
20+
)
21+
end
1622
private getter network : Hetzner::Network?
1723
private getter ssh_key : Hetzner::SSHKey
1824
private getter load_balancer : Hetzner::LoadBalancer?

src/cluster/run.cr

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ class Cluster::Run
150150

151151
puts "Nodes that will be affected:"
152152
instances.each do |instance|
153-
if instance.host_ip_address
154-
puts " - #{instance.name} (#{instance.host_ip_address})"
153+
host_ip_address = instance.host_ip_address(settings.networking.ssh.use_private_ip)
154+
if host_ip_address
155+
puts " - #{instance.name} (#{host_ip_address})"
155156
else
156157
puts " - #{instance.name} (no IP address - will be skipped)"
157158
end
@@ -165,8 +166,9 @@ class Cluster::Run
165166
puts
166167

167168
puts "Node that will be affected:"
168-
if instance.host_ip_address
169-
puts " - #{instance.name} (#{instance.host_ip_address})"
169+
host_ip_address = instance.host_ip_address(settings.networking.ssh.use_private_ip)
170+
if host_ip_address
171+
puts " - #{instance.name} (#{host_ip_address})"
170172
else
171173
puts " - #{instance.name} (no IP address - will be skipped)"
172174
end
@@ -188,7 +190,8 @@ class Cluster::Run
188190
private def setup_ssh_connection
189191
Util::SSH.new(
190192
settings.networking.ssh.private_key_path,
191-
settings.networking.ssh.public_key_path
193+
settings.networking.ssh.public_key_path,
194+
settings.networking.ssh.use_private_ip
192195
)
193196
end
194197

@@ -228,8 +231,9 @@ class Cluster::Run
228231
output_lines = [] of String
229232
success = true
230233

231-
if instance.host_ip_address
232-
output_lines << "=== Instance: #{instance.name} (#{instance.host_ip_address}) ==="
234+
host_ip_address = instance.host_ip_address(settings.networking.ssh.use_private_ip)
235+
if host_ip_address
236+
output_lines << "=== Instance: #{instance.name} (#{host_ip_address}) ==="
233237

234238
begin
235239
success_message = block.call(ssh, instance)

src/configuration/loader.cr

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ class Configuration::Loader
6868

6969
getter new_k3s_version : String?
7070
getter configuration_file_path : String
71+
getter skip_current_ip_validation : Bool = false
7172

7273
private property force : Bool = false
7374

74-
def initialize(@configuration_file_path, @new_k3s_version, @force)
75+
def initialize(@configuration_file_path, @new_k3s_version, @force, @skip_current_ip_validation = false)
7576
@settings = Configuration::Main.from_yaml(File.read(configuration_file_path))
7677

7778
Configuration::Validators::ConfigurationFilePath.new(errors, configuration_file_path).validate
@@ -98,7 +99,8 @@ class Configuration::Loader
9899
masters_pool: masters_pool,
99100
instance_types: instance_types,
100101
all_locations: all_locations,
101-
new_k3s_version: new_k3s_version
102+
new_k3s_version: new_k3s_version,
103+
skip_current_ip_validation: skip_current_ip_validation
102104
).validate(command)
103105
end
104106

src/configuration/models/networking_config/ssh.cr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class Configuration::Models::NetworkingConfig::SSH
44

55
getter port : Int32 = 22
66
getter use_agent : Bool = false
7+
getter use_private_ip : Bool = false
78
getter private_key_path : String = "~/.ssh/id_rsa"
89
getter public_key_path : String = "~/.ssh/id_rsa.pub"
910

src/configuration/validators/command_specific_settings.cr

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Configuration::Validators::CommandSpecificSettings
1616
getter instance_types : Array(Hetzner::InstanceType)
1717
getter all_locations : Array(Hetzner::Location)
1818
getter new_k3s_version : String?
19+
getter skip_current_ip_validation : Bool = false
1920

2021
def initialize(
2122
@errors,
@@ -25,7 +26,8 @@ class Configuration::Validators::CommandSpecificSettings
2526
@masters_pool,
2627
@instance_types,
2728
@all_locations,
28-
@new_k3s_version
29+
@new_k3s_version,
30+
@skip_current_ip_validation = false
2931
)
3032
end
3133

@@ -50,7 +52,8 @@ class Configuration::Validators::CommandSpecificSettings
5052
hetzner_client: hetzner_client,
5153
masters_pool: masters_pool,
5254
instance_types: instance_types,
53-
all_locations: all_locations
55+
all_locations: all_locations,
56+
skip_current_ip_validation: skip_current_ip_validation
5457
).validate
5558
end
5659

@@ -66,4 +69,4 @@ class Configuration::Validators::CommandSpecificSettings
6669
private def validate_run_settings
6770
Configuration::Validators::RunSettings.new(errors).validate
6871
end
69-
end
72+
end

src/configuration/validators/create_settings.cr

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Configuration::Validators::CreateSettings
2121
getter masters_pool : Configuration::Models::MasterNodePool
2222
getter instance_types : Array(Hetzner::InstanceType)
2323
getter all_locations : Array(Hetzner::Location)
24+
getter skip_current_ip_validation : Bool = false
2425

2526
def initialize(
2627
@errors,
@@ -29,7 +30,8 @@ class Configuration::Validators::CreateSettings
2930
@hetzner_client,
3031
@masters_pool,
3132
@instance_types,
32-
@all_locations
33+
@all_locations,
34+
@skip_current_ip_validation = false
3335
)
3436
end
3537

@@ -40,7 +42,14 @@ class Configuration::Validators::CreateSettings
4042

4143
Configuration::Validators::Datastore.new(errors, settings.datastore).validate
4244

43-
Configuration::Validators::Networking.new(errors, settings.networking, settings, hetzner_client, settings.networking.private_network).validate
45+
Configuration::Validators::Networking.new(
46+
errors,
47+
settings.networking,
48+
settings,
49+
hetzner_client,
50+
settings.networking.private_network,
51+
skip_current_ip_validation: skip_current_ip_validation
52+
).validate
4453

4554
Configuration::Validators::MastersPool.new(
4655
errors: errors,

src/configuration/validators/networking.cr

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ class Configuration::Validators::Networking
1717
getter settings : Configuration::Main
1818
getter hetzner_client : Hetzner::Client
1919
getter private_network : Configuration::Models::NetworkingConfig::PrivateNetwork
20+
getter skip_current_ip_validation : Bool = false
2021

21-
def initialize(@errors, @networking, @settings, @hetzner_client, @private_network)
22+
def initialize(@errors, @networking, @settings, @hetzner_client, @private_network, @skip_current_ip_validation = false)
2223
end
2324

2425
def validate
2526
Configuration::Validators::NetworkingConfig::CNI.new(errors, networking.cni, private_network).validate
26-
Configuration::Validators::NetworkingConfig::AllowedNetworks.new(errors, networking.allowed_networks).validate
27+
Configuration::Validators::NetworkingConfig::AllowedNetworks.new(
28+
errors,
29+
networking.allowed_networks,
30+
skip_current_ip_validation: skip_current_ip_validation
31+
).validate
2732
Configuration::Validators::NetworkingConfig::PrivateNetwork.new(errors, private_network, hetzner_client).validate
2833
Configuration::Validators::NetworkingConfig::PublicNetwork.new(errors, networking.public_network, settings).validate
2934
Configuration::Validators::NetworkingConfig::SSH.new(errors, networking.ssh, hetzner_client, settings.cluster_name).validate
3035
end
31-
end
36+
end

0 commit comments

Comments
 (0)