Merge pull request #82257 from prameshj/ilbsubnet

Support specifying a custom subnet for ILB ip in GCE
This commit is contained in:
Kubernetes Prow Robot 2019-11-10 22:59:41 -08:00 committed by GitHub
commit cf5ec7615b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 23 deletions

View File

@ -607,11 +607,9 @@ func (g *Cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder,
g.clientBuilder = clientBuilder g.clientBuilder = clientBuilder
g.client = clientBuilder.ClientOrDie("cloud-provider") g.client = clientBuilder.ClientOrDie("cloud-provider")
if g.OnXPN() { g.eventBroadcaster = record.NewBroadcaster()
g.eventBroadcaster = record.NewBroadcaster() g.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: g.client.CoreV1().Events("")})
g.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: g.client.CoreV1().Events("")}) g.eventRecorder = g.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "g-cloudprovider"})
g.eventRecorder = g.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "g-cloudprovider"})
}
go g.watchClusterID(stop) go g.watchClusterID(stop)
} }

View File

@ -27,6 +27,9 @@ const (
// AlphaFeatureILBSubsets allows InternalLoadBalancer services to include a subset // AlphaFeatureILBSubsets allows InternalLoadBalancer services to include a subset
// of cluster nodes as backends instead of all nodes. // of cluster nodes as backends instead of all nodes.
AlphaFeatureILBSubsets = "ILBSubsets" AlphaFeatureILBSubsets = "ILBSubsets"
// AlphaFeatureILBCustomSubnet allows InternalLoadBalancer services to specify a
// network subnet to allocate ip addresses from.
AlphaFeatureILBCustomSubnet = "ILBCustomSubnet"
) )
// AlphaFeatureGate contains a mapping of alpha features to whether they are enabled // AlphaFeatureGate contains a mapping of alpha features to whether they are enabled

View File

