hetzner-k3s 0.4.3 → 0.4.7

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: c3e7bedd3a695b38522a25598d9d0523bee9e5120aea6e76c462ba91dc8f3f12
4
- data.tar.gz: d54f24ebe59f8b0354c74fdacff6f747169b288eddb99f070456f147523a07b4
3
+ metadata.gz: 0e4c46ae74be52152443a9fab3b7b7dd00fa5e7cae5dffb99457b522646103ba
4
+ data.tar.gz: e2495e717211c2351c2e203bd2741d3079e84729c305e8c6ad7434c24cf3d989
5
5
  SHA512:
6
- metadata.gz: 36c460db4001ba080ef27dec517c4c44ce853a75935fee7d0fc5e4a13d689a5e812d762ec842a22503ac07d555a24be545293db9bb838ddcb7d27cd1866d9a1e
7
- data.tar.gz: 2e8f67ca3c74292b7ad3c3a2098b1a4c8d096b1cb01677e8cc94ae199ee4b0df25eb869d2400b32451da6475a7518c7265cd1b70ec6bb0090e25b76047a03e4e
6
+ metadata.gz: c82cd916cdde9cec408d0681dc7d46902309bd1246424c8267ed2164345aef54e8e6eca8730c668b146245129450e1daabe97a698e2aca165e7f2a63cb9e5b0d
7
+ data.tar.gz: 857abb763f92c64a65eeb2d484528708060db50aa121d7ccad847a5f6cbc2fec3c8d9a3873e8568ba2f03ea76ab53b529bc50267271d235b369d69e67d6c4f5a
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.2 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.7 create-cluster --config-file /cluster/test.yaml
42
42
  ```
43
43
 
44
44
  Replace `test.yaml` with the name of your config file.
@@ -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
 
@@ -96,8 +97,20 @@ curl \
96
97
  'https://api.hetzner.cloud/v1/server_types'
97
98
  ```
98
99
 
100
+ By default, the image in use is Ubuntu 20.04, but you can specify an image to use with the `image` config option. This makes it also possible
101
+ to use a snapshot that you have already created from and existing server (for example to preinstall some tools). If you want to use a custom
102
+ snapshot you'll need to specify the **ID** of the snapshot/image, not the description you gave when you created the template server. To find
103
+ the ID of your custom image/snapshot, run:
99
104
 
100
- Note: the option `verify_host_key` is by default set to `false` to disable host key verification. This is because sometimes when creating new servers, Hetzner may assign IP addresses that were previously used by other servers you owned in the past. Therefore the host key verification would fail. If you set this option to `true` and this happens, the tool won't be able to continue creating the cluster until you resolve the issue with one of the suggestions it will give you.
105
+ ```bash
106
+ curl \
107
+ -H "Authorization: Bearer $API_TOKEN" \
108
+ 'https://api.hetzner.cloud/v1/images'
109
+ ```
110
+
111
+ Note that if you use a custom image, the creation of the servers may take longer than when using the default image.
112
+
113
+ Also note: the option `verify_host_key` is by default set to `false` to disable host key verification. This is because sometimes when creating new servers, Hetzner may assign IP addresses that were previously used by other servers you owned in the past. Therefore the host key verification would fail. If you set this option to `true` and this happens, the tool won't be able to continue creating the cluster until you resolve the issue with one of the suggestions it will give you.
101
114
 
102
115
  Finally, to create the cluster run:
103
116
 
@@ -241,6 +254,19 @@ I recommend that you create a separate Hetzner project for each cluster, because
241
254
 
242
255
  ## changelog
243
256
 
257
+ - 0.4.7
258
+ - Made it possible to specify a custom image/snapshot for the servers
259
+
260
+ - 0.4.6
261
+ - 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.
262
+
263
+ - 0.4.5
264
+ - Fix network creation (bug introduced in the previous version)
265
+
266
+ - 0.4.4
267
+ - Add support for the new Ashburn, Virginia (USA) location
268
+ - Automatically use a placement group so that the instances are all created on different physical hosts for high availability
269
+
244
270
  - 0.4.3
245
271
  - Fix an issue with SSH key creation
246
272
 
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.3 \
9
+ docker build -t ${IMAGE}:v0.4.7 \
10
10
  --platform=linux/amd64 \
11
- --cache-from ${IMAGE}:v0.4.2 \
11
+ --cache-from ${IMAGE}:v0.4.6 \
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.7
@@ -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:, image:)
9
9
  puts
10
10
 
11
11
  server_name = "#{cluster_name}-#{instance_type}-#{instance_id}"
@@ -21,7 +21,7 @@ module Hetzner
21
21
  server_config = {
22
22
  name: server_name,
23
23
  location: location,
24
- image: "ubuntu-20.04",
24
+ image: image,
25
25
  firewalls: [
26
26
  { firewall: firewall_id }
27
27
  ],
@@ -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:)
@@ -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,9 @@ 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,
124
+ image: image
117
125
  }
118
126
  end
119
127
 
@@ -136,7 +144,9 @@ class Cluster
136
144
  instance_id: "pool-#{worker_node_pool_name}-worker#{i+1}",
137
145
  firewall_id: firewall_id,
138
146
  network_id: network_id,
139
- ssh_key_id: ssh_key_id
147
+ ssh_key_id: ssh_key_id,
148
+ placement_group_id: placement_group_id,
149
+ image: image
140
150
  }
141
151
  end
142
152
  end
@@ -149,6 +159,10 @@ class Cluster
149
159
 
150
160
  threads.each(&:join) unless threads.empty?
151
161
 
162
+ while servers.size != server_configs.size
163
+ sleep 1
164
+ end
165
+
152
166
  puts
153
167
  threads = servers.map do |server|
154
168
  Thread.new { wait_for_ssh server }
@@ -158,6 +172,11 @@ class Cluster
158
172
  end
159
173
 
160
174
  def delete_resources
175
+ Hetzner::PlacementGroup.new(
176
+ hetzner_client: hetzner_client,
177
+ cluster_name: cluster_name
178
+ ).delete
179
+
161
180
  Hetzner::LoadBalancer.new(
162
181
  hetzner_client: hetzner_client,
163
182
  cluster_name: cluster_name
@@ -217,28 +236,28 @@ class Cluster
217
236
  taint = schedule_workloads_on_masters? ? " " : " --node-taint CriticalAddonsOnly=true:NoExecute "
218
237
 
219
238
  <<~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 -
239
+ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="#{k3s_version}" K3S_TOKEN="#{k3s_token}" INSTALL_K3S_EXEC="server \
240
+ --disable-cloud-controller \
241
+ --disable servicelb \
242
+ --disable traefik \
243
+ --disable local-storage \
244
+ --disable metrics-server \
245
+ --write-kubeconfig-mode=644 \
246
+ --node-name="$(hostname -f)" \
247
+ --cluster-cidr=10.244.0.0/16 \
248
+ --etcd-expose-metrics=true \
249
+ --kube-controller-manager-arg="address=0.0.0.0" \
250
+ --kube-controller-manager-arg="bind-address=0.0.0.0" \
251
+ --kube-proxy-arg="metrics-bind-address=0.0.0.0" \
252
+ --kube-scheduler-arg="address=0.0.0.0" \
253
+ --kube-scheduler-arg="bind-address=0.0.0.0" \
254
+ #{taint} \
255
+ --kubelet-arg="cloud-provider=external" \
256
+ --advertise-address=$(hostname -I | awk '{print $2}') \
257
+ --node-ip=$(hostname -I | awk '{print $2}') \
258
+ --node-external-ip=$(hostname -I | awk '{print $1}') \
259
+ --flannel-iface=#{flannel_interface} \
260
+ #{server} #{tls_sans}" sh -
242
261
  EOF
243
262
  end
244
263
 
@@ -246,12 +265,12 @@ class Cluster
246
265
  flannel_interface = find_flannel_interface(worker)
247
266
 
248
267
  <<~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 -
268
+ 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 \
269
+ --node-name="$(hostname -f)" \
270
+ --kubelet-arg="cloud-provider=external" \
271
+ --node-ip=$(hostname -I | awk '{print $2}') \
272
+ --node-external-ip=$(hostname -I | awk '{print $1}') \
273
+ --flannel-iface=#{flannel_interface}" sh -
255
274
  EOF
256
275
  end
257
276
 
@@ -638,4 +657,8 @@ class Cluster
638
657
  schedule_workloads_on_masters ? !!schedule_workloads_on_masters : false
639
658
  end
640
659
 
660
+ def image
661
+ configuration.dig("image") || "ubuntu-20.04"
662
+ end
663
+
641
664
  end
@@ -1,5 +1,5 @@
1
1
  module Hetzner
2
2
  module K3s
3
- VERSION = "0.4.3"
3
+ VERSION = "0.4.7"
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.3
4
+ version: 0.4.7
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-17 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