Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloudprovider hetzner #3640

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9628240
Partial implementation of Hetzner cloudprovider
Fgruntjes Oct 21, 2020
4f0d0a6
Updating vendor against git@github.com:kubernetes/kubernetes.git:3eb9…
Fgruntjes Oct 21, 2020
67a7b6b
Fixed invalid stringformat modifier
Fgruntjes Oct 21, 2020
49c4e5d
further implementation of hetzner cloud manager
Fgruntjes Oct 23, 2020
28a933f
less logging when server is not found
Fgruntjes Oct 23, 2020
a4fd478
Revert "Updating vendor against git@github.com:kubernetes/kubernetes.…
Fgruntjes Oct 23, 2020
3a86a44
Moved hcloud client from vendor to cloud provider folder
Fgruntjes Oct 23, 2020
5c4bdaf
Ensured the provider ID prefix is equal to the one used by the hcloud…
Fgruntjes Oct 23, 2020
9b5698a
Fixed setting target size
Fgruntjes Oct 23, 2020
ac6e633
Fixed with incorrect auto provisioned group
Fgruntjes Oct 23, 2020
ee4c915
Added OWNERS file
Fgruntjes Oct 23, 2020
5c94282
Added hcloud-go package to ignored linting files
Fgruntjes Oct 23, 2020
4bbc71b
Removed patching external id in node, this is the job of the cluster
Fgruntjes Oct 26, 2020
c07fd86
Added readme reference to hetzner provider and removed the fake node …
Fgruntjes Oct 29, 2020
798c470
Added debug logging when scale groups get refreshed
Fgruntjes Nov 9, 2020
23b156e
fixed hetzner provider pod template to allow scaling to zero
Fgruntjes Nov 9, 2020
a78d75a
Code style fixes
Fgruntjes Nov 9, 2020
5e449db
Removed Pieter from code owners for hetzner cloud provider to simplif…
Fgruntjes Nov 9, 2020
34cdf12
Update cluster-autoscaler/cloudprovider/builder/builder_hetzner.go
Fgruntjes Jan 11, 2021
3b23956
Update cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provide…
Fgruntjes Jan 11, 2021
7e50426
Update cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provide…
Fgruntjes Jan 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cluster-autoscaler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ Supported cloud providers:
* DigitalOcean https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/digitalocean/README.md
* Exoscale https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/exoscale/README.md
* Packet https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/packet/README.md
* Hetzner https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/hetzner/README.md
4 changes: 4 additions & 0 deletions cluster-autoscaler/cloudprovider/builder/builder_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/digitalocean"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/exoscale"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/gce"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/huaweicloud"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/magnum"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/packet"
Expand All @@ -45,6 +46,7 @@ var AvailableCloudProviders = []string{
cloudprovider.DigitalOceanProviderName,
cloudprovider.ExoscaleProviderName,
cloudprovider.HuaweicloudProviderName,
cloudprovider.HetznerProviderName,
clusterapi.ProviderName,
}

Expand All @@ -71,6 +73,8 @@ func buildCloudProvider(opts config.AutoscalingOptions, do cloudprovider.NodeGro
return magnum.BuildMagnum(opts, do, rl)
case cloudprovider.HuaweicloudProviderName:
return huaweicloud.BuildHuaweiCloud(opts, do, rl)
case cloudprovider.HetznerProviderName:
return hetzner.BuildHetzner(opts, do, rl)
case packet.ProviderName:
return packet.BuildPacket(opts, do, rl)
case clusterapi.ProviderName:
Expand Down
42 changes: 42 additions & 0 deletions cluster-autoscaler/cloudprovider/builder/builder_hetzner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// +build hetzner

/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package builder

import (
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner"
"k8s.io/autoscaler/cluster-autoscaler/config"
)

// AvailableCloudProviders supported by the Hetzner cloud provider builder.
var AvailableCloudProviders = []string{
cloudprovider.HetznerProviderName,
}

// DefaultCloudProvider is Hetzner.
const DefaultCloudProvider = cloudprovider.HetznerProviderName

func buildCloudProvider(opts config.AutoscalingOptions, do cloudprovider.NodeGroupDiscoveryOptions, rl *cloudprovider.ResourceLimiter) cloudprovider.CloudProvider {
switch opts.CloudProviderName {
case cloudprovider.HetznerProviderName:
return hetzner.BuildHetzner(opts, do, rl)
}

return nil
}
2 changes: 2 additions & 0 deletions cluster-autoscaler/cloudprovider/cloud_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
ExoscaleProviderName = "exoscale"
// GceProviderName gets the provider name of gce
GceProviderName = "gce"
// HetznerProviderName gets the provider name of hetzner
HetznerProviderName = "hetzner"
// MagnumProviderName gets the provider name of magnum
MagnumProviderName = "magnum"
// KubemarkProviderName gets the provider name of kubemark
Expand Down
4 changes: 4 additions & 0 deletions cluster-autoscaler/cloudprovider/hetzner/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
approvers:
- Fgruntjes
reviewers:
- Fgruntjes
42 changes: 42 additions & 0 deletions cluster-autoscaler/cloudprovider/hetzner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Cluster Autoscaler for Hetzner Cloud

The cluster autoscaler for Hetzner Cloud scales worker nodes.

# Configuration

`HCLOUD_TOKEN` Required Hetzner Cloud token.
`HCLOUD_CLOUD_INIT` Base64 encoded Cloud Init yaml with commands to join the cluster
`HCLOUD_IMAGE` Defaults to `ubuntu-20.04`, @see https://docs.hetzner.cloud/#images

Node groups must be defined with the `--nodes=<min-servers>:<max-servers>:<instance-type>:<region>:<name>` flag.
Multiple flags will create multiple node pools. For example:
```
--nodes=1:10:CPX51:FSN1:pool1
--nodes=1:10:CPX51:NBG1:pool2
--nodes=1:10:CX41:NBG1:pool3
```

# Development

Make sure you're inside the root path of the [autoscaler
repository](https://github.com/kubernetes/autoscaler)

1.) Build the `cluster-autoscaler` binary:


