Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion cmd/cloud-controller-manager/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ go_library(
name = "cloud-controller-manager_lib",
srcs = [
"gkenetworkparamsetcontroller.go",
"gkeservicecontroller.go",
"main.go",
"nodeipamcontroller.go",
],
Expand All @@ -29,12 +30,14 @@ go_library(
"//pkg/controller/nodeipam",
"//pkg/controller/nodeipam/config",
"//pkg/controller/nodeipam/ipam",
"//pkg/controller/service",
"//providers/gce",
"//vendor/github.com/GoogleCloudPlatform/gke-networking-api/client/network/clientset/versioned",
"//vendor/github.com/GoogleCloudPlatform/gke-networking-api/client/network/informers/externalversions",
"//vendor/github.com/GoogleCloudPlatform/gke-networking-api/client/nodetopology/clientset/versioned",
"//vendor/github.com/spf13/pflag",
"//vendor/k8s.io/apimachinery/pkg/util/wait",
"//vendor/k8s.io/apiserver/pkg/util/feature",
"//vendor/k8s.io/cloud-provider",
"//vendor/k8s.io/cloud-provider/app",
"//vendor/k8s.io/cloud-provider/app/config",
Expand All @@ -58,10 +61,15 @@ image(binary = ":cloud-controller-manager")

go_test(
name = "cloud-controller-manager_test",
srcs = ["nodeipamcontroller_test.go"],
srcs = [
"gkeservicecontroller_test.go",
"nodeipamcontroller_test.go",
],
embed = [":cloud-controller-manager_lib"],
deps = [
"//pkg/controller/nodeipam/config",
"//pkg/controller/service",
"//vendor/k8s.io/api/core/v1:core",
"//vendor/k8s.io/cloud-provider",
"//vendor/k8s.io/cloud-provider/app/config",
"//vendor/k8s.io/cloud-provider/config",
Expand Down
42 changes: 42 additions & 0 deletions cmd/cloud-controller-manager/gkeservicecontroller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"context"

utilfeature "k8s.io/apiserver/pkg/util/feature"
cloudprovider "k8s.io/cloud-provider"
gkeservicecontroller "k8s.io/cloud-provider-gcp/pkg/controller/service"
"k8s.io/cloud-provider/app"
cloudcontrollerconfig "k8s.io/cloud-provider/app/config"
controllermanagerapp "k8s.io/controller-manager/app"
"k8s.io/controller-manager/controller"
"k8s.io/klog/v2"
)

// startGkeServiceControllerWrapper is used to take cloud config as input and start the GKE service controller
func startGkeServiceControllerWrapper(initContext app.ControllerInitContext, completedConfig *cloudcontrollerconfig.CompletedConfig, cloud cloudprovider.Interface) app.InitFunc {
return func(ctx context.Context, controllerContext controllermanagerapp.ControllerContext) (controller.Interface, bool, error) {
return startGkeServiceController(ctx, initContext, controllerContext, completedConfig, cloud)
}
}

func startGkeServiceController(ctx context.Context, initContext app.ControllerInitContext, controlexContext controllermanagerapp.ControllerContext, completedConfig *cloudcontrollerconfig.CompletedConfig, cloud cloudprovider.Interface) (controller.Interface, bool, error) {
// Start the service controller
serviceController, err := gkeservicecontroller.New(
cloud,
completedConfig.ClientBuilder.ClientOrDie(initContext.ClientName),
completedConfig.SharedInformers.Core().V1().Services(),
completedConfig.SharedInformers.Core().V1().Nodes(),
completedConfig.ComponentConfig.KubeCloudShared.ClusterName,
utilfeature.DefaultFeatureGate,
)
if err != nil {
// This error shouldn't fail. It lives like this as a legacy.
klog.Errorf("Failed to start service controller: %v", err)
return nil, false, nil
}

go serviceController.Run(ctx, int(completedConfig.ComponentConfig.ServiceController.ConcurrentServiceSyncs), controlexContext.ControllerManagerMetrics)

return nil, true, nil
}
84 changes: 84 additions & 0 deletions cmd/cloud-controller-manager/gkeservicecontroller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"testing"

v1 "k8s.io/api/core/v1"
gkeservicecontroller "k8s.io/cloud-provider-gcp/pkg/controller/service"
)

// TestWantsLoadBalancer verifies that the forked and modified WantsLoadBalancer
// function in k8s.io/cloud-provider-gcp/pkg/controller/service has the correct
// logic. This test acts as a safeguard to prevent future updates from
// overwriting the custom logic. It ensures that the controller correctly
// processes services with GKE-specific legacy LoadBalancerClasses and,
// just as importantly, ignores services that have no LoadBalancerClass set
// or have a class that is not managed by this controller.
func TestWantsLoadBalancer(t *testing.T) {
gkeLegacyExternalClass := "networking.gke.io/l4-regional-external-legacy"
gkeLegacyInternalClass := "networking.gke.io/l4-regional-internal-legacy"
otherClass := "some-other-class"

testCases := []struct {
name string
service *v1.Service
want bool
}{
{
name: "service is not of type LoadBalancer",
service: &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
},
},
want: false,
},
{
name: "service is of type LoadBalancer with no loadBalancerClass",
service: &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
},
want: false,
},
{
name: "service is of type LoadBalancer with GKE legacy external loadBalancerClass",
service: &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
LoadBalancerClass: &gkeLegacyExternalClass,
},
},
want: true,
},
{
name: "service is of type LoadBalancer with GKE legacy internal loadBalancerClass",
service: &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
LoadBalancerClass: &gkeLegacyInternalClass,
},
},
want: true,
},
{
name: "service is of type LoadBalancer with other loadBalancerClass",
service: &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
LoadBalancerClass: &otherClass,
},
},
want: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := gkeservicecontroller.WantsLoadBalancer(tc.service); got != tc.want {
t.Errorf("WantsLoadBalancer() = %v, want %v", got, tc.want)
}
})
}
}
15 changes: 15 additions & 0 deletions cmd/cloud-controller-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ import (
kcmnames "k8s.io/kubernetes/cmd/kube-controller-manager/names"
)

