Skip to content

Commit ae98179

Browse files
Kamil PrzybylKamil Przybyl
authored andcommitted
add application load balancer controller manager
1 parent fda1cfa commit ae98179

20 files changed

+2869
-7
lines changed
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/*
2+
Copyright 2025.
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 main
18+
19+
import (
20+
"crypto/tls"
21+
"flag"
22+
"fmt"
23+
"os"
24+
"path/filepath"
25+
26+
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
27+
// to ensure that exec-entrypoint and run can make use of them.
28+
_ "k8s.io/client-go/plugin/pkg/client/auth"
29+
30+
sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config"
31+
"k8s.io/apimachinery/pkg/runtime"
32+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
33+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
34+
ctrl "sigs.k8s.io/controller-runtime"
35+
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
36+
"sigs.k8s.io/controller-runtime/pkg/healthz"
37+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
38+
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
39+
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
40+
"sigs.k8s.io/controller-runtime/pkg/webhook"
41+
42+
albclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit"
43+
albsdk "github.com/stackitcloud/stackit-sdk-go/services/alb"
44+
certsdk "github.com/stackitcloud/stackit-sdk-go/services/certificates"
45+
// +kubebuilder:scaffold:imports
46+
)
47+
48+
var (
49+
scheme = runtime.NewScheme()
50+
setupLog = ctrl.Log.WithName("setup")
51+
)
52+
53+
func init() {
54+
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
55+
56+
// +kubebuilder:scaffold:scheme
57+
}
58+
59+
// nolint:gocyclo
60+
func main() {
61+
var metricsAddr string
62+
var metricsCertPath, metricsCertName, metricsCertKey string
63+
var webhookCertPath, webhookCertName, webhookCertKey string
64+
var enableLeaderElection bool
65+
var leaderElectionNamespace string
66+
var leaderElectionID string
67+
var probeAddr string
68+
var secureMetrics bool
69+
var enableHTTP2 bool
70+
var tlsOpts []func(*tls.Config)
71+
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
72+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
73+
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
74+
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
75+
"Enable leader election for controller manager. "+
76+
"Enabling this will ensure there is only one active controller manager.")
77+
flag.StringVar(&leaderElectionNamespace, "leader-election-namespace", "default", "The namespace in which the leader "+
78+
"election resource will be created.")
79+
flag.StringVar(&leaderElectionID, "leader-election-id", "d0fbe9c4.stackit.cloud", "The name of the resource that "+
80+
"leader election will use for holding the leader lock.")
81+
flag.BoolVar(&secureMetrics, "metrics-secure", true,
82+
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
83+
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
84+
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
85+
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
86+
flag.StringVar(&metricsCertPath, "metrics-cert-path", "",
87+
"The directory that contains the metrics server certificate.")
88+
flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.")
89+
flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
90+
flag.BoolVar(&enableHTTP2, "enable-http2", false,
91+
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
92+
opts := zap.Options{
93+
Development: true,
94+
}
95+
opts.BindFlags(flag.CommandLine)
96+
flag.Parse()
97+
98+
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
99+
100+
// if the enable-http2 flag is false (the default), http/2 should be disabled
101+
// due to its vulnerabilities. More specifically, disabling http/2 will
102+
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
103+
// Rapid Reset CVEs. For more information see:
104+
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
105+
// - https://github.com/advisories/GHSA-4374-p667-p6c8
106+
disableHTTP2 := func(c *tls.Config) {
107+
setupLog.Info("disabling http/2")
108+
c.NextProtos = []string{"http/1.1"}
109+
}
110+
111+
if !enableHTTP2 {
112+
tlsOpts = append(tlsOpts, disableHTTP2)
113+
}
114+
115+
// Create watchers for metrics and webhooks certificates
116+
var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher
117+
118+
// Initial webhook TLS options
119+
webhookTLSOpts := tlsOpts
120+
121+
if len(webhookCertPath) > 0 {
122+
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
123+
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)
124+
125+
var err error
126+
webhookCertWatcher, err = certwatcher.New(
127+
filepath.Join(webhookCertPath, webhookCertName),
128+
filepath.Join(webhookCertPath, webhookCertKey),
129+
)
130+
if err != nil {
131+
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
132+
os.Exit(1)
133+
}
134+
135+
webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
136+
config.GetCertificate = webhookCertWatcher.GetCertificate
137+
})
138+
}
139+
140+
webhookServer := webhook.NewServer(webhook.Options{
141+
TLSOpts: webhookTLSOpts,
142+
})
143+
144+
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
145+
// More info:
146+
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.4/pkg/metrics/server
147+
// - https://book.kubebuilder.io/reference/metrics.html
148+
metricsServerOptions := metricsserver.Options{
149+
BindAddress: metricsAddr,
150+
SecureServing: secureMetrics,
151+
TLSOpts: tlsOpts,
152+
}
153+
154+
if secureMetrics {
155+
// FilterProvider is used to protect the metrics endpoint with authn/authz.
156+
// These configurations ensure that only authorized users and service accounts
157+
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
158+
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.4/pkg/metrics/filters#WithAuthenticationAndAuthorization
159+
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
160+
}
161+
162+
// If the certificate is not specified, controller-runtime will automatically
163+
// generate self-signed certificates for the metrics server. While convenient for development and testing,
164+
// this setup is not recommended for production.
165+
//
166+
// TODO(user): If you enable certManager, uncomment the following lines:
167+
// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates
168+
// managed by cert-manager for the metrics server.
169+
// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.
170+
if len(metricsCertPath) > 0 {
171+
setupLog.Info("Initializing metrics certificate watcher using provided certificates",
172+
"metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey)
173+
174+
var err error
175+
metricsCertWatcher, err = certwatcher.New(
176+
filepath.Join(metricsCertPath, metricsCertName),
177+
filepath.Join(metricsCertPath, metricsCertKey),
178+
)
179+
if err != nil {
180+
setupLog.Error(err, "to initialize metrics certificate watcher", "error", err)
181+
os.Exit(1)
182+
}
183+
184+
metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) {
185+
config.GetCertificate = metricsCertWatcher.GetCertificate
186+
})
187+
}
188+
189+
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
190+
Scheme: scheme,
191+
Metrics: metricsServerOptions,
192+
WebhookServer: webhookServer,
193+
HealthProbeBindAddress: probeAddr,
194+
LeaderElection: enableLeaderElection,
195+
LeaderElectionID: leaderElectionID,
196+
LeaderElectionNamespace: leaderElectionNamespace,
197+
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
198+
// when the Manager ends. This requires the binary to immediately end when the
199+
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
200+
// speeds up voluntary leader transitions as the new leader don't have to wait
201+
// LeaseDuration time first.
202+
//
203+
// In the default scaffold provided, the program ends immediately after
204+
// the manager stops, so would be fine to enable this option. However,
205+
// if you are doing or is intended to do any operation such as perform cleanups
206+
// after the manager stops then its usage might be unsafe.
207+
// LeaderElectionReleaseOnCancel: true,
208+
})
209+
if err != nil {
210+
setupLog.Error(err, "unable to start manager")
211+
os.Exit(1)
212+
}
213+
214+
albURL, _ := os.LookupEnv("STACKIT_LOAD_BALANCER_API_ALB_URL")
215+
216+
certURL, _ := os.LookupEnv("STACKIT_LOAD_BALANCER_API_CERT_URL")
217+
218+
region, set := os.LookupEnv("STACKIT_REGION")
219+
if !set {
220+
setupLog.Error(err, "STACKIT_REGION not set", "controller", "IngressClass")
221+
os.Exit(1)
222+
}
223+
projectID, set := os.LookupEnv("PROJECT_ID")
224+
if !set {
225+
setupLog.Error(err, "PROJECT_ID not set", "controller", "IngressClass")
226+
os.Exit(1)
227+
}
228+
networkID, set := os.LookupEnv("NETWORK_ID")
229+
if !set {
230+
setupLog.Error(err, "NETWORK_ID not set", "controller", "IngressClass")
231+
os.Exit(1)
232+
}
233+
234+
// Create an ALB SDK client
235+
albOpts := []sdkconfig.ConfigurationOption{}
236+
if albURL != "" {
237+
albOpts = append(albOpts, sdkconfig.WithEndpoint(albURL))
238+
}
239+
240+
certOpts := []sdkconfig.ConfigurationOption{}
241+
if certURL != "" {
242+
certOpts = append(certOpts, sdkconfig.WithEndpoint(certURL))
243+
}
244+
245+
fmt.Printf("Create ALB SDK client\n")
246+
sdkClient, err := albsdk.NewAPIClient(albOpts...)
247+
if err != nil {
248+
setupLog.Error(err, "unable to create ALB SDK client", "controller", "IngressClass")
249+
os.Exit(1)
250+
}
251+
// Create an ALB client
252+
fmt.Printf("Create ALB client\n")
253+
albClient, err := albclient.NewClient(sdkClient)
254+
if err != nil {
255+
setupLog.Error(err, "unable to create ALB client", "controller", "IngressClass")
256+
os.Exit(1)
257+
}
258+
259+
// Create an Certificates SDK client
260+
certificateAPI, err := certsdk.NewAPIClient(certOpts...)
261+
if err != nil {
262+
setupLog.Error(err, "unable to create certificate SDK client", "controller", "IngressClass")
263+
os.Exit(1)
264+
}
265+
// Create an Certificates API client
266+
certificateClient, err := certificateclient.NewCertClient(certificateAPI)
267+
if err != nil {
268+
setupLog.Error(err, "unable to create Certificates client", "controller", "IngressClass")
269+
os.Exit(1)
270+
}
271+
272+
if err = (&controller.IngressClassReconciler{
273+
Client: mgr.GetClient(),
274+
ALBClient: albClient,
275+
CertificateClient: certificateClient,
276+
Scheme: mgr.GetScheme(),
277+
ProjectID: projectID,
278+
NetworkID: networkID,
279+
Region: region,
280+
}).SetupWithManager(mgr); err != nil {
281+
setupLog.Error(err, "unable to create controller", "controller", "IngressClass")
282+
os.Exit(1)
283+
}
284+
// +kubebuilder:scaffold:builder
285+
286+
if metricsCertWatcher != nil {
287+
setupLog.Info("Adding metrics certificate watcher to manager")
288+
if err := mgr.Add(metricsCertWatcher); err != nil {
289+
setupLog.Error(err, "unable to add metrics certificate watcher to manager")
290+
os.Exit(1)
291+
}
292+
}
293+
294+
if webhookCertWatcher != nil {
295+
setupLog.Info("Adding webhook certificate watcher to manager")
296+
if err := mgr.Add(webhookCertWatcher); err != nil {
297+
setupLog.Error(err, "unable to add webhook certificate watcher to manager")
298+
os.Exit(1)
299+
}
300+
}
301+
302+
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
303+
setupLog.Error(err, "unable to set up health check")
304+
os.Exit(1)
305+
}
306+
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
307+
setupLog.Error(err, "unable to set up ready check")
308+
os.Exit(1)
309+
}
310+
311+
setupLog.Info("starting manager")
312+
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
313+
setupLog.Error(err, "problem running manager")
314+
os.Exit(1)
315+
}
316+
}

go.mod

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ toolchain go1.26.1
77
require (
88
github.com/container-storage-interface/spec v1.12.0
99
github.com/go-viper/mapstructure/v2 v2.5.0
10+
github.com/google/go-cmp v0.7.0
1011
github.com/google/uuid v1.6.0
1112
github.com/kubernetes-csi/csi-lib-utils v0.23.2
1213
github.com/kubernetes-csi/csi-test/v5 v5.4.0
@@ -15,7 +16,9 @@ require (
1516
github.com/prometheus/client_golang v1.23.2
1617
github.com/spf13/cobra v1.10.2
1718
github.com/spf13/pflag v1.0.10
18-
github.com/stackitcloud/stackit-sdk-go/core v0.22.0
19+
github.com/stackitcloud/stackit-sdk-go/core v0.23.0
20+
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1
21+
github.com/stackitcloud/stackit-sdk-go/services/certificates v1.4.1
1922
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.3.5
2023
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.8.0
2124
go.uber.org/mock v0.6.0
@@ -32,6 +35,7 @@ require (
3235
k8s.io/klog/v2 v2.140.0
3336
k8s.io/mount-utils v0.35.2
3437
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
38+
sigs.k8s.io/controller-runtime v0.23.3
3539
)
3640

3741
replace k8s.io/cloud-provider => github.com/stackitcloud/cloud-provider v0.35.1-ske-1
@@ -50,11 +54,13 @@ require (
5054
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
5155
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
5256
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
57+
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
5358
github.com/felixge/httpsnoop v1.0.4 // indirect
5459
github.com/fsnotify/fsnotify v1.9.0 // indirect
5560
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
5661
github.com/go-logr/logr v1.4.3 // indirect
5762
github.com/go-logr/stdr v1.2.2 // indirect
63+
github.com/go-logr/zapr v1.3.0 // indirect
5864
github.com/go-openapi/jsonpointer v0.22.1 // indirect
5965
github.com/go-openapi/jsonreference v0.21.2 // indirect
6066
github.com/go-openapi/swag v0.25.1 // indirect
@@ -77,7 +83,6 @@ require (
7783
github.com/google/btree v1.1.3 // indirect
7884
github.com/google/cel-go v0.26.0 // indirect
7985
github.com/google/gnostic-models v0.7.0 // indirect
80-
github.com/google/go-cmp v0.7.0 // indirect
8186
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
8287
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
8388
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
@@ -123,12 +128,14 @@ require (
123128
golang.org/x/text v0.33.0 // indirect
124129
golang.org/x/time v0.10.0 // indirect
125130
golang.org/x/tools v0.41.0 // indirect
131+
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
126132
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
127133
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
128134
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
129135
gopkg.in/inf.v0 v0.9.1 // indirect
130136
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
131137
gopkg.in/yaml.v2 v2.4.0 // indirect
138+
k8s.io/apiextensions-apiserver v0.35.0 // indirect
132139
k8s.io/apiserver v0.35.1 // indirect
133140
k8s.io/component-helpers v0.35.1 // indirect
134141
k8s.io/controller-manager v0.35.1 // indirect
@@ -137,6 +144,6 @@ require (
137144
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
138145
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
139146
sigs.k8s.io/randfill v1.0.0 // indirect
140-
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
147+
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect
141148
sigs.k8s.io/yaml v1.6.0 // indirect
142149
)

0 commit comments

Comments
 (0)