hetzner-k3s 0.4.2 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 703675cd5cbfeb28c857486046b88a1202835316b4522ac3b964c42479ca91e9
4
- data.tar.gz: d496229db7199e5d72bf19e5534ce83c7235ffe91eb93aec2151435d4c3a9c2c
3
+ metadata.gz: 60167b5a135a2932bff53b3d030b22940e3a7a943584ab9645d420fdfa2f59f4
4
+ data.tar.gz: 6b36f71696ffb4f94d1dfde94b5215c46b9f92fc482c0da148ea84c0cfa5a56f
5
5
  SHA512:
6
- metadata.gz: 75f9af0a29e2e34e7e4fd703ee1a98f2a2e2b9cd8143783583f74ce26427b1bbfbf53930f258b1a5fc9faf9fe1e1a84e7f15f5c92122ecfd1b4ae6ae7913031e
7
- data.tar.gz: b89c51f47038eca49083cf8936d7cc4927a8edbe9fe41582d834dd546a2be8d5b80ef03bf3bf0bce7605637e35445e153c1c72bf206ed58df9632c4a245d9f3b
6
+ metadata.gz: 73242160d8701cfb92332bf18aefcf7e2c15275493b2e4dedfc16d1e577367e91e338b2c57fc3d5731a5f68317751e1644b7d2be4d505371ee1543f04365685f
7
+ data.tar.gz: 2da1c1ef1bf32b3c403868d47bc94fbeee1ad8479aa1272db9141c749b695e70489c85c19f8e891fd332a91de6deda000d273e959f1d1d6a1e88edf9efe4310f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hetzner-k3s (0.4.0)
4
+ hetzner-k3s (0.4.5)
5
5
  bcrypt_pbkdf
6
6
  ed25519
7
7
  http
@@ -20,12 +20,12 @@ GEM
20
20
  diff-lcs (1.4.4)
21
21
  domain_name (0.5.20190701)
22
22
  unf (>= 0.0.5, < 1.0.0)
23
- dry-configurable (0.12.1)
23
+ dry-configurable (0.13.0)
24
24
  concurrent-ruby (~> 1.0)
25
- dry-core (~> 0.5, >= 0.5.0)
26
- dry-container (0.8.0)
25
+ dry-core (~> 0.6)
26
+ dry-container (0.9.0)
27
27
  concurrent-ruby (~> 1.0)
28
- dry-configurable (~> 0.1, >= 0.1.3)
28
+ dry-configurable (~> 0.13, >= 0.13.0)
29
29
  dry-core (0.7.1)
30
30
  concurrent-ruby (~> 1.0)
31
31
  dry-equalizer (0.3.0)
@@ -99,7 +99,7 @@ GEM
99
99
  to_regexp (0.2.1)
100
100
  unf (0.1.4)
101
101
  unf_ext
102
- unf_ext (0.0.7.7)
102
+ unf_ext (0.0.8)
103
103
  yajl-ruby (1.4.1)
104
104
  yaml-safe_load_stream (0.1.1)
105
105
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is a CLI tool - based on a Ruby gem - to quickly create and manage Kubernetes clusters in [Hetzner Cloud](https://www.hetzner.com/cloud) using the lightweight Kubernetes distribution [k3s](https://k3s.io/) from [Rancher](https://rancher.com/).
4
4
 
5
- Hetzner Cloud is an awesome cloud provider which offers a truly great service with the best performance/cost ratio in the market. I highly recommend them if European locations (Germany and Finland) are OK for your projects (the Nuremberg data center has decent latency for US users as well). With Hetzner's Cloud Controller Manager and CSI driver you can provision load balancers and persistent volumes very easily.
5
+ Hetzner Cloud is an awesome cloud provider which offers a truly great service with the best performance/cost ratio in the market. With Hetzner's Cloud Controller Manager and CSI driver you can provision load balancers and persistent volumes very easily.
6
6
 
7
7
  k3s is my favorite Kubernetes distribution now because it uses much less memory and CPU, leaving more resources to workloads. It is also super quick to deploy because it's a single binary.
8
8
 
@@ -38,7 +38,7 @@ This will install the `hetzner-k3s` executable in your PATH.
38
38
  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):
39
39
 
40
40
  ```bash
41
- docker run --rm -it -v ${PWD}:/cluster -v ${HOME}/.ssh:/tmp/.ssh vitobotta/hetzner-k3s:v0.4.1 create-cluster --config-file /cluster/test.yaml
41
+ docker run --rm -it -v ${PWD}:/cluster -v ${HOME}/.ssh:/tmp/.ssh vitobotta/hetzner-k3s:v0.4.6 create-cluster --config-file /cluster/test.yaml
42
42
  ```
43
43
 
44
44
  Replace `test.yaml` with the name of your config file.
