Skip to content

Commit 98f95b2

Browse files
author
Matt Pryor
committed
Allow clusters without floating IPs
1 parent 7a9b9d1 commit 98f95b2

File tree

8 files changed

+142
-51
lines changed

8 files changed

+142
-51
lines changed

api/v1alpha4/openstackcluster_types.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,29 @@ type OpenStackClusterSpec struct {
5858
ExternalNetworkID string `json:"externalNetworkId,omitempty"`
5959

6060
// ManagedAPIServerLoadBalancer defines whether a LoadBalancer for the
61-
// APIServer should be created. If set to true the following properties are
62-
// mandatory: APIServerFloatingIP, APIServerPort
61+
// APIServer should be created.
6362
// +optional
6463
ManagedAPIServerLoadBalancer bool `json:"managedAPIServerLoadBalancer"`
6564

66-
// APIServerFloatingIP is the floatingIP which will be associated
67-
// to the APIServer. The floatingIP will be created if it not
68-
// already exists.
65+
// DisableAPIServerFloatingIP determines whether or not to attempt to attach a floating
66+
// IP to the API server. This allows for the creation of clusters when attaching a floating
67+
// IP to the API server (and hence, in many cases, exposing the API server to the internet)
68+
// is not possible or desirable, e.g. if using a shared VLAN for communication between
69+
// management and workload clusters or when the management cluster is inside the
70+
// project network.
71+
// This option requires that the API server use a VIP on the cluster network so that the
72+
// underlying machines can change without changing ControlPlaneEndpoint.Host.
73+
// When using a managed load balancer, this VIP will be managed automatically.
74+
// If not using a managed load balancer, cluster configuration will fail without additional
75+
// configuration to manage the VIP on the control plane machines, which falls outside of
76+
// the scope of this controller.
77+
// +optional
78+
DisableAPIServerFloatingIP bool `json:"disableAPIServerFloatingIP"`
79+
80+
// APIServerFloatingIP is the floatingIP which will be associated with the API server.
81+
// The floatingIP will be created if it does not already exist.
82+
// If not specified, a new floatingIP is allocated.
83+
// This field is not used if DisableAPIServerFloatingIP is set.
6984
APIServerFloatingIP string `json:"apiServerFloatingIP,omitempty"`
7085

7186
// APIServerPort is the port on which the listener on the APIServer

config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,8 +1080,9 @@ spec:
10801080
type: boolean
10811081
apiServerFloatingIP:
10821082
description: APIServerFloatingIP is the floatingIP which will be associated
1083-
to the APIServer. The floatingIP will be created if it not already
1084-
exists.
1083+
with the API server. The floatingIP will be created if it does not
1084+
already exist. If not specified, a new floatingIP is allocated.
1085+
This field is not used if DisableAPIServerFloatingIP is set.
10851086
type: string
10861087
apiServerLoadBalancerAdditionalPorts:
10871088
description: APIServerLoadBalancerAdditionalPorts adds additional
@@ -1451,6 +1452,21 @@ spec:
14511452
- host
14521453
- port
14531454
type: object
1455+
disableAPIServerFloatingIP:
1456+
description: DisableAPIServerFloatingIP determines whether or not
1457+
to attempt to attach a floating IP to the API server. This allows
1458+
for the creation of clusters when attaching a floating IP to the
1459+
API server (and hence, in many cases, exposing the API server to
1460+
the internet) is not possible or desirable, e.g. if using a shared
1461+
VLAN for communication between management and workload clusters
1462+
or when the management cluster is inside the project network. This
1463+
option requires that the API server use a VIP on the cluster network
1464+
so that the underlying machines can change without changing ControlPlaneEndpoint.Host.
1465+
When using a managed load balancer, this VIP will be managed automatically.
1466+
If not using a managed load balancer, cluster configuration will
1467+
fail without additional configuration to manage the VIP on the control
1468+
plane machines, which falls outside of the scope of this controller.
1469+
type: boolean
14541470
disablePortSecurity:
14551471
description: DisablePortSecurity disables the port security of the
14561472
network created for the Kubernetes cluster, which also disables
@@ -1554,9 +1570,8 @@ spec:
15541570
- name
15551571
type: object
15561572
managedAPIServerLoadBalancer:
1557-
description: 'ManagedAPIServerLoadBalancer defines whether a LoadBalancer
1558-
for the APIServer should be created. If set to true the following
1559-
properties are mandatory: APIServerFloatingIP, APIServerPort'
1573+
description: ManagedAPIServerLoadBalancer defines whether a LoadBalancer
1574+
for the APIServer should be created.
15601575
type: boolean
15611576
managedSecurityGroups:
15621577
description: ManagedSecurityGroups determines whether OpenStack security

config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ spec:
5959
type: boolean
6060
apiServerFloatingIP:
6161
description: APIServerFloatingIP is the floatingIP which will
62-
be associated to the APIServer. The floatingIP will be created
63-
if it not already exists.
62+
be associated with the API server. The floatingIP will be
63+
created if it does not already exist. If not specified,
64+
a new floatingIP is allocated. This field is not used if
65+
DisableAPIServerFloatingIP is set.
6466
type: string
6567
apiServerLoadBalancerAdditionalPorts:
6668
description: APIServerLoadBalancerAdditionalPorts adds additional
@@ -438,6 +440,23 @@ spec:
438440
- host
439441
- port
440442
type: object
443+
disableAPIServerFloatingIP:
444+
description: DisableAPIServerFloatingIP determines whether
445+
or not to attempt to attach a floating IP to the API server.
446+
This allows for the creation of clusters when attaching
447+
a floating IP to the API server (and hence, in many cases,
448+
exposing the API server to the internet) is not possible
449+
or desirable, e.g. if using a shared VLAN for communication
450+
between management and workload clusters or when the management
451+
cluster is inside the project network. This option requires
452+
that the API server use a VIP on the cluster network so
453+
that the underlying machines can change without changing
454+
ControlPlaneEndpoint.Host. When using a managed load balancer,
455+
this VIP will be managed automatically. If not using a managed
456+
load balancer, cluster configuration will fail without additional
457+
configuration to manage the VIP on the control plane machines,
458+
which falls outside of the scope of this controller.
459+
type: boolean
441460
disablePortSecurity:
442461
description: DisablePortSecurity disables the port security
443462
of the network created for the Kubernetes cluster, which
@@ -545,10 +564,8 @@ spec:
545564
- name
546565
type: object
547566
managedAPIServerLoadBalancer:
548-
description: 'ManagedAPIServerLoadBalancer defines whether
549-
a LoadBalancer for the APIServer should be created. If set
550-
to true the following properties are mandatory: APIServerFloatingIP,
551-
APIServerPort'
567+
description: ManagedAPIServerLoadBalancer defines whether
568+
a LoadBalancer for the APIServer should be created.
552569
type: boolean
553570
managedSecurityGroups:
554571
description: ManagedSecurityGroups determines whether OpenStack

controllers/openstackcluster_controller.go

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -438,44 +438,69 @@ func reconcileNetworkComponents(log logr.Logger, osProviderClient *gophercloud.P
438438
return errors.Errorf("failed to reconcile router: %v", err)
439439
}
440440
}
441-
if !openStackCluster.Spec.ControlPlaneEndpoint.IsValid() {
442-
var port int32
443-
if openStackCluster.Spec.APIServerPort == 0 {
444-
port = 6443
445-
} else {
446-
port = int32(openStackCluster.Spec.APIServerPort)
447-
}
448-
fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, clusterName, openStackCluster.Spec.APIServerFloatingIP)
449-
if err != nil {
450-
handleUpdateOSCError(openStackCluster, errors.Errorf("Floating IP cannot be got or created: %v", err))
451-
return errors.Errorf("Floating IP cannot be got or created: %v", err)
452-
}
453-
// Set APIEndpoints so the Cluster API Cluster Controller can pull them
454-
openStackCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
455-
Host: fp.FloatingIP,
456-
Port: port,
457-
}
458-
}
459441

