Skip to content

Commit 07ba90a

Browse files
authored
Merge pull request #339 from linode/ipv6-address-lb-svc
[feat] Add IPv6 ingress address support for NodeBalancer services
2 parents dcaae71 + 7098890 commit 07ba90a

File tree

11 files changed

+194
-21
lines changed

11 files changed

+194
-21
lines changed

cloud/annotations/annotations.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ const (
3030
AnnLinodeCloudFirewallID = "service.beta.kubernetes.io/linode-loadbalancer-firewall-id"
3131
AnnLinodeCloudFirewallACL = "service.beta.kubernetes.io/linode-loadbalancer-firewall-acl"
3232

33+
// AnnLinodeEnableIPv6Ingress is the annotation used to specify that a service should include both IPv4 and IPv6
34+
// addresses for its LoadBalancer ingress. When set to "true", both addresses will be included in the status.
35+
AnnLinodeEnableIPv6Ingress = "service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress"
36+
3337
AnnLinodeNodePrivateIP = "node.k8s.linode.com/private-ip"
3438
AnnLinodeHostUUID = "node.k8s.linode.com/host-uuid"
3539

cloud/linode/cloud.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,17 @@ var Options struct {
3939
EnableRouteController bool
4040
EnableTokenHealthChecker bool
4141
// Deprecated: use VPCNames instead
42-
VPCName string
43-
VPCNames string
44-
SubnetNames string
45-
LoadBalancerType string
46-
BGPNodeSelector string
47-
IpHolderSuffix string
48-
LinodeExternalNetwork *net.IPNet
49-
NodeBalancerTags []string
50-
DefaultNBType string
51-
GlobalStopChannel chan<- struct{}
42+
VPCName string
43+
VPCNames string
44+
SubnetNames string
45+
LoadBalancerType string
46+
BGPNodeSelector string
47+
IpHolderSuffix string
48+
LinodeExternalNetwork *net.IPNet
49+
NodeBalancerTags []string
50+
DefaultNBType string
51+
GlobalStopChannel chan<- struct{}
52+
EnableIPv6ForLoadBalancers bool
5253
}
5354

5455
type linodeCloud struct {

cloud/linode/loadbalancers.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,13 +1052,44 @@ func makeLoadBalancerStatus(service *v1.Service, nb *linodego.NodeBalancer) *v1.
10521052
ingress := v1.LoadBalancerIngress{
10531053
Hostname: *nb.Hostname,
10541054
}
1055-
if !getServiceBoolAnnotation(service, annotations.AnnLinodeHostnameOnlyIngress) {
1056-
if val := envBoolOptions("LINODE_HOSTNAME_ONLY_INGRESS"); val {
1057-
klog.Infof("LINODE_HOSTNAME_ONLY_INGRESS: (%v)", val)
1058-
} else {
1059-
ingress.IP = *nb.IPv4
1055+
1056+
// Return hostname-only if annotation is set or environment variable is set
1057+
if getServiceBoolAnnotation(service, annotations.AnnLinodeHostnameOnlyIngress) {
1058+
return &v1.LoadBalancerStatus{
1059+
Ingress: []v1.LoadBalancerIngress{ingress},
1060+
}
1061+
}
1062+
1063+
if val := envBoolOptions("LINODE_HOSTNAME_ONLY_INGRESS"); val {
1064+
klog.Infof("LINODE_HOSTNAME_ONLY_INGRESS: (%v)", val)
1065+
return &v1.LoadBalancerStatus{
1066+
Ingress: []v1.LoadBalancerIngress{ingress},
1067+
}
1068+
}
1069+
1070+
// Check for per-service IPv6 annotation first, then fall back to global setting
1071+
useIPv6 := getServiceBoolAnnotation(service, annotations.AnnLinodeEnableIPv6Ingress) || Options.EnableIPv6ForLoadBalancers
1072+
1073+
// When IPv6 is enabled (either per-service or globally), include both IPv4 and IPv6
1074+
if useIPv6 && nb.IPv6 != nil && *nb.IPv6 != "" {
1075+
ingresses := []v1.LoadBalancerIngress{
1076+
{
1077+
Hostname: *nb.Hostname,
1078+
IP: *nb.IPv4,
1079+
},
1080+
{
1081+
Hostname: *nb.Hostname,
1082+
IP: *nb.IPv6,
1083+
},
1084+
}
1085+
klog.V(4).Infof("Using both IPv4 and IPv6 addresses for NodeBalancer (%d): %s, %s", nb.ID, *nb.IPv4, *nb.IPv6)
1086+
return &v1.LoadBalancerStatus{
1087+
Ingress: ingresses,
10601088
}
10611089
}
1090+
1091+
// Default case - just use IPv4
1092+
ingress.IP = *nb.IPv4
10621093
return &v1.LoadBalancerStatus{
10631094
Ingress: []v1.LoadBalancerIngress{ingress},
10641095
}

cloud/linode/loadbalancers_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ func TestCCMLoadBalancers(t *testing.T) {
256256
name: "makeLoadBalancerStatus",
257257
f: testMakeLoadBalancerStatus,
258258
},
259+
{
260+
name: "makeLoadBalancerStatusWithIPv6",
261+
f: testMakeLoadBalancerStatusWithIPv6,
262+
},
259263
{
260264
name: "makeLoadBalancerStatusEnvVar",
261265
f: testMakeLoadBalancerStatusEnvVar,
@@ -3191,6 +3195,71 @@ func testMakeLoadBalancerStatus(t *testing.T, client *linodego.Client, _ *fakeAP
31913195
}
31923196
}
31933197

3198+
func testMakeLoadBalancerStatusWithIPv6(t *testing.T, client *linodego.Client, _ *fakeAPI) {
3199+
ipv4 := "192.168.0.1"
3200+
ipv6 := "2600:3c00::f03c:91ff:fe24:3a2f"
3201+
hostname := "nb-192-168-0-1.newark.nodebalancer.linode.com"
3202+
nb := &linodego.NodeBalancer{
3203+
IPv4: &ipv4,
3204+
IPv6: &ipv6,
3205+
Hostname: &hostname,
3206+
}
3207+
3208+
svc := &v1.Service{
3209+
ObjectMeta: metav1.ObjectMeta{
3210+
Name: "test",
3211+
Annotations: make(map[string]string, 1),
3212+
},
3213+
}
3214+
3215+
// Test with EnableIPv6ForLoadBalancers = false (default)
3216+
Options.EnableIPv6ForLoadBalancers = false
3217+
expectedStatus := &v1.LoadBalancerStatus{
3218+
Ingress: []v1.LoadBalancerIngress{{
3219+
Hostname: hostname,
3220+
IP: ipv4,
3221+
}},
3222+
}
3223+
status := makeLoadBalancerStatus(svc, nb)
3224+
if !reflect.DeepEqual(status, expectedStatus) {
3225+
t.Errorf("expected status with EnableIPv6ForLoadBalancers=false to be %#v; got %#v", expectedStatus, status)
3226+
}
3227+
3228+
// Test with EnableIPv6ForLoadBalancers = true
3229+
Options.EnableIPv6ForLoadBalancers = true
3230+
expectedStatus = &v1.LoadBalancerStatus{
3231+
Ingress: []v1.LoadBalancerIngress{
3232+
{
3233+
Hostname: hostname,
3234+
IP: ipv4,
3235+
},
3236+
{
3237+
Hostname: hostname,
3238+
IP: ipv6,
3239+
},
3240+
},
3241+
}
3242+
status = makeLoadBalancerStatus(svc, nb)
3243+
if !reflect.DeepEqual(status, expectedStatus) {
3244+
t.Errorf("expected status with EnableIPv6ForLoadBalancers=true to be %#v; got %#v", expectedStatus, status)
3245+
}
3246+
3247+
// Test with per-service annotation
3248+
// Reset the global flag to false and set the annotation
3249+
Options.EnableIPv6ForLoadBalancers = false
3250+
svc.Annotations[annotations.AnnLinodeEnableIPv6Ingress] = "true"
3251+
3252+
// Expect the same result as when the global flag is enabled
3253+
status = makeLoadBalancerStatus(svc, nb)
3254+
if !reflect.DeepEqual(status, expectedStatus) {
3255+
t.Errorf("expected status with %s=true annotation to be %#v; got %#v",
3256+
annotations.AnnLinodeEnableIPv6Ingress, expectedStatus, status)
3257+
}
3258+
3259+
// Reset the flag to its default value
3260+
Options.EnableIPv6ForLoadBalancers = false
3261+
}
3262+
31943263
func testMakeLoadBalancerStatusEnvVar(t *testing.T, client *linodego.Client, _ *fakeAPI) {
31953264
ipv4 := "192.168.0.1"
31963265
hostname := "nb-192-168-0-1.newark.nodebalancer.linode.com"

deploy/chart/templates/daemonset.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ spec:
100100
{{- if .Values.defaultNBType }}
101101
- --default-nodebalancer-type={{ .Values.defaultNBType }}
102102
{{- end }}
103+
{{- if .Values.enableIPv6ForLoadBalancers }}
104+
- --enable-ipv6-for-loadbalancers={{ .Values.enableIPv6ForLoadBalancers }}
105+
{{- end }}
103106
{{- with .Values.containerSecurityContext }}
104107
securityContext:
105108
{{- toYaml . | nindent 12 }}

deploy/chart/values.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ tolerations:
8686
# Default NodeBalancer type to create("common" or "premium"). Default is "common"
8787
# defaultNBType: "common"
8888

89+
# Enable IPv6 ingress addresses for LoadBalancer services
90+
# When enabled, both IPv4 and IPv6 addresses will be included in the LoadBalancer status for all services
91+
# This can also be controlled per-service using the "service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress" annotation
92+
# enableIPv6ForLoadBalancers: true
93+
8994
# This section adds the ability to pass environment variables to adjust CCM defaults
9095
# https://github.com/linode/linode-cloud-controller-manager/blob/master/cloud/linode/loadbalancers.go
9196
# LINODE_HOSTNAME_ONLY_INGRESS type bool is supported

docs/configuration/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ The Linode Cloud Controller Manager (CCM) offers extensive configuration options
2828
- Node controller behavior
2929
- [See node management](nodes.md#node-controller-behavior)
3030

31-
4. **[Environment Variables](environment.md)**
31+
4. **[Environment Variables and Flags](environment.md)**
3232
- Cache settings
3333
- API configuration
3434
- Network settings
3535
- BGP configuration
36-
- [See environment reference](environment.md#available-variables)
36+
- IPv6 configuration
37+
- [See configuration reference](environment.md#flags)
3738

3839
5. **[Firewall Setup](firewall.md)**
3940
- CCM-managed firewalls

docs/configuration/annotations.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ For implementation details, see:
3333
| `firewall-id` | string | | An existing Cloud Firewall ID to be attached to the NodeBalancer instance. See [Firewall Setup](firewall.md) |
3434
| `firewall-acl` | string | | The Firewall rules to be applied to the NodeBalancer. See [Firewall Configuration](#firewall-configuration) |
3535
| `nodebalancer-type` | string | | The type of NodeBalancer to create (options: common, premium). See [NodeBalancer Types](#nodebalancer-type) |
36+
| `enable-ipv6-ingress` | bool | `false` | When `true`, both IPv4 and IPv6 addresses will be included in the LoadBalancerStatus ingress |
3637
| `backend-ipv4-range` | string | | The IPv4 range from VPC subnet to be applied to the NodeBalancer backend. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) |
3738
| `backend-vpc-name` | string | | VPC which is connected to the NodeBalancer backend. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) |
3839
| `backend-subnet-name` | string | | Subnet within VPC which is connected to the NodeBalancer backend. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) |
@@ -124,6 +125,13 @@ metadata:
124125
service.beta.kubernetes.io/linode-loadbalancer-subnet-name: "subnet1"
125126
```
126127

128+
### Service with IPv6 Address
129+
```yaml
130+
metadata:
131+
annotations:
132+
service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress: "true"
133+
```
134+
127135
For more examples and detailed configuration options, see:
128136
- [LoadBalancer Configuration](loadbalancer.md)
129137
- [Firewall Configuration](firewall.md)

docs/configuration/environment.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Environment Variables
1+
# Environment Variables and Flags
22

33
## Overview
44

5-
Environment variables provide global configuration options for the CCM. These settings affect caching, API behavior, and networking configurations.
5+
The CCM can be configured using environment variables and flags. Environment variables provide global configuration options, while flags control specific features.
66

7-
## Available Variables
7+
## Environment Variables
88

99
### Cache Configuration
1010

@@ -28,6 +28,24 @@ Environment variables provide global configuration options for the CCM. These se
2828
| `BGP_CUSTOM_ID_MAP` | "" | Use your own map instead of default region map for BGP |
2929
| `BGP_PEER_PREFIX` | `2600:3c0f` | Use your own BGP peer prefix instead of default one |
3030

31+
## Flags
32+
33+
The CCM supports the following flags:
34+
35+
| Flag | Default | Description |
36+
|------|---------|-------------|
37+
| `--linodego-debug` | `false` | Enables debug output for the LinodeAPI wrapper |
38+
| `--enable-route-controller` | `false` | Enables route_controller for CCM |
39+
| `--enable-token-health-checker` | `false` | Enables Linode API token health checker |
40+
| `--vpc-names` | `""` | Comma separated VPC names whose routes will be managed by route-controller |
41+
| `--subnet-names` | `""` | Comma separated subnet names whose routes will be managed by route-controller (requires vpc-names flag) |
42+
| `--load-balancer-type` | `nodebalancer` | Configures which type of load-balancing to use (options: nodebalancer, cilium-bgp) |
43+
| `--bgp-node-selector` | `""` | Node selector to use to perform shared IP fail-over with BGP |
44+
| `--ip-holder-suffix` | `""` | Suffix to append to the IP holder name when using shared IP fail-over with BGP |
45+
| `--default-nodebalancer-type` | `common` | Default type of NodeBalancer to create (options: common, premium) |
46+
| `--nodebalancer-tags` | `[]` | Linode tags to apply to all NodeBalancers |
47+
| `--enable-ipv6-for-loadbalancers` | `false` | Set both IPv4 and IPv6 addresses for all LoadBalancer services (when disabled, only IPv4 is used). This can also be configured per-service using the `service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress` annotation. |
48+
3149
## Configuration Methods
3250

3351
### Helm Chart
@@ -36,6 +54,9 @@ Configure via `values.yaml`:
3654
env:
3755
- name: LINODE_INSTANCE_CACHE_TTL
3856
value: "30"
57+
args:
58+
- --enable-ipv6-for-loadbalancers
59+
- --enable-route-controller
3960
```
4061
4162
### Manual Deployment
@@ -49,6 +70,9 @@ spec:
4970
env:
5071
- name: LINODE_INSTANCE_CACHE_TTL
5172
value: "30"
73+
args:
74+
- --enable-ipv6-for-loadbalancers
75+
- --enable-route-controller
5276
```
5377
5478
## Usage Guidelines

docs/configuration/loadbalancer.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@ When using NodeBalancers, the CCM automatically:
1818

1919
For more details, see [Linode NodeBalancer Documentation](https://www.linode.com/docs/products/networking/nodebalancers/).
2020

21+
### IPv6 Support
22+
23+
NodeBalancers support both IPv4 and IPv6 ingress addresses. By default, the CCM uses only IPv4 address for LoadBalancer services.
24+
25+
You can enable IPv6 addresses globally for all services by setting the `enable-ipv6-for-loadbalancers` flag:
26+
27+
```yaml
28+
spec:
29+
template:
30+
spec:
31+
containers:
32+
- name: ccm-linode
33+
args:
34+
- --enable-ipv6-for-loadbalancers=true
35+
```
36+
37+
Alternatively, you can enable IPv6 addresses for individual services using the annotation:
38+
39+
```yaml
40+
metadata:
41+
annotations:
42+
service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress: "true"
43+
```
44+
45+
When IPv6 is enabled (either globally or per-service), both IPv4 and IPv6 addresses will be included in the service's LoadBalancer status.
46+
2147
### Basic Configuration
2248
2349
Create a LoadBalancer service:
@@ -219,7 +245,7 @@ metadata:
219245
- [Service Annotations](annotations.md)
220246
- [Firewall Configuration](firewall.md)
221247
- [Session Affinity](session-affinity.md)
222-
- [Environment Variables](environment.md)
248+
- [Environment Variables and Flags](environment.md)
223249
- [Route Configuration](routes.md)
224250
- [Linode NodeBalancer Documentation](https://www.linode.com/docs/products/networking/nodebalancers/)
225251
- [Cilium BGP Documentation](https://docs.cilium.io/en/stable/network/bgp-control-plane/bgp-control-plane/)

0 commit comments

Comments
 (0)