@@ -78,7 +78,7 @@ If you are using Docker, then set `kubeconfig_path` to `/cluster/kubeconfig` so
78
78
 
79
79
  If you don't want to specify the Hetzner token in the config file (for example if you want to use the tool with CI), then you can use the `HCLOUD_TOKEN` environment variable instead, which has predecence.
80
80
 
81
- **Important**: The tool assignes the label `cluster` to each server it creates, with the clsuter name you specify in the config file, as the value. So please ensure you don't create unrelated servers in the same project having
81
+ **Important**: The tool assignes the label `cluster` to each server it creates, with the cluster name you specify in the config file, as the value. So please ensure you don't create unrelated servers in the same project having
82
82
  the label `cluster=<cluster name>`, because otherwise they will be deleted if you delete the cluster. I recommend you create a separate Hetzner project for each cluster, see note at the end of this README for more details.
83
83
 
84
84
 
@@ -86,7 +86,8 @@ If you set `masters.instance_count` to 1 then the tool will create a non highly
86
86
 
87
87
  You can specify any number of worker node pools for example to have mixed nodes with different specs for different workloads.
88
88
 
89
- At the moment Hetzner Cloud has three locations: two in Germany (`nbg1`, Nuremberg and `fsn1`, Falkensteing) and one in Finland (`hel1`, Helsinki).
89
+ At the moment Hetzner Cloud has four locations: two in Germany (`nbg1`, Nuremberg and `fsn1`, Falkensteing), one in Finland (`hel1`, Helsinki) and one in the USA (`ash`, Ashburn, Virginia). Please note that the Ashburn, Virginia location has just
90
+ been announced and it's limited to AMD instances for now.
90
91
 
91
92
  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:
92
93
 
@@ -241,6 +242,19 @@ I recommend that you create a separate Hetzner project for each cluster, because
241
242
 
242
243
  ## changelog
243
244
 
245
+ - 0.4.5
246
+ - Added a check to abort gracefully when for some reason one or more servers are not created, for example due to temporary problems with the Hetzner API.
247
+
248
+ - 0.4.5
249
+ - Fix network creation (bug introduced in the previous version)
250
+
251
+ - 0.4.4
252
+ - Add support for the new Ashburn, Virginia (USA) location
253
+ - Automatically use a placement group so that the instances are all created on different physical hosts for high availability
254
+
255
+ - 0.4.3
256
+ - Fix an issue with SSH key creation
257
+
244
258
  - 0.4.2
245
259
  - Update Hetzner CSI driver to v1.6.0
246
260
  - Update System Upgrade Controller to v0.8.0
data/bin/build.sh CHANGED
@@ -6,9 +6,9 @@ set -e
6
6
 
7
7
  IMAGE="vitobotta/hetzner-k3s"
8
8
 
9
- docker build -t ${IMAGE}:v0.4.2 \
9
+ docker build -t ${IMAGE}:v0.4.6 \
10
10
  --platform=linux/amd64 \
11
- --cache-from ${IMAGE}:v0.4.1 \
11
+ --cache-from ${IMAGE}:v0.4.5 \
12
12
  --build-arg BUILDKIT_INLINE_CACHE=1 .
13
13
 
14
- docker push vitobotta/hetzner-k3s:v0.4.2
14
+ docker push vitobotta/hetzner-k3s:v0.4.6
@@ -5,7 +5,8 @@ module Hetzner
5
5
  @cluster_name = cluster_name
6
6
  end
7
7
 
8
- def create
8
+ def create(location:)
9
+ @location = location
9
10
  puts
10
11
 
11
12
  if network = find_network
@@ -38,7 +39,7 @@ module Hetzner
38
39
 
39
40
  private
40
41
 
41
- attr_reader :hetzner_client, :cluster_name
42
+ attr_reader :hetzner_client, :cluster_name, :location
42
43
 
43
44
  def network_config
44
45
  {
@@ -47,7 +48,7 @@ module Hetzner
47
48
  subnets: [
48
49
  {
49
50
  ip_range: "10.0.0.0/16",
50
- network_zone: "eu-central",
51
+ network_zone: (location == "ash" ? "us-east" : "eu-central"),
51
52
  type: "cloud"
52
53
  }
53
54
  ]
@@ -0,0 +1,55 @@
1
+ module Hetzner
2
+ class PlacementGroup
3
+ def initialize(hetzner_client:, cluster_name:)
4
+ @hetzner_client = hetzner_client
5
+ @cluster_name = cluster_name
6
+ end
7
+
8
+ def create
9
+ puts
10
+
11
+ if (placement_group = find_placement_group)
12
+ puts "Placement group already exists, skipping."
13
+ puts
14
+ return placement_group["id"]
15
+ end
16
+
17
+ puts "Creating placement group..."
18
+
19
+ response = hetzner_client.post("/placement_groups", placement_group_config).body
20
+
21
+ puts "...placement group created."
22
+ puts
23
+
24
+ JSON.parse(response)["placement_group"]["id"]
25
+ end
26
+
27
+ def delete
28
+ 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
+ else
33
+ puts "Placement group no longer exists, skipping."
34
+ end
35
+
36
+ puts
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :hetzner_client, :cluster_name
42
+
43
+ def placement_group_config
44
+ {
45
+ name: cluster_name,
46
+ type: "spread"
47
+ }
48
+ end
49
+
50
+ def find_placement_group
51
+ hetzner_client.get("/placement_groups")["placement_groups"].detect{ |placement_group| placement_group["name"] == cluster_name }
52
+ end
53
+
54
+ end
55
+ end
@@ -5,7 +5,7 @@ module Hetzner
5
5
  @cluster_name = cluster_name
6
6
  end
7
7
 
8
- def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:)
8
+ def create(location:, instance_type:, instance_id:, firewall_id:, network_id:, ssh_key_id:, placement_group_id:)
9
9
  puts
