hetzner-k3s 0.4.8 → 0.4.9

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: 894c97e8725c519b223e42c190f0432bcf1808c50f6f5c636bc4db06a41ed4e2
4
- data.tar.gz: efd986dd375e93b6ed9f84e1b1bc2742f4d7146f7db76987eb0a02df29b544ee
3
+ metadata.gz: 22358cdc272faa5e09ae2f561bf4225990a87b0461f4920439f9d5e9543fbe59
4
+ data.tar.gz: fc7dc822d53cd881e01a18c509d666bdd0321ed25c636f2873bca3fa1e5e0ce9
5
5
  SHA512:
6
- metadata.gz: df042a8f3da37960b0058f2c3d88a6bfafe0b1e7c4f49eeedfe840b08dcc23c7b922e1ccca893f81caf35084770fbdad261be2d2ce50ac5aebe7c0fae88cfa82
7
- data.tar.gz: 877feedfbe211d1b2c75ce532fa6299888e570c1425cc6168cb882e8064dff9ec1d42d572c8f80f958d21acf48e7d828466f515d540c4239d9ae12b02751f505
6
+ metadata.gz: 35a06d127f14f4848a6a87611292b67e0edbf6cc3fb1ae8b803214428369cfc519942702fe36c14c2537732b38fa024b5d2361f77db56ea58724446f4822537d
7
+ data.tar.gz: bfc7751afa7db09a5e929b8164cdab060dfcb7b86125ae30a4804e179ba8a0550bdb07c9a181e148284e10ef7a632fe26ad81da1e278aeeab492a2899e2cdffb
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.0
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hetzner-k3s (0.4.7)
4
+ hetzner-k3s (0.4.8)
5
5
  bcrypt_pbkdf
6
6
  ed25519
7
7
  http
8
- k8s-ruby
9
8
  net-ssh
10
9
  sshkey
10
+ subprocess
11
11
  thor
12
12
 
13
13
  GEM
@@ -16,43 +16,14 @@ GEM
16
16
  addressable (2.8.0)
17
17
  public_suffix (>= 2.0.2, < 5.0)
18
18
  bcrypt_pbkdf (1.1.0)
19
- concurrent-ruby (1.1.9)
20
19
  diff-lcs (1.4.4)
21
20
  domain_name (0.5.20190701)
22
21
  unf (>= 0.0.5, < 1.0.0)
23
- dry-configurable (0.13.0)
24
- concurrent-ruby (~> 1.0)
25
- dry-core (~> 0.6)
26
- dry-container (0.9.0)
27
- concurrent-ruby (~> 1.0)
28
- dry-configurable (~> 0.13, >= 0.13.0)
29
- dry-core (0.7.1)
30
- concurrent-ruby (~> 1.0)
31
- dry-equalizer (0.3.0)
32
- dry-inflector (0.2.1)
33
- dry-logic (0.6.1)
34
- concurrent-ruby (~> 1.0)
35
- dry-core (~> 0.2)
36
- dry-equalizer (~> 0.2)
37
- dry-struct (0.5.1)
38
- dry-core (~> 0.4, >= 0.4.3)
39
- dry-equalizer (~> 0.2)
40
- dry-types (~> 0.13)
41
- ice_nine (~> 0.11)
42
- dry-types (0.13.4)
43
- concurrent-ruby (~> 1.0)
44
- dry-container (~> 0.3)
45
- dry-core (~> 0.4, >= 0.4.4)
46
- dry-equalizer (~> 0.2)
47
- dry-inflector (~> 0.1, >= 0.1.2)
48
- dry-logic (~> 0.4, >= 0.4.2)
49
22
  ed25519 (1.2.4)
50
- excon (0.88.0)
51
23
  ffi (1.15.4)
52
24
  ffi-compiler (1.0.1)
53
25
  ffi (>= 1.0.0)
54
26
  rake
55
- hashdiff (1.0.1)
56
27
  http (4.4.1)
57
28
  addressable (~> 2.3)
58
29
  http-cookie (~> 1.0)
@@ -63,24 +34,9 @@ GEM
63
34
  http-form_data (2.3.0)
64
35
  http-parser (1.2.3)
65
36
  ffi-compiler (>= 1.0, < 2.0)
66
- ice_nine (0.11.2)
67
- jsonpath (0.9.9)
68
- multi_json
69
- to_regexp (~> 0.2.1)
70
- k8s-ruby (0.10.5)
71
- dry-struct (~> 0.5.0)
72
- dry-types (~> 0.13.0)
73
- excon (~> 0.71)
74
- hashdiff (~> 1.0.0)
75
- jsonpath (~> 0.9.5)
76
- recursive-open-struct (~> 1.1.0)
77
- yajl-ruby (~> 1.4.0)
78
- yaml-safe_load_stream (~> 0.1)
79
- multi_json (1.15.0)
80
37
  net-ssh (6.1.0)
