hetzner-k3s 0.5.5 → 0.5.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +14 -0
- data/.ruby-version +1 -1
- data/Dockerfile +1 -1
- data/Gemfile.lock +5 -5
- data/README.md +33 -6
- data/bin/build.sh +3 -5
- data/hetzner-k3s.gemspec +1 -1
- data/lib/hetzner/infra/server.rb +73 -46
- data/lib/hetzner/k3s/cli.rb +14 -386
- data/lib/hetzner/k3s/cluster.rb +25 -20
- data/lib/hetzner/k3s/configuration.rb +454 -0
- data/lib/hetzner/k3s/version.rb +1 -1
- data/lib/hetzner/utils.rb +7 -4
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0d855f62ab9e222986d220edcdde203f7eb363ab725c8aa4e1f1389f2b251e2
|
4
|
+
data.tar.gz: 19d6a1ff6769cbec2207539d615d6a873eaede07aec9b15a43e6ef9d79101731
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8fdc127e71f790e530d3abf97ca30d22b28d75eff687e436d1351336595ae7ba912c0c947c8b1738ab9d50447e577c4be753db3c20a2bfbcca195fcc6a0d193
|
7
|
+
data.tar.gz: 5cb4203c6270f0e82b66049fefd7244aaeb42e1dd89e257a0e1ba4b126db04ed8adf27b715b10874248f6661b6fed3126e562913ec2342f086bbff4c717c329b
|
data/.rubocop.yml
CHANGED
@@ -119,3 +119,17 @@ Metrics/ParameterLists:
|
|
119
119
|
Style/FrozenStringLiteralComment:
|
120
120
|
Exclude:
|
121
121
|
- exe/hetzner-k3s
|
122
|
+
Lint/RefinementImportMethods: # new in 1.27
|
123
|
+
Enabled: true
|
124
|
+
Security/CompoundHash: # new in 1.28
|
125
|
+
Enabled: true
|
126
|
+
Style/EnvHome: # new in 1.29
|
127
|
+
Enabled: true
|
128
|
+
Style/FetchEnvVar: # new in 1.28
|
129
|
+
Enabled: true
|
130
|
+
Style/NestedFileDirname: # new in 1.26
|
131
|
+
Enabled: true
|
132
|
+
Style/ObjectThen: # new in 1.28
|
133
|
+
Enabled: true
|
134
|
+
Style/RedundantInitialize: # new in 1.27
|
135
|
+
Enabled: true
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.
|
1
|
+
ruby-3.1.2
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hetzner-k3s (0.5.
|
4
|
+
hetzner-k3s (0.5.7)
|
5
5
|
bcrypt_pbkdf
|
6
6
|
ed25519
|
7
7
|
http
|
@@ -30,7 +30,7 @@ GEM
|
|
30
30
|
http-cookie (~> 1.0)
|
31
31
|
http-form_data (~> 2.2)
|
32
32
|
http-parser (~> 1.2.0)
|
33
|
-
http-cookie (1.0.
|
33
|
+
http-cookie (1.0.5)
|
34
34
|
domain_name (~> 0.5)
|
35
35
|
http-form_data (2.3.0)
|
36
36
|
http-parser (1.2.3)
|
@@ -39,7 +39,7 @@ GEM
|
|
39
39
|
parallel (1.21.0)
|
40
40
|
parser (3.1.0.0)
|
41
41
|
ast (~> 2.4.1)
|
42
|
-
public_suffix (4.0.
|
42
|
+
public_suffix (4.0.7)
|
43
43
|
rainbow (3.1.1)
|
44
44
|
rake (12.3.3)
|
45
45
|
regexp_parser (2.2.0)
|
@@ -74,7 +74,7 @@ GEM
|
|
74
74
|
thor (1.2.1)
|
75
75
|
unf (0.1.4)
|
76
76
|
unf_ext
|
77
|
-
unf_ext (0.0.8)
|
77
|
+
unf_ext (0.0.8.2)
|
78
78
|
unicode-display_width (2.1.0)
|
79
79
|
|
80
80
|
PLATFORMS
|
@@ -87,4 +87,4 @@ DEPENDENCIES
|
|
87
87
|
rubocop
|
88
88
|
|
89
89
|
BUNDLED WITH
|
90
|
-
2.3.
|
90
|
+
2.3.14
|
data/README.md
CHANGED
@@ -16,6 +16,8 @@ Using this tool, creating a highly available k3s cluster with 3 masters for the
|
|
16
16
|
|
17
17
|
See roadmap [here](https://github.com/vitobotta/hetzner-k3s/projects/1) for the features planned or in progress.
|
18
18
|
|
19
|
+
Also see this [wiki page](https://github.com/vitobotta/hetzner-k3s/wiki/Tutorial:---Setting-up-a-cluster) for a tutorial on how to set up a cluster with the most common setup to get you started.
|
20
|
+
|
19
21
|
## Requirements
|
20
22
|
|
21
23
|
All that is needed to use this tool is
|
@@ -26,7 +28,7 @@ All that is needed to use this tool is
|
|
26
28
|
|
27
29
|
## Installation
|
28
30
|
|
29
|
-
Once you have the Ruby runtime up and running (3.1.
|
31
|
+
Once you have the Ruby runtime up and running (3.1.2 or newer), you just need to install the gem:
|
30
32
|
|
31
33
|
```bash
|
32
34
|
gem install hetzner-k3s
|
@@ -39,7 +41,12 @@ This will install the `hetzner-k3s` executable in your PATH.
|
|
39
41
|
Alternatively, if you don't want to set up a Ruby runtime but have Docker installed, you can use a container. Run the following from inside the directory where you have the config file for the cluster (described in the next section):
|
40
42
|
|
41
43
|
```bash
|
42
|
-
docker run --rm -it
|
44
|
+
docker run --rm -it \
|
45
|
+
-v ${PWD}:/cluster \
|
46
|
+
-v ${HOME}/.ssh:/tmp/.ssh \
|
47
|
+
vitobotta/hetzner-k3s:v0.5.8 \
|
48
|
+
create-cluster \
|
49
|
+
--config-file /cluster/test.yaml
|
43
50
|
```
|
44
51
|
|
45
52
|
Replace `test.yaml` with the name of your config file.
|
@@ -73,6 +80,11 @@ worker_node_pools:
|
|
73
80
|
instance_count: 2
|
74
81
|
additional_packages:
|
75
82
|
- somepackage
|
83
|
+
post_create_commands:
|
84
|
+
- apt update
|
85
|
+
- apt upgrade -y
|
86
|
+
- apt autoremove -y
|
87
|
+
- shutdown -r now
|
76
88
|
enable_encryption: true
|
77
89
|
# kube_api_server_args:
|
78
90
|
# - arg1
|
@@ -109,7 +121,7 @@ If you set `masters.instance_count` to 1 then the tool will create a non highly
|
|
109
121
|
|
110
122
|
You can specify any number of worker node pools for example to have mixed nodes with different specs for different workloads.
|
111
123
|
|
112
|
-
At the moment Hetzner Cloud has four locations: two in Germany (`nbg1`, Nuremberg and `fsn1`,
|
124
|
+
At the moment Hetzner Cloud has four locations: two in Germany (`nbg1`, Nuremberg and `fsn1`, Falkenstein), one in Finland (`hel1`, Helsinki) and one in the USA (`ash`, Ashburn, Virginia). Please note that the Ashburn, Virginia location has just
|
113
125
|
been announced and it's limited to AMD instances for now.
|
114
126
|
|
115
127
|
For the available instance types and their specs, either check from inside a project when adding a server manually or run the following with your Hetzner token:
|
@@ -159,6 +171,8 @@ The `create-cluster` command can be run any number of times with the same config
|
|
159
171
|
|
160
172
|
To add one or more nodes to a node pool, just change the instance count in the configuration file for that node pool and re-run the create command.
|
161
173
|
|
174
|
+
**Important**: if you are increasing the size of a node pool created prior to v0.5.7, please see [this thread](https://github.com/vitobotta/hetzner-k3s/issues/80).
|
175
|
+
|
162
176
|
### Scaling down a node pool
|
163
177
|
|
164
178
|
To make a node pool smaller:
|
@@ -194,7 +208,6 @@ Note that the API server will briefly be unavailable during the upgrade of the c
|
|
194
208
|
|
195
209
|
To check the upgrade progress, run `watch kubectl get nodes -owide`. You will see the masters being upgraded one per time, followed by the worker nodes.
|
196
210
|
|
197
|
-
|
198
211
|
### What to do if the upgrade doesn't go smoothly
|
199
212
|
|
200
213
|
If the upgrade gets stuck for some reason, or it doesn't upgrade all the nodes:
|
@@ -220,7 +233,8 @@ I have noticed that sometimes I need to re-run the upgrade command a couple of t
|
|
220
233
|
You can also check the logs of the system upgrade controller's pod:
|
221
234
|
|
222
235
|
```bash
|
223
|
-
kubectl -n system-upgrade
|
236
|
+
kubectl -n system-upgrade \
|
237
|
+
logs -f $(kubectl -n system-upgrade get pod -l pod-template-hash -o jsonpath="{.items[0].metadata.name}")
|
224
238
|
```
|
225
239
|
|
226
240
|
A final note about upgrades is that if for some reason the upgrade gets stuck after upgrading the masters and before upgrading the worker nodes, just cleaning up the resources as described above might not be enough. In that case also try running the following to tell the upgrade job for the workers that the masters have already been upgraded, so the upgrade can continue for the workers:
|
@@ -229,6 +243,15 @@ A final note about upgrades is that if for some reason the upgrade gets stuck af
|
|
229
243
|
kubectl label node <master1> <master2> <master2> plan.upgrade.cattle.io/k3s-server=upgraded
|
230
244
|
```
|
231
245
|
|
246
|
+
## Upgrading the OS on nodes
|
247
|
+
|
248
|
+
- consider adding a temporary node during the process if you don't have enough spare capacity in the cluster
|
249
|
+
- drain one node
|
250
|
+
- update etc
|
251
|
+
- reboot
|
252
|
+
- uncordon
|
253
|
+
- proceed with the next node
|
254
|
+
|
232
255
|
## Deleting a cluster
|
233
256
|
|
234
257
|
To delete a cluster, running
|
@@ -263,7 +286,7 @@ I set `load-balancer.hetzner.cloud/hostname` to a valid hostname that I configur
|
|
263
286
|
|
264
287
|
The annotation `load-balancer.hetzner.cloud/use-private-ip: "true"` ensures that the communication between the load balancer and the nodes happens through the private network, so we don't have to open any ports on the nodes (other than the port 6443 for the Kubernetes API server).
|
265
288
|
|
266
|
-
The other annotations should be self explanatory. You can find a list of the available annotations here.
|
289
|
+
The other annotations should be self explanatory. You can find a list of the available annotations [here](https://pkg.go.dev/github.com/hetznercloud/hcloud-cloud-controller-manager/internal/annotation).
|
267
290
|
|
268
291
|
## Persistent volumes
|
269
292
|
|
@@ -279,6 +302,10 @@ I recommend that you create a separate Hetzner project for each cluster, because
|
|
279
302
|
|
280
303
|
Please create a PR if you want to propose any changes, or open an issue if you are having trouble with the tool - I will do my best to help if I can.
|
281
304
|
|
305
|
+
Contributors:
|
306
|
+
|
307
|
+
- [TitanFighter](https://github.com/TitanFighter) for [this awesome tutorial](https://github.com/vitobotta/hetzner-k3s/wiki/Tutorial:---Setting-up-a-cluster)
|
308
|
+
|
282
309
|
## License
|
283
310
|
|
284
311
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/build.sh
CHANGED
@@ -2,13 +2,11 @@
|
|
2
2
|
|
3
3
|
set -e
|
4
4
|
|
5
|
-
|
6
|
-
|
7
5
|
IMAGE="vitobotta/hetzner-k3s"
|
8
6
|
|
9
|
-
docker build -t ${IMAGE}:v0.5.
|
7
|
+
docker build -t ${IMAGE}:v0.5.8 \
|
10
8
|
--platform=linux/amd64 \
|
11
|
-
--cache-from ${IMAGE}:v0.5.
|
9
|
+
--cache-from ${IMAGE}:v0.5.7 \
|
12
10
|
--build-arg BUILDKIT_INLINE_CACHE=1 .
|
13
11
|
|
14
|
-
docker push vitobotta/hetzner-k3s:v0.5.
|
12
|
+
docker push vitobotta/hetzner-k3s:v0.5.8
|
data/hetzner-k3s.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = 'A CLI to create a Kubernetes cluster in Hetzner Cloud very quickly using k3s.'
|
13
13
|
spec.homepage = 'https://github.com/vitobotta/hetzner-k3s'
|
14
14
|
spec.license = 'MIT'
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 3.1.
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.1.2')
|
16
16
|
|
17
17
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
18
18
|
|
data/lib/hetzner/infra/server.rb
CHANGED
@@ -7,13 +7,20 @@ module Hetzner
|
|
7
7
|
@cluster_name = cluster_name
|
8
8
|
end
|
9
9
|
|
10
|
-
def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:, image:, additional_packages: [])
|
10
|
+
def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:, image:, additional_packages: [], additional_post_create_commands: [])
|
11
|
+
@location = location
|
12
|
+
@instance_type = instance_type
|
13
|
+
@instance_id = instance_id
|
14
|
+
@firewall_id = firewall_id
|
15
|
+
@network_id = network_id
|
16
|
+
@ssh_key_id = ssh_key_id
|
17
|
+
@placement_group_id = placement_group_id
|
18
|
+
@image = image
|
11
19
|
@additional_packages = additional_packages
|
20
|
+
@additional_post_create_commands = additional_post_create_commands
|
12
21
|
|
13
22
|
puts
|
14
23
|
|
15
|
-
server_name = "#{cluster_name}-#{instance_type}-#{instance_id}"
|
16
|
-
|
17
24
|
if (server = find_server(server_name))
|
18
25
|
puts "Server #{server_name} already exists, skipping."
|
19
26
|
puts
|
@@ -22,44 +29,16 @@ module Hetzner
|
|
22
29
|
|
23
30
|
puts "Creating server #{server_name}..."
|
24
31
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
image:,
|
29
|
-
firewalls: [
|
30
|
-
{ firewall: firewall_id }
|
31
|
-
],
|
32
|
-
networks: [
|
33
|
-
network_id
|
34
|
-
],
|
35
|
-
server_type: instance_type,
|
36
|
-
ssh_keys: [
|
37
|
-
ssh_key_id
|
38
|
-
],
|
39
|
-
user_data:,
|
40
|
-
labels: {
|
41
|
-
cluster: cluster_name,
|
42
|
-
role: (server_name =~ /master/ ? 'master' : 'worker')
|
43
|
-
},
|
44
|
-
placement_group: placement_group_id
|
45
|
-
}
|
46
|
-
|
47
|
-
response = hetzner_client.post('/servers', server_config)
|
48
|
-
response_body = response.body
|
49
|
-
|
50
|
-
server = JSON.parse(response_body)['server']
|
32
|
+
if (server = make_request)
|
33
|
+
puts "...server #{server_name} created."
|
34
|
+
puts
|
51
35
|
|
52
|
-
|
36
|
+
server
|
37
|
+
else
|
53
38
|
puts "Error creating server #{server_name}. Response details below:"
|
54
39
|
puts
|
55
40
|
p response
|
56
|
-
return
|
57
41
|
end
|
58
|
-
|
59
|
-
puts "...server #{server_name} created."
|
60
|
-
puts
|
61
|
-
|
62
|
-
server
|
63
42
|
end
|
64
43
|
|
65
44
|
def delete(server_name:)
|
@@ -74,30 +53,78 @@ module Hetzner
|
|
74
53
|
|
75
54
|
private
|
76
55
|
|
77
|
-
attr_reader :hetzner_client, :cluster_name, :additional_packages
|
56
|
+
attr_reader :hetzner_client, :cluster_name, :location, :instance_type, :instance_id, :firewall_id, :network_id, :ssh_key_id, :placement_group_id, :image, :additional_packages, :additional_post_create_commands
|
78
57
|
|
79
58
|
def find_server(server_name)
|
80
59
|
hetzner_client.get('/servers?sort=created:desc')['servers'].detect { |network| network['name'] == server_name }
|
81
60
|
end
|
82
61
|
|
83
62
|
def user_data
|
84
|
-
packages = [
|
63
|
+
packages = %w[fail2ban wireguard]
|
85
64
|
packages += additional_packages if additional_packages
|
86
65
|
packages = "'#{packages.join("', '")}'"
|
87
66
|
|
67
|
+
post_create_commands = [
|
68
|
+
'crontab -l > /etc/cron_bkp',
|
69
|
+
'echo "@reboot echo true > /etc/ready" >> /etc/cron_bkp',
|
70
|
+
'crontab /etc/cron_bkp',
|
71
|
+
'sed -i \'s/[#]*PermitRootLogin yes/PermitRootLogin prohibit-password/g\' /etc/ssh/sshd_config',
|
72
|
+
'sed -i \'s/[#]*PasswordAuthentication yes/PasswordAuthentication no/g\' /etc/ssh/sshd_config',
|
73
|
+
'systemctl restart sshd',
|
74
|
+
'systemctl stop systemd-resolved',
|
75
|
+
'systemctl disable systemd-resolved',
|
76
|
+
'rm /etc/resolv.conf',
|
77
|
+
'echo \'nameserver 1.1.1.1\' > /etc/resolv.conf',
|
78
|
+
'echo \'nameserver 1.0.0.1\' >> /etc/resolv.conf'
|
79
|
+
]
|
80
|
+
|
81
|
+
post_create_commands += additional_post_create_commands if additional_post_create_commands
|
82
|
+
|
83
|
+
post_create_commands += ['shutdown -r now'] if post_create_commands.grep(/shutdown|reboot/).grep_v(/@reboot/).empty?
|
84
|
+
|
85
|
+
post_create_commands = " - #{post_create_commands.join("\n - ")}"
|
86
|
+
|
88
87
|
<<~YAML
|
89
88
|
#cloud-config
|
90
89
|
packages: [#{packages}]
|
91
90
|
runcmd:
|
92
|
-
|
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
|
91
|
+
#{post_create_commands}
|
100
92
|
YAML
|
101
93
|
end
|
94
|
+
|
95
|
+
def server_name
|
96
|
+
@server_name ||= "#{cluster_name}-#{instance_type}-#{instance_id}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def server_config
|
100
|
+
@server_config ||= {
|
101
|
+
name: server_name,
|
102
|
+
location:,
|
103
|
+
image:,
|
104
|
+
firewalls: [
|
105
|
+
{ firewall: firewall_id }
|
106
|
+
],
|
107
|
+
networks: [
|
108
|
+
network_id
|
109
|
+
],
|
110
|
+
server_type: instance_type,
|
111
|
+
ssh_keys: [
|
112
|
+
ssh_key_id
|
113
|
+
],
|
114
|
+
user_data:,
|
115
|
+
labels: {
|
116
|
+
cluster: cluster_name,
|
117
|
+
role: (server_name =~ /master/ ? 'master' : 'worker')
|
118
|
+
},
|
119
|
+
placement_group: placement_group_id
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def make_request
|
124
|
+
response = hetzner_client.post('/servers', server_config)
|
125
|
+
response_body = response.body
|
126
|
+
|
127
|
+
JSON.parse(response_body)['server']
|
128
|
+
end
|
102
129
|
end
|
103
130
|
end
|