hetzner-k3s 0.4.9 → 0.5.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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hetzner
2
4
  class Firewall
3
5
  def initialize(hetzner_client:, cluster_name:)
@@ -5,38 +7,39 @@ module Hetzner
5
7
  @cluster_name = cluster_name
6
8
  end
7
9
 
8
- def create(ha:, networks:)
9
- @ha = ha
10
+ def create(high_availability:, networks:)
11
+ @high_availability = high_availability
10
12
  @networks = networks
11
13
  puts
12
14
 
13
- if firewall = find_firewall
14
- puts "Firewall already exists, skipping."
15
+ if (firewall = find_firewall)
16
+ puts 'Firewall already exists, skipping.'
15
17
  puts
16
- return firewall["id"]
18
+ return firewall['id']
17
19
  end
18
20
 
19
- puts "Creating firewall..."
21
+ puts 'Creating firewall...'
20
22
 
21
- response = hetzner_client.post("/firewalls", create_firewall_config).body
22
- puts "...firewall created."
23
+ response = hetzner_client.post('/firewalls', create_firewall_config).body
24
+ puts '...firewall created.'
23
25
  puts
24
26
 
25
- JSON.parse(response)["firewall"]["id"]
27
+ JSON.parse(response)['firewall']['id']
26
28
  end
27
29
 
28
30
  def delete(servers)
29
- if firewall = find_firewall
30
- puts "Deleting firewall..."
31
+ if (firewall = find_firewall)
32
+ puts 'Deleting firewall...'
31
33
 
32
34
  servers.each do |server|
33
- hetzner_client.post("/firewalls/#{firewall["id"]}/actions/remove_from_resources", remove_targets_config(server["id"]))
35
+ hetzner_client.post("/firewalls/#{firewall['id']}/actions/remove_from_resources",
36
+ remove_targets_config(server['id']))
34
37
  end
35
38
 
36
- hetzner_client.delete("/firewalls", firewall["id"])
37
- puts "...firewall deleted."
39
+ hetzner_client.delete('/firewalls', firewall['id'])
40
+ puts '...firewall deleted.'
38
41
  else
39
- puts "Firewall no longer exists, skipping."
42
+ puts 'Firewall no longer exists, skipping.'
40
43
  end
41
44
 
42
45
  puts
@@ -44,87 +47,86 @@ module Hetzner
44
47
 
45
48
  private
46
49
 
47
- attr_reader :hetzner_client, :cluster_name, :firewall, :ha, :networks
48
-
49
- def create_firewall_config
50
- rules = [
51
- {
52
- "description": "Allow port 22 (SSH)",
53
- "direction": "in",
54
- "protocol": "tcp",
55
- "port": "22",
56
- "source_ips": networks,
57
- "destination_ips": []
58
- },
59
- {
60
- "description": "Allow ICMP (ping)",
61
- "direction": "in",
62
- "protocol": "icmp",
63
- "port": nil,
64
- "source_ips": [
65
- "0.0.0.0/0",
66
- "::/0"
67
- ],
68
- "destination_ips": []
69
- },
70
- {
71
- "description": "Allow all TCP traffic between nodes on the private network",
72
- "direction": "in",
73
- "protocol": "tcp",
74
- "port": "any",
75
- "source_ips": [
76
- "10.0.0.0/16"
77
- ],
78
- "destination_ips": []
79
- },
80
- {
81
- "description": "Allow all UDP traffic between nodes on the private network",
82
- "direction": "in",
83
- "protocol": "udp",
84
- "port": "any",
85
- "source_ips": [
86
- "10.0.0.0/16"
87
- ],
88
- "destination_ips": []
89
- }
90
- ]
91
-
92
- unless ha
93
- rules << {
94
- "description": "Allow port 6443 (Kubernetes API server)",
95
- "direction": "in",
96
- "protocol": "tcp",
97
- "port": "6443",
98
- "source_ips": [
99
- "0.0.0.0/0",
100
- "::/0"
101
- ],
102
- "destination_ips": []
103
- }
104
- end
50
+ attr_reader :hetzner_client, :cluster_name, :firewall, :high_availability, :networks
105
51
 