10
10
 
11
11
  server_name = "#{cluster_name}-#{instance_type}-#{instance_id}"
@@ -36,15 +36,26 @@ module Hetzner
36
36
  labels: {
37
37
  cluster: cluster_name,
38
38
  role: (server_name =~ /master/ ? "master" : "worker")
39
- }
39
+ },
40
+ placement_group: placement_group_id
40
41
  }
41
42
 
42
- response = hetzner_client.post("/servers", server_config).body
43
+ response = hetzner_client.post("/servers", server_config)
44
+ response_body = response.body
45
+
46
+ server = JSON.parse(response_body)["server"]
47
+
48
+ unless server
49
+ puts "Error creating server #{server_name}. Response details below:"
50
+ puts
51
+ p response
52
+ return
53
+ end
43
54
 
44
55
  puts "...server #{server_name} created."
45
56
  puts
46
57
 
47
- JSON.parse(response)["server"]
58
+ server
48
59
  end
49
60
 
50
61
  def delete(server_name:)
@@ -55,7 +55,7 @@ module Hetzner
55
55
  def ssh_key_config
56
56
  {
57
57
  name: cluster_name,
58
- public_ssh_key: public_ssh_key
58
+ public_key: public_ssh_key
59
59
  }
60
60
  end
61
61
 
@@ -193,7 +193,7 @@ module Hetzner
193
193
 
194
194
  def validate_location
195
195
  return if locations.empty? && !valid_token?
196
- errors << "Invalid location - available locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland)" unless locations.include? configuration.dig("location")
196
+ errors << "Invalid location - available locations: nbg1 (Nuremberg, Germany), fsn1 (Falkenstein, Germany), hel1 (Helsinki, Finland) or ash (Ashburn, Virginia, USA)" unless locations.include? configuration.dig("location")
197
197
  end
198
198
 
199
199
  def find_available_releases
@@ -11,6 +11,7 @@ require_relative "../infra/network"
11
11
  require_relative "../infra/ssh_key"
12
12
  require_relative "../infra/server"
13
13
  require_relative "../infra/load_balancer"
14
+ require_relative "../infra/placement_group"
14
15
 
15
16
  require_relative "../k3s/client_patch"
16
17
 
@@ -89,6 +90,11 @@ class Cluster
89
90
  master_instance_type = masters_config["instance_type"]
90
91
  masters_count = masters_config["instance_count"]
91
92
 
93
+ placement_group_id = Hetzner::PlacementGroup.new(
94
+ hetzner_client: hetzner_client,
95
+ cluster_name: cluster_name
96
+ ).create
97
+
92
98
  firewall_id = Hetzner::Firewall.new(
93
99
  hetzner_client: hetzner_client,
94
100
  cluster_name: cluster_name
@@ -97,7 +103,7 @@ class Cluster
97
103
  network_id = Hetzner::Network.new(
98
104
  hetzner_client: hetzner_client,
99
105
  cluster_name: cluster_name
100
- ).create
106
+ ).create(location: location)
101
107
 
102
108
  ssh_key_id = Hetzner::SSHKey.new(
103
109
  hetzner_client: hetzner_client,
@@ -113,7 +119,8 @@ class Cluster
113
119
  instance_id: "master#{i+1}",
114
120
  firewall_id: firewall_id,
115
121
  network_id: network_id,
116
- ssh_key_id: ssh_key_id
122
+ ssh_key_id: ssh_key_id,
123
+ placement_group_id: placement_group_id
117
124
  }
118
125
  end
119
126
 
@@ -136,7 +143,8 @@ class Cluster
136
143
  instance_id: "pool-#{worker_node_pool_name}-worker#{i+1}",
137
144
  firewall_id: firewall_id,
138
145
  network_id: network_id,
