-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Hetzner Cloud Provider (code by Fgruntjes)
- Loading branch information
1 parent
bb3b24c
commit 908638b
Showing
47 changed files
with
9,298 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
cluster-autoscaler/cloudprovider/builder/builder_hetzner.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
approvers: | ||
- Fgruntjes | ||
reviewers: | ||
- Fgruntjes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
226
cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
/* | ||
Copyright 2018 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 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 | ||
} |
Oops, something went wrong.