81
38
  public_suffix (4.0.6)
82
39
  rake (12.3.3)
83
- recursive-open-struct (1.1.3)
84
40
  rspec (3.10.0)
85
41
  rspec-core (~> 3.10.0)
86
42
  rspec-expectations (~> 3.10.0)
@@ -95,13 +51,11 @@ GEM
95
51
  rspec-support (~> 3.10.0)
96
52
  rspec-support (3.10.2)
97
53
  sshkey (2.0.0)
98
- thor (1.1.0)
99
- to_regexp (0.2.1)
54
+ subprocess (1.5.5)
55
+ thor (1.2.1)
100
56
  unf (0.1.4)
101
57
  unf_ext
102
58
  unf_ext (0.0.8)
103
- yajl-ruby (1.4.1)
104
- yaml-safe_load_stream (0.1.1)
105
59
 
106
60
  PLATFORMS
107
61
  ruby
@@ -112,4 +66,4 @@ DEPENDENCIES
112
66
  rspec (~> 3.0)
113
67
 
114
68
  BUNDLED WITH
115
- 2.1.4
69
+ 2.3.4
data/README.md CHANGED
@@ -38,7 +38,7 @@ This will install the `hetzner-k3s` executable in your PATH.
38
38
  Alternatively, if you don't want to set up a Ruby runtime but have Docker installed, you can use a container. Run the following from inside the directory where you have the config file for the cluster (described in the next section):
39
39
 
40
40
  ```bash
41
- docker run --rm -it -v ${PWD}:/cluster -v ${HOME}/.ssh:/tmp/.ssh vitobotta/hetzner-k3s:v0.4.7 create-cluster --config-file /cluster/test.yaml
41
+ docker run --rm -it -v ${PWD}:/cluster -v ${HOME}/.ssh:/tmp/.ssh vitobotta/hetzner-k3s:v0.4.9 create-cluster --config-file /cluster/test.yaml
42
42
  ```
43
43
 
44
44
  Replace `test.yaml` with the name of your config file.
@@ -108,7 +108,7 @@ curl \
108
108
  'https://api.hetzner.cloud/v1/images'
109
109
  ```
110
110
 
111
- Note that if you use a custom image, the creation of the servers may take longer than when using the default image.
111
+ Note that if you use a custom image, the creation of the servers may take longer than when using the default image.
112
112
 
113
113
  Also note: the option `verify_host_key` is by default set to `false` to disable host key verification. This is because sometimes when creating new servers, Hetzner may assign IP addresses that were previously used by other servers you owned in the past. Therefore the host key verification would fail. If you set this option to `true` and this happens, the tool won't be able to continue creating the cluster until you resolve the issue with one of the suggestions it will give you.
114
114
 
@@ -211,7 +211,7 @@ kubectl label node <master1> <master2> <master2> plan.upgrade.cattle.io/k3s-serv
211
211
  To delete a cluster, running
212
212
 
213
213
  ```bash