52
+ def create_firewall_config
53
+ rules = [
106
54
  {
107
- name: cluster_name,
108
- rules: rules
109
- }
110
- end
111
-
112
- def remove_targets_config(server_id)
55
+ description: 'Allow port 22 (SSH)',
56
+ direction: 'in',
57
+ protocol: 'tcp',
58
+ port: '22',
59
+ source_ips: networks,
60
+ destination_ips: []
61
+ },
62
+ {
63
+ description: 'Allow ICMP (ping)',
64
+ direction: 'in',
65
+ protocol: 'icmp',
66
+ port: nil,
67
+ source_ips: [
68
+ '0.0.0.0/0',
69
+ '::/0'
70
+ ],
71
+ destination_ips: []
72
+ },
73
+ {
74
+ description: 'Allow all TCP traffic between nodes on the private network',
75
+ direction: 'in',
76
+ protocol: 'tcp',
77
+ port: 'any',
78
+ source_ips: [
79
+ '10.0.0.0/16'
80
+ ],
81
+ destination_ips: []
82
+ },
113
83
  {
114
- "remove_from": [
115
- {
116
- "server": {
117
- "id": server_id
118
- },
119
- "type": "server"
120
- }
121
- ]
84
+ description: 'Allow all UDP traffic between nodes on the private network',
85
+ direction: 'in',
86
+ protocol: 'udp',
87
+ port: 'any',
88
+ source_ips: [
89
+ '10.0.0.0/16'
90
+ ],
91
+ destination_ips: []
92
+ }
93
+ ]
94
+
95
+ unless high_availability
96
+ rules << {
97
+ description: 'Allow port 6443 (Kubernetes API server)',
98
+ direction: 'in',
99
+ protocol: 'tcp',
100
+ port: '6443',
101
+ source_ips: [
102
+ '0.0.0.0/0',
103
+ '::/0'
104
+ ],
105
+ destination_ips: []
122
106
  }
123
107
  end
124
108
 
125
- def find_firewall
126
- hetzner_client.get("/firewalls")["firewalls"].detect{ |firewall| firewall["name"] == cluster_name }
127
- end
109
+ {
110
+ name: cluster_name,
111
+ rules:
112
+ }
113
+ end
128
114
 
115
+ def remove_targets_config(server_id)
116
+ {
117
+ remove_from: [
118
+ {
119
+ server: {
120
+ id: server_id
121
+ },
122
+ type: 'server'
123
+ }
124
+ ]
125
+ }
126
+ end
127
+
128
+ def find_firewall
129
+ hetzner_client.get('/firewalls')['firewalls'].detect { |firewall| firewall['name'] == cluster_name }
130
+ end
129
131
  end
130
132
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hetzner
2
4
  class LoadBalancer
3
5
  def initialize(hetzner_client:, cluster_name:)
@@ -11,31 +13,31 @@ module Hetzner
11
13
 
12
14
  puts
13
15
 
14
- if load_balancer = find_load_balancer
15
- puts "API load balancer already exists, skipping."
16
+ if (load_balancer = find_load_balancer)
17
+ puts 'API load balancer already exists, skipping.'
16
18
  puts
17
- return load_balancer["id"]
19
+ return load_balancer['id']
18
20
  end
19
21
 
20
- puts "Creating API load_balancer..."
22
+ puts 'Creating API load_balancer...'
21
23
 
22
- response = hetzner_client.post("/load_balancers", create_load_balancer_config).body
23
- puts "...API load balancer created."
24
+ response = hetzner_client.post('/load_balancers', create_load_balancer_config).body
25
+ puts '...API load balancer created.'
24
26
  puts
25
27
 
26
- JSON.parse(response)["load_balancer"]["id"]
28
+ JSON.parse(response)['load_balancer']['id']
27
29
  end
28
30
 
29
- def delete(ha:)
30
- if load_balancer = find_load_balancer
31
- puts "Deleting API load balancer..." unless ha
31
+ def delete(high_availability:)
32
+ if (load_balancer = find_load_balancer)
33
+ puts 'Deleting API load balancer...' unless high_availability
32
34
 
33
- hetzner_client.post("/load_balancers/#{load_balancer["id"]}/actions/remove_target", remove_targets_config)
35
+ hetzner_client.post("/load_balancers/#{load_balancer['id']}/actions/remove_target", remove_targets_config)
34
36
 
35
- hetzner_client.delete("/load_balancers", load_balancer["id"])
36
- puts "...API load balancer deleted." unless ha
37
- elsif ha
38
- puts "API load balancer no longer exists, skipping."
37
+ hetzner_client.delete('/load_balancers', load_balancer['id'])
38
+ puts '...API load balancer deleted.' unless high_availability
39
+ elsif high_availability
40
+ puts 'API load balancer no longer exists, skipping.'
39
41
  end