139
- ssh_key_id: ssh_key_id
146
+ ssh_key_id: ssh_key_id,
147
+ placement_group_id: placement_group_id
140
148
  }
141
149
  end
142
150
  end
@@ -149,6 +157,11 @@ class Cluster
149
157
 
150
158
  threads.each(&:join) unless threads.empty?
151
159
 
160
+ if server_configs.size != servers.size
161
+ puts "Something went wrong while creating some servers, please try again."
162
+ exit 1
163
+ end
164
+
152
165
  puts
153
166
  threads = servers.map do |server|
154
167
  Thread.new { wait_for_ssh server }
@@ -158,6 +171,11 @@ class Cluster
158
171
  end
159
172
 
160
173
  def delete_resources
174
+ Hetzner::PlacementGroup.new(
175
+ hetzner_client: hetzner_client,
176
+ cluster_name: cluster_name
177
+ ).delete
178
+
161
179
  Hetzner::LoadBalancer.new(
162
180
  hetzner_client: hetzner_client,
163
181
  cluster_name: cluster_name
@@ -217,28 +235,28 @@ class Cluster
217
235
  taint = schedule_workloads_on_masters? ? " " : " --node-taint CriticalAddonsOnly=true:NoExecute "
218
236
 
219
237
  <<~EOF
220
- curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="#{k3s_version}" K3S_TOKEN="#{k3s_token}" INSTALL_K3S_EXEC="server \
221
- --disable-cloud-controller \
222
- --disable servicelb \
223
- --disable traefik \
224
- --disable local-storage \
225
- --disable metrics-server \
226
- --write-kubeconfig-mode=644 \
227
- --node-name="$(hostname -f)" \
228
- --cluster-cidr=10.244.0.0/16 \
229
- --etcd-expose-metrics=true \
230
- --kube-controller-manager-arg="address=0.0.0.0" \
231
- --kube-controller-manager-arg="bind-address=0.0.0.0" \
232
- --kube-proxy-arg="metrics-bind-address=0.0.0.0" \
233
- --kube-scheduler-arg="address=0.0.0.0" \
234
- --kube-scheduler-arg="bind-address=0.0.0.0" \
235
- #{taint} \
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 -
238
+ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="#{k3s_version}" K3S_TOKEN="#{k3s_token}" INSTALL_K3S_EXEC="server \
239
+ --disable-cloud-controller \
240
+ --disable servicelb \
241
+ --disable traefik \
242
+ --disable local-storage \
243
+ --disable metrics-server \
244
+ --write-kubeconfig-mode=644 \
245
+ --node-name="$(hostname -f)" \
246
+ --cluster-cidr=10.244.0.0/16 \
247
+ --etcd-expose-metrics=true \
248
+ --kube-controller-manager-arg="address=0.0.0.0" \
249
+ --kube-controller-manager-arg="bind-address=0.0.0.0" \
250
+ --kube-proxy-arg="metrics-bind-address=0.0.0.0" \
251
+ --kube-scheduler-arg="address=0.0.0.0" \
252
+ --kube-scheduler-arg="bind-address=0.0.0.0" \
253
+ #{taint} \
254
+ --kubelet-arg="cloud-provider=external" \
255
+ --advertise-address=$(hostname -I | awk '{print $2}') \
256
+ --node-ip=$(hostname -I | awk '{print $2}') \
257
+ --node-external-ip=$(hostname -I | awk '{print $1}') \
258
+ --flannel-iface=#{flannel_interface} \
259
+ #{server} #{tls_sans}" sh -
242
260
  EOF
243
261
  end
244
262
 
@@ -246,12 +264,12 @@ class Cluster
246
264
  flannel_interface = find_flannel_interface(worker)
247
265
 
248
266
  <<~EOF
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 -
267
+ 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 \
268
+ --node-name="$(hostname -f)" \
269
+ --kubelet-arg="cloud-provider=external" \
270
+ --node-ip=$(hostname -I | awk '{print $2}') \
271
+ --node-external-ip=$(hostname -I | awk '{print $1}') \
272
+ --flannel-iface=#{flannel_interface}" sh -
255
273
  EOF
256
274
  end
257
275
 
@@ -1,5 +1,5 @@
1
1
  module Hetzner
2
2
  module K3s
3
- VERSION = "0.4.2"
3
+ VERSION = "0.4.6"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hetzner-k3s
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vito Botta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-03 00:00:00.000000000 Z
11
+ date: 2021-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -140,6 +140,7 @@ files:
140
140
  - lib/hetzner/infra/firewall.rb
141
141
  - lib/hetzner/infra/load_balancer.rb
142
142
  - lib/hetzner/infra/network.rb
143
+ - lib/hetzner/infra/placement_group.rb
143
144
  - lib/hetzner/infra/server.rb
144
145
  - lib/hetzner/infra/ssh_key.rb
145
146
  - lib/hetzner/k3s/cli.rb