hetzner-k3s 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6c87ae355b79344110f8b822668072438b9b496b602177b3d6528e56a75aea8
4
- data.tar.gz: 9be4a8945d32c22f4810389b9330707177749d8c18d6416d73a6bbbdef4ff3a9
3
+ metadata.gz: d251d84c9600608af2e382ae10838104bb8fe81fa87b1058b25fd571187a0a88
4
+ data.tar.gz: 9530af5905cebe724e6be2eafaf60da07172a7a640b1913e3572ee419b0feaed
5
5
  SHA512:
6
- metadata.gz: 7c852a9f0b9b4e3dd80f5c33007f39d4191a9588874cac0d03904c15f185686757e45793525a0bf2b87787fe4d8de3969318797300e40aad4811cb74c883628f
7
- data.tar.gz: 321d20a09b451ef355095268cf0da68ebefd88a43c3b7261293923b346b5ee5641ee55214cda1a8b53aaf44fa8fedb178303b1e9dd9d7de5856332c135546628
6
+ metadata.gz: c3b567d8c78c29bc0785d943c52ab65eab40d934ebdf01045c7efd0c76d74087eca2f81c1a790fb4d700bf39c92a696aca9794e17c252467de7838bb2149e56e
7
+ data.tar.gz: 010a87dd114c2d1209f8da453ebb8123fbbf4f4684dacf3aa2e85d648715c78013f798e68b413603a25f9869e153afced6104cceca5914060aaedb6de8a358b2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hetzner-k3s (0.1.0)
4
+ hetzner-k3s (0.3.0)
5
5
  http
6
6
  k8s-ruby
7
7
  net-ssh
data/README.md CHANGED
@@ -208,6 +208,17 @@ The other annotations should be self explanatory. You can find a list of the ava
208
208
 
209
209
  Once the cluster is ready you can create persistent volumes out of the box with the default storage class `hcloud-volumes`, since the Hetzner CSI driver is installed automatically. This will use Hetzner's block storage (based on Ceph so it's replicated and highly available) for your persistent volumes. Note that the minimum size of a volume is 10Gi. If you specify a smaller size for a volume, the volume will be created with a capacity of 10Gi anyway.
210
210
 
211
+
212
+ ## changelog
213
+
214
+ - 0.3.0
215
+ - Handle case when an SSH key with the given fingerprint already exists in the Hetzner project
216
+ - Handle a timeout of 5 seconds for requests to the Hetzner API
217
+ - Retry waiting for server to be up when timeouts/host-unreachable errors occur
218
+ - Ignore known_hosts entry to prevent errors when recreating servers with IPs that have been used previously
219
+
220
+ - 0.2.0
221
+ - Allow mixing servers of different series Intel/AMD
211
222
  ## Contributing and support
212
223
 
213
224
  Please create a PR if you want to propose any changes, or open an issue if you are having trouble with the tool - I will do my best to help if I can.
@@ -9,15 +9,21 @@ module Hetzner
9
9
  end
10
10
 
11
11
  def get(path)
12
- JSON.parse HTTP.headers(headers).get(BASE_URI + path).body
12
+ make_request do
13
+ JSON.parse HTTP.headers(headers).get(BASE_URI + path).body
14
+ end
13
15
  end
14
16
 
15
17
  def post(path, data)
16
- HTTP.headers(headers).post(BASE_URI + path, json: data)
18
+ make_request do
19
+ HTTP.headers(headers).post(BASE_URI + path, json: data)
20
+ end
17
21
  end
18
22
 
19
23
  def delete(path, id)
20
- HTTP.headers(headers).delete(BASE_URI + path + "/" + id.to_s)
24
+ make_request do
25
+ HTTP.headers(headers).delete(BASE_URI + path + "/" + id.to_s)
26
+ end
21
27
  end
22
28
 
23
29
  private
@@ -28,5 +34,13 @@ module Hetzner
28
34
  "Content-Type": "application/json"
29
35
  }
30
36
  end
37
+
38
+ def make_request &block
39
+ Timeout::timeout(5) do
40
+ block.call
41
+ end
42
+ rescue Timeout::Error
43
+ retry
44
+ end
31
45
  end
32
46
  end
@@ -26,12 +26,12 @@ module Hetzner
26
26
  JSON.parse(response)["load_balancer"]["id"]
27
27
  end
28
28
 
29
- def delete
29
+ def delete(ha:)
30
30
  if load_balancer = find_load_balancer
31
- puts "Deleting API load balancer..."
31
+ puts "Deleting API load balancer..." unless ha
32
32
  hetzner_client.delete("/load_balancers", load_balancer["id"])
33
- puts "...API load balancer deleted."
34
- else
33
+ puts "...API load balancer deleted." unless ha
34
+ elsif ha
35
35
  puts "API load balancer no longer exists, skipping."
36
36
  end
37
37
 
@@ -26,11 +26,17 @@ module Hetzner
26
26
  JSON.parse(response)["ssh_key"]["id"]
27
27
  end
28
28
 
29
- def delete
29
+ def delete(ssh_key_path:)
30
+ @ssh_key_path = ssh_key_path
31
+
30
32
  if ssh_key = find_ssh_key
31
- puts "Deleting ssh_key..."
32
- hetzner_client.delete("/ssh_keys", ssh_key["id"])
33
- puts "...ssh_key deleted."
33
+ if ssh_key["name"] == cluster_name
34
+ puts "Deleting ssh_key..."
35
+ hetzner_client.delete("/ssh_keys", ssh_key["id"])
36
+ puts "...ssh_key deleted."
37
+ else
38
+ puts "The SSH key existed before creating the cluster, so I won't delete it."
39
+ end
34
40
  else
