Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions docs/guides/load-balancer/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ Load Balancers are configured via Kubernetes [annotations](https://kubernetes.io

For convenience, you can set the following environment variables as cluster-wide defaults, so you don't have to set them on each load balancer service. If a load balancer service has the corresponding annotation set, it overrides the default.

- `HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE`
- `HCLOUD_LOAD_BALANCERS_DISABLE_IPV6`
- `HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS`
- `HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK`
- `HCLOUD_LOAD_BALANCERS_ENABLED`
- `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL`
- `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES`
- `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT`
- `HCLOUD_LOAD_BALANCERS_LOCATION` (mutually exclusive with `HCLOUD_LOAD_BALANCERS_NETWORK_ZONE`)
- `HCLOUD_LOAD_BALANCERS_NETWORK_ZONE` (mutually exclusive with `HCLOUD_LOAD_BALANCERS_LOCATION`)
- `HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS`
- `HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE`
- `HCLOUD_LOAD_BALANCERS_TYPE`
- `HCLOUD_LOAD_BALANCERS_USE_PRIVATE_IP`
- `HCLOUD_LOAD_BALANCERS_ENABLED`
- `HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL`
99 changes: 96 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package config
import (
"errors"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"

"k8s.io/klog/v2"

"github.com/hetznercloud/hcloud-go/v2/hcloud"
"github.com/hetznercloud/hcloud-go/v2/hcloud/exp/kit/envutil"
)

Expand All @@ -31,12 +34,20 @@ const (
hcloudNetworkDisableAttachedCheck = "HCLOUD_NETWORK_DISABLE_ATTACHED_CHECK"
hcloudNetworkRoutesEnabled = "HCLOUD_NETWORK_ROUTES_ENABLED"

hcloudLoadBalancersAlgorithmType = "HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE"
hcloudLoadBalancersDisableIPv6 = "HCLOUD_LOAD_BALANCERS_DISABLE_IPV6"
hcloudLoadBalancersDisablePrivateIngress = "HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS"
hcloudLoadBalancersDisablePublicNetwork = "HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK"
hcloudLoadBalancersEnabled = "HCLOUD_LOAD_BALANCERS_ENABLED"
hcloudLoadBalancersHealthCheckInterval = "HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL"
hcloudLoadBalancersHealthCheckRetries = "HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES"
hcloudLoadBalancersHealthCheckTimeout = "HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT"
hcloudLoadBalancersLocation = "HCLOUD_LOAD_BALANCERS_LOCATION"
hcloudLoadBalancersNetworkZone = "HCLOUD_LOAD_BALANCERS_NETWORK_ZONE"
hcloudLoadBalancersDisablePrivateIngress = "HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS"
hcloudLoadBalancersPrivateSubnetIPRange = "HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE"
hcloudLoadBalancersType = "HCLOUD_LOAD_BALANCERS_TYPE"
hcloudLoadBalancersUsePrivateIP = "HCLOUD_LOAD_BALANCERS_USE_PRIVATE_IP"
hcloudLoadBalancersDisableIPv6 = "HCLOUD_LOAD_BALANCERS_DISABLE_IPV6"
hcloudLoadBalancersUsesProxyProtocol = "HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL"

hcloudMetricsEnabled = "HCLOUD_METRICS_ENABLED"
hcloudMetricsAddress = "HCLOUD_METRICS_ADDRESS"
Expand Down Expand Up @@ -76,12 +87,20 @@ type InstanceConfiguration struct {
}

type LoadBalancerConfiguration struct {
AlgorithmType hcloud.LoadBalancerAlgorithmType
DisablePublicNetwork *bool
Enabled bool
HealthCheckInterval time.Duration
HealthCheckRetries int
HealthCheckTimeout time.Duration
IPv6Enabled bool
Location string
NetworkZone string
PrivateIngressEnabled bool
PrivateIPEnabled bool
IPv6Enabled bool
PrivateSubnetIPRange string
ProxyProtocolEnabled *bool
Type string
}

type NetworkConfiguration struct {
Expand Down Expand Up @@ -188,12 +207,49 @@ func Read() (HCCMConfiguration, error) {
errs = append(errs, err)
}

cfg.LoadBalancer.ProxyProtocolEnabled, err = getEnvBoolPtr(hcloudLoadBalancersUsesProxyProtocol)
if err != nil {
errs = append(errs, err)
}

disableIPv6, err := getEnvBool(hcloudLoadBalancersDisableIPv6, false)
if err != nil {
errs = append(errs, err)
}
cfg.LoadBalancer.IPv6Enabled = !disableIPv6 // Invert the logic, as the env var is prefixed with DISABLE_.

if subnetRange, ok := os.LookupEnv(hcloudLoadBalancersPrivateSubnetIPRange); ok {
cfg.LoadBalancer.PrivateSubnetIPRange = subnetRange
}
Comment thread
lukasmetzner marked this conversation as resolved.

if algorithmType, ok := os.LookupEnv(hcloudLoadBalancersAlgorithmType); ok {
cfg.LoadBalancer.AlgorithmType = hcloud.LoadBalancerAlgorithmType(algorithmType)
}

cfg.LoadBalancer.HealthCheckInterval, err = getEnvDuration(hcloudLoadBalancersHealthCheckInterval)
if err != nil {
errs = append(errs, err)
}

cfg.LoadBalancer.HealthCheckTimeout, err = getEnvDuration(hcloudLoadBalancersHealthCheckTimeout)
if err != nil {
errs = append(errs, err)
}

if retries := os.Getenv(hcloudLoadBalancersHealthCheckRetries); retries != "" {
cfg.LoadBalancer.HealthCheckRetries, err = strconv.Atoi(retries)
if err != nil {
errs = append(errs, fmt.Errorf("failed to parse %s: %w", hcloudLoadBalancersHealthCheckRetries, err))
}
}

cfg.LoadBalancer.DisablePublicNetwork, err = getEnvBoolPtr(hcloudLoadBalancersDisablePublicNetwork)
if err != nil {
errs = append(errs, err)
}

cfg.LoadBalancer.Type = os.Getenv(hcloudLoadBalancersType)

cfg.Network.NameOrID = os.Getenv(hcloudNetwork)
disableAttachedCheck, err := getEnvBool(hcloudNetworkDisableAttachedCheck, false)
if err != nil {
Expand Down Expand Up @@ -235,6 +291,18 @@ func (c HCCMConfiguration) Validate() (err error) {
errs = append(errs, fmt.Errorf("invalid value for %q/%q, only one of them can be set", hcloudLoadBalancersLocation, hcloudLoadBalancersNetworkZone))
}

if c.LoadBalancer.PrivateSubnetIPRange != "" {
if _, _, err := net.ParseCIDR(c.LoadBalancer.PrivateSubnetIPRange); err != nil {
errs = append(errs, fmt.Errorf("invalid value for %q: must be a valid CIDR: %w", hcloudLoadBalancersPrivateSubnetIPRange, err))
}
}

if c.LoadBalancer.AlgorithmType != "" {
if _, err := parseLoadBalancerAlgorithmType(string(c.LoadBalancer.AlgorithmType)); err != nil {
errs = append(errs, fmt.Errorf("invalid value for %q: %w", hcloudLoadBalancersAlgorithmType, err))
}
}

if c.Robot.Enabled {
if c.Robot.User == "" {
errs = append(errs, fmt.Errorf("environment variable %q is required if Robot support is enabled", robotUser))
Expand Down Expand Up @@ -270,6 +338,22 @@ func getEnvBool(key string, defaultValue bool) (bool, error) {
return b, nil
}

// getEnvBoolPtr returns a pointer to the boolean parsed from the environment variable with the given key.
// Returns nil if the env var is unset.
func getEnvBoolPtr(key string) (*bool, error) {
v, ok := os.LookupEnv(key)
if !ok {
return nil, nil
}

b, err := strconv.ParseBool(v)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", key, err)
}

return &b, nil
}

// getEnvDuration returns the duration parsed from the environment variable with the given key and a potential error
// parsing the var. Returns false if the env var is unset.
func getEnvDuration(key string) (time.Duration, error) {
Expand All @@ -285,3 +369,12 @@ func getEnvDuration(key string) (time.Duration, error) {

return b, nil
}

func parseLoadBalancerAlgorithmType(value string) (hcloud.LoadBalancerAlgorithmType, error) {
v := strings.ToLower(strings.TrimSpace(value))
alg := hcloud.LoadBalancerAlgorithmType(v)
if alg == hcloud.LoadBalancerAlgorithmTypeRoundRobin || alg == hcloud.LoadBalancerAlgorithmTypeLeastConnections {
return alg, nil
}
return "", fmt.Errorf("unsupported value %q", value)
}
39 changes: 39 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"

"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/testsupport"
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

func TestRead(t *testing.T) {
Expand Down Expand Up @@ -123,6 +124,14 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
"HCLOUD_TOKEN", "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq",
"HCLOUD_ENDPOINT", "https://api.example.com",
"HCLOUD_DEBUG", "true",
"HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE", "10.1.0.0/24",
"HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL", "true",
"HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE", "least_connections",
"HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL", "30s",
"HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT", "5s",
"HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES", "5",
"HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK", "true",
"HCLOUD_LOAD_BALANCERS_TYPE", "lb21",
},
want: HCCMConfiguration{
HCloudClient: HCloudClientConfiguration{
Expand All @@ -140,6 +149,14 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
Enabled: true,
PrivateIngressEnabled: true,
IPv6Enabled: true,
PrivateSubnetIPRange: "10.1.0.0/24",
ProxyProtocolEnabled: hcloud.Ptr(true),
AlgorithmType: hcloud.LoadBalancerAlgorithmTypeLeastConnections,
HealthCheckInterval: 30 * time.Second,
HealthCheckTimeout: 5 * time.Second,
HealthCheckRetries: 5,
DisablePublicNetwork: hcloud.Ptr(true),
Type: "lb21",
},
},
wantErr: nil,
Expand Down Expand Up @@ -444,6 +461,28 @@ func TestHCCMConfiguration_Validate(t *testing.T) {
},
wantErr: errors.New("invalid value for \"HCLOUD_LOAD_BALANCERS_LOCATION\"/\"HCLOUD_LOAD_BALANCERS_NETWORK_ZONE\", only one of them can be set"),
},
{
name: "LB private subnet invalid cidr",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
LoadBalancer: LoadBalancerConfiguration{
PrivateSubnetIPRange: "10.0.0.0/33",
},
},
wantErr: errors.New("invalid value for \"HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE\": must be a valid CIDR: invalid CIDR address: 10.0.0.0/33"),
},
{
name: "algorithm type invalid",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
LoadBalancer: LoadBalancerConfiguration{
AlgorithmType: "invalid",
},
},
wantErr: errors.New("invalid value for \"HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE\": unsupported value \"invalid\""),
},
{
name: "robot enabled but missing credentials",
fields: fields{
Expand Down
Loading
Loading