214
- hetzner-k3s delete-cluster --config-file cluster_config.yam
214
+ hetzner-k3s delete-cluster --config-file cluster_config.yaml
215
215
  ```
216
216
 
217
217
  This will delete all the resources in the Hetzner Cloud project for the cluster being deleted.
@@ -254,6 +254,12 @@ I recommend that you create a separate Hetzner project for each cluster, because
254
254
 
255
255
  ## changelog
256
256
 
257
+ - 0.4.9
258
+ - Ensure the program always exits with exit code 1 if the config file fails validation
259
+ - Upgrade System Upgrade Controller to 0.8.1
260
+ - Remove dependency on unmaintained gem k8s-ruby
261
+ - Make the gem compatible with Ruby 3.1.0
262
+
257
263
  - 0.4.8
258
264
  - Increase timeout with API requests to 30 seconds
259
265
  - Limit number of retries for API requests to 3
data/bin/build.sh CHANGED
@@ -6,9 +6,9 @@ set -e
6
6
 
7
7
  IMAGE="vitobotta/hetzner-k3s"
8
8
 
9
- docker build -t ${IMAGE}:v0.4.8 \
9
+ docker build -t ${IMAGE}:v9 \
10
10
  --platform=linux/amd64 \
11
- --cache-from ${IMAGE}:v0.4.7 \
11
+ --cache-from ${IMAGE}:v0.4.8 \
12
12
  --build-arg BUILDKIT_INLINE_CACHE=1 .
13
13
 
14
- docker push vitobotta/hetzner-k3s:v0.4.8
14
+ docker push vitobotta/hetzner-k3s:v0.4.9
data/hetzner-k3s.gemspec CHANGED
@@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency "thor"
22
22
  spec.add_dependency "http"
23
23
  spec.add_dependency "net-ssh"
24
- spec.add_dependency "k8s-ruby"
25
24
  spec.add_dependency "sshkey"
26
25
  spec.add_dependency "ed25519"
27
26
  spec.add_dependency "bcrypt_pbkdf"
27
+ spec.add_dependency "subprocess"
28
28
 
29
29
  # Specify which files should be added to the gem when it is released.
30
30
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -3,10 +3,12 @@ require "http"
3
3
  require "sshkey"
4
4
  require 'ipaddr'
5
5
  require 'open-uri'
6
+ require "yaml"
6
7
 
7
8
  require_relative "cluster"
8
9
  require_relative "version"
9
10
 
11
+
10
12
  module Hetzner
11
13
  module K3s
12
14
  class CLI < Thor
@@ -24,7 +26,6 @@ module Hetzner
24
26
 
25
27
  def create_cluster
26
28
  validate_config_file :create
27
-
28
29
  Cluster.new(hetzner_client: hetzner_client, hetzner_token: find_hetzner_token).create configuration: configuration
29
30
  end
30
31
 
@@ -64,14 +65,17 @@ module Hetzner
64
65
  if File.exists?(config_file_path)
65
66
  begin
66
67
  @configuration = YAML.load_file(options[:config_file])
67
- raise "invalid" unless configuration.is_a? Hash
68
- rescue
68
+ unless configuration.is_a? Hash
69
+ raise "Configuration is invalid"
70
+ exit 1
71
+ end
72
+ rescue => e
69
73
  puts "Please ensure that the config file is a correct YAML manifest."
70
- return
74
+ exit 1
71
75
  end
72
76
  else
73
77
  puts "Please specify a correct path for the config file."
74
- return
78
+ exit 1
75
79
  end
76
80
 
77
81
  @errors = []
@@ -96,7 +100,6 @@ module Hetzner
96
100
  when :upgrade
97
101
  validate_kubeconfig_path_must_exist
98
102
  validate_new_k3s_version
99
- validate_new_k3s_version_must_be_more_recent
100
103
  end
101
104
 
102
105
  errors.flatten!
@@ -271,36 +274,6 @@ module Hetzner
271
274
  schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
272
275
  end
273
276
 
274
- def validate_new_k3s_version_must_be_more_recent
275
- return if options[:force] == "true"
276
- return unless kubernetes_client
277
-
278
- begin
279
- Timeout::timeout(5) do
280
- servers = kubernetes_client.api("v1").resource("nodes").list
281
-
282
- if servers.size == 0
283
- errors << "The cluster seems to have no nodes, nothing to upgrade"
284
- else
285
- available_releases = find_available_releases
286
-
287
- current_k3s_version = servers.first.dig(:status, :nodeInfo, :kubeletVersion)
288
- current_k3s_version_index = available_releases.index(current_k3s_version) || 1000
289
-
290
- new_k3s_version = options[:new_k3s_version]
291
- new_k3s_version_index = available_releases.index(new_k3s_version) || 1000
292
-
293
- unless new_k3s_version_index < current_k3s_version_index
294
- errors << "The new k3s version must be more recent than the current one"
295
- end
296
- end
297
- end
298
-
299
- rescue Timeout::Error
300
- puts "Cannot upgrade: Unable to fetch nodes from Kubernetes API. Is the cluster online?"
301
- end
302
- end
303
-
304
277
  def validate_instance_group(instance_group, workers: true)
305
278
  instance_group_errors = []
306
279
 
@@ -333,17 +306,6 @@ module Hetzner
333
306
  errors << instance_group_errors
334
307
  end
335
308
 
336
- def kubernetes_client
337
- return @kubernetes_client if @kubernetes_client
338
-
339
- config_hash = YAML.load_file(File.expand_path(configuration["kubeconfig_path"]))
340
- config_hash['current-context'] = configuration["cluster_name"]
341
- @kubernetes_client = K8s::Client.config(K8s::Config.new(config_hash))
342
- rescue
343
- errors << "Cannot connect to the Kubernetes cluster"
344
- false
345
- end
346
-
347
309
  def validate_verify_host_key
348
310
  return unless [true, false].include?(configuration.fetch("public_ssh_key_path", false))
349
311
  errors << "Please set the verify_host_key option to either true or false"
@@ -2,8 +2,8 @@ require 'thread'
2
2
  require 'net/ssh'
3
3
  require "securerandom"
4
4
  require "base64"
5
- require "k8s-ruby"
6
5
  require 'timeout'
6
+ require "subprocess"
7
7
 
8
8
  require_relative "../infra/client"
9
9
  require_relative "../infra/firewall"
@@ -13,10 +13,12 @@ require_relative "../infra/server"
13
13
  require_relative "../infra/load_balancer"
14
14
  require_relative "../infra/placement_group"
15
15
 
16
- require_relative "../k3s/client_patch"
16
+ require_relative "../utils"
17
17
 
18
18
 
19
19
  class Cluster
20
+ include Utils
21
+
20
22
  def initialize(hetzner_client:, hetzner_token:)
21
23
  @hetzner_client = hetzner_client
22
24
  @hetzner_token = hetzner_token
@@ -76,7 +78,7 @@ class Cluster
76
78
 
77
79
  attr_reader :hetzner_client, :cluster_name, :kubeconfig_path, :k3s_version,
78
80
  :masters_config, :worker_node_pools,
79
- :location, :public_ssh_key_path, :kubernetes_client,
81
+ :location, :public_ssh_key_path,
80
82
  :hetzner_token, :tls_sans, :new_k3s_version, :configuration,
81
83
  :config_file, :verify_host_key, :networks, :private_ssh_key_path, :configuration
82
84
 
@@ -153,7 +155,7 @@ class Cluster
153
155
 
154
156
  threads = server_configs.map do |server_config|
155
157
  Thread.new do
156
- servers << Hetzner::Server.new(hetzner_client: hetzner_client, cluster_name: cluster_name).create(server_config)
158
+ servers << Hetzner::Server.new(hetzner_client: hetzner_client, cluster_name: cluster_name).create(**server_config)
157
159
  end
158
160
  end
159
161
 
@@ -207,25 +209,71 @@ class Cluster
207
209
  end
208
210
 
209
211
  def upgrade_cluster
210
- resources = K8s::Resource.from_files(ugrade_plan_manifest_path)
211
-
212
- begin
213
- kubernetes_client.api("upgrade.cattle.io/v1").resource("plans").get("k3s-server", namespace: "system-upgrade")
214
-
215
- puts "Aborting - an upgrade is already in progress."
216
-
217
- rescue K8s::Error::NotFound
218
- resources.each do |resource|
219
- kubernetes_client.create_resource(resource)
220
- end
221
-
222
- puts "Upgrade will now start. Run `watch kubectl get nodes` to see the nodes being upgraded. This should take a few minutes for a small cluster."
223
- puts "The API server may be briefly unavailable during the upgrade of the controlplane."
224
-
225
- configuration["k3s_version"] = new_k3s_version
212
+ worker_upgrade_concurrency = workers.size - 1
213
+ worker_upgrade_concurrency = 1 if worker_upgrade_concurrency == 0
226
214
 
227
- File.write(config_file, configuration.to_yaml)
228
- end
215
+ cmd = <<~EOS
216
+ kubectl apply -f - <<-EOF
217
+ apiVersion: upgrade.cattle.io/v1
218
+ kind: Plan
219
+ metadata:
220
+ name: k3s-server
221
+ namespace: system-upgrade
222
+ labels:
223
+ k3s-upgrade: server
224
+ spec:
225
+ concurrency: 1
226
+ version: #{new_k3s_version}
227
+ nodeSelector:
228
+ matchExpressions:
229
+ - {key: node-role.kubernetes.io/master, operator: In, values: ["true"]}
230
+ serviceAccountName: system-upgrade
231
+ tolerations:
232
+ - key: "CriticalAddonsOnly"
233
+ operator: "Equal"
234
+ value: "true"
235
+ effect: "NoExecute"
236
+ cordon: true
237
+ upgrade:
238
+ image: rancher/k3s-upgrade
239
+ EOF
240
+ EOS
241
+
242
+ run cmd, kubeconfig_path: kubeconfig_path
243
+
244
+ cmd = <<~EOS
245
+ kubectl apply -f - <<-EOF
246
+ apiVersion: upgrade.cattle.io/v1
247
+ kind: Plan
248
+ metadata:
249
+ name: k3s-agent
250
+ namespace: system-upgrade
251
+ labels:
252
+ k3s-upgrade: agent
253
+ spec:
254
+ concurrency: #{worker_upgrade_concurrency}
255
+ version: #{new_k3s_version}
256
+ nodeSelector:
257
+ matchExpressions:
258
+ - {key: node-role.kubernetes.io/master, operator: NotIn, values: ["true"]}
259
+ serviceAccountName: system-upgrade
260
+ prepare:
261
+ image: rancher/k3s-upgrade
262
+ args: ["prepare", "k3s-server"]
263
+ cordon: true
264
+ upgrade:
265
+ image: rancher/k3s-upgrade
266
+ EOF
267
+ EOS
268
+
269
+ run cmd, kubeconfig_path: kubeconfig_path
270
+
271
+ puts "Upgrade will now start. Run `watch kubectl get nodes` to see the nodes being upgraded. This should take a few minutes for a small cluster."
272
+ puts "The API server may be briefly unavailable during the upgrade of the controlplane."
273
+
274
+ configuration["k3s_version"] = new_k3s_version
275
+
276
+ File.write(config_file, configuration.to_yaml)
229
277
  end
230
278
 
231
279
 
@@ -317,201 +365,71 @@ class Cluster
317
365
  end
318
366
 
319
367
  def deploy_cloud_controller_manager
368
+ check_kubectl
369
+
320
370
  puts
321
371
  puts "Deploying Hetzner Cloud Controller Manager..."
322
372
 
323
- begin
324
- kubernetes_client.api("v1").resource("secrets").get("hcloud", namespace: "kube-system")
325
-
326
- rescue K8s::Error::NotFound
327
- secret = K8s::Resource.new(
328
- apiVersion: "v1",
329
- kind: "Secret",
330
- metadata: {
331
- namespace: 'kube-system',
332
- name: 'hcloud',
333
- },
334
- data: {
335
- network: Base64.encode64(cluster_name),
336
- token: Base64.encode64(hetzner_token)
337
- }
338
- )
339
-
340
- kubernetes_client.api('v1').resource('secrets').create_resource(secret)
341
- end
342
-
373
+ cmd = <<~EOS
374
+ kubectl apply -f - <<-EOF
375
+ apiVersion: "v1"
376
+ kind: "Secret"
377
+ metadata:
378
+ namespace: 'kube-system'
379
+ name: 'hcloud'
380
+ stringData:
381
+ network: "#{cluster_name}"
382
+ token: "#{hetzner_token}"
383
+ EOF
384
+ EOS
343
385
 
344
- manifest = fetch_manifest("https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml")
345
-
346
- File.write("/tmp/cloud-controller-manager.yaml", manifest)
347
-
348
- resources = K8s::Resource.from_files("/tmp/cloud-controller-manager.yaml")
349
-
350
- begin
351
- kubernetes_client.api("apps/v1").resource("deployments").get("hcloud-cloud-controller-manager", namespace: "kube-system")
352
-
353
- resources.each do |resource|
354
- kubernetes_client.update_resource(resource)
355
- end
386
+ run cmd, kubeconfig_path: kubeconfig_path
356
387
 
357
- rescue K8s::Error::NotFound
358
- resources.each do |resource|
359
- kubernetes_client.create_resource(resource)
360
- end
388
+ cmd = "kubectl apply -f https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml"
361
389
 
362
- end
390
+ run cmd, kubeconfig_path: kubeconfig_path
363
391
 
364
392
  puts "...Cloud Controller Manager deployed"
365
- rescue Excon::Error::Socket
366
- retry
367
- end
368
-
369
- def fetch_manifest(url)
370
- retries ||= 1
371
- HTTP.follow.get(url).body
372
- rescue
373
- retry if (retries += 1) <= 10
374
393
  end
375
394
 
376
395
  def deploy_system_upgrade_controller
396
+ check_kubectl
397
+
377
398
  puts
378
399
  puts "Deploying k3s System Upgrade Controller..."
379
400
 
380
- manifest = HTTP.follow.get("https://github.com/rancher/system-upgrade-controller/releases/download/v0.8.0/system-upgrade-controller.yaml").body
381
-
382
- File.write("/tmp/system-upgrade-controller.yaml", manifest)
383
-
384
- resources = K8s::Resource.from_files("/tmp/system-upgrade-controller.yaml")
385
-
386
- begin
387
- kubernetes_client.api("apps/v1").resource("deployments").get("system-upgrade-controller", namespace: "system-upgrade")
388
-
389
- resources.each do |resource|
390
- kubernetes_client.update_resource(resource)
391
- end
392
-
393
- rescue K8s::Error::NotFound
394
- resources.each do |resource|
395
- kubernetes_client.create_resource(resource)
396
- end
401
+ cmd = "kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/download/v0.8.1/system-upgrade-controller.yaml"
397
402
 
398
- end
403
+ run cmd, kubeconfig_path: kubeconfig_path
399
404
 
400
405
  puts "...k3s System Upgrade Controller deployed"
401
- rescue Excon::Error::Socket
402
- retry
403
406
  end
404
407
 
405
408
  def deploy_csi_driver
409
+ check_kubectl
410
+
406
411
  puts
407
412
  puts "Deploying Hetzner CSI Driver..."
408
413
 
409
- begin
410
- kubernetes_client.api("v1").resource("secrets").get("hcloud-csi", namespace: "kube-system")
411
-
412
- rescue K8s::Error::NotFound
413
- secret = K8s::Resource.new(
414
- apiVersion: "v1",
415
- kind: "Secret",
416
- metadata: {
417
- namespace: 'kube-system',
418
- name: 'hcloud-csi',
419
- },
420
- data: {
421
- token: Base64.encode64(hetzner_token)
422
- }
423
- )
424
-
425
- kubernetes_client.api('v1').resource('secrets').create_resource(secret)
426
- end
427
-
428
-
429
- manifest = HTTP.follow.get("https://raw.githubusercontent.com/hetznercloud/csi-driver/v1.6.0/deploy/kubernetes/hcloud-csi.yml").body
430
-
431
- File.write("/tmp/csi-driver.yaml", manifest)
414
+ cmd = <<~EOS
415
+ kubectl apply -f - <<-EOF
416
+ apiVersion: "v1"
417
+ kind: "Secret"
418
+ metadata:
419
+ namespace: 'kube-system'
420
+ name: 'hcloud-csi'
421
+ stringData:
422
+ token: "#{hetzner_token}"
423
+ EOF
424
+ EOS
432
425
 
433
- resources = K8s::Resource.from_files("/tmp/csi-driver.yaml")
426
+ run cmd, kubeconfig_path: kubeconfig_path
434
427
 
435
- begin
436
- kubernetes_client.api("apps/v1").resource("daemonsets").get("hcloud-csi-node", namespace: "kube-system")
428
+ cmd = "kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/v1.6.0/deploy/kubernetes/hcloud-csi.yml"
437
429
 
438
-
439
- resources.each do |resource|
440
- begin
441
- kubernetes_client.update_resource(resource)
442
- rescue K8s::Error::Invalid => e
443
- raise e unless e.message =~ /must be specified/i
444
- end
445
- end
446
-
447
- rescue K8s::Error::NotFound
448
- resources.each do |resource|
449
- kubernetes_client.create_resource(resource)
450
- end
451
-
452
- end
430
+ run cmd, kubeconfig_path: kubeconfig_path
453
431
 
454
432
  puts "...CSI Driver deployed"
455
- rescue Excon::Error::Socket
456
- retry
457
- end
458
-
459
- def wait_for_ssh(server)
460
- Timeout::timeout(5) do
461
- server_name = server["name"]
462
-
463
- puts "Waiting for server #{server_name} to be up..."
464
-
465
- loop do
466
- result = ssh(server, "echo UP")
467
- break if result == "UP"
468
- end
469
-
470
- puts "...server #{server_name} is now up."
471
- end
472
- rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH, Timeout::Error, IOError
473
- retry
474
- end
475
-
476
- def ssh(server, command, print_output: false)
477
- public_ip = server.dig("public_net", "ipv4", "ip")
478
- output = ""
479
-
480
- params = { verify_host_key: (verify_host_key ? :always : :never) }
481
-
482
- if private_ssh_key_path
483
- params[:keys] = [private_ssh_key_path]
484
- end
485
-
486
- Net::SSH.start(public_ip, "root", params) do |session|
487
- session.exec!(command) do |channel, stream, data|
488
- output << data
489
- puts data if print_output
490
- end
491
- end
492
- output.chop
493
- rescue Net::SSH::Disconnect => e
494
- retry unless e.message =~ /Too many authentication failures/
495
- rescue Net::SSH::ConnectionTimeout, Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EHOSTUNREACH
496
- retry
497
- rescue Net::SSH::AuthenticationFailed
498
- puts
499
- puts "Cannot continue: SSH authentication failed. Please ensure that the private SSH key is correct."
500
- exit 1
501
- rescue Net::SSH::HostKeyMismatch
502
- puts
503
- puts "Cannot continue: Unable to SSH into server with IP #{public_ip} because the existing fingerprint in the known_hosts file does not match that of the actual host key."
504
- puts "This is due to a security check but can also happen when creating a new server that gets assigned the same IP address as another server you've owned in the past."
505
- puts "If are sure no security is being violated here and you're just creating new servers, you can eiher remove the relevant lines from your known_hosts (see IPs from the cloud console) or disable host key verification by setting the option 'verify_host_key' to false in the configuration file for the cluster."
506
- exit 1
507
- end
508
-
509
- def kubernetes_client
510
- return @kubernetes_client if @kubernetes_client
511
-
512
- config_hash = YAML.load_file(kubeconfig_path)
513
- config_hash['current-context'] = cluster_name
514
- @kubernetes_client = K8s::Client.config(K8s::Config.new(config_hash))
515
433
  end
516
434
 
517
435
  def find_flannel_interface(server)
@@ -591,63 +509,6 @@ class Cluster
591
509
  FileUtils.chmod "go-r", kubeconfig_path
592
510
  end
593
511
 
594
- def ugrade_plan_manifest_path
595
- worker_upgrade_concurrency = workers.size - 1
596
- worker_upgrade_concurrency = 1 if worker_upgrade_concurrency == 0
597
-
598
- manifest = <<~EOF
599
- apiVersion: upgrade.cattle.io/v1
600
- kind: Plan
601
- metadata:
602
- name: k3s-server
603
- namespace: system-upgrade
604
- labels:
605
- k3s-upgrade: server
606
- spec:
607
- concurrency: 1
608
- version: #{new_k3s_version}
609
- nodeSelector:
610
- matchExpressions:
611
- - {key: node-role.kubernetes.io/master, operator: In, values: ["true"]}
612
- serviceAccountName: system-upgrade
613
- tolerations:
614
- - key: "CriticalAddonsOnly"
615
- operator: "Equal"
616
- value: "true"
617
- effect: "NoExecute"
618
- cordon: true
619
- upgrade:
620
- image: rancher/k3s-upgrade
621
- ---
622
- apiVersion: upgrade.cattle.io/v1
623
- kind: Plan
624
- metadata:
625
- name: k3s-agent
626
- namespace: system-upgrade
627
- labels:
628
- k3s-upgrade: agent
629
- spec:
630
- concurrency: #{worker_upgrade_concurrency}
631
- version: #{new_k3s_version}
632
- nodeSelector:
633
- matchExpressions:
634
- - {key: node-role.kubernetes.io/master, operator: NotIn, values: ["true"]}
635
- serviceAccountName: system-upgrade
636
- prepare:
637
- image: rancher/k3s-upgrade
638
- args: ["prepare", "k3s-server"]
639
- cordon: true
640
- upgrade:
641
- image: rancher/k3s-upgrade
642
- EOF
643
-
644
- temp_file_path = "/tmp/k3s-upgrade-plan.yaml"
645
-
646
- File.write(temp_file_path, manifest)
647
-
648
- temp_file_path
649
- end
650
-
651
512
  def belongs_to_cluster?(server)
652
513
  server.dig("labels", "cluster") == cluster_name
653
514
  end
@@ -661,4 +522,11 @@ class Cluster
661
522
  configuration.dig("image") || "ubuntu-20.04"
662
523
  end
663
524
 
525
+ def check_kubectl
526
+ unless which("kubectl")
527
+ puts "Please ensure kubectl is installed and in your PATH."
528
+ exit 1
529
+ end
530
+ end
531
+
664
532
  end
@@ -1,5 +1,5 @@
1
1
  module Hetzner
2
2
  module K3s
3
- VERSION = "0.4.8"
3
+ VERSION = "0.4.9"
4
4
  end
5
5
  end
@@ -0,0 +1,99 @@
1
+ module Utils
2
+ def which(cmd)
3
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
4
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
5
+ exts.each do |ext|
6
+ exe = File.join(path, "#{cmd}#{ext}")
7
+ return exe if File.executable?(exe) && !File.directory?(exe)
8
+ end
9
+ end
10
+ nil
11
+ end
12
+
13
+ def run(command, kubeconfig_path:)
14
+ env = ENV.to_hash.merge({
15
+ "KUBECONFIG" => kubeconfig_path
16
+ })
17
+
18
+ cmd_path = "/tmp/cli.cmd"
19
+
20
+ File.open(cmd_path, "w") do |f|
21
+ f.write("set -euo pipefail\n")
22
+ f.write(command)
23
+ end
24
+
25
+ FileUtils.chmod("+x", cmd_path)
26
+
27
+ begin
28
+ process = nil
29
+
30
+ at_exit do
31
+ begin
32
+ process&.send_signal("SIGTERM")
33
+ rescue Errno::ESRCH, Interrupt
34
+ end
35
+ end
36
+
37
+ Subprocess.check_call(["bash", "-c", cmd_path], env: env) do |p|
38
+ process = p
39
+ end
40
+
41
+ rescue Subprocess::NonZeroExit
42
+ puts "Command failed: non-zero exit code"
43
+ exit 1
44
+ rescue Interrupt
45
+ puts "Command interrupted"
46
+ exit 1
47
+ end
48
+ end
49
+
50
+ def wait_for_ssh(server)
51
+ Timeout::timeout(5) do
52
+ server_name = server["name"]
53
+
54
+ puts "Waiting for server #{server_name} to be up..."
55
+
56
+ loop do
57
+ result = ssh(server, "echo UP")
58
+ break if result == "UP"
59
+ end
60
+
61
+ puts "...server #{server_name} is now up."
62
+ end
63
+ rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH, Timeout::Error, IOError
64
+ retry
65
+ end
66
+
67
+ def ssh(server, command, print_output: false)
68
+ public_ip = server.dig("public_net", "ipv4", "ip")
69
+ output = ""
70
+
71
+ params = { verify_host_key: (verify_host_key ? :always : :never) }
72
+
73
+ if private_ssh_key_path
74
+ params[:keys] = [private_ssh_key_path]
75
+ end
76
+
77
+ Net::SSH.start(public_ip, "root", params) do |session|
78
+ session.exec!(command) do |channel, stream, data|
79
+ output << data
80
+ puts data if print_output
81
+ end
82
+ end
83
+ output.chop
84
+ rescue Net::SSH::Disconnect => e
85
+ retry unless e.message =~ /Too many authentication failures/
86
+ rescue Net::SSH::ConnectionTimeout, Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EHOSTUNREACH
87
+ retry
88
+ rescue Net::SSH::AuthenticationFailed
89
+ puts
90
+ puts "Cannot continue: SSH authentication failed. Please ensure that the private SSH key is correct."
91
+ exit 1
92
+ rescue Net::SSH::HostKeyMismatch
93
+ puts
94
+ puts "Cannot continue: Unable to SSH into server with IP #{public_ip} because the existing fingerprint in the known_hosts file does not match that of the actual host key."
95
+ puts "This is due to a security check but can also happen when creating a new server that gets assigned the same IP address as another server you've owned in the past."
96
+ puts "If are sure no security is being violated here and you're just creating new servers, you can eiher remove the relevant lines from your known_hosts (see IPs from the cloud console) or disable host key verification by setting the option 'verify_host_key' to false in the configuration file for the cluster."
97
+ exit 1
98
+ end
99
+ 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.4.8
4
+ version: 0.4.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vito Botta
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-17 00:00:00.000000000 Z
11
+ date: 2022-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: k8s-ruby
56
+ name: sshkey
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: sshkey
70
+ name: ed25519
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: ed25519
84
+ name: bcrypt_pbkdf
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: bcrypt_pbkdf
98
+ name: subprocess
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -119,6 +119,7 @@ extra_rdoc_files: []
119
119
  files:
120
120
  - ".gitignore"
121
121
  - ".rspec"
122
+ - ".ruby-version"
122
123
  - ".travis.yml"
123
124
  - CODE_OF_CONDUCT.md
124
125
  - Dockerfile
@@ -144,9 +145,9 @@ files:
144
145
  - lib/hetzner/infra/server.rb
145
146
  - lib/hetzner/infra/ssh_key.rb
146
147
  - lib/hetzner/k3s/cli.rb
147
- - lib/hetzner/k3s/client_patch.rb
148
148
  - lib/hetzner/k3s/cluster.rb
149
149
  - lib/hetzner/k3s/version.rb
150
+ - lib/hetzner/utils.rb
150
151
  homepage: https://github.com/vitobotta/hetzner-k3s
151
152
  licenses:
152
153
  - MIT
@@ -154,7 +155,7 @@ metadata:
154
155
  homepage_uri: https://github.com/vitobotta/hetzner-k3s
155
156
  source_code_uri: https://github.com/vitobotta/hetzner-k3s
156
157
  changelog_uri: https://github.com/vitobotta/hetzner-k3s
157
- post_install_message:
158
+ post_install_message:
158
159
  rdoc_options: []
159
160
  require_paths:
160
161
  - lib
@@ -169,8 +170,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  - !ruby/object:Gem::Version
170
171
  version: '0'
171
172
  requirements: []
172
- rubygems_version: 3.1.4
173
- signing_key:
173
+ rubygems_version: 3.3.3
174
+ signing_key:
174
175
  specification_version: 4
175
176
  summary: A CLI to create a Kubernetes cluster in Hetzner Cloud very quickly using
176
177
  k3s.
@@ -1,38 +0,0 @@
1
- module K8s
2
- class ResourceClient
3
- def initialize(transport, api_client, api_resource, namespace: nil, resource_class: K8s::Resource)
4
- @transport = transport
5
- @api_client = api_client
6
- @api_resource = api_resource
7
- @namespace = namespace
8
- @resource_class = resource_class
9
-
10
- if @api_resource.name.include? '/'
11
- @resource, @subresource = @api_resource.name.split('/', 2)
12
- else
13
- @resource = @api_resource.name
14
- @subresource = nil
15
- end
16
-
17
- # fail "Resource #{api_resource.name} is not namespaced" unless api_resource.namespaced || !namespace
18
- end
19
-
20
- def path(name = nil, subresource: @subresource, namespace: @namespace)
21
- namespace_part = namespace ? ['namespaces', namespace] : []
22
-
23
- if namespaced?
24
- if name && subresource
25
- @api_client.path(*namespace_part, @resource, name, subresource)
26
- elsif name
27
- @api_client.path(*namespace_part, @resource, name)
28
- else namespaced?
29
- @api_client.path(*namespace_part, @resource)
30
- end
31
- elsif name
32
- @api_client.path(@resource, name)
33
- else
34
- @api_client.path(@resource)
35
- end
36
- end
37
- end
38
- end