460442
err = networkingService.ReconcileSecurityGroups(openStackCluster, clusterName)
461443
if err != nil {
462444
handleUpdateOSCError(openStackCluster, errors.Errorf("failed to reconcile security groups: %v", err))
463445
return errors.Errorf("failed to reconcile security groups: %v", err)
464446
}
465447

448+
// Calculate the port that we will use for the API server
449+
var apiServerPort int
450+
if openStackCluster.Spec.ControlPlaneEndpoint.IsValid() {
451+
apiServerPort = int(openStackCluster.Spec.ControlPlaneEndpoint.Port)
452+
} else if openStackCluster.Spec.APIServerPort != 0 {
453+
apiServerPort = openStackCluster.Spec.APIServerPort
454+
} else {
455+
apiServerPort = 6443
456+
}
457+
466458
if openStackCluster.Spec.ManagedAPIServerLoadBalancer {
467459
loadBalancerService, err := loadbalancer.NewService(osProviderClient, clientOpts, log)
468460
if err != nil {
469461
return err
470462
}
471463

472-
err = loadBalancerService.ReconcileLoadBalancer(openStackCluster, clusterName)
464+
err = loadBalancerService.ReconcileLoadBalancer(openStackCluster, clusterName, apiServerPort)
473465
if err != nil {
474466
handleUpdateOSCError(openStackCluster, errors.Errorf("failed to reconcile load balancer: %v", err))
475467
return errors.Errorf("failed to reconcile load balancer: %v", err)
476468
}
477469
}
478470