40
42
 
41
43
  puts
@@ -43,54 +45,55 @@ module Hetzner
43
45
 
44
46
  private
45
47
 
46
- attr_reader :hetzner_client, :cluster_name, :load_balancer, :location, :network_id
48
+ attr_reader :hetzner_client, :cluster_name, :load_balancer, :location, :network_id
47
49
 
48
- def load_balancer_name
49
- "#{cluster_name}-api"
50
- end
50
+ def load_balancer_name
51
+ "#{cluster_name}-api"
52
+ end
51
53
 
52
- def create_load_balancer_config
53
- {
54
- "algorithm": {
55
- "type": "round_robin"
56
- },
57
- "load_balancer_type": "lb11",
58
- "location": location,
59
- "name": load_balancer_name,
60
- "network": network_id,
61
- "public_interface": true,
62
- "services": [
63
- {
64
- "destination_port": 6443,
65
- "listen_port": 6443,
66
- "protocol": "tcp",
67
- "proxyprotocol": false
68
- }
69
- ],
70
- "targets": [
71
- {
72
- "label_selector": {
73
- "selector": "cluster=#{cluster_name},role=master"
74
- },
75
- "type": "label_selector",
76
- "use_private_ip": true
77
- }
78
- ]
79
- }
80
- end
54
+ def create_load_balancer_config
55
+ {
56
+ algorithm: {
57
+ type: 'round_robin'
58
+ },
59
+ load_balancer_type: 'lb11',
60
+ location:,
61
+ name: load_balancer_name,
62
+ network: network_id,
63
+ public_interface: true,
64
+ services: [
65
+ {
66
+ destination_port: 6443,
67
+ listen_port: 6443,
68
+ protocol: 'tcp',
69
+ proxyprotocol: false
70
+ }
71
+ ],
72
+ targets: [
73
+ {
74
+ label_selector: {
75
+ selector: "cluster=#{cluster_name},role=master"
76
+ },
77
+ type: 'label_selector',
78
+ use_private_ip: true
79
+ }
80
+ ]
81
+ }
82
+ end
81
83
 
82
- def remove_targets_config
83
- {
84
- "label_selector": {
85
- "selector": "cluster=#{cluster_name},role=master"
86
- },
87
- "type": "label_selector"
88
- }
89
- end
84
+ def remove_targets_config
85
+ {
86
+ label_selector: {
87
+ selector: "cluster=#{cluster_name},role=master"
88
+ },
89
+ type: 'label_selector'
90
+ }
91
+ end
90
92
 
91
- def find_load_balancer
92
- hetzner_client.get("/load_balancers")["load_balancers"].detect{ |load_balancer| load_balancer["name"] == load_balancer_name }
93
+ def find_load_balancer
94
+ hetzner_client.get('/load_balancers')['load_balancers'].detect do |load_balancer|
95
+ load_balancer['name'] == load_balancer_name
93
96
  end
94
-
97
+ end
95
98
  end
96
99
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hetzner
2
4
  class Network
3
5
  def initialize(hetzner_client:, cluster_name:)
@@ -9,29 +11,29 @@ module Hetzner
9
11
  @location = location
10
12
  puts
11
13
 
12
- if network = find_network
13
- puts "Private network already exists, skipping."
14
+ if (network = find_network)
15
+ puts 'Private network already exists, skipping.'
14
16
  puts
15
- return network["id"]
17
+ return network['id']
16
18
  end
17
19
 
18
- puts "Creating private network..."
20
+ puts 'Creating private network...'
19
21
 
20
- response = hetzner_client.post("/networks", network_config).body
22
+ response = hetzner_client.post('/networks', network_config).body
21
23
 
22
- puts "...private network created."
24
+ puts '...private network created.'
23
25
  puts
24
26
 
25
- JSON.parse(response)["network"]["id"]
27
+ JSON.parse(response)['network']['id']
26
28
  end
27
29
 
28
30
  def delete
29
- if network = find_network
30
- puts "Deleting network..."
31
- hetzner_client.delete("/networks", network["id"])
32
- puts "...network deleted."
31
+ if (network = find_network)
32
+ puts 'Deleting network...'
33
+ hetzner_client.delete('/networks', network['id'])
34
+ puts '...network deleted.'
33
35
  else