```
make build-in-docker
```

2.) Build the docker image:

```
docker build -t hetzner/cluster-autoscaler:dev .
```


3.) Push the docker image to Docker hub:

```
docker push hetzner/cluster-autoscaler:dev
```
226 changes: 226 additions & 0 deletions cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
Copyright 2018 The Kubernetes Authors.
Fgruntjes marked this conversation as resolved.
Show resolved Hide resolved

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package hcloud

import (
"context"
"fmt"
"net/url"
"time"

"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
)

// Action represents an action in the Hetzner Cloud.
type Action struct {
ID int
Status ActionStatus
Command string
Progress int
Started time.Time
Finished time.Time
ErrorCode string
ErrorMessage string
Resources []*ActionResource
}

// ActionStatus represents an action's status.
type ActionStatus string

// List of action statuses.
const (
ActionStatusRunning ActionStatus = "running"
ActionStatusSuccess ActionStatus = "success"
ActionStatusError ActionStatus = "error"
)

// ActionResource references other resources from an action.
type ActionResource struct {
ID int
Type ActionResourceType
}

// ActionResourceType represents an action's resource reference type.
type ActionResourceType string

// List of action resource reference types.
const (
ActionResourceTypeServer ActionResourceType = "server"
ActionResourceTypeImage ActionResourceType = "image"
ActionResourceTypeISO ActionResourceType = "iso"
ActionResourceTypeFloatingIP ActionResourceType = "floating_ip"
ActionResourceTypeVolume ActionResourceType = "volume"
)

// ActionError is the error of an action.
type ActionError struct {
Code string
Message string
}

func (e ActionError) Error() string {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}

func (a *Action) Error() error {
if a.ErrorCode != "" && a.ErrorMessage != "" {
return ActionError{
Code: a.ErrorCode,
Message: a.ErrorMessage,
}
}
return nil
}

// ActionClient is a client for the actions API.
type ActionClient struct {
client *Client
}

// GetByID retrieves an action by its ID. If the action does not exist, nil is returned.
func (c *ActionClient) GetByID(ctx context.Context, id int) (*Action, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/actions/%d", id), nil)
if err != nil {
return nil, nil, err
}

var body schema.ActionGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ActionFromSchema(body.Action), resp, nil
}

// ActionListOpts specifies options for listing actions.
type ActionListOpts struct {
ListOpts
Status []ActionStatus
Sort []string
}

func (l ActionListOpts) values() url.Values {
vals := l.ListOpts.values()
for _, status := range l.Status {
vals.Add("status", string(status))
}
for _, sort := range l.Sort {
vals.Add("sort", sort)
}
return vals
}

// List returns a list of actions for a specific page.
//
// Please note that filters specified in opts are not taken into account
// when their value corresponds to their zero value or when they are empty.
func (c *ActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) {
path := "/actions?" + opts.values().Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

var body schema.ActionListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
actions := make([]*Action, 0, len(body.Actions))
for _, i := range body.Actions {
actions = append(actions, ActionFromSchema(i))
}
return actions, resp, nil
}

// All returns all actions.
func (c *ActionClient) All(ctx context.Context) ([]*Action, error) {
allActions := []*Action{}

opts := ActionListOpts{}
opts.PerPage = 50

_, err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
actions, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allActions = append(allActions, actions...)
return resp, nil
})
if err != nil {
return nil, err
}

return allActions, nil
}

// WatchProgress watches the action's progress until it completes with success or error.
func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-chan int, <-chan error) {
errCh := make(chan error, 1)
progressCh := make(chan int)

go func() {
defer close(errCh)
defer close(progressCh)

ticker := time.NewTicker(c.client.pollInterval)
sendProgress := func(p int) {
select {
case progressCh <- p:
break
default:
break
}
}

for {
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case <-ticker.C:
break
}

a, _, err := c.GetByID(ctx, action.ID)
if err != nil {
errCh <- err
return
}

switch a.Status {
case ActionStatusRunning:
sendProgress(a.Progress)
break
case ActionStatusSuccess:
sendProgress(100)
errCh <- nil
return
case ActionStatusError:
errCh <- a.Error()
return
}
}
}()

return progressCh, errCh
}
Loading