hetzner-k3s 0.6.2 → 0.6.3

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: 873c76ec7a993a8c890f72c8158daa82597eb994e3f5e70c9b53d98604903f38
4
- data.tar.gz: d0b1f622ff21728d1bb6b41b2a373eca693121a0eb619776adf9590ef926f80a
3
+ metadata.gz: 2bbaf7bd5387cc92f0725308a42f982df7b47596d5d1a3d50693cf3b6a741359
4
+ data.tar.gz: 0617b66a6ca8299c5c5ada69c586a532e915657c857c6f6821cd81d29c17a774
5
5
  SHA512:
6
- metadata.gz: 4bdc7f2fa5f6ef40bcd1e089d89b26c228b77fe3c06fb4af884910e4fe69fb6ad67951160a4ecdbcb6f076adbc16521bfd45b661644e7f6b927ba002e5a3ba67
7
- data.tar.gz: 94a8f6b3df49db94d7412d5f350ce3f996980f0741e694a8b91a7ff0c71783cdaa12b4263cab31395288497dd3c790be45ca62e00f844d9df863a13b3623de79
6
+ metadata.gz: a457da09e05ac40da2d0b40de65193f16ecd42ae926f6d59271ca9fedc77587faf2bb90b4c4a62e326e509076b0f44e84062fb6c72536e5e8c1ce4b716d56813
7
+ data.tar.gz: '0628cf98daa772f50251257b626ca148420d1a8853618652c9fd7dd6750694393d9902ea684c786029e0db3bcf8d01729aa217e9fc3c070067fbbe6afb395dd8'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hetzner-k3s (0.6.1)
4
+ hetzner-k3s (0.6.2)
5
5
  bcrypt_pbkdf
6
6
  childprocess
7
7
  ed25519
data/README.md CHANGED
@@ -58,13 +58,13 @@ Before using the tool, be sure to have kubectl installed as it's required to ins
58
58
  #### With Homebrew
59
59
 
60
60
  ```bash
61
- brew install vitobotta/tap/hetzner-k3s
61
+ brew install vitobotta/tap/hetzner_k3s
62
62
  ```
63
63
 
64
64
  #### Binary installation (Intel)
65
65
 
66
66
  ```bash
67
- wget https://github.com/vitobotta/hetzner-k3s/releases/download/v0.6.1/hetzner-k3s-mac-amd64
67
+ wget https://github.com/vitobotta/hetzner-k3s/releases/download/v0.6.3/hetzner-k3s-mac-amd64
68
68
  chmod +x hetzner-k3s-mac-x64
69
69
  sudo mv hetzner-k3s-mac-x64 /usr/local/bin/hetzner-k3s
70
70
  ```
@@ -72,7 +72,7 @@ sudo mv hetzner-k3s-mac-x64 /usr/local/bin/hetzner-k3s
72
72
  #### Binary installation (Apple Silicon/M1)
73
73
 
74
74
  ```bash
75
- wget https://github.com/vitobotta/hetzner-k3s/releases/download/v0.6.1/hetzner-k3s-mac-arm64
75
+ wget https://github.com/vitobotta/hetzner-k3s/releases/download/v0.6.3/hetzner-k3s-mac-arm64
76
76
  chmod +x hetzner-k3s-mac-arm
77
77
  sudo mv hetzner-k3s-mac-arm /usr/local/bin/hetzner-k3s
78
78
  ```