471+
if !openStackCluster.Spec.ControlPlaneEndpoint.IsValid() {
472+
var host string
473+
// If there is a load balancer use the floating IP for it if set, falling back to the internal IP
474+
if openStackCluster.Spec.ManagedAPIServerLoadBalancer {
475+
if openStackCluster.Status.Network.APIServerLoadBalancer.IP != "" {
476+
host = openStackCluster.Status.Network.APIServerLoadBalancer.IP
477+
} else {
478+
host = openStackCluster.Status.Network.APIServerLoadBalancer.InternalIP
479+
}
480+
} else if !openStackCluster.Spec.DisableAPIServerFloatingIP {
481+
// If floating IPs are not disabled, get one to use as the VIP for the control plane
482+
fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, clusterName, openStackCluster.Spec.APIServerFloatingIP)
483+
if err != nil {
484+
handleUpdateOSCError(openStackCluster, errors.Errorf("Floating IP cannot be got or created: %v", err))
485+
return errors.Errorf("Floating IP cannot be got or created: %v", err)
486+
}
487+
host = fp.FloatingIP
488+
} else {
489+
// This case is not managed for now (i.e. no load balancer + no floating IP)
490+
// We could manage a VIP port on the cluster network and set allowedAddressPairs accordingly
491+
// when creating control plane machines, but this would require us to deploy software on the
492+
// control plane hosts to manage the VIP (e.g. keepalived/kube-vip)
493+
// It is still possible for a user to deploy this case manually using existing options
494+
return errors.New("unable to determine VIP for API server - either load balancer or floating IP must be enabled")
495+
}
496+
497+
// Set APIEndpoints so the Cluster API Cluster Controller can pull them
498+
openStackCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
499+
Host: host,
500+
Port: int32(apiServerPort),
501+
}
502+
}
503+
479504
return nil
480505
}
481506

