hetzner-k3s 0.5.0 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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:)
@@ -12,7 +14,7 @@ module Hetzner
12
14
 
13
15
  server_name = "#{cluster_name}-#{instance_type}-#{instance_id}"
14
16
 
15
- if server = find_server(server_name)
17
+ if (server = find_server(server_name))
16
18
  puts "Server #{server_name} already exists, skipping."
17
19
  puts
18
20
  return server
@@ -22,8 +24,8 @@ module Hetzner
22
24
 
23
25
  server_config = {
24
26
  name: server_name,
25
- location: location,
26
- image: image,
27
+ location:,
28
+ image:,
27
29
  firewalls: [
28
30
  { firewall: firewall_id }
29
31
  ],
@@ -34,18 +36,18 @@ module Hetzner
34
36
  ssh_keys: [
35
37
  ssh_key_id
36
38
  ],
37
- user_data: user_data,
39
+ user_data:,
38
40
  labels: {
39
41
  cluster: cluster_name,
40
- role: (server_name =~ /master/ ? "master" : "worker")
42
+ role: (server_name =~ /master/ ? 'master' : 'worker')
41
43
  },
42
44
  placement_group: placement_group_id
43
45
  }
44
46
 
45
- response = hetzner_client.post("/servers", server_config)
47
+ response = hetzner_client.post('/servers', server_config)
46
48
  response_body = response.body
47
49
 
48
- server = JSON.parse(response_body)["server"]
50
+ server = JSON.parse(response_body)['server']
49
51
 
50
52
  unless server
51
53
  puts "Error creating server #{server_name}. Response details below:"
@@ -61,9 +63,9 @@ module Hetzner
61
63
  end
62
64
 
63
65
  def delete(server_name:)
64
- if server = find_server(server_name)
66
+ if (server = find_server(server_name))
65
67
  puts "Deleting server #{server_name}..."
66
- hetzner_client.delete "/servers", server["id"]
68
+ hetzner_client.delete '/servers', server['id']
67
69
  puts "...server #{server_name} deleted."
68
70
  else
69
71
  puts "Server #{server_name} no longer exists, skipping."
@@ -72,31 +74,30 @@ module Hetzner
72
74
 
73
75
  private
74
76
 
75
- attr_reader :hetzner_client, :cluster_name, :additional_packages
76
-
77
- def find_server(server_name)
78
- hetzner_client.get("/servers?sort=created:desc")["servers"].detect{ |network| network["name"] == server_name }
79
- end
77
+ attr_reader :hetzner_client, :cluster_name, :additional_packages
80
78
 
81
- def user_data
82
- packages = ["fail2ban"]
83
- packages += additional_packages if additional_packages
84
- packages = "'" + packages.join("', '") + "'"
85
-
86
- <<~EOS
87
- #cloud-config
88
- packages: [#{packages}]
89
- runcmd:
90
- - sed -i 's/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
91
- - sed -i 's/[#]*PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config
92
- - systemctl restart sshd
93
- - systemctl stop systemd-resolved
94
- - systemctl disable systemd-resolved
95
- - rm /etc/resolv.conf
96
- - echo "nameserver 1.1.1.1" > /etc/resolv.conf
97
- - echo "nameserver 1.0.0.1" >> /etc/resolv.conf
98
- EOS
99
- end
79
+ def find_server(server_name)
80
+ hetzner_client.get('/servers?sort=created:desc')['servers'].detect { |network| network['name'] == server_name }
81
+ end
100
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
101
102
  end
102
103
  end