Skip to content

Commit 7964e30

Browse files
committed
Support the node label node.kubernetes.io/exclude-from-external-load-balancers
1 parent d8185e5 commit 7964e30

File tree

8 files changed

+308
-40
lines changed

8 files changed

+308
-40
lines changed

staging/src/k8s.io/legacy-cloud-providers/azure/azure_loadbalancer.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,15 @@ func (az *Cloud) findFrontendIPConfigOfService(
981981
return nil, false, nil
982982
}
983983

984+
func nodeNameInNodes(nodeName string, nodes []*v1.Node) bool {
985+
for _, node := range nodes {
986+
if strings.EqualFold(nodeName, node.Name) {
987+
return true
988+
}
989+
}
990+
return false
991+
}
992+
984993
// This ensures load balancer exists and the frontend ip config is setup.
985994
// This also reconciles the Service's Ports with the LoadBalancer config.
986995
// This entails adding rules/probes for expected Ports and removing stale rules/ports.
@@ -1022,6 +1031,42 @@ func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service,
10221031
if strings.EqualFold(*bp.Name, lbBackendPoolName) {
10231032
klog.V(10).Infof("reconcileLoadBalancer for service (%s)(%t): lb backendpool - found wanted backendpool. not adding anything", serviceName, wantLb)
10241033
foundBackendPool = true
1034+
1035+
var backendIPConfigurationsToBeDeleted []network.InterfaceIPConfiguration
1036+
if bp.BackendAddressPoolPropertiesFormat != nil && bp.BackendIPConfigurations != nil {
1037+
for _, ipConf := range *bp.BackendIPConfigurations {
1038+
ipConfID := to.String(ipConf.ID)
1039+
nodeName, err := az.VMSet.GetNodeNameByIPConfigurationID(ipConfID)
1040+
if err != nil {
1041+
return nil, err
1042+
}
1043+
// If a node is not supposed to be included in the LB, it
1044+
// would not be in the `nodes` slice. We need to check the nodes that
1045+
// have been added to the LB's backendpool, find the unwanted ones and
1046+
// delete them from the pool.
1047+
if !nodeNameInNodes(nodeName, nodes) {
1048+
klog.V(2).Infof("reconcileLoadBalancer for service (%s)(%t): lb backendpool - found unwanted node %s, decouple it from the LB", serviceName, wantLb, nodeName)
1049+
// construct a backendPool that only contains the IP config of the node to be deleted
1050+
backendIPConfigurationsToBeDeleted = append(backendIPConfigurationsToBeDeleted, network.InterfaceIPConfiguration{ID: to.StringPtr(ipConfID)})
1051+
}
1052+
}
1053+
if len(backendIPConfigurationsToBeDeleted) > 0 {
1054+
backendpoolToBeDeleted := &[]network.BackendAddressPool{
1055+
{
1056+
ID: to.StringPtr(lbBackendPoolID),
1057+
BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{
1058+
BackendIPConfigurations: &backendIPConfigurationsToBeDeleted,
1059+
},
1060+
},
1061+
}
1062+
vmSetName := az.mapLoadBalancerNameToVMSet(lbName, clusterName)
1063+
// decouple the backendPool from the node
1064+
err = az.VMSet.EnsureBackendPoolDeleted(service, lbBackendPoolID, vmSetName, backendpoolToBeDeleted)
1065+
if err != nil {
1066+
return nil, err
1067+
}
1068+
}
1069+
}
10251070
break
10261071
} else {
10271072
klog.V(10).Infof("reconcileLoadBalancer for service (%s)(%t): lb backendpool - found other backendpool %s", serviceName, wantLb, *bp.Name)
@@ -1294,6 +1339,10 @@ func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service,
12941339
// Remove backend pools from vmSets. This is required for virtual machine scale sets before removing the LB.
12951340
vmSetName := az.mapLoadBalancerNameToVMSet(lbName, clusterName)
12961341
klog.V(10).Infof("EnsureBackendPoolDeleted(%s,%s) for service %s: start", lbBackendPoolID, vmSetName, serviceName)
1342+
if _, ok := az.VMSet.(*availabilitySet); ok {
1343+
// do nothing for availability set
1344+
lb.BackendAddressPools = nil
1345+
}
12971346
err := az.VMSet.EnsureBackendPoolDeleted(service, lbBackendPoolID, vmSetName, lb.BackendAddressPools)
12981347
if err != nil {
12991348
klog.Errorf("EnsureBackendPoolDeleted(%s) for service %s failed: %v", lbBackendPoolID, serviceName, err)
@@ -2170,8 +2219,8 @@ func equalLoadBalancingRulePropertiesFormat(s *network.LoadBalancingRuleProperti
21702219
reflect.DeepEqual(s.FrontendPort, t.FrontendPort) &&
21712220
reflect.DeepEqual(s.BackendPort, t.BackendPort) &&
21722221
reflect.DeepEqual(s.EnableFloatingIP, t.EnableFloatingIP) &&
2173-
reflect.DeepEqual(s.EnableTCPReset, t.EnableTCPReset) &&
2174-
reflect.DeepEqual(s.DisableOutboundSnat, t.DisableOutboundSnat)
2222+
reflect.DeepEqual(to.Bool(s.EnableTCPReset), to.Bool(t.EnableTCPReset)) &&
2223+
reflect.DeepEqual(to.Bool(s.DisableOutboundSnat), to.Bool(t.DisableOutboundSnat))
21752224

21762225
if wantLB && s.IdleTimeoutInMinutes != nil && t.IdleTimeoutInMinutes != nil {
21772226
return properties && reflect.DeepEqual(s.IdleTimeoutInMinutes, t.IdleTimeoutInMinutes)

staging/src/k8s.io/legacy-cloud-providers/azure/azure_loadbalancer_test.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,11 +1762,12 @@ func getTestLoadBalancer(name, rgName, clusterName, identifier *string, service
17621762
ID: to.StringPtr("/subscriptions/subscription/resourceGroups/" + *rgName + "/providers/" +
17631763
"Microsoft.Network/loadBalancers/" + *name + "/backendAddressPools/" + *clusterName),
17641764
},
1765-
LoadDistribution: network.LoadDistribution("Default"),
1766-
FrontendPort: to.Int32Ptr(service.Spec.Ports[0].Port),
1767-
BackendPort: to.Int32Ptr(service.Spec.Ports[0].Port),
1768-
EnableFloatingIP: to.BoolPtr(true),
1769-
EnableTCPReset: to.BoolPtr(strings.EqualFold(lbSku, "standard")),
1765+
LoadDistribution: network.LoadDistribution("Default"),
1766+
FrontendPort: to.Int32Ptr(service.Spec.Ports[0].Port),
1767+
BackendPort: to.Int32Ptr(service.Spec.Ports[0].Port),
1768+
EnableFloatingIP: to.BoolPtr(true),
1769+
EnableTCPReset: to.BoolPtr(strings.EqualFold(lbSku, "standard")),
1770+
DisableOutboundSnat: to.BoolPtr(false),
17701771
Probe: &network.SubResource{
17711772
ID: to.StringPtr("/subscriptions/subscription/resourceGroups/" + *rgName + "/providers/Microsoft.Network/loadBalancers/testCluster/probes/aservice1-TCP-80"),
17721773
},
@@ -1833,8 +1834,6 @@ func TestReconcileLoadBalancer(t *testing.T) {
18331834
},
18341835
}
18351836
expectedLb1 := getTestLoadBalancer(to.StringPtr("testCluster"), to.StringPtr("rg"), to.StringPtr("testCluster"), to.StringPtr("aservice1"), service3, "Basic")
1836-
(*expectedLb1.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].DisableOutboundSnat = to.BoolPtr(false)
1837-
(*expectedLb1.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].EnableTCPReset = nil
18381837
expectedLb1.FrontendIPConfigurations = &[]network.FrontendIPConfiguration{
18391838
{
18401839
Name: to.StringPtr("aservice1"),
@@ -1970,9 +1969,7 @@ func TestReconcileLoadBalancer(t *testing.T) {
19701969
lb6.Probes = &[]network.Probe{}
19711970
expectedLB6 := getTestLoadBalancer(to.StringPtr("testCluster"), to.StringPtr("rg"), to.StringPtr("testCluster"), to.StringPtr("aservice1"), service6, "basic")
19721971
expectedLB6.Probes = &[]network.Probe{}
1973-
(*expectedLB6.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].Probe = nil
1974-
(*expectedLB6.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].EnableTCPReset = nil
1975-
(*expectedLB6.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].DisableOutboundSnat = to.BoolPtr(false)
1972+
(*expectedLB6.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].Probe = &network.SubResource{ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/testCluster/probes/aservice1-TCP-80")}
19761973
expectedLB6.FrontendIPConfigurations = &[]network.FrontendIPConfiguration{
19771974
{
19781975
Name: to.StringPtr("aservice1"),
@@ -1994,7 +1991,7 @@ func TestReconcileLoadBalancer(t *testing.T) {
19941991
ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/testCluster/probes/aservice1-UDP-80"),
19951992
}
19961993
(*expectedLB7.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].EnableTCPReset = nil
1997-
(*expectedLB7.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].DisableOutboundSnat = to.BoolPtr(false)
1994+
(*lb7.LoadBalancerPropertiesFormat.LoadBalancingRules)[0].DisableOutboundSnat = to.BoolPtr(true)
19981995
expectedLB7.FrontendIPConfigurations = &[]network.FrontendIPConfiguration{
19991996
{
20001997
Name: to.StringPtr("aservice1"),

staging/src/k8s.io/legacy-cloud-providers/azure/azure_standard.go

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,14 @@ const (
7171
loadBalancerRuleNameMaxLength = 80
7272
)
7373

74-
var errNotInVMSet = errors.New("vm is not in the vmset")
75-
var providerIDRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/Microsoft.Compute/virtualMachines/(.+)$`)
76-
var backendPoolIDRE = regexp.MustCompile(`^/subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Network/loadBalancers/(.+)/backendAddressPools/(?:.*)`)
77-
var nicResourceGroupRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/resourceGroups/(.+)/providers/Microsoft.Network/networkInterfaces/(?:.*)`)
74+
var (
75+
errNotInVMSet = errors.New("vm is not in the vmset")
76+
providerIDRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/Microsoft.Compute/virtualMachines/(.+)$`)
77+
backendPoolIDRE = regexp.MustCompile(`^/subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Network/loadBalancers/(.+)/backendAddressPools/(?:.*)`)
78+
nicResourceGroupRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/resourceGroups/(.+)/providers/Microsoft.Network/networkInterfaces/(?:.*)`)
79+
nicIDRE = regexp.MustCompile(`/subscriptions/(?:.*)/resourceGroups/(?:.+)/providers/Microsoft.Network/networkInterfaces/(.+)-nic-(.+)/ipConfigurations/(?:.*)`)
80+
vmasIDRE = regexp.MustCompile(`/subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Compute/availabilitySets/(.+)`)
81+
)
7882

7983
// getStandardMachineID returns the full identifier of a virtual machine.
8084
func (az *Cloud) getStandardMachineID(subscriptionID, resourceGroup, machineName string) string {
@@ -700,7 +704,8 @@ func (as *availabilitySet) GetVMSetNames(service *v1.Service, nodes []*v1.Node)
700704

701705
// GetPrimaryInterface gets machine primary network interface by node name.
702706
func (as *availabilitySet) GetPrimaryInterface(nodeName string) (network.Interface, error) {
703-
return as.getPrimaryInterfaceWithVMSet(nodeName, "")
707+
nic, _, err := as.getPrimaryInterfaceWithVMSet(nodeName, "")
708+
return nic, err
704709
}
705710

706711
// extractResourceGroupByNicID extracts the resource group name by nicID.
@@ -714,26 +719,26 @@ func extractResourceGroupByNicID(nicID string) (string, error) {
714719
}
715720

716721
// getPrimaryInterfaceWithVMSet gets machine primary network interface by node name and vmSet.
717-
func (as *availabilitySet) getPrimaryInterfaceWithVMSet(nodeName, vmSetName string) (network.Interface, error) {
722+
func (as *availabilitySet) getPrimaryInterfaceWithVMSet(nodeName, vmSetName string) (network.Interface, string, error) {
718723
var machine compute.VirtualMachine
719724

720725
machine, err := as.GetVirtualMachineWithRetry(types.NodeName(nodeName), azcache.CacheReadTypeDefault)
721726
if err != nil {
722727
klog.V(2).Infof("GetPrimaryInterface(%s, %s) abort backoff", nodeName, vmSetName)
723-
return network.Interface{}, err
728+
return network.Interface{}, "", err
724729
}
725730

726731
primaryNicID, err := getPrimaryInterfaceID(machine)
727732
if err != nil {
728-
return network.Interface{}, err
733+
return network.Interface{}, "", err
729734
}
730735
nicName, err := getLastSegment(primaryNicID, "/")
731736
if err != nil {
732-
return network.Interface{}, err
737+
return network.Interface{}, "", err
733738
}
734739
nodeResourceGroup, err := as.GetNodeResourceGroup(nodeName)
735740
if err != nil {
736-
return network.Interface{}, err
741+
return network.Interface{}, "", err
737742
}
738743

739744
// Check availability set name. Note that vmSetName is empty string when getting
@@ -748,31 +753,35 @@ func (as *availabilitySet) getPrimaryInterfaceWithVMSet(nodeName, vmSetName stri
748753
if machine.AvailabilitySet == nil || !strings.EqualFold(*machine.AvailabilitySet.ID, expectedAvailabilitySetName) {
749754
klog.V(3).Infof(
750755
"GetPrimaryInterface: nic (%s) is not in the availabilitySet(%s)", nicName, vmSetName)
751-
return network.Interface{}, errNotInVMSet
756+
return network.Interface{}, "", errNotInVMSet
752757
}
753758
}
754759

755760
nicResourceGroup, err := extractResourceGroupByNicID(primaryNicID)
756761
if err != nil {
757-
return network.Interface{}, err
762+
return network.Interface{}, "", err
758763
}
759764

760765
ctx, cancel := getContextWithCancel()
761766
defer cancel()
762767
nic, rerr := as.InterfacesClient.Get(ctx, nicResourceGroup, nicName, "")
763768
if rerr != nil {
764-
return network.Interface{}, rerr.Error()
769+
return network.Interface{}, "", rerr.Error()
765770
}
766771

767-
return nic, nil
772+
var availabilitySetID string
773+
if machine.VirtualMachineProperties != nil && machine.AvailabilitySet != nil {
774+
availabilitySetID = to.String(machine.AvailabilitySet.ID)
775+
}
776+
return nic, availabilitySetID, nil
768777
}
769778

770779
// EnsureHostInPool ensures the given VM's Primary NIC's Primary IP Configuration is
771780
// participating in the specified LoadBalancer Backend Pool.
772781
func (as *availabilitySet) EnsureHostInPool(service *v1.Service, nodeName types.NodeName, backendPoolID string, vmSetName string, isInternal bool) (string, string, string, *compute.VirtualMachineScaleSetVM, error) {
773782
vmName := mapNodeNameToVMName(nodeName)
774783
serviceName := getServiceName(service)
775-
nic, err := as.getPrimaryInterfaceWithVMSet(vmName, vmSetName)
784+
nic, _, err := as.getPrimaryInterfaceWithVMSet(vmName, vmSetName)
776785
if err != nil {
777786
if err == errNotInVMSet {
778787
klog.V(3).Infof("EnsureHostInPool skips node %s because it is not in the vmSet %s", nodeName, vmSetName)
@@ -895,7 +904,111 @@ func (as *availabilitySet) EnsureHostsInPool(service *v1.Service, nodes []*v1.No
895904

896905
// EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified nodes.
897906
func (as *availabilitySet) EnsureBackendPoolDeleted(service *v1.Service, backendPoolID, vmSetName string, backendAddressPools *[]network.BackendAddressPool) error {
898-
// Do nothing for availability set.
907+
// Returns nil if backend address pools already deleted.
908+
if backendAddressPools == nil {
909+
return nil
910+
}
911+
912+
mc := metrics.NewMetricContext("services", "vmas_ensure_backend_pool_deleted", as.ResourceGroup, as.SubscriptionID, service.Name)
913+
isOperationSucceeded := false
914+
defer func() {
915+
mc.ObserveOperationWithResult(isOperationSucceeded)
916+
}()
917+
918+
ipConfigurationIDs := []string{}
919+
for _, backendPool := range *backendAddressPools {
920+
if strings.EqualFold(to.String(backendPool.ID), backendPoolID) &&
921+
backendPool.BackendAddressPoolPropertiesFormat != nil &&
922+
backendPool.BackendIPConfigurations != nil {
923+
for _, ipConf := range *backendPool.BackendIPConfigurations {
924+
if ipConf.ID == nil {
925+
continue
926+
}
927+
928+
ipConfigurationIDs = append(ipConfigurationIDs, *ipConf.ID)
929+
}
930+
}
931+
}
932+
nicUpdaters := make([]func() error, 0)
933+
errors := make([]error, 0)
934+
for i := range ipConfigurationIDs {
935+
ipConfigurationID := ipConfigurationIDs[i]
936+
nodeName, err := as.GetNodeNameByIPConfigurationID(ipConfigurationID)
937+
if err != nil {
938+
klog.Errorf("Failed to GetNodeNameByIPConfigurationID(%s): %v", ipConfigurationID, err)
939+
errors = append(errors, err)
940+
continue
941+
}
942+
943+
vmName := mapNodeNameToVMName(types.NodeName(nodeName))
944+
nic, vmasID, err := as.getPrimaryInterfaceWithVMSet(vmName, vmSetName)
945+
if err != nil {
946+
if err == errNotInVMSet {
947+
klog.V(3).Infof("EnsureBackendPoolDeleted skips node %s because it is not in the vmSet %s", nodeName, vmSetName)
948+
return nil
949+
}
950+
951+
klog.Errorf("error: az.EnsureBackendPoolDeleted(%s), az.VMSet.GetPrimaryInterface.Get(%s, %s), err=%v", nodeName, vmName, vmSetName, err)
952+
return err
953+
}
954+
matches := vmasIDRE.FindStringSubmatch(vmasID)
955+
if len(matches) != 2 {
956+
return fmt.Errorf("EnsureBackendPoolDeleted: failed to parse the VMAS ID %s: %v", vmasID, err)
957+
}
958+
vmasName := matches[1]
959+
// Only remove nodes belonging to specified vmSet to basic LB backends.
960+
if !strings.EqualFold(vmasName, vmSetName) {
961+
klog.V(2).Infof("EnsureBackendPoolDeleted: skipping the node %s belonging to another vm set %s", nodeName, vmasName)
962+
continue
963+
}
964+
965+
if nic.ProvisioningState != nil && *nic.ProvisioningState == nicFailedState {
966+
klog.Warningf("EnsureBackendPoolDeleted skips node %s because its primary nic %s is in Failed state", nodeName, *nic.Name)
967+
return nil
968+
}
969+
970+
if nic.InterfacePropertiesFormat != nil && nic.InterfacePropertiesFormat.IPConfigurations != nil {
971+
newIPConfigs := *nic.IPConfigurations
972+
for j, ipConf := range newIPConfigs {
973+
if !to.Bool(ipConf.Primary) {
974+
continue
975+
}
976+
// found primary ip configuration
977+
if ipConf.LoadBalancerBackendAddressPools != nil {
978+
newLBAddressPools := *ipConf.LoadBalancerBackendAddressPools
979+
for k, pool := range newLBAddressPools {
980+
if strings.EqualFold(to.String(pool.ID), backendPoolID) {
981+
newLBAddressPools = append(newLBAddressPools[:k], newLBAddressPools[k+1:]...)
982+
break
983+
}
984+
}
985+
newIPConfigs[j].LoadBalancerBackendAddressPools = &newLBAddressPools
986+
}
987+
}
988+
nic.IPConfigurations = &newIPConfigs
989+
nicUpdaters = append(nicUpdaters, func() error {
990+
ctx, cancel := getContextWithCancel()
991+
defer cancel()
992+
klog.V(2).Infof("EnsureBackendPoolDeleted begins to CreateOrUpdate for NIC(%s, %s) with backendPoolID %s", as.resourceGroup, to.String(nic.Name), backendPoolID)
993+
rerr := as.InterfacesClient.CreateOrUpdate(ctx, as.ResourceGroup, to.String(nic.Name), nic)
994+
if rerr != nil {
995+
klog.Errorf("EnsureBackendPoolDeleted CreateOrUpdate for NIC(%s, %s) failed with error %v", as.resourceGroup, to.String(nic.Name), rerr.Error())
996+
return rerr.Error()
997+
}
998+
return nil
999+
})
1000+
}
1001+
}
1002+
errs := utilerrors.AggregateGoroutines(nicUpdaters...)
1003+
if errs != nil {
1004+
return utilerrors.Flatten(errs)
1005+
}
1006+
// Fail if there are other errors.
1007+
if len(errors) > 0 {
1008+
return utilerrors.Flatten(utilerrors.NewAggregate(errors))
1009+
}
1010+
1011+
isOperationSucceeded = true
8991012
return nil
9001013
}
9011014

@@ -908,3 +1021,16 @@ func generateStorageAccountName(accountNamePrefix string) string {
9081021
}
9091022
return accountName
9101023
}
1024+
1025+
// GetNodeNameByIPConfigurationID gets the node name by IP configuration ID.
1026+
func (as *availabilitySet) GetNodeNameByIPConfigurationID(ipConfigurationID string) (string, error) {
1027+
matches := nicIDRE.FindStringSubmatch(ipConfigurationID)
1028+
if len(matches) != 3 {
1029+
klog.V(4).Infof("Can not extract VM name from ipConfigurationID (%s)", ipConfigurationID)
1030+
return "", fmt.Errorf("invalid ip config ID %s", ipConfigurationID)
1031+
}
1032+
1033+
prefix := matches[1]
1034+
suffix := matches[2]
1035+
return fmt.Sprintf("%s-%s", prefix, suffix), nil
1036+
}

0 commit comments

Comments
 (0)