34
- puts "Network no longer exists, skipping."
36
+ puts 'Network no longer exists, skipping.'
35
37
  end
36
38
 
37
39
  puts
@@ -39,25 +41,24 @@ module Hetzner
39
41
 
40
42
  private
41
43
 
42
- attr_reader :hetzner_client, :cluster_name, :location
43
-
44
- def network_config
45
- {
46
- name: cluster_name,
47
- ip_range: "10.0.0.0/16",
48
- subnets: [
49
- {
50
- ip_range: "10.0.0.0/16",
51
- network_zone: (location == "ash" ? "us-east" : "eu-central"),
52
- type: "cloud"
53
- }
54
- ]
55
- }
56
- end
44
+ attr_reader :hetzner_client, :cluster_name, :location
57
45
 
58
- def find_network
59
- hetzner_client.get("/networks")["networks"].detect{ |network| network["name"] == cluster_name }
60
- end
46
+ def network_config
47
+ {
48
+ name: cluster_name,
49
+ ip_range: '10.0.0.0/16',
50
+ subnets: [
51
+ {
52
+ ip_range: '10.0.0.0/16',
53
+ network_zone: (location == 'ash' ? 'us-east' : 'eu-central'),
54
+ type: 'cloud'
55
+ }
56
+ ]
57
+ }
58
+ end
61
59
 
60
+ def find_network
61
+ hetzner_client.get('/networks')['networks'].detect { |network| network['name'] == cluster_name }
62
+ end
62
63
  end
63
64
  end
@@ -1,36 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hetzner
2
4
  class PlacementGroup
3
- def initialize(hetzner_client:, cluster_name:)
5
+ def initialize(hetzner_client:, cluster_name:, pool_name: nil)
4
6
  @hetzner_client = hetzner_client
5
7
  @cluster_name = cluster_name
8
+ @placement_group_name = pool_name ? "#{cluster_name}-#{pool_name}" : cluster_name
6
9
  end
7
10
 
8
11
  def create
9
12
  puts
10
13
 
11
14
  if (placement_group = find_placement_group)
12
- puts "Placement group already exists, skipping."
15
+ puts "Placement group #{placement_group_name} already exists, skipping."
13
16
  puts
14
- return placement_group["id"]
17
+ return placement_group['id']
15
18
  end
16
19
 
17
- puts "Creating placement group..."
20
+ puts "Creating placement group #{placement_group_name}..."
18
21
 
19
- response = hetzner_client.post("/placement_groups", placement_group_config).body
22
+ response = hetzner_client.post('/placement_groups', placement_group_config).body
20
23
 
21
- puts "...placement group created."
24
+ puts "...placement group #{placement_group_name} created."
22
25
  puts
23
26
 
24
- JSON.parse(response)["placement_group"]["id"]
27
+ JSON.parse(response)['placement_group']['id']
25
28
  end
26
29
 
27
30
  def delete
28
31
  if (placement_group = find_placement_group)
29
- puts "Deleting placement group..."
30
- hetzner_client.delete("/placement_groups", placement_group["id"])
31
- puts "...placement group deleted."
32
+ puts "Deleting placement group #{placement_group_name}..."
33
+ hetzner_client.delete('/placement_groups', placement_group['id'])
34
+ puts "...placement group #{placement_group_name} deleted."
32
35
  else
33
- puts "Placement group no longer exists, skipping."
36
+ puts "Placement group #{placement_group_name} no longer exists, skipping."
34
37
  end
35
38
 
36
39
  puts
@@ -38,18 +41,19 @@ module Hetzner
38
41
 
39
42
  private
40
43
 
41
- attr_reader :hetzner_client, :cluster_name
44
+ attr_reader :hetzner_client, :cluster_name, :placement_group_name
42
45
 
43
- def placement_group_config
44
- {
45
- name: cluster_name,
46
- type: "spread"
47
- }
48
- end
46
+ def placement_group_config
47
+ {
48
+ name: placement_group_name,
49
+ type: 'spread'
50
+ }
51
+ end
49
52
 
50
- def find_placement_group
51
- hetzner_client.get("/placement_groups")["placement_groups"].detect{ |placement_group| placement_group["name"] == cluster_name }
53
+ def find_placement_group
54
+ hetzner_client.get('/placement_groups')['placement_groups'].detect do |placement_group|
55
+ placement_group['name'] == placement_group_name
52
56
  end
53
-
57
+ end
54
58
  end