const (
gkeServiceLBControllerName = "gke-service-lb-controller"
gkeServiceControllerClientName = "gke-service-controller"
gkeServiceAlias = "gke-service"
)

// enableMultiProject is bound to a command-line flag. When true, it enables the
// projectFromNodeProviderID option of the GCE cloud provider, instructing it to
// use the project specified in the Node's providerID for GCE API calls.
Expand Down Expand Up @@ -92,10 +98,19 @@ func main() {
Constructor: startGkeNetworkParamSetControllerWrapper,
}

controllerInitializers[gkeServiceLBControllerName] = app.ControllerInitFuncConstructor{
InitContext: app.ControllerInitContext{
ClientName: gkeServiceControllerClientName,
},
Constructor: startGkeServiceControllerWrapper,
}

// add controllers disabled by default
app.ControllersDisabledByDefault.Insert("gkenetworkparamset")
app.ControllersDisabledByDefault.Insert(gkeServiceLBControllerName)
aliasMap := names.CCMControllerAliases()
aliasMap["nodeipam"] = kcmnames.NodeIpamController
aliasMap[gkeServiceAlias] = gkeServiceLBControllerName
command := app.NewCloudControllerManagerCommand(ccmOptions, cloudInitializer, controllerInitializers, aliasMap, fss, wait.NeverStop)

logs.InitLogs()
Expand Down
37 changes: 37 additions & 0 deletions pkg/controller/service/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "service",
srcs = [
"controller.go",
"controller_gke.go",
"doc.go",
"metrics.go",
],
importpath = "k8s.io/cloud-provider-gcp/pkg/controller/service",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/api/core/v1:core",
"//vendor/k8s.io/apimachinery/pkg/api/errors",
"//vendor/k8s.io/apimachinery/pkg/labels",
"//vendor/k8s.io/apimachinery/pkg/util/runtime",
"//vendor/k8s.io/apimachinery/pkg/util/sets",
"//vendor/k8s.io/apimachinery/pkg/util/wait",
"//vendor/k8s.io/client-go/informers/core/v1:core",
"//vendor/k8s.io/client-go/kubernetes",
"//vendor/k8s.io/client-go/kubernetes/scheme",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:core",
"//vendor/k8s.io/client-go/listers/core/v1:core",
"//vendor/k8s.io/client-go/tools/cache",
"//vendor/k8s.io/client-go/tools/record",
"//vendor/k8s.io/client-go/util/workqueue",
"//vendor/k8s.io/cloud-provider",
"//vendor/k8s.io/cloud-provider/api",
"//vendor/k8s.io/cloud-provider/service/helpers",
"//vendor/k8s.io/component-base/featuregate",
"//vendor/k8s.io/component-base/metrics",
"//vendor/k8s.io/component-base/metrics/legacyregistry",
"//vendor/k8s.io/component-base/metrics/prometheus/controllers",
"//vendor/k8s.io/klog/v2:klog",
],
)
8 changes: 8 additions & 0 deletions pkg/controller/service/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
approvers:
- aojea
- bowei
- mmamczur
- thockin
- felipeyepez
labels:
- sig/network
22 changes: 22 additions & 0 deletions pkg/controller/service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Service Controller

This service controller is a fork of the upstream service controller from `k8s.io/cloud-provider/controllers/service`.

## Modifications

The primary modification in this fork is to the `WantsLoadBalancer` function in `controller.go`. This change allows the controller to manage services that have a `loadBalancerClass` set to one of the following GKE-specific values:

- `networking.gke.io/l4-regional-external-legacy`
- `networking.gke.io/l4-regional-internal-legacy`

The upstream controller ignores any service with a non-nil `loadBalancerClass`. This fork extends that logic to claim these specific classes for the GKE cloud provider. This ensures that this controller will only process services with a valid `loadBalancerClass`, while the default service controller handles all other services without a `LoadBalancerClass`.

When updating this forked controller from upstream, it is critical that the custom logic in the `WantsLoadBalancer` function is preserved. The purpose of this modification is to allow the controller to manage services that have a `loadBalancerClass` set to one of the GKE-specific values mentioned above.

## Controller Startup

This controller is started using a wrapper function, `startGkeServiceControllerWrapper`, located in `@cmd/cloud-controller-manager/gkeservicecontroller.go`. This wrapper initializes and runs the GKE-specific service controller when the `--controllers` flag includes `gke-service`.

## Configuration

The configuration for this controller, defined in `@vendor/k8s.io/cloud-provider/controllers/service/config/**`, is intentionally not forked. This approach allows the controller to reuse the existing command-line flags and configuration parameters from the upstream service controller, ensuring backward compatibility and simplifying maintenance.
Loading