hetzner-k3s 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/README.md +8 -3
- data/bin/build.sh +3 -3
- data/lib/hetzner/infra/firewall.rb +6 -8
- data/lib/hetzner/infra/network.rb +19 -9
- data/lib/hetzner/k3s/cluster.rb +29 -10
- data/lib/hetzner/k3s/configuration.rb +46 -16
- data/lib/hetzner/k3s/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65fe7fa7e6bdeeb49175cfb92184ab320ef8ac40a3aa03f921f1e5fda1b172d7
|
4
|
+
data.tar.gz: 1448919374ef90f7614580251b28000c470b0ed7fba39724e8953f29f176c1bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15f783adb7408a1df5c3e9a114d9dcbc61830ead33acb7bc162ca935b6cdcfe26507e2c404d48c831cae600fe765adc31e686b511c4f675c561d6eb9acabafd2
|
7
|
+
data.tar.gz: df10039025e735d6a73c84fb60516d70bc0aa48220db1bab9d99dd30caad87339ba6be83e2d3f1029f40f95719a6fcde5784956c86ef20d524b67d504f93920a
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -44,7 +44,7 @@ Alternatively, if you don't want to set up a Ruby runtime but have Docker instal
|
|
44
44
|
docker run --rm -it \
|
45
45
|
-v ${PWD}:/cluster \
|
46
46
|
-v ${HOME}/.ssh:/tmp/.ssh \
|
47
|
-
vitobotta/hetzner-k3s:v0.5.
|
47
|
+
vitobotta/hetzner-k3s:v0.5.9 \
|
48
48
|
create-cluster \
|
49
49
|
--config-file /cluster/test.yaml
|
50
50
|
```
|
@@ -65,6 +65,8 @@ public_ssh_key_path: "~/.ssh/id_rsa.pub"
|
|
65
65
|
private_ssh_key_path: "~/.ssh/id_rsa"
|
66
66
|
ssh_allowed_networks:
|
67
67
|
- 0.0.0.0/0
|
68
|
+
api_allowed_networks:
|
69
|
+
- 0.0.0.0/0
|
68
70
|
verify_host_key: false
|
69
71
|
location: nbg1
|
70
72
|
schedule_workloads_on_masters: false
|
@@ -104,6 +106,7 @@ enable_encryption: true
|
|
104
106
|
# kube_proxy_args:
|
105
107
|
# - arg1
|
106
108
|
# - ...
|
109
|
+
# existing_network: <specify if you want to use an existing network, otherwise one will be created for this cluster>
|
107
110
|
|
108
111
|
```
|
109
112
|
|
@@ -143,9 +146,11 @@ curl \
|
|
143
146
|
'https://api.hetzner.cloud/v1/images'
|
144
147
|
```
|
145
148
|
|
146
|
-
|
149
|
+
Notes:
|
147
150
|
|
148
|
-
|
151
|
+
- if you use a custom image, the creation of the servers may take longer than when using the default image
|
152
|
+
- 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
|
153
|
+
- the setting `api_allowed_networks` allows specifying which networks can access the Kubernetes API, but this only works with single master clusters currently. Multi-master HA clusters require a load balancer for the API, but load balancers are not yet covered by Hetzner's firewalls.
|
149
154
|
|
150
155
|
Finally, to create the cluster run:
|
151
156
|
|
data/bin/build.sh
CHANGED
@@ -4,9 +4,9 @@ set -e
|
|
4
4
|
|
5
5
|
IMAGE="vitobotta/hetzner-k3s"
|
6
6
|
|
7
|
-
docker build -t ${IMAGE}:v0.5.
|
7
|
+
docker build -t ${IMAGE}:v0.5.9 \
|
8
8
|
--platform=linux/amd64 \
|
9
|
-
--cache-from ${IMAGE}:v0.5.
|
9
|
+
--cache-from ${IMAGE}:v0.5.8 \
|
10
10
|
--build-arg BUILDKIT_INLINE_CACHE=1 .
|
11
11
|
|
12
|
-
docker push vitobotta/hetzner-k3s:v0.5.
|
12
|
+
docker push vitobotta/hetzner-k3s:v0.5.9
|
@@ -7,9 +7,10 @@ module Hetzner
|
|
7
7
|
@cluster_name = cluster_name
|
8
8
|
end
|
9
9
|
|
10
|
-
def create(high_availability:,
|
10
|
+
def create(high_availability:, ssh_networks:, api_networks:)
|
11
11
|
@high_availability = high_availability
|
12
|
-
@
|
12
|
+
@ssh_networks = ssh_networks
|
13
|
+
@api_networks = api_networks
|
13
14
|
puts
|
14
15
|
|
15
16
|
if (firewall = find_firewall)
|
@@ -47,7 +48,7 @@ module Hetzner
|
|
47
48
|
|
48
49
|
private
|
49
50
|
|
50
|
-
attr_reader :hetzner_client, :cluster_name, :firewall, :high_availability, :
|
51
|
+
attr_reader :hetzner_client, :cluster_name, :firewall, :high_availability, :ssh_networks, :api_networks
|
51
52
|
|
52
53
|
def create_firewall_config
|
53
54
|
rules = [
|
@@ -56,7 +57,7 @@ module Hetzner
|
|
56
57
|
direction: 'in',
|
57
58
|
protocol: 'tcp',
|
58
59
|
port: '22',
|
59
|
-
source_ips:
|
60
|
+
source_ips: ssh_networks,
|
60
61
|
destination_ips: []
|
61
62
|
},
|
62
63
|
{
|
@@ -98,10 +99,7 @@ module Hetzner
|
|
98
99
|
direction: 'in',
|
99
100
|
protocol: 'tcp',
|
100
101
|
port: '6443',
|
101
|
-
source_ips:
|
102
|
-
'0.0.0.0/0',
|
103
|
-
'::/0'
|
104
|
-
],
|
102
|
+
source_ips: api_networks,
|
105
103
|
destination_ips: []
|
106
104
|
}
|
107
105
|
end
|
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
module Hetzner
|
4
4
|
class Network
|
5
|
-
def initialize(hetzner_client:, cluster_name:)
|
5
|
+
def initialize(hetzner_client:, cluster_name:, existing_network:)
|
6
6
|
@hetzner_client = hetzner_client
|
7
7
|
@cluster_name = cluster_name
|
8
|
+
@existing_network = existing_network
|
8
9
|
end
|
9
10
|
|
10
11
|
def create(location:)
|
@@ -29,9 +30,13 @@ module Hetzner
|
|
29
30
|
|
30
31
|
def delete
|
31
32
|
if (network = find_network)
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
if network["name"] == existing_network
|
34
|
+
puts "Network existed before cluster, skipping."
|
35
|
+
else
|
36
|
+
puts 'Deleting network...'
|
37
|
+
hetzner_client.delete('/networks', network['id'])
|
38
|
+
puts '...network deleted.'
|
39
|
+
end
|
35
40
|
else
|
36
41
|
puts 'Network no longer exists, skipping.'
|
37
42
|
end
|
@@ -39,9 +44,18 @@ module Hetzner
|
|
39
44
|
puts
|
40
45
|
end
|
41
46
|
|
47
|
+
def find_network
|
48
|
+
network_name = existing_network || cluster_name
|
49
|
+
hetzner_client.get('/networks')['networks'].detect { |network| network['name'] == network_name }
|
50
|
+
end
|
51
|
+
|
52
|
+
def get
|
53
|
+
find_network
|
54
|
+
end
|
55
|
+
|
42
56
|
private
|
43
57
|
|
44
|
-
attr_reader :hetzner_client, :cluster_name, :location
|
58
|
+
attr_reader :hetzner_client, :cluster_name, :location, :existing_network
|
45
59
|
|
46
60
|
def network_config
|
47
61
|
{
|
@@ -56,9 +70,5 @@ module Hetzner
|
|
56
70
|
]
|
57
71
|
}
|
58
72
|
end
|
59
|
-
|
60
|
-
def find_network
|
61
|
-
hetzner_client.get('/networks')['networks'].detect { |network| network['name'] == cluster_name }
|
62
|
-
end
|
63
73
|
end
|
64
74
|
end
|
data/lib/hetzner/k3s/cluster.rb
CHANGED
@@ -35,7 +35,8 @@ class Cluster
|
|
35
35
|
@masters_location = configuration['location']
|
36
36
|
@verify_host_key = configuration.fetch('verify_host_key', false)
|
37
37
|
@servers = []
|
38
|
-
@
|
38
|
+
@ssh_networks = configuration['ssh_allowed_networks']
|
39
|
+
@api_networks = configuration['api_allowed_networks']
|
39
40
|
@enable_encryption = configuration.fetch('enable_encryption', false)
|
40
41
|
@kube_api_server_args = configuration.fetch('kube_api_server_args', [])
|
41
42
|
@kube_scheduler_args = configuration.fetch('kube_scheduler_args', [])
|
@@ -82,10 +83,10 @@ class Cluster
|
|
82
83
|
:masters_config, :worker_node_pools,
|
83
84
|
:masters_location, :public_ssh_key_path,
|
84
85
|
:hetzner_token, :new_k3s_version,
|
85
|
-
:config_file, :verify_host_key, :
|
86
|
+
:config_file, :verify_host_key, :ssh_networks, :private_ssh_key_path,
|
86
87
|
:enable_encryption, :kube_api_server_args, :kube_scheduler_args,
|
87
88
|
:kube_controller_manager_args, :kube_cloud_controller_manager_args,
|
88
|
-
:kubelet_args, :kube_proxy_args
|
89
|
+
:kubelet_args, :kube_proxy_args, :api_networks
|
89
90
|
|
90
91
|
def find_worker_node_pools(configuration)
|
91
92
|
configuration.fetch('worker_node_pools', [])
|
@@ -115,7 +116,7 @@ class Cluster
|
|
115
116
|
|
116
117
|
Hetzner::Firewall.new(hetzner_client:, cluster_name:).delete(all_servers)
|
117
118
|
|
118
|
-
Hetzner::Network.new(hetzner_client:, cluster_name:).delete
|
119
|
+
Hetzner::Network.new(hetzner_client:, cluster_name:, existing_network:).delete
|
119
120
|
|
120
121
|
Hetzner::SSHKey.new(hetzner_client:, cluster_name:).delete(public_ssh_key_path:)
|
121
122
|
|
@@ -195,7 +196,21 @@ class Cluster
|
|
195
196
|
def master_script(master)
|
196
197
|
server = master == first_master ? ' --cluster-init ' : " --server https://#{api_server_ip}:6443 "
|
197
198
|
flannel_interface = find_flannel_interface(master)
|
198
|
-
|
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
|
+
|
199
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}"
|
200
215
|
taint = schedule_workloads_on_masters? ? ' ' : ' --node-taint CriticalAddonsOnly=true:NoExecute '
|
201
216
|
|
@@ -293,7 +308,7 @@ class Cluster
|
|
293
308
|
namespace: 'kube-system'
|
294
309
|
name: 'hcloud'
|
295
310
|
stringData:
|
296
|
-
network: "#{cluster_name}"
|
311
|
+
network: "#{existing_network || cluster_name}"
|
297
312
|
token: "#{configuration.hetzner_token}"
|
298
313
|
EOF
|
299
314
|
BASH
|
@@ -313,7 +328,7 @@ class Cluster
|
|
313
328
|
puts
|
314
329
|
puts 'Deploying k3s System Upgrade Controller...'
|
315
330
|
|
316
|
-
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'
|
317
332
|
|
318
333
|
run cmd, kubeconfig_path: kubeconfig_path
|
319
334
|
|
@@ -340,7 +355,7 @@ class Cluster
|
|
340
355
|
|
341
356
|
run cmd, kubeconfig_path: kubeconfig_path
|
342
357
|
|
343
|
-
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'
|
344
359
|
|
345
360
|
run cmd, kubeconfig_path: kubeconfig_path
|
346
361
|
|
@@ -470,11 +485,11 @@ class Cluster
|
|
470
485
|
end
|
471
486
|
|
472
487
|
def firewall_id
|
473
|
-
@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:)
|
474
489
|
end
|
475
490
|
|
476
491
|
def network_id
|
477
|
-
@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)
|
478
493
|
end
|
479
494
|
|
480
495
|
def ssh_key_id
|
@@ -642,4 +657,8 @@ class Cluster
|
|
642
657
|
def hetzner_client
|
643
658
|
configuration.hetzner_client
|
644
659
|
end
|
660
|
+
|
661
|
+
def existing_network
|
662
|
+
configuration["existing_network"]
|
663
|
+
end
|
645
664
|
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module Hetzner
|
4
4
|
class Configuration
|
5
|
-
GITHUB_DELIM_LINKS = ','
|
6
|
-
GITHUB_LINK_REGEX = /<([^>]+)>; rel
|
5
|
+
GITHUB_DELIM_LINKS = ','
|
6
|
+
GITHUB_LINK_REGEX = /<([^>]+)>; rel="([^"]+)"/
|
7
7
|
|
8
8
|
attr_reader :hetzner_client
|
9
9
|
|
@@ -52,7 +52,7 @@ module Hetzner
|
|
52
52
|
releases = page_releases
|
53
53
|
link_header = response.headers['link']
|
54
54
|
|
55
|
-
|
55
|
+
until link_header.nil?
|
56
56
|
next_page_url = extract_next_github_page_url(link_header)
|
57
57
|
|
58
58
|
break if next_page_url.nil?
|
@@ -67,10 +67,10 @@ module Hetzner
|
|
67
67
|
releases.sort
|
68
68
|
end
|
69
69
|
rescue StandardError
|
70
|
-
if defined?errors
|
71
|
-
errors << 'Cannot fetch the releases with
|
70
|
+
if defined? errors
|
71
|
+
errors << 'Cannot fetch the releases with Github API, please try again later. This may be due to API rate limits.'
|
72
72
|
else
|
73
|
-
puts 'Cannot fetch the releases with
|
73
|
+
puts 'Cannot fetch the releases with Github API, please try again later. This may be due to API rate limits.'
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -104,9 +104,10 @@ module Hetzner
|
|
104
104
|
def self.extract_next_github_page_url(link_header)
|
105
105
|
link_header.split(GITHUB_DELIM_LINKS).each do |link|
|
106
106
|
GITHUB_LINK_REGEX.match(link.strip) do |match|
|
107
|
-
url_part
|
107
|
+
url_part = match[1]
|
108
|
+
meta_part = match[2]
|
108
109
|
next if !url_part || !meta_part
|
109
|
-
return url_part if meta_part ==
|
110
|
+
return url_part if meta_part == 'next'
|
110
111
|
end
|
111
112
|
end
|
112
113
|
|
@@ -115,7 +116,7 @@ module Hetzner
|
|
115
116
|
|
116
117
|
def self.assign_url_part(meta_part, url_part)
|
117
118
|
case meta_part
|
118
|
-
when
|
119
|
+
when 'next'
|
119
120
|
url_part
|
120
121
|
end
|
121
122
|
end
|
@@ -124,6 +125,7 @@ module Hetzner
|
|
124
125
|
validate_public_ssh_key
|
125
126
|
validate_private_ssh_key
|
126
127
|
validate_ssh_allowed_networks
|
128
|
+
validate_api_allowed_networks
|
127
129
|
validate_masters_location
|
128
130
|
validate_k3s_version
|
129
131
|
validate_masters
|
@@ -137,6 +139,7 @@ module Hetzner
|
|
137
139
|
validate_kube_cloud_controller_manager_args
|
138
140
|
validate_kubelet_args
|
139
141
|
validate_kube_proxy_args
|
142
|
+
validate_existing_network
|
140
143
|
end
|
141
144
|
|
142
145
|
def validate_upgrade
|
@@ -165,11 +168,11 @@ module Hetzner
|
|
165
168
|
errors << 'Invalid Private SSH key path'
|
166
169
|
end
|
167
170
|
|
168
|
-
def
|
169
|
-
networks ||= configuration[
|
171
|
+
def validate_networks(configuration_option, access_type)
|
172
|
+
networks ||= configuration[configuration_option]
|
170
173
|
|
171
174
|
if networks.nil? || networks.empty?
|
172
|
-
errors <<
|
175
|
+
errors << "At least one network/IP range must be specified for #{access_type} access"
|
173
176
|
return
|
174
177
|
end
|
175
178
|
|
@@ -181,7 +184,7 @@ module Hetzner
|
|
181
184
|
|
182
185
|
unless invalid_networks.empty?
|
183
186
|
invalid_networks.each do |network|
|
184
|
-
errors << "The network #{network} is an invalid range"
|
187
|
+
errors << "The #{access_type} network #{network} is an invalid range"
|
185
188
|
end
|
186
189
|
end
|
187
190
|
|
@@ -191,7 +194,7 @@ module Hetzner
|
|
191
194
|
|
192
195
|
unless invalid_ranges.empty?
|
193
196
|
invalid_ranges.each do |_network|
|
194
|
-
errors << 'Please use the CIDR notation for the networks to avoid ambiguity'
|
197
|
+
errors << 'Please use the CIDR notation for the #{access_type} networks to avoid ambiguity'
|
195
198
|
end
|
196
199
|
end
|
197
200
|
|
@@ -199,13 +202,30 @@ module Hetzner
|
|
199
202
|
|
200
203
|
current_ip = URI.open('http://whatismyip.akamai.com').read
|
201
204
|
|
202
|
-
|
205
|
+
current_ip_network = networks.detect do |network|
|
203
206
|
IPAddr.new(network).include?(current_ip)
|
204
207
|
rescue StandardError
|
205
208
|
false
|
206
209
|
end
|
207
210
|
|
208
|
-
|
211
|
+
unless current_ip_network
|
212
|
+
case access_type
|
213
|
+
when "SSH"
|
214
|
+
errors << "Your current IP #{current_ip} is not included into any of the #{access_type} networks you've specified, so we won't be able to SSH into the nodes "
|
215
|
+
when "API"
|
216
|
+
errors << "Your current IP #{current_ip} is not included into any of the #{access_type} networks you've specified, so we won't be able to connect to the Kubernetes API"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
def validate_ssh_allowed_networks
|
223
|
+
return
|
224
|
+
validate_networks('ssh_allowed_networks', 'SSH')
|
225
|
+
end
|
226
|
+
|
227
|
+
def validate_api_allowed_networks
|
228
|
+
validate_networks('api_allowed_networks', 'API')
|
209
229
|
end
|
210
230
|
|
211
231
|
def validate_masters_location
|
@@ -450,5 +470,15 @@ module Hetzner
|
|
450
470
|
@errors << 'Cannot fetch server types with Hetzner API, please try again later'
|
451
471
|
false
|
452
472
|
end
|
473
|
+
|
474
|
+
def validate_existing_network
|
475
|
+
if configuration["existing_network"]
|
476
|
+
existing_network = Hetzner::Network.new(hetzner_client:, cluster_name: configuration["cluster_name"], existing_network: configuration["existing_network"]).get
|
477
|
+
|
478
|
+
unless existing_network
|
479
|
+
@errors << "You have specified that you want to use the existing network named '#{configuration["existing_network"]} but this network doesn't exist"
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
453
483
|
end
|
454
484
|
end
|
data/lib/hetzner/k3s/version.rb
CHANGED
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.5.
|
4
|
+
version: 0.5.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vito Botta
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt_pbkdf
|