sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/loadbalancers/spec.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package loadbalancers 18 19 import ( 20 "context" 21 22 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" 23 "github.com/pkg/errors" 24 "k8s.io/utils/ptr" 25 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 26 "sigs.k8s.io/cluster-api-provider-azure/azure" 27 "sigs.k8s.io/cluster-api-provider-azure/azure/converters" 28 ) 29 30 // LBSpec defines the specification for a Load Balancer. 31 type LBSpec struct { 32 Name string 33 ResourceGroup string 34 SubscriptionID string 35 ClusterName string 36 Location string 37 ExtendedLocation *infrav1.ExtendedLocationSpec 38 Role string 39 Type infrav1.LBType 40 SKU infrav1.SKU 41 VNetName string 42 VNetResourceGroup string 43 SubnetName string 44 BackendPoolName string 45 FrontendIPConfigs []infrav1.FrontendIP 46 APIServerPort int32 47 IdleTimeoutInMinutes *int32 48 AdditionalTags map[string]string 49 } 50 51 // ResourceName returns the name of the load balancer. 52 func (s *LBSpec) ResourceName() string { 53 return s.Name 54 } 55 56 // ResourceGroupName returns the name of the resource group. 57 func (s *LBSpec) ResourceGroupName() string { 58 return s.ResourceGroup 59 } 60 61 // OwnerResourceName is a no-op for load balancers. 62 func (s *LBSpec) OwnerResourceName() string { 63 return "" 64 } 65 66 // Parameters returns the parameters for the load balancer. 67 func (s *LBSpec) Parameters(ctx context.Context, existing interface{}) (parameters interface{}, err error) { 68 var ( 69 etag *string 70 frontendIDs []*armnetwork.SubResource 71 frontendIPConfigs []*armnetwork.FrontendIPConfiguration 72 loadBalancingRules []*armnetwork.LoadBalancingRule 73 backendAddressPools []*armnetwork.BackendAddressPool 74 outboundRules []*armnetwork.OutboundRule 75 probes []*armnetwork.Probe 76 ) 77 78 if existing != nil { 79 existingLB, ok := existing.(armnetwork.LoadBalancer) 80 if !ok { 81 return nil, errors.Errorf("%T is not an armnetwork.LoadBalancer", existing) 82 } 83 // LB already exists 84 // We append the existing LB etag to the header to ensure we only apply the updates if the LB has not been modified. 85 etag = existingLB.Etag 86 update := false 87 88 // merge existing LB properties with desired properties 89 frontendIPConfigs = existingLB.Properties.FrontendIPConfigurations 90 wantedIPs, wantedFrontendIDs := getFrontendIPConfigs(*s) 91 for _, ip := range wantedIPs { 92 if !ipExists(frontendIPConfigs, *ip) { 93 update = true 94 frontendIPConfigs = append(frontendIPConfigs, ip) 95 } 96 } 97 98 loadBalancingRules = existingLB.Properties.LoadBalancingRules 99 for _, rule := range getLoadBalancingRules(*s, wantedFrontendIDs) { 100 if !lbRuleExists(loadBalancingRules, *rule) { 101 update = true 102 loadBalancingRules = append(loadBalancingRules, rule) 103 } 104 } 105 106 backendAddressPools = existingLB.Properties.BackendAddressPools 107 for _, pool := range getBackendAddressPools(*s) { 108 if !poolExists(backendAddressPools, *pool) { 109 update = true 110 backendAddressPools = append(backendAddressPools, pool) 111 } 112 } 113 114 outboundRules = existingLB.Properties.OutboundRules 115 for _, rule := range getOutboundRules(*s, wantedFrontendIDs) { 116 if !outboundRuleExists(outboundRules, *rule) { 117 update = true 118 outboundRules = append(outboundRules, rule) 119 } 120 } 121 122 probes = existingLB.Properties.Probes 123 for _, probe := range getProbes(*s) { 124 if !probeExists(probes, *probe) { 125 update = true 126 probes = append(probes, probe) 127 } 128 } 129 130 if !update { 131 // load balancer already exists with all required defaults 132 return nil, nil 133 } 134 } else { 135 frontendIPConfigs, frontendIDs = getFrontendIPConfigs(*s) 136 loadBalancingRules = getLoadBalancingRules(*s, frontendIDs) 137 backendAddressPools = getBackendAddressPools(*s) 138 outboundRules = getOutboundRules(*s, frontendIDs) 139 probes = getProbes(*s) 140 } 141 142 lb := armnetwork.LoadBalancer{ 143 Etag: etag, 144 SKU: &armnetwork.LoadBalancerSKU{Name: ptr.To(converters.SKUtoSDK(s.SKU))}, 145 Location: ptr.To(s.Location), 146 ExtendedLocation: converters.ExtendedLocationToNetworkSDK(s.ExtendedLocation), 147 Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ 148 ClusterName: s.ClusterName, 149 Lifecycle: infrav1.ResourceLifecycleOwned, 150 Role: ptr.To(s.Role), 151 Additional: s.AdditionalTags, 152 })), 153 Properties: &armnetwork.LoadBalancerPropertiesFormat{ 154 FrontendIPConfigurations: frontendIPConfigs, 155 BackendAddressPools: backendAddressPools, 156 OutboundRules: outboundRules, 157 Probes: probes, 158 LoadBalancingRules: loadBalancingRules, 159 }, 160 } 161 162 return lb, nil 163 } 164 165 func getFrontendIPConfigs(lbSpec LBSpec) ([]*armnetwork.FrontendIPConfiguration, []*armnetwork.SubResource) { 166 frontendIPConfigurations := make([]*armnetwork.FrontendIPConfiguration, 0) 167 frontendIDs := make([]*armnetwork.SubResource, 0) 168 for _, ipConfig := range lbSpec.FrontendIPConfigs { 169 var properties armnetwork.FrontendIPConfigurationPropertiesFormat 170 if lbSpec.Type == infrav1.Internal { 171 properties = armnetwork.FrontendIPConfigurationPropertiesFormat{ 172 PrivateIPAllocationMethod: ptr.To(armnetwork.IPAllocationMethodStatic), 173 Subnet: &armnetwork.Subnet{ 174 ID: ptr.To(azure.SubnetID(lbSpec.SubscriptionID, lbSpec.VNetResourceGroup, lbSpec.VNetName, lbSpec.SubnetName)), 175 }, 176 PrivateIPAddress: ptr.To(ipConfig.PrivateIPAddress), 177 } 178 } else { 179 properties = armnetwork.FrontendIPConfigurationPropertiesFormat{ 180 PublicIPAddress: &armnetwork.PublicIPAddress{ 181 ID: ptr.To(azure.PublicIPID(lbSpec.SubscriptionID, lbSpec.ResourceGroup, ipConfig.PublicIP.Name)), 182 }, 183 } 184 } 185 frontendIPConfigurations = append(frontendIPConfigurations, &armnetwork.FrontendIPConfiguration{ 186 Properties: &properties, 187 Name: ptr.To(ipConfig.Name), 188 }) 189 frontendIDs = append(frontendIDs, &armnetwork.SubResource{ 190 ID: ptr.To(azure.FrontendIPConfigID(lbSpec.SubscriptionID, lbSpec.ResourceGroup, lbSpec.Name, ipConfig.Name)), 191 }) 192 } 193 return frontendIPConfigurations, frontendIDs 194 } 195 196 func getOutboundRules(lbSpec LBSpec, frontendIDs []*armnetwork.SubResource) []*armnetwork.OutboundRule { 197 if lbSpec.Type == infrav1.Internal { 198 return []*armnetwork.OutboundRule{} 199 } 200 return []*armnetwork.OutboundRule{ 201 { 202 Name: ptr.To(outboundNAT), 203 Properties: &armnetwork.OutboundRulePropertiesFormat{ 204 Protocol: ptr.To(armnetwork.LoadBalancerOutboundRuleProtocolAll), 205 IdleTimeoutInMinutes: lbSpec.IdleTimeoutInMinutes, 206 FrontendIPConfigurations: frontendIDs, 207 BackendAddressPool: &armnetwork.SubResource{ 208 ID: ptr.To(azure.AddressPoolID(lbSpec.SubscriptionID, lbSpec.ResourceGroup, lbSpec.Name, lbSpec.BackendPoolName)), 209 }, 210 }, 211 }, 212 } 213 } 214 215 func getLoadBalancingRules(lbSpec LBSpec, frontendIDs []*armnetwork.SubResource) []*armnetwork.LoadBalancingRule { 216 if lbSpec.Role == infrav1.APIServerRole { 217 // We disable outbound SNAT explicitly in the HTTPS LB rule and enable TCP and UDP outbound NAT with an outbound rule. 218 // For more information on Standard LB outbound connections see https://learn.microsoft.com/azure/load-balancer/load-balancer-outbound-connections. 219 var frontendIPConfig *armnetwork.SubResource 220 if len(frontendIDs) != 0 { 221 frontendIPConfig = frontendIDs[0] 222 } 223 return []*armnetwork.LoadBalancingRule{ 224 { 225 Name: ptr.To(lbRuleHTTPS), 226 Properties: &armnetwork.LoadBalancingRulePropertiesFormat{ 227 DisableOutboundSnat: ptr.To(true), 228 Protocol: ptr.To(armnetwork.TransportProtocolTCP), 229 FrontendPort: ptr.To[int32](lbSpec.APIServerPort), 230 BackendPort: ptr.To[int32](lbSpec.APIServerPort), 231 IdleTimeoutInMinutes: lbSpec.IdleTimeoutInMinutes, 232 EnableFloatingIP: ptr.To(false), 233 LoadDistribution: ptr.To(armnetwork.LoadDistributionDefault), 234 FrontendIPConfiguration: frontendIPConfig, 235 BackendAddressPool: &armnetwork.SubResource{ 236 ID: ptr.To(azure.AddressPoolID(lbSpec.SubscriptionID, lbSpec.ResourceGroup, lbSpec.Name, lbSpec.BackendPoolName)), 237 }, 238 Probe: &armnetwork.SubResource{ 239 ID: ptr.To(azure.ProbeID(lbSpec.SubscriptionID, lbSpec.ResourceGroup, lbSpec.Name, httpsProbe)), 240 }, 241 }, 242 }, 243 } 244 } 245 return []*armnetwork.LoadBalancingRule{} 246 } 247 248 func getBackendAddressPools(lbSpec LBSpec) []*armnetwork.BackendAddressPool { 249 return []*armnetwork.BackendAddressPool{ 250 { 251 Name: ptr.To(lbSpec.BackendPoolName), 252 }, 253 } 254 } 255 256 func getProbes(lbSpec LBSpec) []*armnetwork.Probe { 257 if lbSpec.Role == infrav1.APIServerRole { 258 return []*armnetwork.Probe{ 259 { 260 Name: ptr.To(httpsProbe), 261 Properties: &armnetwork.ProbePropertiesFormat{ 262 Protocol: ptr.To(armnetwork.ProbeProtocolHTTPS), 263 Port: ptr.To[int32](lbSpec.APIServerPort), 264 RequestPath: ptr.To(httpsProbeRequestPath), 265 IntervalInSeconds: ptr.To[int32](15), 266 NumberOfProbes: ptr.To[int32](4), 267 }, 268 }, 269 } 270 } 271 return []*armnetwork.Probe{} 272 } 273 274 func probeExists(probes []*armnetwork.Probe, probe armnetwork.Probe) bool { 275 for _, p := range probes { 276 if ptr.Deref(p.Name, "") == ptr.Deref(probe.Name, "") { 277 return true 278 } 279 } 280 return false 281 } 282 283 func outboundRuleExists(rules []*armnetwork.OutboundRule, rule armnetwork.OutboundRule) bool { 284 for _, r := range rules { 285 if ptr.Deref(r.Name, "") == ptr.Deref(rule.Name, "") { 286 return true 287 } 288 } 289 return false 290 } 291 292 func poolExists(pools []*armnetwork.BackendAddressPool, pool armnetwork.BackendAddressPool) bool { 293 for _, p := range pools { 294 if ptr.Deref(p.Name, "") == ptr.Deref(pool.Name, "") { 295 return true 296 } 297 } 298 return false 299 } 300 301 func lbRuleExists(rules []*armnetwork.LoadBalancingRule, rule armnetwork.LoadBalancingRule) bool { 302 for _, r := range rules { 303 if ptr.Deref(r.Name, "") == ptr.Deref(rule.Name, "") { 304 return true 305 } 306 } 307 return false 308 } 309 310 func ipExists(configs []*armnetwork.FrontendIPConfiguration, config armnetwork.FrontendIPConfiguration) bool { 311 for _, ip := range configs { 312 if ptr.Deref(ip.Name, "") == ptr.Deref(config.Name, "") { 313 return true 314 } 315 } 316 return false 317 }