controllers/openstackmachine_controller.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,12 @@ func (r *OpenStackMachineReconciler) reconcileNormal(ctx context.Context, logger
360360
handleUpdateMachineError(logger, openStackMachine, errors.Errorf("LoadBalancerMember cannot be reconciled: %v", err))
361361
return ctrl.Result{}, nil
362362
}
363-
} else if util.IsControlPlaneMachine(machine) {
364-
fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, clusterName, openStackCluster.Spec.ControlPlaneEndpoint.Host)
363+
} else if util.IsControlPlaneMachine(machine) && !openStackCluster.Spec.DisableAPIServerFloatingIP {
364+
floatingIPAddress := openStackCluster.Spec.ControlPlaneEndpoint.Host
365+
if openStackCluster.Spec.APIServerFloatingIP != "" {
366+
floatingIPAddress = openStackCluster.Spec.APIServerFloatingIP
367+
}
368+
fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, clusterName, floatingIPAddress)
365369
if err != nil {
366370
handleUpdateMachineError(logger, openStackMachine, errors.Errorf("Floating IP cannot be got or created: %v", err))
367371
return ctrl.Result{}, nil

pkg/cloud/services/loadbalancer/loadbalancer.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const (
4040
kubeapiLBSuffix string = "kubeapi"
4141
)
4242

43-
func (s *Service) ReconcileLoadBalancer(openStackCluster *infrav1.OpenStackCluster, clusterName string) error {
43+
func (s *Service) ReconcileLoadBalancer(openStackCluster *infrav1.OpenStackCluster, clusterName string, apiServerPort int) error {
4444
loadBalancerName := getLoadBalancerName(clusterName)
4545
s.logger.Info("Reconciling load balancer", "name", loadBalancerName)
4646

@@ -49,19 +49,25 @@ func (s *Service) ReconcileLoadBalancer(openStackCluster *infrav1.OpenStackClust
4949
return err
5050
}
5151

52-
floatingIPAddress := openStackCluster.Spec.ControlPlaneEndpoint.Host
53-
if openStackCluster.Spec.APIServerFloatingIP != "" {
54-
floatingIPAddress = openStackCluster.Spec.APIServerFloatingIP
55-
}
56-
fp, err := s.networkingService.GetOrCreateFloatingIP(openStackCluster, clusterName, floatingIPAddress)
57-
if err != nil {
58-
return err
59-
}
60-
if err = s.networkingService.AssociateFloatingIP(openStackCluster, fp, lb.VipPortID); err != nil {
61-
return err
52+
var lbFloatingIP string
53+
if !openStackCluster.Spec.DisableAPIServerFloatingIP {
54+
var floatingIPAddress string
55+
if openStackCluster.Spec.APIServerFloatingIP != "" {
56+
floatingIPAddress = openStackCluster.Spec.APIServerFloatingIP
57+
} else if openStackCluster.Spec.ControlPlaneEndpoint.IsValid() {
58+
floatingIPAddress = openStackCluster.Spec.ControlPlaneEndpoint.Host
59+
}
60+
fp, err := s.networkingService.GetOrCreateFloatingIP(openStackCluster, clusterName, floatingIPAddress)
61+
if err != nil {
62+
return err
63+
}
64+
if err = s.networkingService.AssociateFloatingIP(openStackCluster, fp, lb.VipPortID); err != nil {
65+
return err
66+
}
67+
lbFloatingIP = fp.FloatingIP
6268
}
6369

64-
portList := []int{int(openStackCluster.Spec.ControlPlaneEndpoint.Port)}
70+
portList := []int{apiServerPort}
6571
portList = append(portList, openStackCluster.Spec.APIServerLoadBalancerAdditionalPorts...)
6672
for _, port := range portList {
6773
lbPortObjectsName := fmt.Sprintf("%s-%d", loadBalancerName, port)
@@ -84,7 +90,7 @@ func (s *Service) ReconcileLoadBalancer(openStackCluster *infrav1.OpenStackClust
8490
Name: lb.Name,
8591
ID: lb.ID,
8692
InternalIP: lb.VipAddress,
87-
IP: fp.FloatingIP,
93+
IP: lbFloatingIP,
8894
}
8995
return nil
9096
}

pkg/cloud/services/networking/floatingip.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package networking
1818

1919
import (
20+
"errors"
2021
"time"
2122

2223
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags"
@@ -46,6 +47,11 @@ func (s *Service) GetOrCreateFloatingIP(openStackCluster *infrav1.OpenStackClust
4647
fpCreateOpts.FloatingIP = ip
4748
}
4849

50+
// Trying to create a new floating IP is not possible without an external network
51+
if openStackCluster.Status.ExternalNetwork == nil || openStackCluster.Status.ExternalNetwork.ID == "" {
52+
return nil, errors.New("cannot create floating IP without external network")
53+
}
54+
4955
fpCreateOpts.FloatingNetworkID = openStackCluster.Status.ExternalNetwork.ID
5056
fpCreateOpts.Description = names.GetDescription(clusterName)
5157

pkg/cloud/services/networking/network.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ func (s *Service) ReconcileExternalNetwork(openStackCluster *infrav1.OpenStackCl
7272

7373
switch len(networkList) {
7474
case 0:
75-
return fmt.Errorf("external network not found")
75+
// Not finding an external network is fine
76+
openStackCluster.Status.ExternalNetwork = &infrav1.Network{}
77+
s.logger.Info("No external network found - proceeding with internal network only")
78+
return nil
7679
case 1:
7780
openStackCluster.Status.ExternalNetwork = &infrav1.Network{
7881
ID: networkList[0].ID,

0 commit comments

Comments
 (0)