hetzner-k3s 0.6.2 → 0.6.3

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 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