35
41
  puts "SSH key no longer exists, skipping."
36
42
  end
@@ -42,15 +48,33 @@ module Hetzner
42
48
 
43
49
  attr_reader :hetzner_client, :cluster_name, :ssh_key_path
44
50
 
51
+ def public_key
52
+ @public_key ||= File.read(ssh_key_path).chop
53
+ end
54
+
45
55
  def ssh_key_config
46
56
  {
47
57
  name: cluster_name,
48
- public_key: File.read(ssh_key_path)
58
+ public_key: public_key
49
59
  }
50
60
  end
51
61
 
62
+ def fingerprint
63
+ @fingerprint ||= ::SSHKey.fingerprint(public_key)
64
+ end
65
+
52
66
  def find_ssh_key
53
- hetzner_client.get("/ssh_keys")["ssh_keys"].detect{ |ssh_key| ssh_key["name"] == cluster_name }
67
+ key = hetzner_client.get("/ssh_keys")["ssh_keys"].detect do |ssh_key|
68
+ ssh_key["fingerprint"] == fingerprint
69
+ end
70
+
71
+ unless key
72
+ key = hetzner_client.get("/ssh_keys")["ssh_keys"].detect do |ssh_key|
73
+ ssh_key["name"] == cluster_name
74
+ end
75
+ end
76
+
77
+ key
54
78
  end
55
79
 
56
80
  end
@@ -45,6 +45,7 @@ class Cluster
45
45
  def delete(configuration:)
46
46
  @cluster_name = configuration.dig("cluster_name")
47
47
  @kubeconfig_path = File.expand_path(configuration.dig("kubeconfig_path"))
48
+ @ssh_key_path = File.expand_path(configuration.dig("ssh_key_path"))
48
49
 
49
50
  delete_resources
50
51
  end
@@ -148,6 +149,7 @@ class Cluster
148
149
  end
149
150
 
150
151
  def delete_resources
152
+ # Deleting nodes defined according to Kubernetes first
151
153
  begin
152
154
  Timeout::timeout(5) do
153
155
  servers = kubernetes_client.api("v1").resource("nodes").list
@@ -164,6 +166,17 @@ class Cluster
164
166
  puts "Unable to fetch nodes from Kubernetes API. Is the cluster online?"
165
167
  end
166
168
 
169
+ # Deleting nodes defined in the config file just in case there are leftovers i.e. nodes that
170
+ # were not part of the cluster for some reason
171
+
172
+ threads = all_servers.each do |server|
173
+ Thread.new do
174
+ Hetzner::Server.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(server_name: server["name"])
175
+ end
176
+ end
177
+
178
+ threads.each(&:join)
179
+
167
180
  puts
168
181
 
169
182
  sleep 5 # give time for the servers to actually be deleted
@@ -181,12 +194,12 @@ class Cluster
181
194
  Hetzner::SSHKey.new(
182
195
  hetzner_client: hetzner_client,
183
196
  cluster_name: cluster_name
184
- ).delete
197
+ ).delete(ssh_key_path: ssh_key_path)
185
198
 
186
199
  Hetzner::LoadBalancer.new(
187
200
  hetzner_client: hetzner_client,
188
201
  cluster_name: cluster_name
189
- ).delete
202
+ ).delete(ha: (masters.size > 1))
190
203
 
191
204
  end
192
205
 
@@ -431,17 +444,19 @@ class Cluster
431
444
  end
432
445
 
433
446
  def wait_for_ssh(server)
434
- server_name = server["name"]
447
+ Timeout::timeout(5) do
448
+ server_name = server["name"]
435
449
 
436
- puts "Waiting for server #{server_name} to be up..."
450
+ puts "Waiting for server #{server_name} to be up..."
437
451
 
438
- loop do
439
- result = ssh(server, "echo UP")
440
- break if result == "UP"
441
- end
452
+ loop do
453
+ result = ssh(server, "echo UP")
454
+ break if result == "UP"
455
+ end
442
456
 
443
- puts "...server #{server_name} is now up."
444
- rescue Errno::ENETUNREACH
457
+ puts "...server #{server_name} is now up."
458
+ end
459
+ rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH, Timeout::Error
445
460
  retry
446
461
  end
447
462
 
@@ -449,7 +464,7 @@ class Cluster
449
464
  public_ip = server.dig("public_net", "ipv4", "ip")
450
465
  output = ""
451
466
 
452
- Net::SSH.start(public_ip, "root") do |session|
467
+ Net::SSH.start(public_ip, "root", verify_host_key: :never) do |session|
453
468
  session.exec!(command) do |channel, stream, data|
454
469
  output << data
455
470
  puts data if print_output
@@ -457,11 +472,9 @@ class Cluster
457
472
  end
458
473
 
459
474
  output.chop
460
- rescue Net::SSH::ConnectionTimeout
461
- retry
462
475
  rescue Net::SSH::Disconnect => e
463
476
  retry unless e.message =~ /Too many authentication failures/
464
- rescue Errno::ECONNREFUSED
477
+ rescue Net::SSH::ConnectionTimeout, Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EHOSTUNREACH
465
478
  retry
466
479
  end
467
480
 
@@ -1,5 +1,5 @@
1
1
  module Hetzner
2
2
  module K3s
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hetzner-k3s
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vito Botta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-08 00:00:00.000000000 Z
11
+ date: 2021-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor