Skip to content

Commit 1c28ac2

Browse files
Merge pull request 'feat(hetzner/hcloud) : adding cluster bootstrap command support for HCloud' (#137) from hetzner/hybrid-cloud into main
Reviewed-on: https://gitea.obmondo.com/EnableIT/kubeaid-bootstrap-script/pulls/137
2 parents c2ce6ec + 50dda80 commit 1c28ac2

24 files changed

+189
-135
lines changed

Makefile

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,28 @@ recover-cluster-azure-dev:
109109
sample-config-generate-hetzner-dev:
110110
@go run ./cmd/ config generate hetzner
111111

112-
.PHONY: bootstrap-cluster-hetzner-dev
113-
bootstrap-cluster-hetzner-dev:
112+
.PHONY: devenv-create-hetzner-hcloud-dev
113+
devenv-create-hetzner-hcloud-dev:
114+
@go run ./cmd/ devenv create \
115+
--debug \
116+
--configs-directory ./outputs/configs/hetzner/hcloud \
117+
--skip-pr-flow \
118+
--skip-monitoring-setup \
119+
--skip-kube-prometheus-build
120+
121+
.PHONY: bootstrap-cluster-hetzner-hcloud-dev
122+
bootstrap-cluster-hetzner-hcloud-dev:
114123
@go run ./cmd/ cluster bootstrap \
115124
--debug \
116-
--configs-directory ./outputs/configs/hcloud/ \
125+
--configs-directory ./outputs/configs/hetzner/hcloud/ \
126+
--skip-pr-flow \
127+
--skip-monitoring-setup \
117128
--skip-kube-prometheus-build
118129

119-
.PHONY: delete-provisioned-cluster-hetzner-dev
120-
delete-provisioned-cluster-hetzner-dev:
130+
.PHONY: delete-provisioned-cluster-hetzner-hcloud-dev
131+
delete-provisioned-cluster-hetzner-hcloud-dev:
121132
@go run ./cmd/ cluster delete \
122-
--configs-directory ./outputs/configs/hcloud/
133+
--configs-directory ./outputs/configs/hetzner/hcloud/
123134

124135
.PHONY: sample-config-generate-local-dev
125136
sample-config-generate-local-dev:

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ make bootstrap-cluster-local-dev
114114
- [x] Support scale to / from zero for the node-groups.
115115
> Currently, I have added extra ClusterRole and ClusterRoleBinding in the KubeAid [cluster-autoscaler Helm chart](https://github.com/Obmondo/kubeaid/tree/master/argocd-helm-charts/cluster-autoscaler) to support this feature.
116116
> But I have also opened an issue in the kubernetes-sigs/autoscaler repository regarding this : [Allow adding extra rules to the Role / ClusterRole template of the Cluster AutoScaler Helm chart](https://github.com/kubernetes/autoscaler/issues/7680).
117-
- [ ] `recover cluster` command
118117

119118
## REFERENCES
120119

@@ -165,3 +164,9 @@ make bootstrap-cluster-local-dev
165164
- [Uploading a SARIF file to GitHub](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github)
166165

167166
- [What is CA bundle?](https://www.namecheap.com/support/knowledgebase/article.aspx/986/69/what-is-ca-bundle/)
167+
168+
[What is RAID 0, 1, 5, & 10?](https://www.youtube.com/watch?v=U-OCdTeZLac)
169+
170+
[What is RAID Parity?](https://www.youtube.com/watch?v=BjuBloMHhKk)
171+
172+
[RAID 5 vs RAID 6](https://www.youtube.com/watch?v=UuUgfCvt9-Q)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
github.com/go-playground/validator/v10 v10.26.0
2525
github.com/go-sprout/sprout v1.0.0
2626
github.com/google/uuid v1.6.0
27+
github.com/hetznercloud/hcloud-go v1.59.2
2728
github.com/k3d-io/k3d/v5 v5.8.3
2829
github.com/sagikazarmark/slog-shim v0.1.0
2930
github.com/siderolabs/talos/pkg/machinery v1.10.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,8 @@ github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGN
489489
github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
490490
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
491491
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
492+
github.com/hetznercloud/hcloud-go v1.59.2 h1:NkCPwYiPv85FnOV3IW9/gxfW61TPIUSwyPHRSLwCkHA=
493+
github.com/hetznercloud/hcloud-go v1.59.2/go.mod h1:oTebZCjd+osj75jlI76Z+zjN1sTxmMiQ1MWoO8aRl1c=
492494
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
493495
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
494496
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=

pkg/cloud/aws/services/iam.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"log/slog"
88
"strings"
99

10-
aws "github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/aws"
1111
"github.com/aws/aws-sdk-go-v2/service/iam"
1212

1313
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/config"

pkg/cloud/cloud_provider.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@ type (
2323
VMSpec struct {
2424
CPU uint32
2525
Memory uint32 // (in MiB).
26+
27+
// Only used in case of HCloud, since the root volume size is fixed unlike in case of other
28+
// hyper-scalars like AWS / Azure.
29+
RootVolumeSize *uint32
2630
}
2731
)

pkg/cloud/hetzner/execute_failover_script.go

Lines changed: 0 additions & 20 deletions
This file was deleted.

pkg/cloud/hetzner/get_vm_specs.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package hetzner
2+
3+
import (
4+
"context"
5+
6+
"github.com/aws/aws-sdk-go-v2/aws"
7+
8+
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud"
9+
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/utils/assert"
10+
)
11+
12+
func (h *Hetzner) GetVMSpecs(ctx context.Context, machineType string) *cloud.VMSpec {
13+
machineDetails, _, err := h.hcloudClient.ServerType.GetByName(ctx, machineType)
14+
assert.AssertErrNil(ctx, err, "Failed getting machine details")
15+
assert.AssertNotNil(ctx, machineDetails, "Got empty machine details")
16+
17+
return &cloud.VMSpec{
18+
CPU: uint32(machineDetails.Cores),
19+
Memory: uint32(machineDetails.Memory * 1024),
20+
RootVolumeSize: aws.Uint32(uint32(machineDetails.Disk)),
21+
}
22+
}

pkg/cloud/hetzner/hetzner.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,24 @@ import (
55

66
"sigs.k8s.io/controller-runtime/pkg/client"
77

8+
"github.com/hetznercloud/hcloud-go/hcloud"
9+
810
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud"
11+
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/config"
912
)
1013

11-
type Hetzner struct{}
14+
type Hetzner struct {
15+
hcloudClient *hcloud.Client
16+
}
1217

1318
func NewHetznerCloudProvider() cloud.CloudProvider {
14-
return &Hetzner{}
15-
}
19+
hcloudClient := hcloud.NewClient(
20+
hcloud.WithToken(config.ParsedSecretsConfig.Hetzner.APIToken),
21+
)
1622

17-
func (*Hetzner) GetVMSpecs(ctx context.Context, vmType string) *cloud.VMSpec {
18-
panic("unimplemented")
23+
return &Hetzner{
24+
hcloudClient,
25+
}
1926
}
2027

2128
func (*Hetzner) SetupDisasterRecovery(ctx context.Context) {

pkg/config/config.go

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -207,68 +207,41 @@ type (
207207
// Hetzner specific.
208208
type (
209209
HetznerConfig struct {
210-
HCloud HCloud `yaml:"hcloud" validate:"required"`
211-
HetznerBareMetal *HetznerBareMetal `yaml:"robot"`
212-
}
213-
214-
HCloud struct {
215-
SSHKeyName string `yaml:"sshKeyName" validate:"required,notblank"`
216-
Enabled bool `yaml:"enabled"`
217-
ControlPlane HCloudControlPlane `yaml:"controlPlane"`
218-
NodeGroups []HCloudNodeGroup `yaml:"nodeGroups"`
219-
}
220-
221-
HCloudControlPlane struct {
222-
LoadBalancer HetznerControlPlaneLoadBalancer `yaml:"loadBalancer"`
223-
Regions []string `yaml:"regions"`
224-
MachineType string `yaml:"machineType" validate:"required,notblank"`
225-
Replicas int `yaml:"replicas" validate:"required"`
226-
}
210+
Mode string `yaml:"mode" default:"hcloud" validate:"required,notblank"`
227211

228-
HetznerControlPlaneLoadBalancer struct {
229-
Enabled bool `yaml:"enabled" validate:"required"`
230-
Region string `yaml:"region" validate:"required,notblank"`
231-
}
212+
Zone string `yaml:"zone" validate:"required,notblank"`
213+
Region string `yaml:"region" validate:"required,notblank"`
232214

233-
HCloudNodeGroup struct {
234-
NodeGroup `yaml:",inline"`
215+
HCloudSSHKeyPairName string `yaml:"hcloudSSHKeyPairName" validate:"required,notblank"`
235216

236-
FailureDomain string `yaml:"failureDomain" validate:"required,notblank"`
237-
SSHKeys []HCloudNodeGroupSSHKey `yaml:"sshKeys" validate:"required"`
238-
}
217+
NetworkEnabled bool `yaml:"networkEnabled" default:"True" validate:"required"`
218+
ImageName string `yaml:"imageName" default:"ubuntu-24.04" validate:"required,notblank"`
239219

240-
HCloudNodeGroupSSHKey struct {
241-
Name string `yaml:"name" validate:"required,notblank"`
220+
ControlPlane HetznerControlPlane `yaml:"controlPlane" validate:"required"`
221+
NodeGroups HetznerNodeGroups `yaml:"nodeGroups" validate:"required"`
242222
}
243223

244-
HetznerBareMetal struct {
245-
Enabled bool `yaml:"enabled" validate:"required"`
246-
RobotSSHKeyPair SSHKeyPairConfig `yaml:"robotSSHKey" validate:"required"`
247-
ControlPlane HetznerBareMetalControlPlane `yaml:"controlPlane"`
248-
NodeGroups []HetznerBareMetalNodeGroup `yaml:"nodeGroups"`
224+
HetznerControlPlane struct {
225+
MachineType string `yaml:"machineType" validate:"required,notblank"`
226+
Replicas uint `yaml:"replicas" validate:"required"`
227+
Regions []string `yaml:"regions" validate:"required,gt=0"`
228+
LoadBalancer HCloudControlPlaneLoadBalancer `yaml:"loadBalancer"`
249229
}
250230

251-
HetznerBareMetalControlPlane struct {
252-
Endpoint HetznerControlPlaneEndpoint `yaml:"endpoint" validate:"required,notblank"`
253-
Nodes []HetznerBareMetalNode `yaml:"nodes"`
231+
HCloudControlPlaneLoadBalancer struct {
232+
Enabled bool `yaml:"enabled" validate:"required"`
233+
Region string `yaml:"region" validate:"required,notblank"`
254234
}
255235

256-
HetznerControlPlaneEndpoint struct {
257-
Host string `yaml:"host" validate:"required,notblank"`
258-
Port int `yaml:"port"`
236+
HetznerNodeGroups struct {
237+
HCloud []HCloudNodeGroup `yaml:"hcloud"`
259238
}
260239

261-
HetznerBareMetalNodeGroup struct {
240+
HCloudNodeGroup struct {
262241
NodeGroup `yaml:",inline"`
263242

264-
Nodes []HetznerBareMetalNode `yaml:"nodes" validate:"required"`
265-
}
266-
267-
HetznerBareMetalNode struct {
268-
Name string `yaml:"name" validate:"required,notblank"`
269-
270-
// WWN (World Wide Name) is the unique identifier.
271-
WWN []string `yaml:"wwn" validate:"required,notblank"`
243+
MachineType string `yaml:"machineType" validate:"required,notblank"`
244+
RootVolumeSize uint32 ` validate:"required"`
272245
}
273246
)
274247

pkg/config/hack.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ func NewAWSCloudProvider() cloud.CloudProvider
1313

1414
//go:linkname NewAzureCloudProvider github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/azure.NewAzureCloudProvider
1515
func NewAzureCloudProvider() cloud.CloudProvider
16+
17+
//go:linkname NewHetznerCloudProvider github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/hetzner.NewHetznerCloudProvider
18+
func NewHetznerCloudProvider() cloud.CloudProvider

pkg/config/parse.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/creasty/defaults"
1212
"gopkg.in/yaml.v3"
1313

14-
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/hetzner"
1514
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/constants"
1615
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/globals"
1716
"github.com/Obmondo/kubeaid-bootstrap-script/pkg/utils/assert"
@@ -133,7 +132,7 @@ func setCloudProvider() {
133132
globals.CloudProvider = NewAzureCloudProvider()
134133

135134
case constants.CloudProviderHetzner:
136-
globals.CloudProvider = hetzner.NewHetznerCloudProvider()
135+
globals.CloudProvider = NewHetznerCloudProvider()
137136
}
138137
}
139138

@@ -180,14 +179,25 @@ func hydrateVMSpecs(ctx context.Context) {
180179

181180
case constants.CloudProviderAzure:
182181
for i, nodeGroup := range ParsedGeneralConfig.Cloud.Azure.NodeGroups {
183-
instanceSpecs := globals.CloudProvider.GetVMSpecs(ctx, nodeGroup.VMSize)
182+
vmSpecs := globals.CloudProvider.GetVMSpecs(ctx, nodeGroup.VMSize)
184183

185-
ParsedGeneralConfig.Cloud.Azure.NodeGroups[i].CPU = instanceSpecs.CPU
186-
ParsedGeneralConfig.Cloud.Azure.NodeGroups[i].Memory = instanceSpecs.Memory
184+
ParsedGeneralConfig.Cloud.Azure.NodeGroups[i].CPU = vmSpecs.CPU
185+
ParsedGeneralConfig.Cloud.Azure.NodeGroups[i].Memory = vmSpecs.Memory
187186
}
188187

189188
case constants.CloudProviderHetzner:
190-
panic("unimplemented")
189+
for i, nodeGroup := range ParsedGeneralConfig.Cloud.Hetzner.NodeGroups.HCloud {
190+
machineSpecs := globals.CloudProvider.GetVMSpecs(ctx, nodeGroup.MachineType)
191+
assert.AssertNotNil(
192+
ctx,
193+
machineSpecs.RootVolumeSize,
194+
"Implementation error : machine details returned by HetznerCloudProvider.GetVMSpecs() must include RootVolumeSize",
195+
)
196+
197+
ParsedGeneralConfig.Cloud.Hetzner.NodeGroups.HCloud[i].CPU = machineSpecs.CPU
198+
ParsedGeneralConfig.Cloud.Hetzner.NodeGroups.HCloud[i].Memory = machineSpecs.Memory
199+
ParsedGeneralConfig.Cloud.Hetzner.NodeGroups.HCloud[i].RootVolumeSize = *machineSpecs.RootVolumeSize
200+
}
191201

192202
case constants.CloudProviderLocal:
193203
return

pkg/config/secrets.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ type (
2626
}
2727

2828
HetznerCredentials struct {
29-
HetznerAPIToken string `yaml:"apiToken" validate:"required,notblank"`
30-
HetznerRobotUsername string `yaml:"robotUsername" validate:"required,notblank"`
31-
HetznerRobotPassword string `yaml:"robotPassword" validate:"required,notblank"`
29+
APIToken string `yaml:"apiToken" validate:"required,notblank"`
3230
}
3331
)

pkg/config/ssh.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ func hydrateSSHKeyConfigs() {
2121
)
2222

2323
case constants.CloudProviderHetzner:
24-
// When using Hetzner Bare Metal.
25-
if (ParsedGeneralConfig.Cloud.Hetzner.HetznerBareMetal != nil) &&
26-
ParsedGeneralConfig.Cloud.Hetzner.HetznerBareMetal.Enabled {
27-
hydrateSSHKeyConfig(&ParsedGeneralConfig.Cloud.Hetzner.HetznerBareMetal.RobotSSHKeyPair)
28-
}
2924
}
3025
}
3126

pkg/constants/templates.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,27 +119,22 @@ var (
119119

120120
// Hetzner specific template names.
121121
var (
122-
HetznerBareMetalSpecificNonSecretTemplateNames = []string{
123-
// For Hetzner Robot Failover.
124-
"argocd-apps/templates/hetzner-robot.yaml.tmpl",
125-
"argocd-apps/values-hetzner-robot.yaml.tmpl",
126-
}
127-
128122
HCloudSpecificNonSecretTemplateNames = []string{
129-
// For Hetzner Cloud Controller Manager.
123+
// For HCloud Controller Manager.
130124
"argocd-apps/templates/ccm-hetzner.yaml.tmpl",
131125
"argocd-apps/values-ccm-hetzner.yaml.tmpl",
126+
127+
// For HCloud CSI driver.
128+
"argocd-apps/templates/hcloud-csi-driver.yaml.tmpl",
129+
"argocd-apps/values-hcloud-csi-driver.yaml.tmpl",
132130
}
133131

134-
HetznerSpecificSecretTemplateNames = []string{
135-
// For Hetzner Cloud Controller Manager.
132+
HCloudSpecificSecretTemplateNames = []string{
133+
// For HCloud Controller Manager.
136134
"sealed-secrets/kube-system/cloud-credentials.yaml.tmpl",
137135

138136
// For Cluster API.
139137
"sealed-secrets/capi-cluster/cloud-credentials.yaml.tmpl",
140-
141-
// For Hetzner Robot Failover.
142-
"sealed-secrets/capi-cluster/hcloud-ssh-keypair.yaml.tmpl",
143138
}
144139
)
145140

pkg/core/setup_cluster.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ func SetupCluster(ctx context.Context, args SetupClusterArgs) {
144144
"cert-manager",
145145
"secrets",
146146
}
147-
148147
for _, argoCDApp := range argocdAppsToBeSynced {
149148
kubernetes.SyncArgoCDApp(ctx, argoCDApp, []*argoCDV1Alpha1.SyncOperationResource{})
150149
}

0 commit comments

Comments
 (0)