Skip to content

Commit c07966f

Browse files
authored
feat: extend environment variables for default load balancer configuration (#1052)
This PR introduces additional environment variables for load balancer configuration. These variables are designed to be set globally as defaults and can be overridden using annotations. The main motivation is to improve support for GatewayAPI, as the `Gateway` annotation limit of 8 is restrictive and many settings are commonly needed across all load balancers from the same or even differen GatewayAPI providers. Additionally, this change allows environment-specific presets such as the new [subnet IP range](#1031) to be set globally. This removes the need to configure these settings in each service or use templating/patching to use the same service manifest for different environments. New environment vars: - `HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE` - `HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK` - `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL` - `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES` - `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT` - `HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE` - `HCLOUD_LOAD_BALANCERS_TYPE` - `HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL`
1 parent 4d970c6 commit c07966f

File tree

6 files changed

+457
-56
lines changed

6 files changed

+457
-56
lines changed

docs/guides/load-balancer/configuration.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@ Load Balancers are configured via Kubernetes [annotations](https://kubernetes.io
66

77
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.
88

9+
- `HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE`
10+
- `HCLOUD_LOAD_BALANCERS_DISABLE_IPV6`
11+
- `HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS`
12+
- `HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK`
13+
- `HCLOUD_LOAD_BALANCERS_ENABLED`
14+
- `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL`
15+
- `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES`
16+
- `HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT`
917
- `HCLOUD_LOAD_BALANCERS_LOCATION` (mutually exclusive with `HCLOUD_LOAD_BALANCERS_NETWORK_ZONE`)
1018
- `HCLOUD_LOAD_BALANCERS_NETWORK_ZONE` (mutually exclusive with `HCLOUD_LOAD_BALANCERS_LOCATION`)
11-
- `HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS`
19+
- `HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE`
20+
- `HCLOUD_LOAD_BALANCERS_TYPE`
1221
- `HCLOUD_LOAD_BALANCERS_USE_PRIVATE_IP`
13-
- `HCLOUD_LOAD_BALANCERS_ENABLED`
22+
- `HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL`

internal/config/config.go

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package config
33
import (
44
"errors"
55
"fmt"
6+
"net"
67
"os"
78
"strconv"
9+
"strings"
810
"time"
911

1012
"k8s.io/klog/v2"
1113

14+
"github.com/hetznercloud/hcloud-go/v2/hcloud"
1215
"github.com/hetznercloud/hcloud-go/v2/hcloud/exp/kit/envutil"
1316
)
1417

@@ -31,12 +34,20 @@ const (
3134
hcloudNetworkDisableAttachedCheck = "HCLOUD_NETWORK_DISABLE_ATTACHED_CHECK"
3235
hcloudNetworkRoutesEnabled = "HCLOUD_NETWORK_ROUTES_ENABLED"
3336

37+
hcloudLoadBalancersAlgorithmType = "HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE"
38+
hcloudLoadBalancersDisableIPv6 = "HCLOUD_LOAD_BALANCERS_DISABLE_IPV6"
39+
hcloudLoadBalancersDisablePrivateIngress = "HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS"
40+
hcloudLoadBalancersDisablePublicNetwork = "HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK"
3441
hcloudLoadBalancersEnabled = "HCLOUD_LOAD_BALANCERS_ENABLED"
42+
hcloudLoadBalancersHealthCheckInterval = "HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL"
43+
hcloudLoadBalancersHealthCheckRetries = "HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES"
44+
hcloudLoadBalancersHealthCheckTimeout = "HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT"
3545
hcloudLoadBalancersLocation = "HCLOUD_LOAD_BALANCERS_LOCATION"
3646
hcloudLoadBalancersNetworkZone = "HCLOUD_LOAD_BALANCERS_NETWORK_ZONE"
37-
hcloudLoadBalancersDisablePrivateIngress = "HCLOUD_LOAD_BALANCERS_DISABLE_PRIVATE_INGRESS"
47+
hcloudLoadBalancersPrivateSubnetIPRange = "HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE"
48+
hcloudLoadBalancersType = "HCLOUD_LOAD_BALANCERS_TYPE"
3849
hcloudLoadBalancersUsePrivateIP = "HCLOUD_LOAD_BALANCERS_USE_PRIVATE_IP"
39-
hcloudLoadBalancersDisableIPv6 = "HCLOUD_LOAD_BALANCERS_DISABLE_IPV6"
50+
hcloudLoadBalancersUsesProxyProtocol = "HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL"
4051

4152
hcloudMetricsEnabled = "HCLOUD_METRICS_ENABLED"
4253
hcloudMetricsAddress = "HCLOUD_METRICS_ADDRESS"
@@ -76,12 +87,20 @@ type InstanceConfiguration struct {
7687
}
7788

7889
type LoadBalancerConfiguration struct {
90+
AlgorithmType hcloud.LoadBalancerAlgorithmType
91+
DisablePublicNetwork *bool
7992
Enabled bool
93+
HealthCheckInterval time.Duration
94+
HealthCheckRetries int
95+
HealthCheckTimeout time.Duration
96+
IPv6Enabled bool
8097
Location string
8198
NetworkZone string
8299
PrivateIngressEnabled bool
83100
PrivateIPEnabled bool
84-
IPv6Enabled bool
101+
PrivateSubnetIPRange string
102+
ProxyProtocolEnabled *bool
103+
Type string
85104
}
86105

87106
type NetworkConfiguration struct {
@@ -188,12 +207,49 @@ func Read() (HCCMConfiguration, error) {
188207
errs = append(errs, err)
189208
}
190209

210+
cfg.LoadBalancer.ProxyProtocolEnabled, err = getEnvBoolPtr(hcloudLoadBalancersUsesProxyProtocol)
211+
if err != nil {
212+
errs = append(errs, err)
213+
}
214+
191215
disableIPv6, err := getEnvBool(hcloudLoadBalancersDisableIPv6, false)
192216
if err != nil {
193217
errs = append(errs, err)
194218
}
195219
cfg.LoadBalancer.IPv6Enabled = !disableIPv6 // Invert the logic, as the env var is prefixed with DISABLE_.
196220

221+
if subnetRange, ok := os.LookupEnv(hcloudLoadBalancersPrivateSubnetIPRange); ok {
222+
cfg.LoadBalancer.PrivateSubnetIPRange = subnetRange
223+
}
224+
225+
if algorithmType, ok := os.LookupEnv(hcloudLoadBalancersAlgorithmType); ok {
226+
cfg.LoadBalancer.AlgorithmType = hcloud.LoadBalancerAlgorithmType(algorithmType)
227+
}
228+
229+
cfg.LoadBalancer.HealthCheckInterval, err = getEnvDuration(hcloudLoadBalancersHealthCheckInterval)
230+
if err != nil {
231+
errs = append(errs, err)
232+
}
233+
234+
cfg.LoadBalancer.HealthCheckTimeout, err = getEnvDuration(hcloudLoadBalancersHealthCheckTimeout)
235+
if err != nil {
236+
errs = append(errs, err)
237+
}
238+
239+
if retries := os.Getenv(hcloudLoadBalancersHealthCheckRetries); retries != "" {
240+
cfg.LoadBalancer.HealthCheckRetries, err = strconv.Atoi(retries)
241+
if err != nil {
242+
errs = append(errs, fmt.Errorf("failed to parse %s: %w", hcloudLoadBalancersHealthCheckRetries, err))
243+
}
244+
}
245+
246+
cfg.LoadBalancer.DisablePublicNetwork, err = getEnvBoolPtr(hcloudLoadBalancersDisablePublicNetwork)
247+
if err != nil {
248+
errs = append(errs, err)
249+
}
250+
251+
cfg.LoadBalancer.Type = os.Getenv(hcloudLoadBalancersType)
252+
197253
cfg.Network.NameOrID = os.Getenv(hcloudNetwork)
198254
disableAttachedCheck, err := getEnvBool(hcloudNetworkDisableAttachedCheck, false)
199255
if err != nil {
@@ -235,6 +291,18 @@ func (c HCCMConfiguration) Validate() (err error) {
235291
errs = append(errs, fmt.Errorf("invalid value for %q/%q, only one of them can be set", hcloudLoadBalancersLocation, hcloudLoadBalancersNetworkZone))
236292
}
237293

294+
if c.LoadBalancer.PrivateSubnetIPRange != "" {
295+
if _, _, err := net.ParseCIDR(c.LoadBalancer.PrivateSubnetIPRange); err != nil {
296+
errs = append(errs, fmt.Errorf("invalid value for %q: must be a valid CIDR: %w", hcloudLoadBalancersPrivateSubnetIPRange, err))
297+
}
298+
}
299+
300+
if c.LoadBalancer.AlgorithmType != "" {
301+
if _, err := parseLoadBalancerAlgorithmType(string(c.LoadBalancer.AlgorithmType)); err != nil {
302+
errs = append(errs, fmt.Errorf("invalid value for %q: %w", hcloudLoadBalancersAlgorithmType, err))
303+
}
304+
}
305+
238306
if c.Robot.Enabled {
239307
if c.Robot.User == "" {
240308
errs = append(errs, fmt.Errorf("environment variable %q is required if Robot support is enabled", robotUser))
@@ -270,6 +338,22 @@ func getEnvBool(key string, defaultValue bool) (bool, error) {
270338
return b, nil
271339
}
272340

341+
// getEnvBoolPtr returns a pointer to the boolean parsed from the environment variable with the given key.
342+
// Returns nil if the env var is unset.
343+
func getEnvBoolPtr(key string) (*bool, error) {
344+
v, ok := os.LookupEnv(key)
345+
if !ok {
346+
return nil, nil
347+
}
348+
349+
b, err := strconv.ParseBool(v)
350+
if err != nil {
351+
return nil, fmt.Errorf("failed to parse %s: %w", key, err)
352+
}
353+
354+
return &b, nil
355+
}
356+
273357
// getEnvDuration returns the duration parsed from the environment variable with the given key and a potential error
274358
// parsing the var. Returns false if the env var is unset.
275359
func getEnvDuration(key string) (time.Duration, error) {
@@ -285,3 +369,12 @@ func getEnvDuration(key string) (time.Duration, error) {
285369

286370
return b, nil
287371
}
372+
373+
func parseLoadBalancerAlgorithmType(value string) (hcloud.LoadBalancerAlgorithmType, error) {
374+
v := strings.ToLower(strings.TrimSpace(value))
375+
alg := hcloud.LoadBalancerAlgorithmType(v)
376+
if alg == hcloud.LoadBalancerAlgorithmTypeRoundRobin || alg == hcloud.LoadBalancerAlgorithmTypeLeastConnections {
377+
return alg, nil
378+
}
379+
return "", fmt.Errorf("unsupported value %q", value)
380+
}

internal/config/config_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/stretchr/testify/assert"
99

1010
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/testsupport"
11+
"github.com/hetznercloud/hcloud-go/v2/hcloud"
1112
)
1213

1314
func TestRead(t *testing.T) {
@@ -123,6 +124,14 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
123124
"HCLOUD_TOKEN", "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq",
124125
"HCLOUD_ENDPOINT", "https://api.example.com",
125126
"HCLOUD_DEBUG", "true",
127+
"HCLOUD_LOAD_BALANCERS_PRIVATE_SUBNET_IP_RANGE", "10.1.0.0/24",
128+
"HCLOUD_LOAD_BALANCERS_USES_PROXYPROTOCOL", "true",
129+
"HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE", "least_connections",
130+
"HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_INTERVAL", "30s",
131+
"HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_TIMEOUT", "5s",
132+
"HCLOUD_LOAD_BALANCERS_HEALTH_CHECK_RETRIES", "5",
133+
"HCLOUD_LOAD_BALANCERS_DISABLE_PUBLIC_NETWORK", "true",
134+
"HCLOUD_LOAD_BALANCERS_TYPE", "lb21",
126135
},
127136
want: HCCMConfiguration{
128137
HCloudClient: HCloudClientConfiguration{
@@ -140,6 +149,14 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
140149
Enabled: true,
141150
PrivateIngressEnabled: true,
142151
IPv6Enabled: true,
152+
PrivateSubnetIPRange: "10.1.0.0/24",
153+
ProxyProtocolEnabled: hcloud.Ptr(true),
154+
AlgorithmType: hcloud.LoadBalancerAlgorithmTypeLeastConnections,
155+
HealthCheckInterval: 30 * time.Second,
156+
HealthCheckTimeout: 5 * time.Second,
157+
HealthCheckRetries: 5,
158+
DisablePublicNetwork: hcloud.Ptr(true),
159+
Type: "lb21",
143160
},
144161
},
145162
wantErr: nil,
@@ -444,6 +461,28 @@ func TestHCCMConfiguration_Validate(t *testing.T) {
444461
},
445462
wantErr: errors.New("invalid value for \"HCLOUD_LOAD_BALANCERS_LOCATION\"/\"HCLOUD_LOAD_BALANCERS_NETWORK_ZONE\", only one of them can be set"),
446463
},
464+
{
465+
name: "LB private subnet invalid cidr",
466+
fields: fields{
467+
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
468+
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
469+
LoadBalancer: LoadBalancerConfiguration{
470+
PrivateSubnetIPRange: "10.0.0.0/33",
471+
},
472+
},
473+
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"),
474+
},
475+
{
476+
name: "algorithm type invalid",
477+
fields: fields{
478+
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
479+
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
480+
LoadBalancer: LoadBalancerConfiguration{
481+
AlgorithmType: "invalid",
482+
},
483+
},
484+
wantErr: errors.New("invalid value for \"HCLOUD_LOAD_BALANCERS_ALGORITHM_TYPE\": unsupported value \"invalid\""),
485+
},
447486
{
448487
name: "robot enabled but missing credentials",
449488
fields: fields{

0 commit comments

Comments
 (0)