hetzner-k3s 0.5.6 → 0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -1
- data/.ruby-version +1 -1
- data/Dockerfile +1 -1
- data/Gemfile.lock +5 -5
- data/README.md +39 -7
- data/bin/build.sh +3 -5
- data/hetzner-k3s.gemspec +1 -1
- data/lib/hetzner/infra/firewall.rb +6 -8
- data/lib/hetzner/infra/network.rb +19 -9
- data/lib/hetzner/infra/server.rb +73 -46
- data/lib/hetzner/k3s/cli.rb +14 -386
- data/lib/hetzner/k3s/cluster.rb +53 -29
- data/lib/hetzner/k3s/configuration.rb +484 -0
- data/lib/hetzner/k3s/version.rb +1 -1
- data/lib/hetzner/utils.rb +7 -4
- metadata +5 -4
data/lib/hetzner/k3s/cli.rb
CHANGED
@@ -8,6 +8,7 @@ require 'open-uri'
|
|
8
8
|
require 'yaml'
|
9
9
|
|
10
10
|
require_relative 'cluster'
|
11
|
+
require_relative 'configuration'
|
11
12
|
require_relative 'version'
|
12
13
|
|
13
14
|
module Hetzner
|
@@ -17,13 +18,6 @@ module Hetzner
|
|
17
18
|
true
|
18
19
|
end
|
19
20
|
|
20
|
-
def initialize(*args)
|
21
|
-
@errors = []
|
22
|
-
@used_server_types = []
|
23
|
-
|
24
|
-
super
|
25
|
-
end
|
26
|
-
|
27
21
|
desc 'version', 'Print the version'
|
28
22
|
def version
|
29
23
|
puts Hetzner::K3s::VERSION
|
@@ -32,15 +26,15 @@ module Hetzner
|
|
32
26
|
desc 'create-cluster', 'Create a k3s cluster in Hetzner Cloud'
|
33
27
|
option :config_file, required: true
|
34
28
|
def create_cluster
|
35
|
-
|
36
|
-
Cluster.new(
|
29
|
+
configuration.validate action: :create
|
30
|
+
Cluster.new(configuration:).create
|
37
31
|
end
|
38
32
|
|
39
33
|
desc 'delete-cluster', 'Delete an existing k3s cluster in Hetzner Cloud'
|
40
34
|
option :config_file, required: true
|
41
35
|
def delete_cluster
|
42
|
-
|
43
|
-
Cluster.new(
|
36
|
+
configuration.validate action: :delete
|
37
|
+
Cluster.new(configuration:).delete
|
44
38
|
end
|
45
39
|
|
46
40
|
desc 'upgrade-cluster', 'Upgrade an existing k3s cluster in Hetzner Cloud to a new version'
|
@@ -48,394 +42,28 @@ module Hetzner
|
|
48
42
|
option :new_k3s_version, required: true
|
49
43
|
option :force, default: 'false'
|
50
44
|
def upgrade_cluster
|
51
|
-
|
52
|
-
|
53
|
-
Cluster.new(hetzner_client:, hetzner_token:)
|
54
|
-
.upgrade(configuration:, new_k3s_version: options[:new_k3s_version], config_file: options[:config_file])
|
45
|
+
configuration.validate action: :upgrade
|
46
|
+
Cluster.new(configuration:).upgrade(new_k3s_version: options[:new_k3s_version], config_file: options[:config_file])
|
55
47
|
end
|
56
48
|
|
57
49
|
desc 'releases', 'List available k3s releases'
|
58
50
|
def releases
|
59
|
-
available_releases.each do |release|
|
51
|
+
Hetzner::Configuration.available_releases.each do |release|
|
60
52
|
puts release
|
61
53
|
end
|
62
54
|
end
|
63
55
|
|
64
56
|
private
|
65
57
|
|
66
|
-
attr_reader :
|
67
|
-
attr_accessor :errors, :used_server_types
|
68
|
-
|
69
|
-
def validate_configuration(action)
|
70
|
-
validate_configuration_file
|
71
|
-
validate_token
|
72
|
-
validate_cluster_name
|
73
|
-
validate_kubeconfig_path
|
74
|
-
|
75
|
-
case action
|
76
|
-
when :create
|
77
|
-
validate_create
|
78
|
-
when :delete
|
79
|
-
validate_kubeconfig_path_must_exist
|
80
|
-
when :upgrade
|
81
|
-
validate_upgrade
|
82
|
-
end
|
83
|
-
|
84
|
-
errors.flatten!
|
85
|
-
|
86
|
-
return if errors.empty?
|
87
|
-
|
88
|
-
puts 'Some information in the configuration file requires your attention:'
|
89
|
-
|
90
|
-
errors.each do |error|
|
91
|
-
puts " - #{error}"
|
92
|
-
end
|
93
|
-
|
94
|
-
exit 1
|
95
|
-
end
|
96
|
-
|
97
|
-
def valid_token?
|
98
|
-
return @valid unless @valid.nil?
|
99
|
-
|
100
|
-
begin
|
101
|
-
token = hetzner_token
|
102
|
-
@hetzner_client = Hetzner::Client.new(token:)
|
103
|
-
response = hetzner_client.get('/locations')
|
104
|
-
error_code = response.dig('error', 'code')
|
105
|
-
@valid = error_code&.size != 0
|
106
|
-
rescue StandardError
|
107
|
-
@valid = false
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def validate_token
|
112
|
-
errors << 'Invalid Hetzner Cloud token' unless valid_token?
|
113
|
-
end
|
114
|
-
|
115
|
-
def validate_cluster_name
|
116
|
-
errors << 'Cluster name is an invalid format (only lowercase letters, digits and dashes are allowed)' unless configuration['cluster_name'] =~ /\A[a-z\d-]+\z/
|
117
|
-
|
118
|
-
return if configuration['cluster_name'] =~ /\A[a-z]+.*\z/
|
119
|
-
|
120
|
-
errors << 'Ensure that the cluster name starts with a normal letter'
|
121
|
-
end
|
122
|
-
|
123
|
-
def validate_kubeconfig_path
|
124
|
-
path = File.expand_path(configuration['kubeconfig_path'])
|
125
|
-
errors << 'kubeconfig path cannot be a directory' and return if File.directory? path
|
126
|
-
|
127
|
-
directory = File.dirname(path)
|
128
|
-
errors << "Directory #{directory} doesn't exist" unless File.exist? directory
|
129
|
-
rescue StandardError
|
130
|
-
errors << 'Invalid path for the kubeconfig'
|
131
|
-
end
|
132
|
-
|
133
|
-
def validate_public_ssh_key
|
134
|
-
path = File.expand_path(configuration['public_ssh_key_path'])
|
135
|
-
errors << 'Invalid Public SSH key path' and return unless File.exist? path
|
136
|
-
|
137
|
-
key = File.read(path)
|
138
|
-
errors << 'Public SSH key is invalid' unless ::SSHKey.valid_ssh_public_key?(key)
|
139
|
-
rescue StandardError
|
140
|
-
errors << 'Invalid Public SSH key path'
|
141
|
-
end
|
142
|
-
|
143
|
-
def validate_private_ssh_key
|
144
|
-
private_ssh_key_path = configuration['private_ssh_key_path']
|
145
|
-
|
146
|
-
return unless private_ssh_key_path
|
147
|
-
|
148
|
-
path = File.expand_path(private_ssh_key_path)
|
149
|
-
errors << 'Invalid Private SSH key path' and return unless File.exist?(path)
|
150
|
-
rescue StandardError
|
151
|
-
errors << 'Invalid Private SSH key path'
|
152
|
-
end
|
153
|
-
|
154
|
-
def validate_kubeconfig_path_must_exist
|
155
|
-
path = File.expand_path configuration['kubeconfig_path']
|
156
|
-
errors << 'kubeconfig path is invalid' and return unless File.exist? path
|
157
|
-
|
158
|
-
errors << 'kubeconfig path cannot be a directory' if File.directory? path
|
159
|
-
rescue StandardError
|
160
|
-
errors << 'Invalid kubeconfig path'
|
161
|
-
end
|
162
|
-
|
163
|
-
def server_types
|
164
|
-
return [] unless valid_token?
|
165
|
-
|
166
|
-
@server_types ||= hetzner_client.get('/server_types')['server_types'].map { |server_type| server_type['name'] }
|
167
|
-
rescue StandardError
|
168
|
-
@errors << 'Cannot fetch server types with Hetzner API, please try again later'
|
169
|
-
false
|
170
|
-
end
|
171
|
-
|
172
|
-
def locations
|
173
|
-
return [] unless valid_token?
|
174
|
-
|
175
|
-
@locations ||= hetzner_client.get('/locations')['locations'].map { |location| location['name'] }
|
176
|
-
rescue StandardError
|
177
|
-
@errors << 'Cannot fetch locations with Hetzner API, please try again later'
|
178
|
-
[]
|
179
|
-
end
|
180
|
-
|
181
|
-
def valid_location?(location)
|
182
|
-
return if locations.empty? && !valid_token?
|
183
|
-
|
184
|
-
locations.include? location
|
185
|
-
end
|
186
|
-
|
187
|
-
def validate_masters_location
|
188
|
-
return if valid_location?(configuration['location'])
|
189
|
-
|
190
|
-
errors << 'Invalid location for master nodes - valid locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)'
|
191
|
-
end
|
192
|
-
|
193
|
-
def available_releases
|
194
|
-
@available_releases ||= begin
|
195
|
-
response = HTTP.get('https://api.github.com/repos/k3s-io/k3s/tags?per_page=999').body
|
196
|
-
JSON.parse(response).map { |hash| hash['name'] }
|
197
|
-
end
|
198
|
-
rescue StandardError
|
199
|
-
errors << 'Cannot fetch the releases with Hetzner API, please try again later'
|
200
|
-
end
|
201
|
-
|
202
|
-
def validate_k3s_version
|
203
|
-
k3s_version = configuration['k3s_version']
|
204
|
-
errors << 'Invalid k3s version' unless available_releases.include? k3s_version
|
205
|
-
end
|
206
|
-
|
207
|
-
def validate_new_k3s_version
|
208
|
-
new_k3s_version = options[:new_k3s_version]
|
209
|
-
errors << 'The new k3s version is invalid' unless available_releases.include? new_k3s_version
|
210
|
-
end
|
211
|
-
|
212
|
-
def validate_masters
|
213
|
-
masters_pool = nil
|
214
|
-
|
215
|
-
begin
|
216
|
-
masters_pool = configuration['masters']
|
217
|
-
rescue StandardError
|
218
|
-
errors << 'Invalid masters configuration'
|
219
|
-
return
|
220
|
-
end
|
221
|
-
|
222
|
-
if masters_pool.nil?
|
223
|
-
errors << 'Invalid masters configuration'
|
224
|
-
return
|
225
|
-
end
|
226
|
-
|
227
|
-
validate_instance_group masters_pool, workers: false
|
228
|
-
end
|
229
|
-
|
230
|
-
def validate_worker_node_pools
|
231
|
-
worker_node_pools = configuration['worker_node_pools'] || []
|
232
|
-
|
233
|
-
unless worker_node_pools.size.positive? || schedule_workloads_on_masters?
|
234
|
-
errors << 'Invalid node pools configuration'
|
235
|
-
return
|
236
|
-
end
|
237
|
-
|
238
|
-
return if worker_node_pools.size.zero? && schedule_workloads_on_masters?
|
58
|
+
attr_reader :hetzner_token, :hetzner_client
|
239
59
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
errors << 'Each node pool must have an unique name'
|
246
|
-
elsif server_types
|
247
|
-
worker_node_pools.each do |worker_node_pool|
|
248
|
-
validate_instance_group worker_node_pool
|
249
|
-
end
|
60
|
+
def configuration
|
61
|
+
@configuration ||= begin
|
62
|
+
config = ::Hetzner::Configuration.new(options:)
|
63
|
+
@hetzner_token = config.hetzner_token
|
64
|
+
config
|
250
65
|
end
|
251
66
|
end
|
252
|
-
|
253
|
-
def schedule_workloads_on_masters?
|
254
|
-
schedule_workloads_on_masters = configuration['schedule_workloads_on_masters']
|
255
|
-
schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
|
256
|
-
end
|
257
|
-
|
258
|
-
def validate_instance_group(instance_group, workers: true)
|
259
|
-
instance_group_errors = []
|
260
|
-
|
261
|
-
instance_group_type = workers ? "Worker mode pool '#{instance_group['name']}'" : 'Masters pool'
|
262
|
-
|
263
|
-
instance_group_errors << "#{instance_group_type} has an invalid name" unless !workers || instance_group['name'] =~ /\A([A-Za-z0-9\-_]+)\Z/
|
264
|
-
|
265
|
-
instance_group_errors << "#{instance_group_type} is in an invalid format" unless instance_group.is_a? Hash
|
266
|
-
|
267
|
-
instance_group_errors << "#{instance_group_type} has an invalid instance type" unless !valid_token? || server_types.include?(instance_group['instance_type'])
|
268
|
-
|
269
|
-
if workers
|
270
|
-
location = instance_group.fetch('location', configuration['location'])
|
271
|
-
instance_group_errors << "#{instance_group_type} has an invalid location - valid locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)" unless valid_location?(location)
|
272
|
-
|
273
|
-
in_network_zone = configuration['location'] == 'ash' ? location == 'ash' : location != 'ash'
|
274
|
-
instance_group_errors << "#{instance_group_type} must be in the same network zone as the masters. If the masters are located in Ashburn, all the node pools must be located in Ashburn too, otherwise none of the node pools should be located in Ashburn." unless in_network_zone
|
275
|
-
end
|
276
|
-
|
277
|
-
if instance_group['instance_count'].is_a? Integer
|
278
|
-
if instance_group['instance_count'] < 1
|
279
|
-
instance_group_errors << "#{instance_group_type} must have at least one node"
|
280
|
-
elsif instance_group['instance_count'] > 10
|
281
|
-
instance_group_errors << "#{instance_group_type} cannot have more than 10 nodes due to a limitation with the Hetzner placement groups. You can add more node pools if you need more nodes."
|
282
|
-
elsif !workers
|
283
|
-
instance_group_errors << 'Masters count must equal to 1 for non-HA clusters or an odd number (recommended 3) for an HA cluster' unless instance_group['instance_count'].odd?
|
284
|
-
end
|
285
|
-
else
|
286
|
-
instance_group_errors << "#{instance_group_type} has an invalid instance count"
|
287
|
-
end
|
288
|
-
|
289
|
-
used_server_types << instance_group['instance_type']
|
290
|
-
|
291
|
-
errors << instance_group_errors
|
292
|
-
end
|
293
|
-
|
294
|
-
def validate_verify_host_key
|
295
|
-
return unless [true, false].include?(configuration.fetch('public_ssh_key_path', false))
|
296
|
-
|
297
|
-
errors << 'Please set the verify_host_key option to either true or false'
|
298
|
-
end
|
299
|
-
|
300
|
-
def hetzner_token
|
301
|
-
@token = ENV['HCLOUD_TOKEN']
|
302
|
-
return @token if @token
|
303
|
-
|
304
|
-
@token = configuration['hetzner_token']
|
305
|
-
end
|
306
|
-
|
307
|
-
def validate_ssh_allowed_networks
|
308
|
-
networks ||= configuration['ssh_allowed_networks']
|
309
|
-
|
310
|
-
if networks.nil? || networks.empty?
|
311
|
-
errors << 'At least one network/IP range must be specified for SSH access'
|
312
|
-
return
|
313
|
-
end
|
314
|
-
|
315
|
-
invalid_networks = networks.reject do |network|
|
316
|
-
IPAddr.new(network)
|
317
|
-
rescue StandardError
|
318
|
-
false
|
319
|
-
end
|
320
|
-
|
321
|
-
unless invalid_networks.empty?
|
322
|
-
invalid_networks.each do |network|
|
323
|
-
errors << "The network #{network} is an invalid range"
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
invalid_ranges = networks.reject do |network|
|
328
|
-
network.include? '/'
|
329
|
-
end
|
330
|
-
|
331
|
-
unless invalid_ranges.empty?
|
332
|
-
invalid_ranges.each do |_network|
|
333
|
-
errors << 'Please use the CIDR notation for the networks to avoid ambiguity'
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
return unless invalid_networks.empty?
|
338
|
-
|
339
|
-
current_ip = URI.open('http://whatismyip.akamai.com').read
|
340
|
-
|
341
|
-
current_ip_networks = networks.detect do |network|
|
342
|
-
IPAddr.new(network).include?(current_ip)
|
343
|
-
rescue StandardError
|
344
|
-
false
|
345
|
-
end
|
346
|
-
|
347
|
-
errors << "Your current IP #{current_ip} is not included into any of the networks you've specified, so we won't be able to SSH into the nodes" unless current_ip_networks
|
348
|
-
end
|
349
|
-
|
350
|
-
def validate_additional_packages
|
351
|
-
additional_packages = configuration['additional_packages']
|
352
|
-
errors << 'Invalid additional packages configuration - it should be an array' if additional_packages && !additional_packages.is_a?(Array)
|
353
|
-
end
|
354
|
-
|
355
|
-
def validate_create
|
356
|
-
validate_public_ssh_key
|
357
|
-
validate_private_ssh_key
|
358
|
-
validate_ssh_allowed_networks
|
359
|
-
validate_masters_location
|
360
|
-
validate_k3s_version
|
361
|
-
validate_masters
|
362
|
-
validate_worker_node_pools
|
363
|
-
validate_verify_host_key
|
364
|
-
validate_additional_packages
|
365
|
-
validate_kube_api_server_args
|
366
|
-
validate_kube_scheduler_args
|
367
|
-
validate_kube_controller_manager_args
|
368
|
-
validate_kube_cloud_controller_manager_args
|
369
|
-
validate_kubelet_args
|
370
|
-
validate_kube_proxy_args
|
371
|
-
end
|
372
|
-
|
373
|
-
def validate_upgrade
|
374
|
-
validate_kubeconfig_path_must_exist
|
375
|
-
validate_new_k3s_version
|
376
|
-
end
|
377
|
-
|
378
|
-
def validate_configuration_file
|
379
|
-
config_file_path = options[:config_file]
|
380
|
-
|
381
|
-
if File.exist?(config_file_path)
|
382
|
-
begin
|
383
|
-
@configuration = YAML.load_file(options[:config_file])
|
384
|
-
unless configuration.is_a? Hash
|
385
|
-
puts 'Configuration is invalid'
|
386
|
-
exit 1
|
387
|
-
end
|
388
|
-
rescue StandardError
|
389
|
-
puts 'Please ensure that the config file is a correct YAML manifest.'
|
390
|
-
exit 1
|
391
|
-
end
|
392
|
-
else
|
393
|
-
puts 'Please specify a correct path for the config file.'
|
394
|
-
exit 1
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
def validate_kube_api_server_args
|
399
|
-
kube_api_server_args = configuration['kube_api_server_args']
|
400
|
-
return unless kube_api_server_args
|
401
|
-
|
402
|
-
errors << 'kube_api_server_args must be an array of arguments' unless kube_api_server_args.is_a? Array
|
403
|
-
end
|
404
|
-
|
405
|
-
def validate_kube_scheduler_args
|
406
|
-
kube_scheduler_args = configuration['kube_scheduler_args']
|
407
|
-
return unless kube_scheduler_args
|
408
|
-
|
409
|
-
errors << 'kube_scheduler_args must be an array of arguments' unless kube_scheduler_args.is_a? Array
|
410
|
-
end
|
411
|
-
|
412
|
-
def validate_kube_controller_manager_args
|
413
|
-
kube_controller_manager_args = configuration['kube_controller_manager_args']
|
414
|
-
return unless kube_controller_manager_args
|
415
|
-
|
416
|
-
errors << 'kube_controller_manager_args must be an array of arguments' unless kube_controller_manager_args.is_a? Array
|
417
|
-
end
|
418
|
-
|
419
|
-
def validate_kube_cloud_controller_manager_args
|
420
|
-
kube_cloud_controller_manager_args = configuration['kube_cloud_controller_manager_args']
|
421
|
-
return unless kube_cloud_controller_manager_args
|
422
|
-
|
423
|
-
errors << 'kube_cloud_controller_manager_args must be an array of arguments' unless kube_cloud_controller_manager_args.is_a? Array
|
424
|
-
end
|
425
|
-
|
426
|
-
def validate_kubelet_args
|
427
|
-
kubelet_args = configuration['kubelet_args']
|
428
|
-
return unless kubelet_args
|
429
|
-
|
430
|
-
errors << 'kubelet_args must be an array of arguments' unless kubelet_args.is_a? Array
|
431
|
-
end
|
432
|
-
|
433
|
-
def validate_kube_proxy_args
|
434
|
-
kube_proxy_args = configuration['kube_proxy_args']
|
435
|
-
return unless kube_proxy_args
|
436
|
-
|
437
|
-
errors << 'kube_proxy_args must be an array of arguments' unless kube_proxy_args.is_a? Array
|
438
|
-
end
|
439
67
|
end
|
440
68
|
end
|
441
69
|
end
|
data/lib/hetzner/k3s/cluster.rb
CHANGED
@@ -19,13 +19,11 @@ require_relative '../utils'
|
|
19
19
|
class Cluster
|
20
20
|
include Utils
|
21
21
|
|
22
|
-
def initialize(
|
23
|
-
@
|
24
|
-
@hetzner_token = hetzner_token
|
22
|
+
def initialize(configuration:)
|
23
|
+
@configuration = configuration
|
25
24
|
end
|
26
25
|
|
27
|
-
def create
|
28
|
-
@configuration = configuration
|
26
|
+
def create
|
29
27
|
@cluster_name = configuration['cluster_name']
|
30
28
|
@kubeconfig_path = File.expand_path(configuration['kubeconfig_path'])
|
31
29
|
@public_ssh_key_path = File.expand_path(configuration['public_ssh_key_path'])
|
@@ -37,7 +35,8 @@ class Cluster
|
|
37
35
|
@masters_location = configuration['location']
|
38
36
|
@verify_host_key = configuration.fetch('verify_host_key', false)
|
39
37
|
@servers = []
|
40
|
-
@
|
38
|
+
@ssh_networks = configuration['ssh_allowed_networks']
|
39
|
+
@api_networks = configuration['api_allowed_networks']
|
41
40
|
@enable_encryption = configuration.fetch('enable_encryption', false)
|
42
41
|
@kube_api_server_args = configuration.fetch('kube_api_server_args', [])
|
43
42
|
@kube_scheduler_args = configuration.fetch('kube_scheduler_args', [])
|
@@ -57,8 +56,7 @@ class Cluster
|
|
57
56
|
deploy_system_upgrade_controller
|
58
57
|
end
|
59
58
|
|
60
|
-
def delete
|
61
|
-
@configuration = configuration
|
59
|
+
def delete
|
62
60
|
@cluster_name = configuration['cluster_name']
|
63
61
|
@kubeconfig_path = File.expand_path(configuration['kubeconfig_path'])
|
64
62
|
@public_ssh_key_path = File.expand_path(configuration['public_ssh_key_path'])
|
@@ -68,8 +66,7 @@ class Cluster
|
|
68
66
|
delete_resources
|
69
67
|
end
|
70
68
|
|
71
|
-
def upgrade(
|
72
|
-
@configuration = configuration
|
69
|
+
def upgrade(new_k3s_version:, config_file:)
|
73
70
|
@cluster_name = configuration['cluster_name']
|
74
71
|
@kubeconfig_path = File.expand_path(configuration['kubeconfig_path'])
|
75
72
|
@new_k3s_version = new_k3s_version
|
@@ -82,14 +79,14 @@ class Cluster
|
|
82
79
|
|
83
80
|
attr_accessor :servers
|
84
81
|
|
85
|
-
attr_reader :
|
82
|
+
attr_reader :configuration, :cluster_name, :kubeconfig_path, :k3s_version,
|
86
83
|
:masters_config, :worker_node_pools,
|
87
84
|
:masters_location, :public_ssh_key_path,
|
88
|
-
:hetzner_token, :new_k3s_version,
|
89
|
-
:config_file, :verify_host_key, :
|
85
|
+
:hetzner_token, :new_k3s_version,
|
86
|
+
:config_file, :verify_host_key, :ssh_networks, :private_ssh_key_path,
|
90
87
|
:enable_encryption, :kube_api_server_args, :kube_scheduler_args,
|
91
88
|
:kube_controller_manager_args, :kube_cloud_controller_manager_args,
|
92
|
-
:kubelet_args, :kube_proxy_args
|
89
|
+
:kubelet_args, :kube_proxy_args, :api_networks
|
93
90
|
|
94
91
|
def find_worker_node_pools(configuration)
|
95
92
|
configuration.fetch('worker_node_pools', [])
|
@@ -119,7 +116,7 @@ class Cluster
|
|
119
116
|
|
120
117
|
Hetzner::Firewall.new(hetzner_client:, cluster_name:).delete(all_servers)
|
121
118
|
|
122
|
-
Hetzner::Network.new(hetzner_client:, cluster_name:).delete
|
119
|
+
Hetzner::Network.new(hetzner_client:, cluster_name:, existing_network:).delete
|
123
120
|
|
124
121
|
Hetzner::SSHKey.new(hetzner_client:, cluster_name:).delete(public_ssh_key_path:)
|
125
122
|
|
@@ -190,15 +187,30 @@ class Cluster
|
|
190
187
|
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.'
|
191
188
|
puts 'The API server may be briefly unavailable during the upgrade of the controlplane.'
|
192
189
|
|
193
|
-
|
190
|
+
updated_configuration = configuration.raw
|
191
|
+
updated_configuration['k3s_version'] = new_k3s_version
|
194
192
|
|
195
|
-
File.write(config_file,
|
193
|
+
File.write(config_file, updated_configuration.to_yaml)
|
196
194
|
end
|
197
195
|
|
198
196
|
def master_script(master)
|
199
197
|
server = master == first_master ? ' --cluster-init ' : " --server https://#{api_server_ip}:6443 "
|
200
198
|
flannel_interface = find_flannel_interface(master)
|
201
|
-
|
199
|
+
|
200
|
+
available_k3s_releases = Hetzner::Configuration.available_releases
|
201
|
+
wireguard_native_min_version_index = available_k3s_releases.find_index('v1.23.6+k3s1')
|
202
|
+
selected_version_index = available_k3s_releases.find_index(k3s_version)
|
203
|
+
|
204
|
+
flannel_wireguard = if enable_encryption
|
205
|
+
if selected_version_index >= wireguard_native_min_version_index
|
206
|
+
' --flannel-backend=wireguard-native '
|
207
|
+
else
|
208
|
+
' --flannel-backend=wireguard '
|
209
|
+
end
|
210
|
+
else
|
211
|
+
' '
|
212
|
+
end
|
213
|
+
|
202
214
|
extra_args = "#{kube_api_server_args_list} #{kube_scheduler_args_list} #{kube_controller_manager_args_list} #{kube_cloud_controller_manager_args_list} #{kubelet_args_list} #{kube_proxy_args_list}"
|
203
215
|
taint = schedule_workloads_on_masters? ? ' ' : ' --node-taint CriticalAddonsOnly=true:NoExecute '
|
204
216
|
|
@@ -214,10 +226,8 @@ class Cluster
|
|
214
226
|
--cluster-cidr=10.244.0.0/16 \
|
215
227
|
--etcd-expose-metrics=true \
|
216
228
|
#{flannel_wireguard} \
|
217
|
-
--kube-controller-manager-arg="address=0.0.0.0" \
|
218
229
|
--kube-controller-manager-arg="bind-address=0.0.0.0" \
|
219
230
|
--kube-proxy-arg="metrics-bind-address=0.0.0.0" \
|
220
|
-
--kube-scheduler-arg="address=0.0.0.0" \
|
221
231
|
--kube-scheduler-arg="bind-address=0.0.0.0" \
|
222
232
|
#{taint} #{extra_args} \
|
223
233
|
--kubelet-arg="cloud-provider=external" \
|
@@ -298,8 +308,8 @@ class Cluster
|
|
298
308
|
namespace: 'kube-system'
|
299
309
|
name: 'hcloud'
|
300
310
|
stringData:
|
301
|
-
network: "#{cluster_name}"
|
302
|
-
token: "#{hetzner_token}"
|
311
|
+
network: "#{existing_network || cluster_name}"
|
312
|
+
token: "#{configuration.hetzner_token}"
|
303
313
|
EOF
|
304
314
|
BASH
|
305
315
|
|
@@ -318,7 +328,7 @@ class Cluster
|
|
318
328
|
puts
|
319
329
|
puts 'Deploying k3s System Upgrade Controller...'
|
320
330
|
|
321
|
-
cmd = 'kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/download/v0.
|
331
|
+
cmd = 'kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/download/v0.9.1/system-upgrade-controller.yaml'
|
322
332
|
|
323
333
|
run cmd, kubeconfig_path: kubeconfig_path
|
324
334
|
|
@@ -339,13 +349,13 @@ class Cluster
|
|
339
349
|
namespace: 'kube-system'
|
340
350
|
name: 'hcloud-csi'
|
341
351
|
stringData:
|
342
|
-
token: "#{hetzner_token}"
|
352
|
+
token: "#{configuration.hetzner_token}"
|
343
353
|
EOF
|
344
354
|
BASH
|
345
355
|
|
346
356
|
run cmd, kubeconfig_path: kubeconfig_path
|
347
357
|
|
348
|
-
cmd = 'kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/
|
358
|
+
cmd = 'kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/master/deploy/kubernetes/hcloud-csi.yml'
|
349
359
|
|
350
360
|
run cmd, kubeconfig_path: kubeconfig_path
|
351
361
|
|
@@ -450,6 +460,10 @@ class Cluster
|
|
450
460
|
configuration['additional_packages'] || []
|
451
461
|
end
|
452
462
|
|
463
|
+
def additional_post_create_commands
|
464
|
+
configuration['post_create_commands'] || []
|
465
|
+
end
|
466
|
+
|
453
467
|
def check_kubectl
|
454
468
|
return if which('kubectl')
|
455
469
|
|
@@ -471,11 +485,11 @@ class Cluster
|
|
471
485
|
end
|
472
486
|
|
473
487
|
def firewall_id
|
474
|
-
@firewall_id ||= Hetzner::Firewall.new(hetzner_client:, cluster_name:).create(high_availability: (masters_count > 1),
|
488
|
+
@firewall_id ||= Hetzner::Firewall.new(hetzner_client:, cluster_name:).create(high_availability: (masters_count > 1), ssh_networks:, api_networks:)
|
475
489
|
end
|
476
490
|
|
477
491
|
def network_id
|
478
|
-
@network_id ||= Hetzner::Network.new(hetzner_client:, cluster_name:).create(location: masters_location)
|
492
|
+
@network_id ||= Hetzner::Network.new(hetzner_client:, cluster_name:, existing_network:).create(location: masters_location)
|
479
493
|
end
|
480
494
|
|
481
495
|
def ssh_key_id
|
@@ -495,7 +509,8 @@ class Cluster
|
|
495
509
|
network_id:,
|
496
510
|
ssh_key_id:,
|
497
511
|
image:,
|
498
|
-
additional_packages
|
512
|
+
additional_packages:,
|
513
|
+
additional_post_create_commands:
|
499
514
|
}
|
500
515
|
end
|
501
516
|
|
@@ -533,7 +548,8 @@ class Cluster
|
|
533
548
|
network_id:,
|
534
549
|
ssh_key_id:,
|
535
550
|
image:,
|
536
|
-
additional_packages
|
551
|
+
additional_packages:,
|
552
|
+
additional_post_create_commands:
|
537
553
|
}
|
538
554
|
end
|
539
555
|
|
@@ -637,4 +653,12 @@ class Cluster
|
|
637
653
|
" --kube-proxy-arg=\"#{arg}\" "
|
638
654
|
end.join
|
639
655
|
end
|
656
|
+
|
657
|
+
def hetzner_client
|
658
|
+
configuration.hetzner_client
|
659
|
+
end
|
660
|
+
|
661
|
+
def existing_network
|
662
|
+
configuration["existing_network"]
|
663
|
+
end
|
640
664
|
end
|