55
59
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hetzner
2
4
  class Server
3
5
  def initialize(hetzner_client:, cluster_name:)
@@ -5,12 +7,14 @@ module Hetzner
5
7
  @cluster_name = cluster_name
6
8
  end
7
9
 
8
- def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:, image:)
10
+ def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:, image:, additional_packages: [])
11
+ @additional_packages = additional_packages
12
+
9
13
  puts
10
14
 
11
15
  server_name = "#{cluster_name}-#{instance_type}-#{instance_id}"
12
16
 
13
- if server = find_server(server_name)
17
+ if (server = find_server(server_name))
14
18
  puts "Server #{server_name} already exists, skipping."
15
19
  puts
16
20
  return server
@@ -20,8 +24,8 @@ module Hetzner
20
24
 
21
25
  server_config = {
22
26
  name: server_name,
23
- location: location,
24
- image: image,
27
+ location:,
28
+ image:,
25
29
  firewalls: [
26
30
  { firewall: firewall_id }
27
31
  ],
@@ -32,18 +36,18 @@ module Hetzner
32
36
  ssh_keys: [
33
37
  ssh_key_id
34
38
  ],
35
- user_data: user_data,
39
+ user_data:,
36
40
  labels: {
37
41
  cluster: cluster_name,
38
- role: (server_name =~ /master/ ? "master" : "worker")
42
+ role: (server_name =~ /master/ ? 'master' : 'worker')
39
43
  },
40
44
  placement_group: placement_group_id
41
45
  }
42
46
 
43
- response = hetzner_client.post("/servers", server_config)
47
+ response = hetzner_client.post('/servers', server_config)
44
48
  response_body = response.body
45
49
 
46
- server = JSON.parse(response_body)["server"]
50
+ server = JSON.parse(response_body)['server']
47
51
 
48
52
  unless server
49
53
  puts "Error creating server #{server_name}. Response details below:"
@@ -59,9 +63,9 @@ module Hetzner
59
63
  end
60
64
 
61
65
  def delete(server_name:)
62
- if server = find_server(server_name)
66
+ if (server = find_server(server_name))
63
67
  puts "Deleting server #{server_name}..."
64
- hetzner_client.delete "/servers", server["id"]
68
+ hetzner_client.delete '/servers', server['id']
65
69
  puts "...server #{server_name} deleted."
66
70
  else
67
71
  puts "Server #{server_name} no longer exists, skipping."
@@ -70,28 +74,30 @@ module Hetzner
70
74
 
71
75
  private
72
76
 
73
- attr_reader :hetzner_client, :cluster_name
74
-
75
- def find_server(server_name)
76
- hetzner_client.get("/servers")["servers"].detect{ |network| network["name"] == server_name }
77
- end
77
+ attr_reader :hetzner_client, :cluster_name, :additional_packages
78
78
 
79
- def user_data
80
- <<~EOS
81
- #cloud-config
82
- packages:
83
- - fail2ban
84
- runcmd:
85
- - sed -i 's/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
86
- - sed -i 's/[#]*PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
87
- - systemctl restart sshd
88
- - systemctl stop systemd-resolved
89
- - systemctl disable systemd-resolved
90
- - rm /etc/resolv.conf
91
- - echo "nameserver 1.1.1.1" > /etc/resolv.conf
92
- - echo "nameserver 1.0.0.1" >> /etc/resolv.conf
93
- EOS
94
- end
79
+ def find_server(server_name)
80
+ hetzner_client.get('/servers?sort=created:desc')['servers'].detect { |network| network['name'] == server_name }
81
+ end
95
82
 
83
+ def user_data
84
+ packages = ['fail2ban', 'wireguard']
85
+ packages += additional_packages if additional_packages
86
+ packages = "'#{packages.join("', '")}'"
87
+
88
+ <<~YAML
89
+ #cloud-config
90
+ packages: [#{packages}]
91
+ runcmd:
92
+ - sed -i 's/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
93
+ - sed -i 's/[#]*PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
94
+ - systemctl restart sshd
95
+ - systemctl stop systemd-resolved
96
+ - systemctl disable systemd-resolved
97
+ - rm /etc/resolv.conf
98
+ - echo "nameserver 1.1.1.1" > /etc/resolv.conf
99
+ - echo "nameserver 1.0.0.1" >> /etc/resolv.conf
100
+ YAML
101
+ end
96
102
  end
97
103
  end