@ -58,6 +58,11 @@ const (
// created in. // created in.
ServiceAnnotationILBAllowGlobalAccess = "networking.gke.io/internal-load-balancer-allow-global-access" ServiceAnnotationILBAllowGlobalAccess = "networking.gke.io/internal-load-balancer-allow-global-access"
// ServiceAnnotationILBSubnet is annotated on a service with the name of the subnetwork
// the ILB IP Address should be assigned from. By default, this is the subnetwork that the
// cluster is created in.
ServiceAnnotationILBSubnet = "networking.gke.io/internal-load-balancer-subnet"
// NetworkTierAnnotationKey is annotated on a Service object to indicate which // NetworkTierAnnotationKey is annotated on a Service object to indicate which
// network tier a GCP LB should use. The valid values are "Standard" and // network tier a GCP LB should use. The valid values are "Standard" and
// "Premium" (default). // "Premium" (default).
@ -132,6 +137,8 @@ func GetServiceNetworkTier(service *v1.Service) (cloud.NetworkTier, error) {
type ILBOptions struct { type ILBOptions struct {
// AllowGlobalAccess Indicates whether global access is allowed for the LoadBalancer // AllowGlobalAccess Indicates whether global access is allowed for the LoadBalancer
AllowGlobalAccess bool AllowGlobalAccess bool
// SubnetName indicates which subnet the LoadBalancer VIPs should be assigned from
SubnetName string
} }
// GetLoadBalancerAnnotationAllowGlobalAccess returns if global access is enabled // GetLoadBalancerAnnotationAllowGlobalAccess returns if global access is enabled
@ -139,3 +146,11 @@ type ILBOptions struct {
func GetLoadBalancerAnnotationAllowGlobalAccess(service *v1.Service) bool { func GetLoadBalancerAnnotationAllowGlobalAccess(service *v1.Service) bool {
return service.Annotations[ServiceAnnotationILBAllowGlobalAccess] == "true" return service.Annotations[ServiceAnnotationILBAllowGlobalAccess] == "true"
} }
// GetLoadBalancerAnnotationSubnet returns the configured subnet to assign LoadBalancer IP from.
func GetLoadBalancerAnnotationSubnet(service *v1.Service) string {
if val, exists := service.Annotations[ServiceAnnotationILBSubnet]; exists {
return val
}
return ""
}

View File

@ -58,6 +58,12 @@ func (g *Cloud) ensureInternalLoadBalancer(clusterName, clusterID string, svc *v
g.eventRecorder.Event(svc, v1.EventTypeWarning, "ILBOptionsIgnored", "Internal LoadBalancer options are not supported with Legacy Networks.") g.eventRecorder.Event(svc, v1.EventTypeWarning, "ILBOptionsIgnored", "Internal LoadBalancer options are not supported with Legacy Networks.")
options = ILBOptions{} options = ILBOptions{}
} }
if !g.AlphaFeatureGate.Enabled(AlphaFeatureILBCustomSubnet) {
if options.SubnetName != "" {
g.eventRecorder.Event(svc, v1.EventTypeWarning, "ILBCustomSubnetOptionIgnored", "Internal LoadBalancer CustomSubnet options ignored as the feature gate is disabled.")
options.SubnetName = ""
}
}
loadBalancerName := g.GetLoadBalancerName(context.TODO(), clusterName, svc) loadBalancerName := g.GetLoadBalancerName(context.TODO(), clusterName, svc)
sharedBackend := shareBackendService(svc) sharedBackend := shareBackendService(svc)
@ -98,23 +104,32 @@ func (g *Cloud) ensureInternalLoadBalancer(clusterName, clusterID string, svc *v
return nil, err return nil, err
} }
subnetworkURL := g.SubnetworkURL()
if g.AlphaFeatureGate.Enabled(AlphaFeatureILBCustomSubnet) {
// If this feature is enabled, changes to subnet annotation will be
// picked up and reflected in the forwarding rule.
// Removing the annotation will set the forwarding rule to use the default subnet.
if options.SubnetName != "" {
subnetworkURL = gceSubnetworkURL("", g.networkProjectID, g.region, options.SubnetName)
}
} else {
// TODO(84885) remove this once ILBCustomSubnet goes beta.
if existingFwdRule != nil && existingFwdRule.Subnetwork != "" {
// If the ILB already exists, continue using the subnet that it's already using.
// This is to support existing ILBs that were setup using the wrong subnet - https://github.com/kubernetes/kubernetes/pull/57861
subnetworkURL = existingFwdRule.Subnetwork
}
}
// Determine IP which will be used for this LB. If no forwarding rule has been established // Determine IP which will be used for this LB. If no forwarding rule has been established
// or specified in the Service spec, then requestedIP = "". // or specified in the Service spec, then requestedIP = "".
requestedIP := determineRequestedIP(svc, existingFwdRule) ipToUse := ilbIPToUse(svc, existingFwdRule, subnetworkURL)
ipToUse := requestedIP
// If the ILB already exists, continue using the subnet that it's already using. klog.V(2).Infof("ensureInternalLoadBalancer(%v): Using subnet %s for LoadBalancer IP %s", loadBalancerName, options.SubnetName, ipToUse)
// This is to support existing ILBs that were setup using the wrong subnet.
subnetworkURL := g.SubnetworkURL()
if existingFwdRule != nil && existingFwdRule.Subnetwork != "" {
// external LBs have an empty Subnetwork field.
subnetworkURL = existingFwdRule.Subnetwork
}
var addrMgr *addressManager var addrMgr *addressManager
// If the network is not a legacy network, use the address manager // If the network is not a legacy network, use the address manager
if !g.IsLegacyNetwork() { if !g.IsLegacyNetwork() {
addrMgr = newAddressManager(g, nm.String(), g.Region(), subnetworkURL, loadBalancerName, requestedIP, cloud.SchemeInternal) addrMgr = newAddressManager(g, nm.String(), g.Region(), subnetworkURL, loadBalancerName, ipToUse, cloud.SchemeInternal)
ipToUse, err = addrMgr.HoldAddress() ipToUse, err = addrMgr.HoldAddress()
if err != nil { if err != nil {
return nil, err return nil, err
@ -758,20 +773,27 @@ func getNameFromLink(link string) string {
return fields[len(fields)-1] return fields[len(fields)-1]
} }
func determineRequestedIP(svc *v1.Service, fwdRule *compute.ForwardingRule) string { // ilbIPToUse determines which IP address needs to be used in the ForwardingRule. If an IP has been
// specified by the user, that is used. If there is an existing ForwardingRule, the ip address from
// that is reused. In case a subnetwork change is requested, the existing ForwardingRule IP is ignored.
func ilbIPToUse(svc *v1.Service, fwdRule *compute.ForwardingRule, requestedSubnet string) string {
if svc.Spec.LoadBalancerIP != "" { if svc.Spec.LoadBalancerIP != "" {
return svc.Spec.LoadBalancerIP return svc.Spec.LoadBalancerIP
} }
if fwdRule == nil {
if fwdRule != nil { return ""
return fwdRule.IPAddress
} }
if requestedSubnet != fwdRule.Subnetwork {
return "" // reset ip address since subnet is being changed.
return ""
}
return fwdRule.IPAddress
} }
func getILBOptions(svc *v1.Service) ILBOptions { func getILBOptions(svc *v1.Service) ILBOptions {
return ILBOptions{AllowGlobalAccess: GetLoadBalancerAnnotationAllowGlobalAccess(svc)} return ILBOptions{AllowGlobalAccess: GetLoadBalancerAnnotationAllowGlobalAccess(svc),
SubnetName: GetLoadBalancerAnnotationSubnet(svc),
}
} }
// forwardingRuleComposite is a composite type encapsulating both the GA and Beta ForwardingRules. // forwardingRuleComposite is a composite type encapsulating both the GA and Beta ForwardingRules.
@ -800,7 +822,8 @@ func (f *forwardingRuleComposite) Equal(other *forwardingRuleComposite) bool {
f.lbScheme == other.lbScheme && f.lbScheme == other.lbScheme &&
equalStringSets(f.ports, other.ports) && equalStringSets(f.ports, other.ports) &&
f.backendService == other.backendService && f.backendService == other.backendService &&
f.allowGlobalAccess == other.allowGlobalAccess f.allowGlobalAccess == other.allowGlobalAccess &&
f.subnetwork == other.subnetwork
} }
// toForwardingRuleComposite converts a compute beta or GA ForwardingRule into the composite type // toForwardingRuleComposite converts a compute beta or GA ForwardingRule into the composite type

View File

@ -1309,3 +1309,89 @@ func TestForwardingRuleCompositeEqual(t *testing.T) {
t.Errorf("Expected frcGA and frcBeta rules to be unequal, got true") t.Errorf("Expected frcGA and frcBeta rules to be unequal, got true")
} }
} }
func TestEnsureInternalLoadBalancerCustomSubnet(t *testing.T) {
t.Parallel()
vals := DefaultTestClusterValues()
gce, err := fakeGCECloud(vals)
require.NoError(t, err)
gce.AlphaFeatureGate = NewAlphaFeatureGate([]string{AlphaFeatureILBCustomSubnet})
nodeNames := []string{"test-node-1"}
nodes, err := createAndInsertNodes(gce, nodeNames, vals.ZoneName)
require.NoError(t, err)
svc := fakeLoadbalancerService(string(LBTypeInternal))
status, err := createInternalLoadBalancer(gce, svc, nil, nodeNames, vals.ClusterName, vals.ClusterID, vals.ZoneName)
lbName := gce.GetLoadBalancerName(context.TODO(), "", svc)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
assert.NotEmpty(t, status.Ingress)
fwdRule, err := gce.GetBetaRegionForwardingRule(lbName, gce.region)
if err != nil || fwdRule == nil {
t.Errorf("Unexpected error %v", err)
}
if fwdRule.Subnetwork != "" {
t.Errorf("Unexpected subnet value %s in ILB ForwardingRule", fwdRule.Subnetwork)
}
// Change service to include the global access annotation and request static ip
requestedIP := "4.5.6.7"
svc.Annotations[ServiceAnnotationILBSubnet] = "test-subnet"
svc.Spec.LoadBalancerIP = requestedIP
status, err = gce.EnsureLoadBalancer(context.Background(), vals.ClusterName, svc, nodes)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
assert.NotEmpty(t, status.Ingress)
if status.Ingress[0].IP != requestedIP {
t.Errorf("Reserved IP %s not propagated, Got %s", requestedIP, status.Ingress[0].IP)
}
fwdRule, err = gce.GetBetaRegionForwardingRule(lbName, gce.region)
if err != nil || fwdRule == nil {
t.Errorf("Unexpected error %v", err)
}
if !strings.HasSuffix(fwdRule.Subnetwork, "test-subnet") {
t.Errorf("Unexpected subnet value %s in ILB ForwardingRule.", fwdRule.Subnetwork)
}
// Change to a different subnet
svc.Annotations[ServiceAnnotationILBSubnet] = "another-subnet"
status, err = gce.EnsureLoadBalancer(context.Background(), vals.ClusterName, svc, nodes)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
assert.NotEmpty(t, status.Ingress)
if status.Ingress[0].IP != requestedIP {
t.Errorf("Reserved IP %s not propagated, Got %s", requestedIP, status.Ingress[0].IP)
}
fwdRule, err = gce.GetBetaRegionForwardingRule(lbName, gce.region)
if err != nil || fwdRule == nil {
t.Errorf("Unexpected error %v", err)
}
if !strings.HasSuffix(fwdRule.Subnetwork, "another-subnet") {
t.Errorf("Unexpected subnet value %s in ILB ForwardingRule.", fwdRule.Subnetwork)
}
// remove the annotation - ILB should revert to default subnet.
delete(svc.Annotations, ServiceAnnotationILBSubnet)
status, err = gce.EnsureLoadBalancer(context.Background(), vals.ClusterName, svc, nodes)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
assert.NotEmpty(t, status.Ingress)
fwdRule, err = gce.GetBetaRegionForwardingRule(lbName, gce.region)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if fwdRule.Subnetwork != "" {
t.Errorf("Unexpected subnet value %s in ILB ForwardingRule.", fwdRule.Subnetwork)
}
// Delete the service
err = gce.EnsureLoadBalancerDeleted(context.Background(), vals.ClusterName, svc)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
assertInternalLbResourcesDeleted(t, gce, svc, vals, true)
}