@@ -82,7 +82,7 @@ NOTE: currently the ARM version still requires [Rosetta](https://support.apple.c
82
82
  ### Linux
83
83
 
84
84
  ```bash
85
- wget https://github.com/vitobotta/hetzner-k3s/releases/download/v0.6.1/hetzner-k3s-linux-x86_64
85
+ wget https://github.com/vitobotta/hetzner-k3s/releases/download/v0.6.3/hetzner-k3s-linux-x86_64
86
86
  chmod +x hetzner-k3s-linux-x86_64
87
87
  sudo mv hetzner-k3s-linux-x86_64 /usr/local/bin/hetzner-k3s
88
88
  ```
@@ -107,7 +107,7 @@ Alternatively, if you don't want to set up a Ruby runtime but have Docker instal
107
107
  docker run --rm -it \
108
108
  -v ${PWD}:/cluster \
109
109
  -v ${HOME}/.ssh:/tmp/.ssh \
110
- vitobotta/hetzner-k3s:v0.6.1 \
110
+ vitobotta/hetzner-k3s:v0.6.3 \
111
111
  create-cluster \
112
112
  --config-file /cluster/test.yaml
113
113
  ```
@@ -6,10 +6,6 @@ module Hetzner
6
6
 
7
7
  attr_reader :token
8
8
 
9
- def initialize(token:)
10
- @token = token
11
- end
12
-
13
9
  def get(path)
14
10
  make_request do
15
11
  JSON.parse HTTParty.get(BASE_URI + path, headers: headers).body
@@ -30,6 +26,10 @@ module Hetzner
30
26
 
31
27
  private
32
28
 
29
+ def initialize(token:)
30
+ @token = token
31
+ end
32
+
33
33
  def headers
34
34
  {
35
35
  'Authorization' => "Bearer #{@token}",
@@ -33,8 +33,7 @@ module Hetzner
33
33
  puts 'Deleting firewall...'
34
34
 
35
35
  servers.each do |server|
36
- hetzner_client.post("/firewalls/#{firewall['id']}/actions/remove_from_resources",
37
- remove_targets_config(server['id']))
36
+ hetzner_client.post("/firewalls/#{firewall['id']}/actions/remove_from_resources", remove_targets_config(server['id']))
38
37
  end
39
38
 
40
39
  hetzner_client.delete('/firewalls', firewall['id'])
@@ -33,8 +33,8 @@ module Hetzner
33
33
  puts 'Deleting API load balancer...' unless high_availability
34
34
 
35
35
  hetzner_client.post("/load_balancers/#{load_balancer['id']}/actions/remove_target", remove_targets_config)
36
-
37
36
  hetzner_client.delete('/load_balancers', load_balancer['id'])
37
+
38
38
  puts '...API load balancer deleted.' unless high_availability
39
39
  elsif high_availability
40
40
  puts 'API load balancer no longer exists, skipping.'
@@ -59,30 +59,6 @@ module Hetzner
59
59
  end
60
60
 
61
61
  def user_data
62
- packages = %w[fail2ban wireguard]
63
- packages += additional_packages if additional_packages
64
- packages = "'#{packages.join("', '")}'"
65
-
66
- post_create_commands = [
67
- 'crontab -l > /etc/cron_bkp',
68
- 'echo "@reboot echo true > /etc/ready" >> /etc/cron_bkp',
69
- 'crontab /etc/cron_bkp',
70
- 'sed -i \'s/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g\' /etc/ssh/sshd_config',
71
- 'sed -i \'s/[#]*PasswordAuthentication yes/PasswordAuthentication no/g\' /etc/ssh/sshd_config',
72
- 'systemctl restart sshd',
73
- 'systemctl stop systemd-resolved',
74
- 'systemctl disable systemd-resolved',
75
- 'rm /etc/resolv.conf',
76
- 'echo \'nameserver 1.1.1.1\' > /etc/resolv.conf',
77
- 'echo \'nameserver 1.0.0.1\' >> /etc/resolv.conf'
78
- ]
79
-
80
- post_create_commands += additional_post_create_commands if additional_post_create_commands
81
-
82
- post_create_commands += ['shutdown -r now'] if post_create_commands.grep(/shutdown|reboot/).grep_v(/@reboot/).empty?
83
-
84
- post_create_commands = " - #{post_create_commands.join("\n - ")}"
85
-
86
62
  <<~YAML
87
63
  #cloud-config
88
64
  packages: [#{packages}]
@@ -125,5 +101,33 @@ module Hetzner
125
101
 
126
102
  JSON.parse(response_body)['server']
127
103
  end
104
+
105
+ def post_create_commands
106
+ commands = [
107
+ 'crontab -l > /etc/cron_bkp',
108
+ 'echo "@reboot echo true > /etc/ready" >> /etc/cron_bkp',
109
+ 'crontab /etc/cron_bkp',
110
+ 'sed -i \'s/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g\' /etc/ssh/sshd_config',
111
+ 'sed -i \'s/[#]*PasswordAuthentication yes/PasswordAuthentication no/g\' /etc/ssh/sshd_config',
112
+ 'systemctl restart sshd',
113
+ 'systemctl stop systemd-resolved',
114
+ 'systemctl disable systemd-resolved',
115
+ 'rm /etc/resolv.conf',
116
+ 'echo \'nameserver 1.1.1.1\' > /etc/resolv.conf',
117
+ 'echo \'nameserver 1.0.0.1\' >> /etc/resolv.conf'
118
+ ]
119
+
120
+ commands += additional_post_create_commands if additional_post_create_commands
121
+
122
+ commands << 'shutdown -r now' if commands.grep(/shutdown|reboot/).grep_v(/@reboot/).empty?
123
+
124
+ " - #{commands.join("\n - ")}"
125
+ end
126
+
127
+ def packages
128
+ packages = %w[fail2ban wireguard]
129
+ packages += additional_packages if additional_packages
130
+ "'#{packages.join("', '")}'"
131
+ end
128
132
  end
129
133
  end
@@ -14,6 +14,8 @@ require_relative '../infra/server'
14
14
  require_relative '../infra/load_balancer'
15
15
  require_relative '../infra/placement_group'
16
16
 
17
+ require_relative '../kubernetes/client'
18
+
17
19
  require_relative '../utils'
18
20
 
19
21
  class Cluster
@@ -25,38 +27,18 @@ class Cluster
25
27
 
26
28
  def create
27
29
  @cluster_name = configuration['cluster_name']
28
- @kubeconfig_path = File.expand_path(configuration['kubeconfig_path'])
29
- @public_ssh_key_path = File.expand_path(configuration['public_ssh_key_path'])
30
- private_ssh_key_path = configuration['private_ssh_key_path']
31
- @private_ssh_key_path = private_ssh_key_path && File.expand_path(private_ssh_key_path)
32
- @k3s_version = configuration['k3s_version']
33
30
  @masters_config = configuration['masters']
34
31
  @worker_node_pools = find_worker_node_pools(configuration)
35
32
  @masters_location = configuration['location']
36
- @verify_host_key = configuration.fetch('verify_host_key', false)
37
33
  @servers = []
38
34
  @ssh_networks = configuration['ssh_allowed_networks']
39
35
  @api_networks = configuration['api_allowed_networks']
40
- @enable_encryption = configuration.fetch('enable_encryption', false)
41
- @kube_api_server_args = configuration.fetch('kube_api_server_args', [])
42
- @kube_scheduler_args = configuration.fetch('kube_scheduler_args', [])
43
- @kube_controller_manager_args = configuration.fetch('kube_controller_manager_args', [])
44
- @kube_cloud_controller_manager_args = configuration.fetch('kube_cloud_controller_manager_args', [])
45
- @kubelet_args = configuration.fetch('kubelet_args', [])
46
- @kube_proxy_args = configuration.fetch('kube_proxy_args', [])
36
+ @private_ssh_key_path = File.expand_path(configuration['private_ssh_key_path'])
37
+ @public_ssh_key_path = File.expand_path(configuration['public_ssh_key_path'])
47
38
 
48
39
  create_resources
49
40
 
50
- deploy_kubernetes
51
-
52
- sleep 10
53
-
54
- label_nodes
55
- taint_nodes
56
-
57
- deploy_cloud_controller_manager
58
- deploy_csi_driver
59
- deploy_system_upgrade_controller
41
+ kubernetes_client.deploy(masters: masters, workers: workers, master_definitions: master_definitions_for_create, worker_definitions: workers_definitions_for_marking)
60
42
  end
61
43
 
62
44
  def delete
@@ -75,378 +57,26 @@ class Cluster
75
57
  @new_k3s_version = new_k3s_version
76
58
  @config_file = config_file
77
59
 
78
- upgrade_cluster
60
+ kubernetes_client.upgrade
79
61
  end
80
62
 
81
63
  private
82
64
 
83
65
  attr_accessor :servers
84
66
 
85
- attr_reader :configuration, :cluster_name, :kubeconfig_path, :k3s_version,
67
+ attr_reader :configuration, :cluster_name, :kubeconfig_path,
86
68
  :masters_config, :worker_node_pools,
87
- :masters_location, :public_ssh_key_path,
69
+ :masters_location, :private_ssh_key_path, :public_ssh_key_path,
88
70
  :hetzner_token, :new_k3s_version,
89
- :config_file, :verify_host_key, :ssh_networks, :private_ssh_key_path,
90
- :enable_encryption, :kube_api_server_args, :kube_scheduler_args,
91
- :kube_controller_manager_args, :kube_cloud_controller_manager_args,
92
- :kubelet_args, :kube_proxy_args, :api_networks
71
+ :config_file, :ssh_networks,
72
+ :api_networks
93
73
 
94
74
  def find_worker_node_pools(configuration)
95
75
  configuration.fetch('worker_node_pools', [])
96
76
  end
97
77
 
98
- def latest_k3s_version
99
- response = HTTParty.get('https://api.github.com/repos/k3s-io/k3s/tags').body
100
- JSON.parse(response).first['name']
101
- end
102
-
103
- def create_resources
104
- create_servers
105
- create_load_balancer if masters.size > 1
106
- end
107
-
108
- def delete_placement_groups
109
- Hetzner::PlacementGroup.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete
110
-
111
- worker_node_pools.each do |pool|
112
- pool_name = pool['name']
113
- Hetzner::PlacementGroup.new(hetzner_client: hetzner_client, cluster_name: cluster_name, pool_name: pool_name).delete
114
- end
115
- end
116
-
117
- def delete_resources
118
- Hetzner::LoadBalancer.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(high_availability: (masters.size > 1))
119
-
120
- Hetzner::Firewall.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(all_servers)
121
-
122
- Hetzner::Network.new(hetzner_client: hetzner_client, cluster_name: cluster_name, existing_network: existing_network).delete
123
-
124
- Hetzner::SSHKey.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(public_ssh_key_path: public_ssh_key_path)
125
-
126
- delete_placement_groups
127
- delete_servers
128
- end
129
-
130
- def upgrade_cluster
131
- worker_upgrade_concurrency = workers.size - 1
132
- worker_upgrade_concurrency = 1 if worker_upgrade_concurrency.zero?
133
-
134
- cmd = <<~BASH
135
- kubectl apply -f - <<-EOF
136
- apiVersion: upgrade.cattle.io/v1
137
- kind: Plan
138
- metadata:
139
- name: k3s-server
140
- namespace: system-upgrade
141
- labels:
142
- k3s-upgrade: server
143
- spec:
144
- concurrency: 1
145
- version: #{new_k3s_version}
146
- nodeSelector:
147
- matchExpressions:
148
- - {key: node-role.kubernetes.io/master, operator: In, values: ["true"]}
149
- serviceAccountName: system-upgrade
150
- tolerations:
151
- - key: "CriticalAddonsOnly"
152
- operator: "Equal"
153
- value: "true"
154
- effect: "NoExecute"
155
- cordon: true
156
- upgrade:
157
- image: rancher/k3s-upgrade
158
- EOF
159
- BASH
160
-
161
- run cmd, kubeconfig_path: kubeconfig_path
162
-
163
- cmd = <<~BASH
164
- kubectl apply -f - <<-EOF
165
- apiVersion: upgrade.cattle.io/v1
166
- kind: Plan
167
- metadata:
168
- name: k3s-agent
169
- namespace: system-upgrade
170
- labels:
171
- k3s-upgrade: agent
172
- spec:
173
- concurrency: #{worker_upgrade_concurrency}
174
- version: #{new_k3s_version}
175
- nodeSelector:
176
- matchExpressions:
177
- - {key: node-role.kubernetes.io/master, operator: NotIn, values: ["true"]}
178
- serviceAccountName: system-upgrade
179
- prepare:
180
- image: rancher/k3s-upgrade
181
- args: ["prepare", "k3s-server"]
182
- cordon: true
183
- upgrade:
184
- image: rancher/k3s-upgrade
185
- EOF
186
- BASH
187
-
188
- run cmd, kubeconfig_path: kubeconfig_path
189
-
190
- 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
- puts 'The API server may be briefly unavailable during the upgrade of the controlplane.'
192
-
193
- updated_configuration = configuration.raw
194
- updated_configuration['k3s_version'] = new_k3s_version
195
-
196
- File.write(config_file, updated_configuration.to_yaml)
197
- end
198
-
199
- def master_script(master)
200
- server = master == first_master ? ' --cluster-init ' : " --server https://#{api_server_ip}:6443 "
201
- flannel_interface = find_flannel_interface(master)
202
-
203
- available_k3s_releases = Hetzner::Configuration.available_releases
204
- wireguard_native_min_version_index = available_k3s_releases.find_index('v1.23.6+k3s1')
205
- selected_version_index = available_k3s_releases.find_index(k3s_version)
206
-
207
- flannel_wireguard = if enable_encryption
208
- if selected_version_index >= wireguard_native_min_version_index
209
- ' --flannel-backend=wireguard-native '
210
- else
211
- ' --flannel-backend=wireguard '
212
- end
213
- else
214
- ' '
215
- end
216
-
217
- 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}"
218
- taint = schedule_workloads_on_masters? ? ' ' : ' --node-taint CriticalAddonsOnly=true:NoExecute '
219
-
220
- <<~SCRIPT
221
- curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="#{k3s_version}" K3S_TOKEN="#{k3s_token}" INSTALL_K3S_EXEC="server \
222
- --disable-cloud-controller \
223
- --disable servicelb \
224
- --disable traefik \
225
- --disable local-storage \
226
- --disable metrics-server \
227
- --write-kubeconfig-mode=644 \
228
- --node-name="$(hostname -f)" \
229
- --cluster-cidr=10.244.0.0/16 \
230
- --etcd-expose-metrics=true \
231
- #{flannel_wireguard} \
232
- --kube-controller-manager-arg="bind-address=0.0.0.0" \
233
- --kube-proxy-arg="metrics-bind-address=0.0.0.0" \
234
- --kube-scheduler-arg="bind-address=0.0.0.0" \
235
- #{taint} #{extra_args} \
236
- --kubelet-arg="cloud-provider=external" \
237
- --advertise-address=$(hostname -I | awk '{print $2}') \
238
- --node-ip=$(hostname -I | awk '{print $2}') \
239
- --node-external-ip=$(hostname -I | awk '{print $1}') \
240
- --flannel-iface=#{flannel_interface} \
241
- #{server} #{tls_sans}" sh -
242
- SCRIPT
243
- end
244
-
245
- def worker_script(worker)
246
- flannel_interface = find_flannel_interface(worker)
247
-
248
- <<~BASH
249
- curl -sfL https://get.k3s.io | K3S_TOKEN="#{k3s_token}" INSTALL_K3S_VERSION="#{k3s_version}" K3S_URL=https://#{first_master_private_ip}:6443 INSTALL_K3S_EXEC="agent \
250
- --node-name="$(hostname -f)" \
251
- --kubelet-arg="cloud-provider=external" \
252
- --node-ip=$(hostname -I | awk '{print $2}') \
253
- --node-external-ip=$(hostname -I | awk '{print $1}') \
254
- --flannel-iface=#{flannel_interface}" sh -
255
- BASH
256
- end
257
-
258
- def deploy_kubernetes
259
- puts
260
- puts "Deploying k3s to first master (#{first_master['name']})..."
261
-
262
- ssh first_master, master_script(first_master), print_output: true
263
-
264
- puts
265
- puts '...k3s has been deployed to first master.'
266
-
267
- save_kubeconfig
268
-
269
- if masters.size > 1
270
- threads = masters[1..].map do |master|
271
- Thread.new do
272
- puts
273
- puts "Deploying k3s to master #{master['name']}..."
274
-
275
- ssh master, master_script(master), print_output: true
276
-
277
- puts
278
- puts "...k3s has been deployed to master #{master['name']}."
279
- end
280
- end
281
-
282
- threads.each(&:join) unless threads.empty?
283
- end
284
-
285
- threads = workers.map do |worker|
286
- Thread.new do
287
- puts
288
- puts "Deploying k3s to worker (#{worker['name']})..."
289
-
290
- ssh worker, worker_script(worker), print_output: true
291
-
292
- puts
293
- puts "...k3s has been deployed to worker (#{worker['name']})."
294
- end
295
- end
296
-
297
- threads.each(&:join) unless threads.empty?
298
- end
299
-
300
- def label_nodes
301
- check_kubectl
302
-
303
- if master_definitions_for_create.first[:labels]
304
- master_labels = master_definitions_for_create.first[:labels].map{ |k, v| "#{k}=#{v}" }.join(' ')
305
- master_node_names = []
306
-
307
- master_definitions_for_create.each do |master|
308
- master_node_names << "#{configuration['cluster_name']}-#{master[:instance_type]}-#{master[:instance_id]}"
309
- end
310
-
311
- master_node_names = master_node_names.join(' ')
312
-
313
- cmd = "kubectl label --overwrite nodes #{master_node_names} #{master_labels}"
314
-
315
- run cmd, kubeconfig_path: kubeconfig_path
316
- end
317
-
318
- workers = []
319
-
320
- worker_node_pools.each do |worker_node_pool|
321
- workers += worker_node_pool_definitions(worker_node_pool)
322
- end
323
-
324
- return unless workers.any?
325
-
326
- workers.each do |worker|
327
- next unless worker[:labels]
328
-
329
- worker_labels = worker[:labels].map{ |k, v| "#{k}=#{v}" }.join(' ')
330
- worker_node_name = "#{configuration['cluster_name']}-#{worker[:instance_type]}-#{worker[:instance_id]}"
331
-
332
- cmd = "kubectl label --overwrite nodes #{worker_node_name} #{worker_labels}"
333
-
334
- run cmd, kubeconfig_path: kubeconfig_path
335
- end
336
- end
337
-
338
- def taint_nodes
339
- check_kubectl
340
-
341
- if master_definitions_for_create.first[:taints]
342
- master_taints = master_definitions_for_create.first[:taints].map{ |k, v| "#{k}=#{v}" }.join(' ')
343
- master_node_names = []
344
-
345
- master_definitions_for_create.each do |master|
346
- master_node_names << "#{configuration['cluster_name']}-#{master[:instance_type]}-#{master[:instance_id]}"
347
- end
348
-
349
- master_node_names = master_node_names.join(' ')
350
-
351
- cmd = "kubectl taint --overwrite nodes #{master_node_names} #{master_taints}"
352
-
353
- run cmd, kubeconfig_path: kubeconfig_path
354
- end
355
-
356
- workers = []
357
-
358
- worker_node_pools.each do |worker_node_pool|
359
- workers += worker_node_pool_definitions(worker_node_pool)
360
- end
361
-
362
- return unless workers.any?
363
-
364
- workers.each do |worker|
365
- next unless worker[:taints]
366
-
367
- worker_taints = worker[:taints].map{ |k, v| "#{k}=#{v}" }.join(' ')
368
- worker_node_name = "#{configuration['cluster_name']}-#{worker[:instance_type]}-#{worker[:instance_id]}"
369
-
370
- cmd = "kubectl taint --overwrite nodes #{worker_node_name} #{worker_taints}"
371
-
372
- run cmd, kubeconfig_path: kubeconfig_path
373
- end
374
- end
375
-
376
- def deploy_cloud_controller_manager
377
- check_kubectl
378
-
379
- puts
380
- puts 'Deploying Hetzner Cloud Controller Manager...'
381
-
382
- cmd = <<~BASH
383
- kubectl apply -f - <<-EOF
384
- apiVersion: "v1"
385
- kind: "Secret"
386
- metadata:
387
- namespace: 'kube-system'
388
- name: 'hcloud'
389
- stringData:
390
- network: "#{existing_network || cluster_name}"
391
- token: "#{configuration.hetzner_token}"
392
- EOF
393
- BASH
394
-
395
- run cmd, kubeconfig_path: kubeconfig_path
396
-
397
- cmd = 'kubectl apply -f https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml'
398
-
399
- run cmd, kubeconfig_path: kubeconfig_path
400
-
401
- puts '...Cloud Controller Manager deployed'
402
- end
403
-
404
- def deploy_system_upgrade_controller
405
- check_kubectl
406
-
407
- puts
408
- puts 'Deploying k3s System Upgrade Controller...'
409
-
410
- cmd = 'kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/download/v0.9.1/system-upgrade-controller.yaml'
411
-
412
- run cmd, kubeconfig_path: kubeconfig_path
413
-
414
- puts '...k3s System Upgrade Controller deployed'
415
- end
416
-
417
- def deploy_csi_driver
418
- check_kubectl
419
-
420
- puts
421
- puts 'Deploying Hetzner CSI Driver...'
422
-
423
- cmd = <<~BASH
424
- kubectl apply -f - <<-EOF
425
- apiVersion: "v1"
426
- kind: "Secret"
427
- metadata:
428
- namespace: 'kube-system'
429
- name: 'hcloud-csi'
430
- stringData:
431
- token: "#{configuration.hetzner_token}"
432
- EOF
433
- BASH
434
-
435
- run cmd, kubeconfig_path: kubeconfig_path
436
-
437
- cmd = 'kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/master/deploy/kubernetes/hcloud-csi.yml'
438
-
439
- run cmd, kubeconfig_path: kubeconfig_path
440
-
441
- puts '...CSI Driver deployed'
442
- end
443
-
444
- def find_flannel_interface(server)
445
- if ssh(server, 'lscpu | grep Vendor') =~ /Intel/
446
- 'ens10'
447
- else
448
- 'enp7s0'
449
- end
78
+ def belongs_to_cluster?(server)
79
+ server.dig('labels', 'cluster') == cluster_name
450
80
  end
451
81
 
452
82
  def all_servers
@@ -463,74 +93,6 @@ class Cluster
463
93
  @workers = all_servers.select { |server| server['name'] =~ /worker\d+\Z/ }.sort { |a, b| a['name'] <=> b['name'] }
464
94
  end
465
95
 
466
- def k3s_token
467
- @k3s_token ||= begin
468
- token = ssh(first_master, '{ TOKEN=$(< /var/lib/rancher/k3s/server/node-token); } 2> /dev/null; echo $TOKEN')
469
-
470
- if token.empty?
471
- SecureRandom.hex
472
- else
473
- token.split(':').last
474
- end
475
- end
476
- end
477
-
478
- def first_master_private_ip
479
- @first_master_private_ip ||= first_master['private_net'][0]['ip']
480
- end
481
-
482
- def first_master
483
- masters.first
484
- end
485
-
486
- def api_server_ip
487
- return @api_server_ip if @api_server_ip
488
-
489
- @api_server_ip = if masters.size > 1
490
- load_balancer_name = "#{cluster_name}-api"
491
- load_balancer = hetzner_client.get('/load_balancers')['load_balancers'].detect do |lb|
492
- lb['name'] == load_balancer_name
493
- end
494
- load_balancer['public_net']['ipv4']['ip']
495
- else
496
- first_master_public_ip
497
- end
498
- end
499
-
500
- def tls_sans
501
- sans = " --tls-san=#{api_server_ip} "
502
-
503
- masters.each do |master|
504
- master_private_ip = master['private_net'][0]['ip']
505
- sans += " --tls-san=#{master_private_ip} "
506
- end
507
-
508
- sans
509
- end
510
-
511
- def first_master_public_ip
512
- @first_master_public_ip ||= first_master.dig('public_net', 'ipv4', 'ip')
513
- end
514
-
515
- def save_kubeconfig
516
- kubeconfig = ssh(first_master, 'cat /etc/rancher/k3s/k3s.yaml')
517
- .gsub('127.0.0.1', api_server_ip)
518
- .gsub('default', cluster_name)
519
-
520
- File.write(kubeconfig_path, kubeconfig)
521
-
522
- FileUtils.chmod 'go-r', kubeconfig_path
523
- end
524
-
525
- def belongs_to_cluster?(server)
526
- server.dig('labels', 'cluster') == cluster_name
527
- end
528
-
529
- def schedule_workloads_on_masters?
530
- schedule_workloads_on_masters = configuration['schedule_workloads_on_masters']
531
- schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
532
- end
533
-
534
96
  def image
535
97
  configuration['image'] || 'ubuntu-20.04'
536
98
  end
@@ -543,34 +105,19 @@ class Cluster
543
105
  configuration['post_create_commands'] || []
544
106
  end
545
107
 
546
- def check_kubectl
547
- return if which('kubectl')
548
-
549
- puts 'Please ensure kubectl is installed and in your PATH.'
550
- exit 1
551
- end
552
-
553
- def placement_group_id(pool_name = nil)
554
- @placement_groups ||= {}
555
- @placement_groups[pool_name || '__masters__'] ||= Hetzner::PlacementGroup.new(hetzner_client: hetzner_client, cluster_name: cluster_name, pool_name: pool_name).create
556
- end
557
-
558
108
  def master_instance_type
559
109
  @master_instance_type ||= masters_config['instance_type']
560
110
  end
561
111
 
562
- def master_labels
563
- @master_labels ||= masters_config['labels']
564
- end
565
-
566
- def master_taints
567
- @master_taints ||= masters_config['taints']
568
- end
569
-
570
112
  def masters_count
571
113
  @masters_count ||= masters_config['instance_count']
572
114
  end
573
115
 
116
+ def placement_group_id(pool_name = nil)
117
+ @placement_groups ||= {}
118
+ @placement_groups[pool_name || '__masters__'] ||= Hetzner::PlacementGroup.new(hetzner_client: hetzner_client, cluster_name: cluster_name, pool_name: pool_name).create
119
+ end
120
+
574
121
  def firewall_id
575
122
  @firewall_id ||= Hetzner::Firewall.new(hetzner_client: hetzner_client, cluster_name: cluster_name).create(high_availability: (masters_count > 1), ssh_networks: ssh_networks, api_networks: api_networks)
576
123
  end
@@ -598,8 +145,8 @@ class Cluster
598
145
  image: image,
599
146
  additional_packages: additional_packages,
600
147
  additional_post_create_commands: additional_post_create_commands,
601
- labels: master_labels,
602
- taints: master_taints
148
+ labels: masters_config['labels'],
149
+ taints: masters_config['taints']
603
150
  }
604
151
  end
605
152
 
@@ -649,10 +196,6 @@ class Cluster
649
196
  definitions
650
197
  end
651
198
 
652
- def create_load_balancer
653
- Hetzner::LoadBalancer.new(hetzner_client: hetzner_client, cluster_name: cluster_name).create(location: masters_location, network_id: network_id)
654
- end
655
-
656
199
  def server_configs
657
200
  return @server_configs if @server_configs
658
201
 
@@ -665,6 +208,47 @@ class Cluster
665
208
  @server_configs
666
209
  end
667
210
 
211
+ def hetzner_client
212
+ configuration.hetzner_client
213
+ end
214
+
215
+ def kubernetes_client
216
+ @kubernetes_client ||= Kubernetes::Client.new(configuration: configuration)
217
+ end
218
+
219
+ def workers_definitions_for_marking
220
+ worker_node_pools.map do |worker_node_pool|
221
+ worker_node_pool_definitions(worker_node_pool)
222
+ end.flatten
223
+ end
224
+
225
+ def create_resources
226
+ create_servers
227
+ create_load_balancer if masters.size > 1
228
+ end
229
+
230
+ def delete_placement_groups
231
+ Hetzner::PlacementGroup.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete
232
+
233
+ worker_node_pools.each do |pool|
234
+ pool_name = pool['name']
235
+ Hetzner::PlacementGroup.new(hetzner_client: hetzner_client, cluster_name: cluster_name, pool_name: pool_name).delete
236
+ end
237
+ end
238
+
239
+ def delete_resources
240
+ Hetzner::LoadBalancer.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(high_availability: (masters.size > 1))
241
+
242
+ Hetzner::Firewall.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(all_servers)
243
+
244
+ Hetzner::Network.new(hetzner_client: hetzner_client, cluster_name: cluster_name, existing_network: existing_network).delete
245
+
246
+ Hetzner::SSHKey.new(hetzner_client: hetzner_client, cluster_name: cluster_name).delete(public_ssh_key_path: public_ssh_key_path)
247
+
248
+ delete_placement_groups
249
+ delete_servers
250
+ end
251
+
668
252
  def create_servers
669
253
  servers = []
670
254
 
@@ -701,56 +285,8 @@ class Cluster
701
285
  threads.each(&:join) unless threads.empty?
702
286
  end
703
287
 
704
- def kube_api_server_args_list
705
- return '' if kube_api_server_args.empty?
706
-
707
- kube_api_server_args.map do |arg|
708
- " --kube-apiserver-arg=\"#{arg}\" "
709
- end.join
710
- end
711
-
712
- def kube_scheduler_args_list
713
- return '' if kube_scheduler_args.empty?
714
-
715
- kube_scheduler_args.map do |arg|
716
- " --kube-scheduler-arg=\"#{arg}\" "
717
- end.join
718
- end
719
-
720
- def kube_controller_manager_args_list
721
- return '' if kube_controller_manager_args.empty?
722
-
723
- kube_controller_manager_args.map do |arg|
724
- " --kube-controller-manager-arg=\"#{arg}\" "
725
- end.join
726
- end
727
-
728
- def kube_cloud_controller_manager_args_list
729
- return '' if kube_cloud_controller_manager_args.empty?
730
-
731
- kube_cloud_controller_manager_args.map do |arg|
732
- " --kube-cloud-controller-manager-arg=\"#{arg}\" "
733
- end.join
734
- end
735
-
736
- def kubelet_args_list
737
- return '' if kubelet_args.empty?
738
-
739
- kubelet_args.map do |arg|
740
- " --kubelet-arg=\"#{arg}\" "
741
- end.join
742
- end
743
-
744
- def kube_proxy_args_list
745
- return '' if kube_proxy_args.empty?
746
-
747
- kube_api_server_args.map do |arg|
748
- " --kube-proxy-arg=\"#{arg}\" "
749
- end.join
750
- end
751
-
752
- def hetzner_client
753
- configuration.hetzner_client
288
+ def create_load_balancer
289
+ Hetzner::LoadBalancer.new(hetzner_client: hetzner_client, cluster_name: cluster_name).create(location: masters_location, network_id: network_id)
754
290
  end
755
291
 
756
292
  def existing_network
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Hetzner
4
4
  module K3s
5
- VERSION = '0.6.2'
5
+ VERSION = '0.6.3'
6
6
  end
7
7
  end
@@ -0,0 +1,475 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../utils'
4
+
5
+ module Kubernetes
6
+ class Client
7
+ include Utils
8
+
9
+ def initialize(configuration:)
10
+ @configuration = configuration
11
+ end
12
+
13
+ def deploy(masters:, workers:, master_definitions:, worker_definitions:)
14
+ @masters = masters
15
+ @workers = workers
16
+ @master_definitions = master_definitions
17
+ @worker_definitions = worker_definitions
18
+
19
+ @kube_api_server_args = configuration.fetch('kube_api_server_args', [])
20
+ @kube_scheduler_args = configuration.fetch('kube_scheduler_args', [])
21
+ @kube_controller_manager_args = configuration.fetch('kube_controller_manager_args', [])
22
+ @kube_cloud_controller_manager_args = configuration.fetch('kube_cloud_controller_manager_args', [])
23
+ @kubelet_args = configuration.fetch('kubelet_args', [])
24
+ @kube_proxy_args = configuration.fetch('kube_proxy_args', [])
25
+ @private_ssh_key_path = File.expand_path(configuration['private_ssh_key_path'])
26
+ @public_ssh_key_path = File.expand_path(configuration['public_ssh_key_path'])
27
+ @cluster_name = configuration['cluster_name']
28
+
29
+ set_up_k3s
30
+
31
+ update_nodes
32
+
33
+ post_setup_deployments
34
+ end
35
+
36
+ def upgrade
37
+ worker_upgrade_concurrency = workers.size - 1
38
+ worker_upgrade_concurrency = 1 if worker_upgrade_concurrency.zero?
39
+
40
+ cmd = <<~BASH
41
+ kubectl apply -f - <<-EOF
42
+ apiVersion: upgrade.cattle.io/v1
43
+ kind: Plan
44
+ metadata:
45
+ name: k3s-server
46
+ namespace: system-upgrade
47
+ labels:
48
+ k3s-upgrade: server
49
+ spec:
50
+ concurrency: 1
51
+ version: #{new_k3s_version}
52
+ nodeSelector:
53
+ matchExpressions:
54
+ - {key: node-role.kubernetes.io/master, operator: In, values: ["true"]}
55
+ serviceAccountName: system-upgrade
56
+ tolerations:
57
+ - key: "CriticalAddonsOnly"
58
+ operator: "Equal"
59
+ value: "true"
60
+ effect: "NoExecute"
61
+ cordon: true
62
+ upgrade:
63
+ image: rancher/k3s-upgrade
64
+ EOF
65
+ BASH
66
+
67
+ run cmd, kubeconfig_path: kubeconfig_path
68
+
69
+ cmd = <<~BASH
70
+ kubectl apply -f - <<-EOF
71
+ apiVersion: upgrade.cattle.io/v1
72
+ kind: Plan
73
+ metadata:
74
+ name: k3s-agent
75
+ namespace: system-upgrade
76
+ labels:
77
+ k3s-upgrade: agent
78
+ spec:
79
+ concurrency: #{worker_upgrade_concurrency}
80
+ version: #{new_k3s_version}
81
+ nodeSelector:
82
+ matchExpressions:
83
+ - {key: node-role.kubernetes.io/master, operator: NotIn, values: ["true"]}
84
+ serviceAccountName: system-upgrade
85
+ prepare:
86
+ image: rancher/k3s-upgrade
87
+ args: ["prepare", "k3s-server"]
88
+ cordon: true
89
+ upgrade:
90
+ image: rancher/k3s-upgrade
91
+ EOF
92
+ BASH
93
+
94
+ run cmd, kubeconfig_path: kubeconfig_path
95
+
96
+ 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.'
97
+ puts 'The API server may be briefly unavailable during the upgrade of the controlplane.'
98
+
99
+ updated_configuration = configuration.raw
100
+ updated_configuration['k3s_version'] = new_k3s_version
101
+
102
+ File.write(config_file, updated_configuration.to_yaml)
103
+ end
104
+
105
+ private
106
+
107
+ attr_reader :configuration, :masters, :workers, :kube_api_server_args, :kube_scheduler_args,
108
+ :kube_controller_manager_args, :kube_cloud_controller_manager_args, :kubelet_args, :kube_proxy_args,
109
+ :private_ssh_key_path, :public_ssh_key_path, :master_definitions, :worker_definitions, :cluster_name
110
+
111
+ def set_up_k3s
112
+ set_up_first_master
113
+ set_up_additional_masters
114
+ set_up_workers
115
+ end
116
+
117
+ def set_up_first_master
118
+ puts
119
+ puts "Deploying k3s to first master (#{first_master['name']})..."
120
+
121
+ ssh first_master, master_install_script(first_master), print_output: true
122
+
123
+ puts
124
+ puts 'Waiting for the control plane to be ready...'
125
+
126
+ sleep 10
127
+
128
+ puts
129
+ puts '...k3s has been deployed to first master.'
130
+
131
+ save_kubeconfig
132
+ end
133
+
134
+ def set_up_additional_masters
135
+ return unless masters.size > 1
136
+
137
+ threads = masters[1..].map do |master|
138
+ Thread.new do
139
+ puts
140
+ puts "Deploying k3s to master #{master['name']}..."
141
+
142
+ ssh master, master_install_script(master), print_output: true
143
+
144
+ puts
145
+ puts "...k3s has been deployed to master #{master['name']}."
146
+ end
147
+ end
148
+
149
+ threads.each(&:join) unless threads.empty?
150
+ end
151
+
152
+ def set_up_workers
153
+ threads = workers.map do |worker|
154
+ Thread.new do
155
+ puts
156
+ puts "Deploying k3s to worker (#{worker['name']})..."
157
+
158
+ ssh worker, worker_install_script(worker), print_output: true
159
+
160
+ puts
161
+ puts "...k3s has been deployed to worker (#{worker['name']})."
162
+ end
163
+ end
164
+
165
+ threads.each(&:join) unless threads.empty?
166
+ end
167
+
168
+ def post_setup_deployments
169
+ deploy_cloud_controller_manager
170
+ deploy_csi_driver
171
+ deploy_system_upgrade_controller
172
+ end
173
+
174
+ def update_nodes
175
+ mark_nodes mark_type: :labels
176
+ mark_nodes mark_type: :taints
177
+ end
178
+
179
+ def first_master
180
+ masters.first
181
+ end
182
+
183
+ def kube_api_server_args_list
184
+ return '' if kube_api_server_args.empty?
185
+
186
+ kube_api_server_args.map do |arg|
187
+ " --kube-apiserver-arg=\"#{arg}\" "
188
+ end.join
189
+ end
190
+
191
+ def kube_scheduler_args_list
192
+ return '' if kube_scheduler_args.empty?
193
+
194
+ kube_scheduler_args.map do |arg|
195
+ " --kube-scheduler-arg=\"#{arg}\" "
196
+ end.join
197
+ end
198
+
199
+ def kube_controller_manager_args_list
200
+ return '' if kube_controller_manager_args.empty?
201
+
202
+ kube_controller_manager_args.map do |arg|
203
+ " --kube-controller-manager-arg=\"#{arg}\" "
204
+ end.join
205
+ end
206
+
207
+ def kube_cloud_controller_manager_args_list
208
+ return '' if kube_cloud_controller_manager_args.empty?
209
+
210
+ kube_cloud_controller_manager_args.map do |arg|
211
+ " --kube-cloud-controller-manager-arg=\"#{arg}\" "
212
+ end.join
213
+ end
214
+
215
+ def kubelet_args_list
216
+ return '' if kubelet_args.empty?
217
+
218
+ kubelet_args.map do |arg|
219
+ " --kubelet-arg=\"#{arg}\" "
220
+ end.join
221
+ end
222
+
223
+ def kube_proxy_args_list
224
+ return '' if kube_proxy_args.empty?
225
+
226
+ kube_api_server_args.map do |arg|
227
+ " --kube-proxy-arg=\"#{arg}\" "
228
+ end.join
229
+ end
230
+
231
+ def api_server_ip
232
+ return @api_server_ip if @api_server_ip
233
+
234
+ @api_server_ip = if masters.size > 1
235
+ load_balancer_name = "#{cluster_name}-api"
236
+ load_balancer = hetzner_client.get('/load_balancers')['load_balancers'].detect do |lb|
237
+ lb['name'] == load_balancer_name
238
+ end
239
+ load_balancer['public_net']['ipv4']['ip']
240
+ else
241
+ first_master_public_ip
242
+ end
243
+ end
244
+
245
+ def master_install_script(master)
246
+ server = master == first_master ? ' --cluster-init ' : " --server https://#{api_server_ip}:6443 "
247
+ flannel_interface = find_flannel_interface(master)
248
+ enable_encryption = configuration.fetch('enable_encryption', false)
249
+ flannel_wireguard = if enable_encryption
250
+ if Gem::Version.new(k3s_version.scan(/\Av(.*)\+.*\Z/).flatten.first) >= Gem::Version.new('1.23.6')
251
+ ' --flannel-backend=wireguard-native '
252
+ else
253
+ ' --flannel-backend=wireguard '
254
+ end
255
+ else
256
+ ' '
257
+ end
258
+
259
+ 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}"
260
+ taint = schedule_workloads_on_masters? ? ' ' : ' --node-taint CriticalAddonsOnly=true:NoExecute '
261
+
262
+ <<~SCRIPT
263
+ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="#{k3s_version}" K3S_TOKEN="#{k3s_token}" INSTALL_K3S_EXEC="server \
264
+ --disable-cloud-controller \
265
+ --disable servicelb \
266
+ --disable traefik \
267
+ --disable local-storage \
268
+ --disable metrics-server \
269
+ --write-kubeconfig-mode=644 \
270
+ --node-name="$(hostname -f)" \
271
+ --cluster-cidr=10.244.0.0/16 \
272
+ --etcd-expose-metrics=true \
273
+ #{flannel_wireguard} \
274
+ --kube-controller-manager-arg="bind-address=0.0.0.0" \
275
+ --kube-proxy-arg="metrics-bind-address=0.0.0.0" \
276
+ --kube-scheduler-arg="bind-address=0.0.0.0" \
277
+ #{taint} #{extra_args} \
278
+ --kubelet-arg="cloud-provider=external" \
279
+ --advertise-address=$(hostname -I | awk '{print $2}') \
280
+ --node-ip=$(hostname -I | awk '{print $2}') \
281
+ --node-external-ip=$(hostname -I | awk '{print $1}') \
282
+ --flannel-iface=#{flannel_interface} \
283
+ #{server} #{tls_sans}" sh -
284
+ SCRIPT
285
+ end
286
+
287
+ def worker_install_script(worker)
288
+ flannel_interface = find_flannel_interface(worker)
289
+
290
+ <<~BASH
291
+ curl -sfL https://get.k3s.io | K3S_TOKEN="#{k3s_token}" INSTALL_K3S_VERSION="#{k3s_version}" K3S_URL=https://#{first_master_private_ip}:6443 INSTALL_K3S_EXEC="agent \
292
+ --node-name="$(hostname -f)" \
293
+ --kubelet-arg="cloud-provider=external" \
294
+ --node-ip=$(hostname -I | awk '{print $2}') \
295
+ --node-external-ip=$(hostname -I | awk '{print $1}') \
296
+ --flannel-iface=#{flannel_interface}" sh -
297
+ BASH
298
+ end
299
+
300
+ def find_flannel_interface(server)
301
+ if ssh(server, 'lscpu | grep Vendor') =~ /Intel/
302
+ 'ens10'
303
+ else
304
+ 'enp7s0'
305
+ end
306
+ end
307
+
308
+ def hetzner_client
309
+ configuration.hetzner_client
310
+ end
311
+
312
+ def first_master_public_ip
313
+ @first_master_public_ip ||= first_master.dig('public_net', 'ipv4', 'ip')
314
+ end
315
+
316
+ def save_kubeconfig
317
+ kubeconfig = ssh(first_master, 'cat /etc/rancher/k3s/k3s.yaml')
318
+ .gsub('127.0.0.1', api_server_ip)
319
+ .gsub('default', configuration['cluster_name'])
320
+
321
+ File.write(kubeconfig_path, kubeconfig)
322
+
323
+ FileUtils.chmod 'go-r', kubeconfig_path
324
+ end
325
+
326
+ def kubeconfig_path
327
+ @kubeconfig_path ||= File.expand_path(configuration['kubeconfig_path'])
328
+ end
329
+
330
+ def schedule_workloads_on_masters?
331
+ schedule_workloads_on_masters = configuration['schedule_workloads_on_masters']
332
+ schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
333
+ end
334
+
335
+ def k3s_version
336
+ @k3s_version ||= configuration['k3s_version']
337
+ end
338
+
339
+ def k3s_token
340
+ @k3s_token ||= begin
341
+ token = ssh(first_master, '{ TOKEN=$(< /var/lib/rancher/k3s/server/node-token); } 2> /dev/null; echo $TOKEN')
342
+
343
+ if token.empty?
344
+ SecureRandom.hex
345
+ else
346
+ token.split(':').last
347
+ end
348
+ end
349
+ end
350
+
351
+ def tls_sans
352
+ sans = " --tls-san=#{api_server_ip} "
353
+
354
+ masters.each do |master|
355
+ master_private_ip = master['private_net'][0]['ip']
356
+ sans += " --tls-san=#{master_private_ip} "
357
+ end
358
+
359
+ sans
360
+ end
361
+
362
+ def mark_nodes(mark_type:)
363
+ check_kubectl
364
+
365
+ action = mark_type == :labels ? 'label' : 'taint'
366
+
367
+ if master_definitions.first[mark_type]
368
+ master_labels = master_definitions.first[mark_type].map { |k, v| "#{k}=#{v}" }.join(' ')
369
+ master_node_names = []
370
+
371
+ master_definitions.each do |master|
372
+ master_node_names << "#{configuration['cluster_name']}-#{master[:instance_type]}-#{master[:instance_id]}"
373
+ end
374
+
375
+ master_node_names = master_node_names.join(' ')
376
+
377
+ cmd = "kubectl #{action} --overwrite nodes #{master_node_names} #{master_labels}"
378
+
379
+ run cmd, kubeconfig_path: kubeconfig_path
380
+ end
381
+
382
+ return unless worker_definitions.any?
383
+
384
+ worker_definitions.each do |worker|
385
+ next unless worker[mark_type]
386
+
387
+ worker_labels = worker[mark_type].map { |k, v| "#{k}=#{v}" }.join(' ')
388
+ worker_node_name = "#{configuration['cluster_name']}-#{worker[:instance_type]}-#{worker[:instance_id]}"
389
+
390
+ cmd = "kubectl #{action} --overwrite nodes #{worker_node_name} #{worker_labels}"
391
+
392
+ run cmd, kubeconfig_path: kubeconfig_path
393
+ end
394
+ end
395
+
396
+ def deploy_cloud_controller_manager
397
+ check_kubectl
398
+
399
+ puts
400
+ puts 'Deploying Hetzner Cloud Controller Manager...'
401
+
402
+ cmd = <<~BASH
403
+ kubectl apply -f - <<-EOF
404
+ apiVersion: "v1"
405
+ kind: "Secret"
406
+ metadata:
407
+ namespace: 'kube-system'
408
+ name: 'hcloud'
409
+ stringData:
410
+ network: "#{configuration['existing_network'] || cluster_name}"
411
+ token: "#{configuration.hetzner_token}"
412
+ EOF
413
+ BASH
414
+
415
+ run cmd, kubeconfig_path: kubeconfig_path
416
+
417
+ cmd = 'kubectl apply -f https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml'
418
+
419
+ run cmd, kubeconfig_path: kubeconfig_path
420
+
421
+ puts '...Cloud Controller Manager deployed'
422
+ end
423
+
424
+ def deploy_system_upgrade_controller
425
+ check_kubectl
426
+
427
+ puts
428
+ puts 'Deploying k3s System Upgrade Controller...'
429
+
430
+ cmd = 'kubectl apply -f https://github.com/rancher/system-upgrade-controller/releases/download/v0.9.1/system-upgrade-controller.yaml'
431
+
432
+ run cmd, kubeconfig_path: kubeconfig_path
433
+
434
+ puts '...k3s System Upgrade Controller deployed'
435
+ end
436
+
437
+ def deploy_csi_driver
438
+ check_kubectl
439
+
440
+ puts
441
+ puts 'Deploying Hetzner CSI Driver...'
442
+
443
+ cmd = <<~BASH
444
+ kubectl apply -f - <<-EOF
445
+ apiVersion: "v1"
446
+ kind: "Secret"
447
+ metadata:
448
+ namespace: 'kube-system'
449
+ name: 'hcloud-csi'
450
+ stringData:
451
+ token: "#{configuration.hetzner_token}"
452
+ EOF
453
+ BASH
454
+
455
+ run cmd, kubeconfig_path: kubeconfig_path
456
+
457
+ cmd = 'kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/master/deploy/kubernetes/hcloud-csi.yml'
458
+
459
+ run cmd, kubeconfig_path: kubeconfig_path
460
+
461
+ puts '...CSI Driver deployed'
462
+ end
463
+
464
+ def check_kubectl
465
+ return if which('kubectl')
466
+
467
+ puts 'Please ensure kubectl is installed and in your PATH.'
468
+ exit 1
469
+ end
470
+
471
+ def first_master_private_ip
472
+ @first_master_private_ip ||= first_master['private_net'][0]['ip']
473
+ end
474
+ end
475
+ end
data/lib/hetzner/utils.rb CHANGED
@@ -85,10 +85,6 @@ module Utils
85
85
  end
86
86
  end
87
87
  output.chop
88
- # rescue StandardError => e
89
- # p [e.class, e.message]
90
- # retries += 1
91
- # retry unless retries > 15 || e.message =~ /Bad file descriptor/
92
88
  rescue Timeout::Error, IOError, Errno::EBADF
93
89
  retries += 1
94
90
  retry unless retries > 15
@@ -109,4 +105,8 @@ module Utils
109
105
  MESSAGE
110
106
  exit 1
111
107
  end
108
+
109
+ def verify_host_key
110
+ @verify_host_key ||= configuration.fetch('verify_host_key', false)
111
+ end
112
112
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hetzner-k3s
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vito Botta
@@ -155,6 +155,7 @@ files:
155
155
  - lib/hetzner/k3s/cluster.rb
156
156
  - lib/hetzner/k3s/configuration.rb
157
157
  - lib/hetzner/k3s/version.rb
158
+ - lib/hetzner/kubernetes/client.rb
158
159
  - lib/hetzner/utils.rb
159
160
  - spec/k3s_spec.rb
160
161
  - spec/spec_helper.rb