Skip to content

Commit d90b6ba

Browse files
Merge pull request #128 from shiftstack/merge-bot-master
Merge https://github.com/kubernetes/cloud-provider-openstack:master into master
2 parents 544da01 + d59f1ef commit d90b6ba

File tree

4 files changed

+91
-30
lines changed

4 files changed

+91
-30
lines changed

cluster/images/cinder-csi-plugin/Dockerfile.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ FROM k8s.gcr.io/build-image/debian-base-${DEBIAN_ARCH}:v2.1.3
44
ARG ARCH=amd64
55

66
# Install e4fsprogs for format
7-
RUN clean-install ca-certificates e2fsprogs mount xfsprogs udev
7+
RUN clean-install btrfs-progs ca-certificates e2fsprogs mount udev xfsprogs
88

99
ADD cinder-csi-plugin-${ARCH} /bin/cinder-csi-plugin

docs/openstack-cloud-controller-manager/expose-applications-using-loadbalancer-type-service.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [Restrict Access For LoadBalancer Service](#restrict-access-for-loadbalancer-service)
1212
- [Use PROXY protocol to preserve client IP](#use-proxy-protocol-to-preserve-client-ip)
1313
- [Sharing load balancer with multiple Services](#sharing-load-balancer-with-multiple-services)
14+
- [IPv4 / IPv6 dual-stack services](#ipv4--ipv6-dual-stack-services)
1415

1516
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
1617

@@ -580,3 +581,21 @@ $ openstack loadbalancer listener list --loadbalancer 2b224530-9414-4302-8163-5a
580581
```
581582

582583
The load balancer will be deleted after `service-2` is deleted.
584+
585+
### IPv4 / IPv6 dual-stack services
586+
Since Kubernetes 1.20, Kubernetes clusters can run in dual-stack mode,
587+
which allows simultaneous usage of both IPv4 and IPv6 addresses in the cluster.
588+
In dual-stack clusters, services can use IPv4, IPv6, or both address families, which
589+
can be indicated in service's `spec.ipFamilies`.
590+
591+
If only one address family is specified in service's `spec.ipFamilies`, OCCM will respect
592+
that and create an IPv4 or IPv6 load balancer based on that.
593+
594+
If two address families are specified in service's `spec.ipFamilies`, OCCM will respect the
595+
specified order and create an IPv4 or IPv6 load balancer based on the first specified address
596+
family. Note that creation of two load balancers for services with two `spec.ipFamilies`
597+
is not yet supported by OCCM.
598+
599+
Internally, OCCM would automatically look for IPv4 or IPv6 subnet to allocate the load balancer
600+
address from based on the service's address family preference. If the subnet with preferred
601+
address family is not available, load balancer can not be created.

pkg/openstack/loadbalancer.go

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
"k8s.io/client-go/kubernetes"
4949
cloudprovider "k8s.io/cloud-provider"
5050
"k8s.io/klog/v2"
51+
netutils "k8s.io/utils/net"
5152

5253
"k8s.io/cloud-provider-openstack/pkg/metrics"
5354
cpoutil "k8s.io/cloud-provider-openstack/pkg/util"
@@ -59,10 +60,11 @@ import (
5960
// Note: when creating a new Loadbalancer (VM), it can take some time before it is ready for use,
6061
// this timeout is used for waiting until the Loadbalancer provisioning status goes to ACTIVE state.
6162
const (
62-
servicePrefix = "kube_service_"
63-
defaultLoadBalancerSourceRanges = "0.0.0.0/0"
64-
activeStatus = "ACTIVE"
65-
annotationXForwardedFor = "X-Forwarded-For"
63+
servicePrefix = "kube_service_"
64+
defaultLoadBalancerSourceRangesIPv4 = "0.0.0.0/0"
65+
defaultLoadBalancerSourceRangesIPv6 = "::/0"
66+
activeStatus = "ACTIVE"
67+
annotationXForwardedFor = "X-Forwarded-For"
6668

6769
ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/openstack-internal-load-balancer"
6870
ServiceAnnotationLoadBalancerConnLimit = "loadbalancer.openstack.org/connection-limit"
@@ -346,6 +348,7 @@ type serviceConfig struct {
346348
healthMonitorDelay int
347349
healthMonitorTimeout int
348350
healthMonitorMaxRetries int
351+
preferredIPFamily corev1.IPFamily // preferred (the first) IP family indicated in service's `spec.ipFamilies`
349352
}
350353

351354
type listenerKey struct {
@@ -651,8 +654,7 @@ func (lbaas *LbaasV2) createFullyPopulatedOctaviaLoadBalancer(name, clusterName
651654
}
652655

653656
// In case subnet ID is not configured
654-
if lbaas.opts.SubnetID == "" {
655-
lbaas.opts.SubnetID = loadbalancer.VipSubnetID
657+
if svcConf.lbMemberSubnetID == "" {
656658
svcConf.lbMemberSubnetID = loadbalancer.VipSubnetID
657659
}
658660

@@ -726,7 +728,8 @@ func cutString(original string) string {
726728
// In case no InternalIP can be found, ExternalIP is tried.
727729
// If neither InternalIP nor ExternalIP can be found an error is
728730
// returned.
729-
func nodeAddressForLB(node *corev1.Node) (string, error) {
731+
// If preferredIPFamily is specified, only address of the specified IP family can be returned.
732+
func nodeAddressForLB(node *corev1.Node, preferredIPFamily corev1.IPFamily) (string, error) {
730733
addrs := node.Status.Addresses
731734
if len(addrs) == 0 {
732735
return "", cpoerrors.ErrNoAddressFound
@@ -737,7 +740,18 @@ func nodeAddressForLB(node *corev1.Node) (string, error) {
737740
for _, allowedAddrType := range allowedAddrTypes {
738741
for _, addr := range addrs {
739742
if addr.Type == allowedAddrType {
740-
return addr.Address, nil
743+
switch preferredIPFamily {
744+
case corev1.IPv4Protocol:
745+
if netutils.IsIPv4String(addr.Address) {
746+
return addr.Address, nil
747+
}
748+
case corev1.IPv6Protocol:
749+
if netutils.IsIPv6String(addr.Address) {
750+
return addr.Address, nil
751+
}
752+
default:
753+
return addr.Address, nil
754+
}
741755
}
742756
}
743757
}
@@ -801,8 +815,8 @@ func getBoolFromServiceAnnotation(service *corev1.Service, annotationKey string,
801815
}
802816

803817
// getSubnetIDForLB returns subnet-id for a specific node
804-
func getSubnetIDForLB(compute *gophercloud.ServiceClient, node corev1.Node) (string, error) {
805-
ipAddress, err := nodeAddressForLB(&node)
818+
func getSubnetIDForLB(compute *gophercloud.ServiceClient, node corev1.Node, preferredIPFamily corev1.IPFamily) (string, error) {
819+
ipAddress, err := nodeAddressForLB(&node, preferredIPFamily)
806820
if err != nil {
807821
return "", err
808822
}
@@ -1308,7 +1322,7 @@ func (lbaas *LbaasV2) buildBatchUpdateMemberOpts(port corev1.ServicePort, nodes
13081322
newMembers := sets.NewString()
13091323

13101324
for _, node := range nodes {
1311-
addr, err := nodeAddressForLB(node)
1325+
addr, err := nodeAddressForLB(node, svcConf.preferredIPFamily)
13121326
if err != nil {
13131327
if err == cpoerrors.ErrNoAddressFound {
13141328
// Node failure, do not create member
@@ -1477,6 +1491,12 @@ func (lbaas *LbaasV2) checkServiceUpdate(service *corev1.Service, nodes []*corev
14771491
}
14781492
serviceName := fmt.Sprintf("%s/%s", service.Namespace, service.Name)
14791493

1494+
if len(service.Spec.IPFamilies) > 0 {
1495+
// Since OCCM does not support multiple load-balancers per service yet,
1496+
// the first IP family will determine the IP family of the load-balancer
1497+
svcConf.preferredIPFamily = service.Spec.IPFamilies[0]
1498+
}
1499+
14801500
svcConf.lbID = getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerID, "")
14811501
svcConf.supportLBTags = openstackutil.IsOctaviaFeatureSupported(lbaas.lb, openstackutil.OctaviaFeatureTags, lbaas.opts.LBProvider)
14821502

@@ -1497,7 +1517,7 @@ func (lbaas *LbaasV2) checkServiceUpdate(service *corev1.Service, nodes []*corev
14971517
} else {
14981518
svcConf.lbMemberSubnetID = getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerSubnetID, lbaas.opts.SubnetID)
14991519
if len(svcConf.lbMemberSubnetID) == 0 && len(nodes) > 0 {
1500-
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0])
1520+
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0], svcConf.preferredIPFamily)
15011521
if err != nil {
15021522
return fmt.Errorf("no subnet-id found for service %s: %v", serviceName, err)
15031523
}
@@ -1549,6 +1569,12 @@ func (lbaas *LbaasV2) checkService(service *corev1.Service, nodes []*corev1.Node
15491569
return fmt.Errorf("no service ports provided")
15501570
}
15511571

1572+
if len(service.Spec.IPFamilies) > 0 {
1573+
// Since OCCM does not support multiple load-balancers per service yet,
1574+
// the first IP family will determine the IP family of the load-balancer
1575+
svcConf.preferredIPFamily = service.Spec.IPFamilies[0]
1576+
}
1577+
15521578
svcConf.lbID = getStringFromServiceAnnotation(service, ServiceAnnotationLoadBalancerID, "")
15531579
svcConf.supportLBTags = openstackutil.IsOctaviaFeatureSupported(lbaas.lb, openstackutil.OctaviaFeatureTags, lbaas.opts.LBProvider)
15541580

@@ -1558,6 +1584,9 @@ func (lbaas *LbaasV2) checkService(service *corev1.Service, nodes []*corev1.Node
15581584
klog.V(3).InfoS("Enforcing internal LB", "annotation", true, "config", false)
15591585
}
15601586
svcConf.internal = true
1587+
} else if svcConf.preferredIPFamily == corev1.IPv6Protocol {
1588+
// floating IPs are not supported in IPv6 networks
1589+
svcConf.internal = true
15611590
} else {
15621591
svcConf.internal = getBoolFromServiceAnnotation(service, ServiceAnnotationLoadBalancerInternal, lbaas.opts.InternalLB)
15631592
}
@@ -1590,13 +1619,12 @@ func (lbaas *LbaasV2) checkService(service *corev1.Service, nodes []*corev1.Node
15901619
svcConf.lbMemberSubnetID = svcConf.lbSubnetID
15911620
}
15921621
if len(svcConf.lbNetworkID) == 0 && len(svcConf.lbSubnetID) == 0 {
1593-
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0])
1622+
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0], svcConf.preferredIPFamily)
15941623
if err != nil {
15951624
return fmt.Errorf("failed to get subnet to create load balancer for service %s: %v", serviceName, err)
15961625
}
15971626
svcConf.lbSubnetID = subnetID
15981627
svcConf.lbMemberSubnetID = subnetID
1599-
lbaas.opts.SubnetID = subnetID
16001628
}
16011629

16021630
if !svcConf.internal {
@@ -1698,7 +1726,7 @@ func (lbaas *LbaasV2) checkService(service *corev1.Service, nodes []*corev1.Node
16981726
}
16991727

17001728
var listenerAllowedCIDRs []string
1701-
sourceRanges, err := GetLoadBalancerSourceRanges(service)
1729+
sourceRanges, err := GetLoadBalancerSourceRanges(service, svcConf.preferredIPFamily)
17021730
if err != nil {
17031731
return fmt.Errorf("failed to get source ranges for loadbalancer service %s: %v", serviceName, err)
17041732
}
@@ -1932,7 +1960,7 @@ func (lbaas *LbaasV2) ensureOctaviaLoadBalancer(ctx context.Context, clusterName
19321960
}
19331961

19341962
if lbaas.opts.ManageSecurityGroups {
1935-
err := lbaas.ensureSecurityGroup(clusterName, service, nodes, loadbalancer)
1963+
err := lbaas.ensureSecurityGroup(clusterName, service, nodes, loadbalancer, svcConf.preferredIPFamily, svcConf.lbMemberSubnetID)
19361964
if err != nil {
19371965
return status, fmt.Errorf("failed when reconciling security groups for LB service %v/%v: %v", service.Namespace, service.Name, err)
19381966
}
@@ -1972,7 +2000,7 @@ func (lbaas *LbaasV2) ensureLoadBalancer(ctx context.Context, clusterName string
19722000
if len(lbaas.opts.SubnetID) == 0 && len(lbaas.opts.NetworkID) == 0 {
19732001
// Get SubnetID automatically.
19742002
// The LB needs to be configured with instance addresses on the same subnet, so get SubnetID by one node.
1975-
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0])
2003+
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0], "")
19762004
if err != nil {
19772005
klog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err)
19782006
return nil, fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+
@@ -2063,7 +2091,7 @@ func (lbaas *LbaasV2) ensureLoadBalancer(ctx context.Context, clusterName string
20632091
}
20642092
}
20652093

2066-
sourceRanges, err := GetLoadBalancerSourceRanges(apiService)
2094+
sourceRanges, err := GetLoadBalancerSourceRanges(apiService, "")
20672095
if err != nil {
20682096
return nil, fmt.Errorf("failed to get source ranges for loadbalancer service %s: %v", serviceName, err)
20692097
}
@@ -2201,7 +2229,7 @@ func (lbaas *LbaasV2) ensureLoadBalancer(ctx context.Context, clusterName string
22012229
return nil, fmt.Errorf("error getting pool members %s: %v", pool.ID, err)
22022230
}
22032231
for _, node := range nodes {
2204-
addr, err := nodeAddressForLB(node)
2232+
addr, err := nodeAddressForLB(node, "")
22052233
if err != nil {
22062234
if err == cpoerrors.ErrNotFound {
22072235
// Node failure, do not create member
@@ -2418,7 +2446,7 @@ func (lbaas *LbaasV2) ensureLoadBalancer(ctx context.Context, clusterName string
24182446
}
24192447

24202448
if lbaas.opts.ManageSecurityGroups {
2421-
err := lbaas.ensureSecurityGroup(clusterName, apiService, nodes, loadbalancer)
2449+
err := lbaas.ensureSecurityGroup(clusterName, apiService, nodes, loadbalancer, "", lbaas.opts.SubnetID)
24222450
if err != nil {
24232451
return status, fmt.Errorf("failed when reconciling security groups for LB service %v/%v: %v", apiService.Namespace, apiService.Name, err)
24242452
}
@@ -2476,7 +2504,8 @@ func (lbaas *LbaasV2) getSubnet(subnet string) (*subnets.Subnet, error) {
24762504

24772505
// ensureSecurityGroup ensures security group exist for specific loadbalancer service.
24782506
// Creating security group for specific loadbalancer service when it does not exist.
2479-
func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *corev1.Service, nodes []*corev1.Node, loadbalancer *loadbalancers.LoadBalancer) error {
2507+
func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *corev1.Service, nodes []*corev1.Node,
2508+
loadbalancer *loadbalancers.LoadBalancer, preferredIPFamily corev1.IPFamily, memberSubnetID string) error {
24802509
// find node-security-group for service
24812510
var err error
24822511
if len(lbaas.opts.NodeSecurityGroupIDs) == 0 && !lbaas.opts.UseOctavia {
@@ -2495,7 +2524,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *corev1
24952524
}
24962525

24972526
// get service source ranges
2498-
sourceRanges, err := GetLoadBalancerSourceRanges(apiService)
2527+
sourceRanges, err := GetLoadBalancerSourceRanges(apiService, preferredIPFamily)
24992528
if err != nil {
25002529
return fmt.Errorf("failed to get source ranges for loadbalancer service %s/%s: %v", apiService.Namespace, apiService.Name, err)
25012530
}
@@ -2628,9 +2657,9 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *corev1
26282657
// traffic from Octavia amphorae to the node port on the worker nodes.
26292658
if lbaas.opts.UseOctavia {
26302659
mc := metrics.NewMetricContext("subnet", "get")
2631-
subnet, err := subnets.Get(lbaas.network, lbaas.opts.SubnetID).Extract()
2660+
subnet, err := subnets.Get(lbaas.network, memberSubnetID).Extract()
26322661
if mc.ObserveRequest(err) != nil {
2633-
return fmt.Errorf("failed to find subnet %s from openstack: %v", lbaas.opts.SubnetID, err)
2662+
return fmt.Errorf("failed to find subnet %s from openstack: %v", memberSubnetID, err)
26342663
}
26352664

26362665
sgListopts := rules.ListOpts{
@@ -2649,6 +2678,11 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *corev1
26492678
continue
26502679
}
26512680

2681+
ethertype := rules.EtherType4
2682+
if netutils.IsIPv6CIDRString(subnet.CIDR) {
2683+
ethertype = rules.EtherType6
2684+
}
2685+
26522686
// The Octavia amphorae and worker nodes are supposed to be in the same subnet. We allow the ingress traffic
26532687
// from the amphorae to the specific node port on the nodes.
26542688
sgRuleCreateOpts := rules.CreateOpts{
@@ -2658,7 +2692,7 @@ func (lbaas *LbaasV2) ensureSecurityGroup(clusterName string, apiService *corev1
26582692
Protocol: toRuleProtocol(port.Protocol),
26592693
RemoteIPPrefix: subnet.CIDR,
26602694
SecGroupID: lbSecGroupID,
2661-
EtherType: rules.EtherType4,
2695+
EtherType: ethertype,
26622696
}
26632697
mc = metrics.NewMetricContext("security_group_rule", "create")
26642698
_, err = rules.Create(lbaas.network, sgRuleCreateOpts).Extract()
@@ -2797,7 +2831,7 @@ func (lbaas *LbaasV2) updateLoadBalancer(ctx context.Context, clusterName string
27972831
if len(lbaas.opts.SubnetID) == 0 && len(nodes) > 0 {
27982832
// Get SubnetID automatically.
27992833
// The LB needs to be configured with instance addresses on the same subnet, so get SubnetID by one node.
2800-
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0])
2834+
subnetID, err := getSubnetIDForLB(lbaas.compute, *nodes[0], "")
28012835
if err != nil {
28022836
klog.Warningf("Failed to find subnet-id for loadbalancer service %s/%s: %v", service.Namespace, service.Name, err)
28032837
return fmt.Errorf("no subnet-id for service %s/%s : subnet-id not set in cloud provider config, "+
@@ -2851,7 +2885,7 @@ func (lbaas *LbaasV2) updateLoadBalancer(ctx context.Context, clusterName string
28512885
// Compose Set of member (addresses) that _should_ exist
28522886
addrs := make(map[string]*corev1.Node)
28532887
for _, node := range nodes {
2854-
addr, err := nodeAddressForLB(node)
2888+
addr, err := nodeAddressForLB(node, "")
28552889
if err != nil {
28562890
return err
28572891
}
@@ -3332,7 +3366,7 @@ func IsAllowAll(ipnets netsets.IPNet) bool {
33323366
// GetLoadBalancerSourceRanges first try to parse and verify LoadBalancerSourceRanges field from a service.
33333367
// If the field is not specified, turn to parse and verify the AnnotationLoadBalancerSourceRangesKey annotation from a service,
33343368
// extracting the source ranges to allow, and if not present returns a default (allow-all) value.
3335-
func GetLoadBalancerSourceRanges(service *corev1.Service) (netsets.IPNet, error) {
3369+
func GetLoadBalancerSourceRanges(service *corev1.Service, preferredIPFamily corev1.IPFamily) (netsets.IPNet, error) {
33363370
var ipnets netsets.IPNet
33373371
var err error
33383372
// if SourceRange field is specified, ignore sourceRange annotation
@@ -3347,7 +3381,11 @@ func GetLoadBalancerSourceRanges(service *corev1.Service) (netsets.IPNet, error)
33473381
val := service.Annotations[corev1.AnnotationLoadBalancerSourceRangesKey]
33483382
val = strings.TrimSpace(val)
33493383
if val == "" {
3350-
val = defaultLoadBalancerSourceRanges
3384+
if preferredIPFamily == corev1.IPv6Protocol {
3385+
val = defaultLoadBalancerSourceRangesIPv6
3386+
} else {
3387+
val = defaultLoadBalancerSourceRangesIPv4
3388+
}
33513389
}
33523390
specs := strings.Split(val, ",")
33533391
ipnets, err = netsets.ParseIPNets(specs...)

pkg/util/errors/errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ func IsNotFound(err error) bool {
5454
}
5555
}
5656

57+
if err == ErrNotFound {
58+
return true
59+
}
60+
5761
return false
5862
}
5963

0 commit comments

Comments
 (0)