From 08957a041c4399daef7a7168280d337ceba5b394 Mon Sep 17 00:00:00 2001 From: Maximilian Geberl Date: Wed, 8 Jul 2026 14:21:41 +0200 Subject: [PATCH 1/4] Integrate ALB Controller in gardener-extension-provider-stackit --- .../charts/runtime/templates/deployment.yaml | 3 + .../charts/runtime/values.yaml | 1 + .../templates/configmap.yaml | 3 - .../values.yaml | 9 +- .../stackit-alb-controller-manager/Chart.yaml | 4 - .../values.yaml | 17 ---- .../Chart.yaml | 4 + .../templates/configmap.yaml | 21 +++++ .../templates/poddisruptionbudget.yaml | 15 +++ ...application-load-balancer-controller.yaml} | 45 +++++---- .../templates/vpa.yaml | 6 +- .../values.yaml | 21 +++++ .../seed-controlplane/requirements.yaml | 4 +- charts/internal/seed-controlplane/values.yaml | 4 +- .../app/app.go | 7 ++ .../app/app.go | 2 - hack/api-reference/api.md | 56 +++++++++++- hack/api-reference/config.md | 11 --- imagevector/images.go | 4 +- imagevector/images.yaml | 8 +- pkg/admission/cmd/options.go | 43 +++++++++ pkg/admission/validator/shoot.go | 12 ++- pkg/admission/validator/webhook.go | 17 +++- pkg/apis/config/types.go | 3 - pkg/apis/config/v1alpha1/types.go | 3 - .../v1alpha1/zz_generated.conversion.go | 2 - .../stackit/v1alpha1/types_cloudprofile.go | 4 +- .../stackit/v1alpha1/types_controlplane.go | 14 +++ .../stackit/v1alpha1/zz_generated.deepcopy.go | 27 +++++- pkg/apis/stackit/validation/controlplane.go | 30 +++++- .../stackit/validation/controlplane_test.go | 6 +- pkg/cmd/config.go | 4 - pkg/controller/controlplane/add.go | 4 +- pkg/controller/controlplane/valuesprovider.go | 91 +++++++++++-------- .../controlplane/valuesprovider_test.go | 47 ++++++---- pkg/controller/healthcheck/add.go | 43 +++++---- pkg/openstack/types.go | 4 +- 37 files changed, 409 insertions(+), 190 deletions(-) delete mode 100644 charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/Chart.yaml delete mode 100644 charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/values.yaml create mode 100644 charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/Chart.yaml create mode 100644 charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/configmap.yaml create mode 100644 charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/poddisruptionbudget.yaml rename charts/internal/seed-controlplane/charts/{stackit-alb-controller-manager/templates/alb-controller-manager.yaml => stackit-application-load-balancer-controller/templates/stackit-application-load-balancer-controller.yaml} (72%) rename charts/internal/seed-controlplane/charts/{stackit-alb-controller-manager => stackit-application-load-balancer-controller}/templates/vpa.yaml (73%) create mode 100644 charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml diff --git a/charts/gardener-extension-admission-stackit/charts/runtime/templates/deployment.yaml b/charts/gardener-extension-admission-stackit/charts/runtime/templates/deployment.yaml index 3066aad6..29ad8db9 100644 --- a/charts/gardener-extension-admission-stackit/charts/runtime/templates/deployment.yaml +++ b/charts/gardener-extension-admission-stackit/charts/runtime/templates/deployment.yaml @@ -63,6 +63,9 @@ spec: {{- if .Values.flatcarImageVersion }} - --flatcar-image-version={{ .Values.flatcarImageVersion }} {{- end }} + {{- if .Values.allowApplicationLoadBalancerController }} + - --allow-application-load-balancer-controller={{ .Values.allowApplicationLoadBalancerController }} + {{- end }} - --health-bind-address=:{{ .Values.healthPort }} - --leader-election-id=gardener-extension-admission-stackit securityContext: diff --git a/charts/gardener-extension-admission-stackit/charts/runtime/values.yaml b/charts/gardener-extension-admission-stackit/charts/runtime/values.yaml index ee55d87a..1e80fc4a 100644 --- a/charts/gardener-extension-admission-stackit/charts/runtime/values.yaml +++ b/charts/gardener-extension-admission-stackit/charts/runtime/values.yaml @@ -22,6 +22,7 @@ kubeconfig: featureGates: {} # SomeGate: true flatcarImageVersion: "" +#allowApplicationLoadBalancerController: false #projectedKubeconfig: # baseMountPath: /var/run/secrets/gardener.cloud diff --git a/charts/gardener-extension-provider-stackit/templates/configmap.yaml b/charts/gardener-extension-provider-stackit/templates/configmap.yaml index 54c62ee6..b8b2da4c 100644 --- a/charts/gardener-extension-provider-stackit/templates/configmap.yaml +++ b/charts/gardener-extension-provider-stackit/templates/configmap.yaml @@ -29,6 +29,3 @@ data: registryCaches: {{- toYaml . | nindent 6 }} {{- end }} -{{- if .Values.config.deployALBIngressController }} - deployALBIngressController: {{ .Values.config.deployALBIngressController }} -{{- end }} diff --git a/charts/gardener-extension-provider-stackit/values.yaml b/charts/gardener-extension-provider-stackit/values.yaml index fb60b55d..337c5cd9 100644 --- a/charts/gardener-extension-provider-stackit/values.yaml +++ b/charts/gardener-extension-provider-stackit/values.yaml @@ -76,11 +76,10 @@ config: provisioner: block-storage.csi.stackit.cloud volumeBindingMode: WaitForFirstConsumer registryCaches: - # - server: reg.example.com - # cache: reg-cache.example.com - # caBundle: LS0tLS1C... #b64 encoded CA bundle, optional - # capabilities: ["pull", "resolve"] - # deployALBIngressController: true + # - server: reg.example.com + # cache: reg-cache.example.com + # caBundle: LS0tLS1C... #b64 encoded CA bundle, optional + # capabilities: ["pull", "resolve"] gardener: version: "" gardenlet: diff --git a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/Chart.yaml b/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/Chart.yaml deleted file mode 100644 index bddaaec5..00000000 --- a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: Helm chart for STACKIT stackit-alb-controller-manager -name: stackit-alb-controller-manager -version: 0.1.0 diff --git a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/values.yaml b/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/values.yaml deleted file mode 100644 index 321616eb..00000000 --- a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -replicas: 1 -config: - region: "" -# stackitNetworkID: foo -# stackitProjectID: foo -# region: foo -# applicationLBApiUrl: foo -# certificateApiUrl: foo -podAnnotations: {} -podLabels: {} -images: - stackit-alb-controller-manager: image:tag -vpa: - resourcePolicy: - maxAllowed: - cpu: 1 - memory: 512Mi diff --git a/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/Chart.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/Chart.yaml new file mode 100644 index 00000000..c2cf00b0 --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: Helm chart for STACKIT stackit-application-load-balancer-controller +name: stackit-application-load-balancer-controller +version: 0.1.0 diff --git a/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/configmap.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/configmap.yaml new file mode 100644 index 00000000..e3f4ec1c --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/configmap.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: stackit-application-load-balancer-controller + namespace: {{ .Release.Namespace }} +data: + cloud.yaml: |- + global: + region: {{ .Values.config.global.region }} + projectId: {{ .Values.config.global.projectId }} + {{- with .Values.config.global.apiEndpoints }} + apiEndpoints: + {{- if .applicationLoadBalancerApi }} + applicationLoadBalancerApi: {{ .applicationLoadBalancerApi }} + {{- end }} + {{- if .applicationLoadBalancerCertificateApi }} + applicationLoadBalancerCertificateApi: {{ .applicationLoadBalancerCertificateApi }} + {{- end }} + {{- end }} + applicationLoadBalancer: + networkId: {{ .Values.config.applicationLoadBalancer.networkId }} diff --git a/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/poddisruptionbudget.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/poddisruptionbudget.yaml new file mode 100644 index 00000000..09a23e85 --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/poddisruptionbudget.yaml @@ -0,0 +1,15 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: stackit-application-load-balancer-controller + namespace: {{ .Release.Namespace }} + labels: + app: kubernetes + role: stackit-application-load-balancer-controller +spec: + maxUnavailable: 1 + selector: + matchLabels: + app: kubernetes + role: stackit-application-load-balancer-controller + unhealthyPodEvictionPolicy: AlwaysAllow diff --git a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/templates/alb-controller-manager.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/stackit-application-load-balancer-controller.yaml similarity index 72% rename from charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/templates/alb-controller-manager.yaml rename to charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/stackit-application-load-balancer-controller.yaml index 6ab8baf8..6470e100 100644 --- a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/templates/alb-controller-manager.yaml +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/stackit-application-load-balancer-controller.yaml @@ -1,11 +1,11 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: stackit-alb-controller-manager + name: stackit-application-load-balancer-controller namespace: {{ .Release.Namespace }} labels: app: kubernetes - role: stackit-alb-controller-manager + role: stackit-application-load-balancer-controller high-availability-config.resources.gardener.cloud/type: controller spec: replicas: {{ .Values.replicas }} @@ -13,7 +13,7 @@ spec: selector: matchLabels: app: kubernetes - role: stackit-alb-controller-manager + role: stackit-application-load-balancer-controller template: metadata: {{- if .Values.podAnnotations }} @@ -23,7 +23,7 @@ spec: labels: gardener.cloud/role: controlplane app: kubernetes - role: stackit-alb-controller-manager + role: stackit-application-load-balancer-controller networking.gardener.cloud/to-dns: allowed networking.gardener.cloud/to-public-networks: allowed networking.gardener.cloud/to-private-networks: allowed @@ -36,35 +36,26 @@ spec: automountServiceAccountToken: false priorityClassName: gardener-system-300 containers: - - name: stackit-alb-controller-manager - image: {{ index .Values.images "stackit-alb-controller-manager" }} + - name: stackit-application-load-balancer-controller + image: {{ index .Values.images "stackit-application-load-balancer-controller" }} args: - --kubeconfig=/var/run/secrets/gardener.cloud/shoot/generic-kubeconfig/kubeconfig - --leader-elect=true + - --metrics-bind-address=:{{ .Values.metricsPort }} - --leader-election-namespace=kube-system - - --leader-election-id=stackit-alb-controller-manager + - --leader-election-id=stackit-application-load-balancer-controller + - --cloud-config=/etc/config/cloud.yaml env: - name: STACKIT_SERVICE_ACCOUNT_KEY_PATH value: /etc/serviceaccount/serviceaccount.json - - name: STACKIT_REGION - value: {{ .Values.config.region }} - - name: PROJECT_ID - value: {{ .Values.config.stackitProjectID }} - - name: NETWORK_ID - value: {{ .Values.config.stackitNetworkID }} - -{{- if .Values.config.tokenUrl }} +{{- if .Values.tokenEndpoint }} - name: STACKIT_TOKEN_BASEURL - value: {{ .Values.config.tokenUrl }} -{{- end }} -{{- if .Values.config.applicationLBApiUrl }} - - name: STACKIT_LOAD_BALANCER_API_ALB_URL - value: {{ .Values.config.applicationLBApiUrl }} -{{- end }} -{{- if .Values.config.certificateApiUrl }} - - name: STACKIT_LOAD_BALANCER_API_CERT_URL - value: {{ .Values.config.certificateApiUrl }} + value: {{ .Values.tokenEndpoint }} {{- end }} + ports: + - containerPort: {{ .Values.metricsPort }} + name: metrics + protocol: TCP {{- if .Values.resources }} resources: {{ toYaml .Values.resources | indent 10 }} @@ -73,6 +64,8 @@ spec: - mountPath: /var/run/secrets/gardener.cloud/shoot/generic-kubeconfig name: kubeconfig readOnly: true + - mountPath: /etc/config + name: config-volume - mountPath: /etc/serviceaccount name: sa-secret - mountPath: /etc/ssl/certs/keystone-ca.crt @@ -80,6 +73,10 @@ spec: subPath: keystone-ca.crt readOnly: true volumes: + - name: config-volume + configMap: + defaultMode: 420 + name: stackit-application-load-balancer-controller - name: sa-secret secret: defaultMode: 420 diff --git a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/templates/vpa.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/vpa.yaml similarity index 73% rename from charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/templates/vpa.yaml rename to charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/vpa.yaml index 28ca5342..27137bf1 100644 --- a/charts/internal/seed-controlplane/charts/stackit-alb-controller-manager/templates/vpa.yaml +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/templates/vpa.yaml @@ -1,18 +1,18 @@ apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: - name: stackit-alb-controller-manager-vpa + name: stackit-application-load-balancer-controller namespace: {{ .Release.Namespace }} spec: targetRef: apiVersion: apps/v1 kind: Deployment - name: stackit-alb-controller-manager + name: stackit-application-load-balancer-controller updatePolicy: updateMode: Auto resourcePolicy: containerPolicies: - - containerName: stackit-alb-controller-manager + - containerName: stackit-application-load-balancer-controller minAllowed: memory: 80M maxAllowed: diff --git a/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml new file mode 100644 index 00000000..14d889bc --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml @@ -0,0 +1,21 @@ +replicas: 1 +config: + global: + region: "" + projectId: "" + # apiEndpoints: + # applicationLoadBalancerApi: "foo" + # applicationLoadBalancerCertificateApi: "foo" + applicationLoadBalancer: + networkId: "" +#tokenEndpoint: "foo" +metricsPort: 9090 +podAnnotations: {} +podLabels: {} +images: + stackit-alb-controller-manager: image:tag +vpa: + resourcePolicy: + maxAllowed: + cpu: 1 + memory: 512Mi diff --git a/charts/internal/seed-controlplane/requirements.yaml b/charts/internal/seed-controlplane/requirements.yaml index b672cf39..7d17325f 100644 --- a/charts/internal/seed-controlplane/requirements.yaml +++ b/charts/internal/seed-controlplane/requirements.yaml @@ -15,10 +15,10 @@ dependencies: version: 0.1.0 repository: http://localhost:10191 condition: stackit-blockstorage-csi-driver.enabled -- name: stackit-alb-controller-manager +- name: stackit-application-load-balancer-controller repository: http://localhost:10191 version: 0.1.0 - condition: stackit-alb-controller-manager.enabled + condition: stackit-application-load-balancer-controller.enabled - name: stackit-pod-identity-webhook repository: http://localhost:10191 version: 0.1.0 diff --git a/charts/internal/seed-controlplane/values.yaml b/charts/internal/seed-controlplane/values.yaml index 8cf3a7ad..e561e5c2 100644 --- a/charts/internal/seed-controlplane/values.yaml +++ b/charts/internal/seed-controlplane/values.yaml @@ -6,7 +6,7 @@ stackit-cloud-controller-manager: enabled: false csi-driver-controller: enabled: true -stackit-alb-controller-manager: +stackit-application-load-balancer-controller: enabled: false stackit-pod-identity-webhook: - enabled: false \ No newline at end of file + enabled: false diff --git a/cmd/gardener-extension-admission-stackit/app/app.go b/cmd/gardener-extension-admission-stackit/app/app.go index 9815188d..8c97d6fb 100644 --- a/cmd/gardener-extension-admission-stackit/app/app.go +++ b/cmd/gardener-extension-admission-stackit/app/app.go @@ -27,6 +27,7 @@ import ( admissioncmd "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/admission/cmd" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/admission/mutator" + "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/admission/validator" stackitinstall "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/install" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/feature" providerstackit "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit" @@ -64,9 +65,12 @@ func NewAdmissionCommand(ctx context.Context) *cobra.Command { webhookSwitches, ) + admissionOpts = &admissioncmd.ConfigOptions{} + aggOption = controllercmd.NewOptionAggregator( restOpts, mgrOpts, + admissionOpts, webhookOptions, ) ) @@ -93,6 +97,9 @@ func NewAdmissionCommand(ctx context.Context) *cobra.Command { managerOptions := mgrOpts.Completed().Options() + admissionConfig := admissionOpts.Completed() + admissionConfig.ApplyAllowApplicationLoadBalancerController(&validator.DefaultAddOptions.AllowApplicationLoadBalancerController) + // Operators can enable the source cluster option via SOURCE_CLUSTER environment variable. // In-cluster config will be used if no SOURCE_KUBECONFIG is specified. // diff --git a/cmd/gardener-extension-provider-stackit/app/app.go b/cmd/gardener-extension-provider-stackit/app/app.go index 92d1770f..50d56e7b 100644 --- a/cmd/gardener-extension-provider-stackit/app/app.go +++ b/cmd/gardener-extension-provider-stackit/app/app.go @@ -190,11 +190,9 @@ func NewControllerManagerCommand(ctx context.Context) *cobra.Command { configFileOpts.Completed().ApplyETCDStorage(&stackitseedprovider.DefaultAddOptions.ETCDStorage) configFileOpts.Completed().ApplyHealthCheckConfig(&healthcheck.DefaultAddOptions.HealthCheckConfig) configFileOpts.Completed().ApplyRegistryCaches(&stackitwebhookcontrolplane.DefaultAddOptions.RegistryCaches) - configFileOpts.Completed().ApplyDeployALBIngressController(&stackitcontrolplane.DeployALBIngressController) configFileOpts.Completed().ApplyCustomLabelDomain(&stackitworker.DefaultAddOptions.CustomLabelDomain) configFileOpts.Completed().ApplyCustomLabelDomain(&stackitcontrolplane.DefaultAddOptions.CustomLabelDomain) configFileOpts.Completed().ApplyCustomLabelDomain(&infrastructure.DefaultAddOptions.CustomLabelDomain) - log.Info("DeployALBIngressController?", "deploy", configFileOpts.Completed().Config.DeployALBIngressController) bastionCtrlOpts.Completed().Apply(&stackitbastion.DefaultAddOptions.Controller) configFileOpts.Completed().ApplyCustomLabelDomain(&stackitbastion.DefaultAddOptions.CustomLabelDomain) diff --git a/hack/api-reference/api.md b/hack/api-reference/api.md index de6c66c1..2313f7b7 100644 --- a/hack/api-reference/api.md +++ b/hack/api-reference/api.md @@ -81,14 +81,14 @@ string -loadbalancerCertificate
+applicationLoadBalancerCertificate
string (Optional) -

LoadbalancerCertificate is the Endpoint of the LoadBalancerCertificate API.

+

ApplicationLoadBalancerCertificate is the Endpoint of the ApplicationLoadBalancerCertificate API.

@@ -155,7 +155,57 @@ string array

+ApplicationLoadBalancerConfig defines the configuration for the +Application Load Balancer (ALB) integration. +

+ + + + + + + + + + + + + + + + + + + +
FieldDescription
+enabled
+ +boolean + +
+

Enabled specifies whether the Application Load Balancer is activated.

+
+ingress
+ +ApplicationLoadBalancerConfigIngress + +
+

Ingress contains the configuration specific to the ALB's Ingress controller.

+
+ + +

ApplicationLoadBalancerConfigIngress +

+ + +

+(Appears on:ApplicationLoadBalancerConfig) +

+ +

+ApplicationLoadBalancerConfigIngress defines the settings for the +Application Load Balancer Ingress resources.

@@ -175,7 +225,7 @@ boolean diff --git a/hack/api-reference/config.md b/hack/api-reference/config.md index bc0036a3..b71839ca 100644 --- a/hack/api-reference/config.md +++ b/hack/api-reference/config.md @@ -76,17 +76,6 @@ ControllerConfiguration defines the configuration for the STACKIT provider. - - - -
-

+

Enabled specifies whether the Ingress controller functionality
for the ALB is activated to manage external access to services.

-deployALBIngressController
- -boolean - -
-

DeployALBIngressController

-
customLabelDomain
string diff --git a/imagevector/images.go b/imagevector/images.go index c7e393d5..9d8cdeb8 100644 --- a/imagevector/images.go +++ b/imagevector/images.go @@ -31,8 +31,8 @@ const ( ImageNameMachineControllerManagerProviderOpenstack = "machine-controller-manager-provider-openstack" // ImageNameMachineControllerManagerProviderStackit is a constant for an image in the image vector with name 'machine-controller-manager-provider-stackit'. ImageNameMachineControllerManagerProviderStackit = "machine-controller-manager-provider-stackit" - // ImageNameStackitAlbControllerManager is a constant for an image in the image vector with name 'stackit-alb-controller-manager'. - ImageNameStackitAlbControllerManager = "stackit-alb-controller-manager" + // ImageNameStackitApplicationLoadBalancerController is a constant for an image in the image vector with name 'stackit-application-load-balancer-controller'. + ImageNameStackitApplicationLoadBalancerController = "stackit-application-load-balancer-controller" // ImageNameStackitCloudControllerManager is a constant for an image in the image vector with name 'stackit-cloud-controller-manager'. ImageNameStackitCloudControllerManager = "stackit-cloud-controller-manager" // ImageNameStackitPodIdentityWebhook is a constant for an image in the image vector with name 'stackit-pod-identity-webhook'. diff --git a/imagevector/images.yaml b/imagevector/images.yaml index 5df95fc1..6253dfad 100644 --- a/imagevector/images.yaml +++ b/imagevector/images.yaml @@ -133,9 +133,11 @@ images: repository: registry.k8s.io/sig-storage/livenessprobe tag: "v2.18.0" -- name: stackit-alb-controller-manager - repository: reg3.infra.ske.eu01.stackit.cloud/temp/alb-controller-manager - tag: "1245" +- name: stackit-application-load-balancer-controller + sourceRepository: github.com/stackitcloud/application-load-balancer-controller + repository: ghcr.io/stackitcloud/application-load-balancer-controller-dev + tag: "albc" + - name: stackit-pod-identity-webhook sourceRepository: github.com/stackitcloud/stackit-pod-identity-webhook repository: ghcr.io/stackitcloud/stackit-pod-identity-webhook diff --git a/pkg/admission/cmd/options.go b/pkg/admission/cmd/options.go index be935c93..38d23988 100644 --- a/pkg/admission/cmd/options.go +++ b/pkg/admission/cmd/options.go @@ -2,6 +2,7 @@ package cmd import ( webhookcmd "github.com/gardener/gardener/extensions/pkg/webhook/cmd" + "github.com/spf13/pflag" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/admission/mutator" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/admission/validator" @@ -14,3 +15,45 @@ func GardenWebhookSwitchOptions() *webhookcmd.SwitchOptions { webhookcmd.Switch(mutator.Name, mutator.New), ) } + +// ConfigOptions are command line options that can be set for admission webhooks. +type ConfigOptions struct { + // AllowApplicationLoadBalancerController configures if the application load balancer controller can be used in the ControlPlaneConfig. + AllowApplicationLoadBalancerController bool + + config *Config +} + +// Config is a completed admission configuration. +type Config struct { + // AllowApplicationLoadBalancerController configures if the application load balancer controller can be used in the ControlPlaneConfig. + AllowApplicationLoadBalancerController bool +} + +// AddFlags implements Flagger.AddFlags. +func (c *ConfigOptions) AddFlags(fs *pflag.FlagSet) { + fs.BoolVar( + &c.AllowApplicationLoadBalancerController, + "allow-application-load-balancer-controller", + false, + "Configures if the application load balancer controller can be used in the ControlPlaneConfig.", + ) +} + +// Complete implements RESTCompleter.Complete. +func (c *ConfigOptions) Complete() error { + c.config = &Config{ + AllowApplicationLoadBalancerController: c.AllowApplicationLoadBalancerController, + } + return nil +} + +// Completed returns the completed Config. Only call this if `Complete` was successful. +func (c *ConfigOptions) Completed() *Config { + return c.config +} + +// ApplyAllowApplicationLoadBalancerController sets the values of this Config in the given config.AllowApplicationLoadBalancerController. +func (c *Config) ApplyAllowApplicationLoadBalancerController(cfg *bool) { + *cfg = c.AllowApplicationLoadBalancerController +} diff --git a/pkg/admission/validator/shoot.go b/pkg/admission/validator/shoot.go index 8a4c031b..10fa264e 100644 --- a/pkg/admission/validator/shoot.go +++ b/pkg/admission/validator/shoot.go @@ -20,11 +20,15 @@ import ( ) // NewShootValidator returns a new instance of a shoot validator. -func NewShootValidator(mgr manager.Manager) extensionswebhook.Validator { - return &shoot{} +func NewShootValidator(mgr manager.Manager, allowApplicationLoadBalancerController bool) extensionswebhook.Validator { + return &shoot{ + allowApplicationLoadBalancerController: allowApplicationLoadBalancerController, + } } -type shoot struct{} +type shoot struct { + allowApplicationLoadBalancerController bool +} // Validate validates the given shoot objects. func (s *shoot) Validate(_ context.Context, newObj, oldObj client.Object) error { @@ -45,7 +49,7 @@ func (s *shoot) Validate(_ context.Context, newObj, oldObj client.Object) error allErrs := field.ErrorList{} - allErrs = append(allErrs, stackitvalidation.ValidateControlPlaneConfig(cpConfig, shoot.Spec.Kubernetes.Version, field.NewPath("spec").Child("provider").Child("controlPlaneConfig"))...) + allErrs = append(allErrs, stackitvalidation.ValidateControlPlaneConfig(cpConfig, shoot.Spec.Kubernetes.Version, s.allowApplicationLoadBalancerController, field.NewPath("spec").Child("provider").Child("controlPlaneConfig").Child("controlPlaneConfig"))...) allErrs = append(allErrs, stackitvalidation.ValidateInfrastructureConfig(infraConfig, ptr.Deref(shoot.Spec.Networking, core.Networking{}).Nodes, field.NewPath("spec").Child("provider").Child("infrastructureConfig"))...) diff --git a/pkg/admission/validator/webhook.go b/pkg/admission/validator/webhook.go index 0a0321f5..7a303c95 100644 --- a/pkg/admission/validator/webhook.go +++ b/pkg/admission/validator/webhook.go @@ -15,6 +15,17 @@ const ( Name = "validator" ) +var ( + // DefaultAddOptions are the default AddOptions for configuring the validator. + DefaultAddOptions = AddOptions{} +) + +// AddOptions are options to apply when adding the STACKIT admission webhook to the manager. +type AddOptions struct { + // AllowApplicationLoadBalancerController configures if the application load balancer controller can be used in the ControlPlaneConfig. + AllowApplicationLoadBalancerController bool +} + var logger = log.Log.WithName("stackit-validator-webhook") // New creates a new webhook that validates Shoot, CloudProfile, NamespacedCloudProfile, SecretBinding and CredentialsBinding resources. @@ -25,9 +36,9 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) { Name: Name, Path: "/webhooks/validate", Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{ - NewCloudProfileValidator(mgr): {{Obj: &core.CloudProfile{}}}, - NewShootValidator(mgr): {{Obj: &core.Shoot{}}}, - NewNamespacedCloudProfileValidator(mgr): {{Obj: &core.NamespacedCloudProfile{}}}, + NewCloudProfileValidator(mgr): {{Obj: &core.CloudProfile{}}}, + NewShootValidator(mgr, DefaultAddOptions.AllowApplicationLoadBalancerController): {{Obj: &core.Shoot{}}}, + NewNamespacedCloudProfileValidator(mgr): {{Obj: &core.NamespacedCloudProfile{}}}, }, Target: extensionswebhook.TargetSeed, ObjectSelector: &metav1.LabelSelector{ diff --git a/pkg/apis/config/types.go b/pkg/apis/config/types.go index 4eed9a47..933affb5 100644 --- a/pkg/apis/config/types.go +++ b/pkg/apis/config/types.go @@ -31,9 +31,6 @@ type ControllerConfiguration struct { // Deprecated: will be removed in a future version RegistryCaches []RegistryCacheConfiguration - // DeployALBIngressController - DeployALBIngressController bool - // CustomLabelDomain is the domain prefix for custom labels applied to STACKIT infrastructure resources. // For example, cluster labels will use "/cluster" (default: "kubernetes.io"). // NOTE: Only change this if you know what you are doing!! diff --git a/pkg/apis/config/v1alpha1/types.go b/pkg/apis/config/v1alpha1/types.go index f6570f88..9c3bf511 100644 --- a/pkg/apis/config/v1alpha1/types.go +++ b/pkg/apis/config/v1alpha1/types.go @@ -33,9 +33,6 @@ type ControllerConfiguration struct { // +optional RegistryCaches []RegistryCacheConfiguration `json:"registryCaches,omitempty"` - // DeployALBIngressController - DeployALBIngressController bool `json:"deployALBIngressController"` - // CustomLabelDomain is the domain prefix for custom labels applied to STACKIT infrastructure resources. // For example, cluster labels will use "/cluster" (default: "kubernetes.io"). // +optional diff --git a/pkg/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/apis/config/v1alpha1/zz_generated.conversion.go index a870fc9c..bb478ef7 100644 --- a/pkg/apis/config/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/config/v1alpha1/zz_generated.conversion.go @@ -85,7 +85,6 @@ func autoConvert_v1alpha1_ControllerConfiguration_To_config_ControllerConfigurat } out.HealthCheckConfig = (*apisconfigv1alpha1.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig)) out.RegistryCaches = *(*[]config.RegistryCacheConfiguration)(unsafe.Pointer(&in.RegistryCaches)) - out.DeployALBIngressController = in.DeployALBIngressController out.CustomLabelDomain = in.CustomLabelDomain return nil } @@ -102,7 +101,6 @@ func autoConvert_config_ControllerConfiguration_To_v1alpha1_ControllerConfigurat } out.HealthCheckConfig = (*apisconfigv1alpha1.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig)) out.RegistryCaches = *(*[]RegistryCacheConfiguration)(unsafe.Pointer(&in.RegistryCaches)) - out.DeployALBIngressController = in.DeployALBIngressController out.CustomLabelDomain = in.CustomLabelDomain return nil } diff --git a/pkg/apis/stackit/v1alpha1/types_cloudprofile.go b/pkg/apis/stackit/v1alpha1/types_cloudprofile.go index bc565426..c57a2e87 100644 --- a/pkg/apis/stackit/v1alpha1/types_cloudprofile.go +++ b/pkg/apis/stackit/v1alpha1/types_cloudprofile.go @@ -219,9 +219,9 @@ type APIEndpoints struct { // ApplicationLoadBalancer is the Endpoint of the Application LoadBalancer API. // +optional ApplicationLoadBalancer *string `json:"applicationLoadBalancer,omitempty"` - // LoadbalancerCertificate is the Endpoint of the LoadBalancerCertificate API. + // ApplicationLoadBalancerCertificate is the Endpoint of the ApplicationLoadBalancerCertificate API. // +optional - LoadBalancerCertificate *string `json:"loadbalancerCertificate,omitempty"` + ApplicationLoadBalancerCertificate *string `json:"applicationLoadBalancerCertificate,omitempty"` // TokenEndpoint is the token endpoint URL. // +optional TokenEndpoint *string `json:"tokenEndpoint,omitempty"` diff --git a/pkg/apis/stackit/v1alpha1/types_controlplane.go b/pkg/apis/stackit/v1alpha1/types_controlplane.go index 426d0df5..c259f9fa 100644 --- a/pkg/apis/stackit/v1alpha1/types_controlplane.go +++ b/pkg/apis/stackit/v1alpha1/types_controlplane.go @@ -33,7 +33,21 @@ type ControlPlaneConfig struct { ApplicationLoadBalancer *ApplicationLoadBalancerConfig `json:"applicationLoadBalancer,omitempty"` } +// ApplicationLoadBalancerConfig defines the configuration for the +// Application Load Balancer (ALB) integration. type ApplicationLoadBalancerConfig struct { + // Enabled specifies whether the Application Load Balancer is activated. + Enabled bool `json:"enabled"` + + // Ingress contains the configuration specific to the ALB's Ingress controller. + Ingress *ApplicationLoadBalancerConfigIngress `json:"ingress,omitempty"` +} + +// ApplicationLoadBalancerConfigIngress defines the settings for the +// Application Load Balancer Ingress resources. +type ApplicationLoadBalancerConfigIngress struct { + // Enabled specifies whether the Ingress controller functionality + // for the ALB is activated to manage external access to services. Enabled bool `json:"enabled"` } diff --git a/pkg/apis/stackit/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/stackit/v1alpha1/zz_generated.deepcopy.go index aa21ec41..6d0c32e7 100644 --- a/pkg/apis/stackit/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/stackit/v1alpha1/zz_generated.deepcopy.go @@ -36,8 +36,8 @@ func (in *APIEndpoints) DeepCopyInto(out *APIEndpoints) { *out = new(string) **out = **in } - if in.LoadBalancerCertificate != nil { - in, out := &in.LoadBalancerCertificate, &out.LoadBalancerCertificate + if in.ApplicationLoadBalancerCertificate != nil { + in, out := &in.ApplicationLoadBalancerCertificate, &out.ApplicationLoadBalancerCertificate *out = new(string) **out = **in } @@ -83,6 +83,11 @@ func (in *AccessControl) DeepCopy() *AccessControl { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationLoadBalancerConfig) DeepCopyInto(out *ApplicationLoadBalancerConfig) { *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = new(ApplicationLoadBalancerConfigIngress) + **out = **in + } return } @@ -96,6 +101,22 @@ func (in *ApplicationLoadBalancerConfig) DeepCopy() *ApplicationLoadBalancerConf return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationLoadBalancerConfigIngress) DeepCopyInto(out *ApplicationLoadBalancerConfigIngress) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationLoadBalancerConfigIngress. +func (in *ApplicationLoadBalancerConfigIngress) DeepCopy() *ApplicationLoadBalancerConfigIngress { + if in == nil { + return nil + } + out := new(ApplicationLoadBalancerConfigIngress) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Bastion) DeepCopyInto(out *Bastion) { *out = *in @@ -334,7 +355,7 @@ func (in *ControlPlaneConfig) DeepCopyInto(out *ControlPlaneConfig) { if in.ApplicationLoadBalancer != nil { in, out := &in.ApplicationLoadBalancer, &out.ApplicationLoadBalancer *out = new(ApplicationLoadBalancerConfig) - **out = **in + (*in).DeepCopyInto(*out) } return } diff --git a/pkg/apis/stackit/validation/controlplane.go b/pkg/apis/stackit/validation/controlplane.go index 66e6d391..f9e6c0fe 100644 --- a/pkg/apis/stackit/validation/controlplane.go +++ b/pkg/apis/stackit/validation/controlplane.go @@ -21,11 +21,13 @@ var ( ) // ValidateControlPlaneConfig validates a ControlPlaneConfig object. -func ValidateControlPlaneConfig(controlPlaneConfig *stackitv1alpha1.ControlPlaneConfig, version string, fldPath *field.Path) field.ErrorList { +func ValidateControlPlaneConfig(controlPlaneConfig *stackitv1alpha1.ControlPlaneConfig, version string, allowApplicationLoadBalancerController bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // nolint:prealloc // size is not known yet allErrs = append(allErrs, validateCloudController(controlPlaneConfig.CloudControllerManager, version, fldPath.Child("cloudControllerManager"))...) + allErrs = append(allErrs, validateApplicationLoadBalancer(controlPlaneConfig.ApplicationLoadBalancer, allowApplicationLoadBalancerController, fldPath.Child("applicationLoadBalancer"))...) + allErrs = append(allErrs, validateStorage(controlPlaneConfig.Storage, fldPath.Child("storage"))...) return allErrs @@ -58,6 +60,32 @@ func validateCloudController(cloudcontroller *stackitv1alpha1.CloudControllerMan return allErrs } +func validateApplicationLoadBalancer(applicationLoadBalancerConfig *stackitv1alpha1.ApplicationLoadBalancerConfig, allowApplicationLoadBalancerController bool, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if applicationLoadBalancerConfig == nil { + return allErrs + } + if !applicationLoadBalancerConfig.Enabled { + return allErrs + } + + if !allowApplicationLoadBalancerController { + allErrs = append(allErrs, field.Invalid(fldPath.Child("enabled"), applicationLoadBalancerConfig.Enabled, "application load balancer support is disabled and cannot be enabled on a shoot")) + } + + var controllerEnabled bool + + if applicationLoadBalancerConfig.Ingress != nil && applicationLoadBalancerConfig.Ingress.Enabled { + controllerEnabled = true + } + + if !controllerEnabled { + allErrs = append(allErrs, field.Invalid(fldPath.Root(), applicationLoadBalancerConfig, "at least one controller has to be enabled is required")) + } + + return allErrs +} + func validateStorage(storage *stackitv1alpha1.Storage, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList if storage == nil || storage.CSI == nil { diff --git a/pkg/apis/stackit/validation/controlplane_test.go b/pkg/apis/stackit/validation/controlplane_test.go index e95a2ac9..13108e4e 100644 --- a/pkg/apis/stackit/validation/controlplane_test.go +++ b/pkg/apis/stackit/validation/controlplane_test.go @@ -26,7 +26,7 @@ var _ = Describe("ControlPlaneConfig validation", func() { Describe("#ValidateControlPlaneConfig", func() { It("should return no errors for a valid configuration", func() { - Expect(ValidateControlPlaneConfig(controlPlane, "", nilPath)).To(BeEmpty()) + Expect(ValidateControlPlaneConfig(controlPlane, "", false, nilPath)).To(BeEmpty()) }) It("should fail with invalid CCM feature gates", func() { @@ -37,7 +37,7 @@ var _ = Describe("ControlPlaneConfig validation", func() { }, } - errorList := ValidateControlPlaneConfig(controlPlane, "1.28.2", nilPath) + errorList := ValidateControlPlaneConfig(controlPlane, "1.28.2", false, nilPath) Expect(errorList).To(ConsistOf( PointTo(MatchFields(IgnoreExtras, Fields{ @@ -51,7 +51,7 @@ var _ = Describe("ControlPlaneConfig validation", func() { controlPlane.Storage = &stackitv1alpha1.Storage{ CSI: &stackitv1alpha1.CSI{Name: "stackit", CompatibilityMode: "bogus"}, } - Expect(ValidateControlPlaneConfig(controlPlane, "", nilPath)).To(ConsistOf( + Expect(ValidateControlPlaneConfig(controlPlane, "", false, nilPath)).To(ConsistOf( PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeInvalid), "Field": Equal("storage.csi.compatibilityMode"), diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index cf5a74b2..91fea546 100644 --- a/pkg/cmd/config.go +++ b/pkg/cmd/config.go @@ -77,10 +77,6 @@ func (c *Config) ApplyRegistryCaches(regCaches *[]config.RegistryCacheConfigurat *regCaches = c.Config.RegistryCaches } -func (c *Config) ApplyDeployALBIngressController(deployALBIngressController *bool) { - *deployALBIngressController = c.Config.DeployALBIngressController -} - // ApplyCustomLabelDomain sets the custom label domain configuration for infrastructure resources. func (c *Config) ApplyCustomLabelDomain(customLabelDomain *string) { *customLabelDomain = c.Config.CustomLabelDomain diff --git a/pkg/controller/controlplane/add.go b/pkg/controller/controlplane/add.go index ab687708..50a7698f 100644 --- a/pkg/controller/controlplane/add.go +++ b/pkg/controller/controlplane/add.go @@ -22,8 +22,6 @@ import ( var ( // DefaultAddOptions are the default AddOptions for AddToManager. DefaultAddOptions = AddOptions{} - - DeployALBIngressController bool ) // AddOptions are options to apply when adding the OpenStack controlplane controller to the manager. @@ -50,7 +48,7 @@ func AddToManagerWithOptions(ctx context.Context, mgr manager.Manager, opts AddO genericActuator, err := genericactuator.NewActuator(mgr, stackit.Name, secretConfigsFunc, shootAccessSecretsFunc, configChart, controlPlaneChart, controlPlaneShootChart, controlPlaneShootCRDsChart, storageClassChart, - NewValuesProvider(mgr, DeployALBIngressController, opts.CustomLabelDomain, csiCompatibilityHandler), + NewValuesProvider(mgr, opts.CustomLabelDomain, csiCompatibilityHandler), extensionscontroller.ChartRendererFactoryFunc(util.NewChartRendererForShoot), imagevector.ImageVector(), "", nil, opts.WebhookServerNamespace) if err != nil { diff --git a/pkg/controller/controlplane/valuesprovider.go b/pkg/controller/controlplane/valuesprovider.go index cda48001..1fbbe678 100644 --- a/pkg/controller/controlplane/valuesprovider.go +++ b/pkg/controller/controlplane/valuesprovider.go @@ -205,12 +205,12 @@ var ( }, }, { - Name: openstack.STACKITALBControllerManagerName, - Images: []string{imagevector.ImageNameStackitAlbControllerManager}, + Name: openstack.STACKITApplicationLoadBalancerControllerName, + Images: []string{imagevector.ImageNameStackitApplicationLoadBalancerController}, Objects: []*chart.Object{ - // stackit-alb-controller-manager - {Type: &appsv1.Deployment{}, Name: openstack.STACKITALBControllerManagerName}, - {Type: &vpaautoscalingv1.VerticalPodAutoscaler{}, Name: openstack.STACKITALBControllerManagerName}, + // stackit-application-load-balancer-controller + {Type: &appsv1.Deployment{}, Name: openstack.STACKITApplicationLoadBalancerControllerName}, + {Type: &vpaautoscalingv1.VerticalPodAutoscaler{}, Name: openstack.STACKITApplicationLoadBalancerControllerName}, }, }, { @@ -363,25 +363,23 @@ type CSICompatibilityHandler interface { } // NewValuesProvider creates a new ValuesProvider for the generic actuator. -func NewValuesProvider(mgr manager.Manager, deployALBIngressController bool, customLabelDomain string, csiCompatibilityHandler CSICompatibilityHandler) genericactuator.ValuesProvider { +func NewValuesProvider(mgr manager.Manager, customLabelDomain string, csiCompatibilityHandler CSICompatibilityHandler) genericactuator.ValuesProvider { return &valuesProvider{ - client: mgr.GetClient(), - decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(), - deployALBIngressController: deployALBIngressController, - customLabelDomain: customLabelDomain, - csiCompatibilityHandler: csiCompatibilityHandler, + client: mgr.GetClient(), + decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(), + customLabelDomain: customLabelDomain, + csiCompatibilityHandler: csiCompatibilityHandler, } } // valuesProvider is a ValuesProvider that provides OpenStack-specific values for the 2 charts applied by the generic actuator. type valuesProvider struct { genericactuator.NoopValuesProvider - client k8sclient.Client - config *rest.Config - decoder runtime.Decoder - deployALBIngressController bool - customLabelDomain string - csiCompatibilityHandler CSICompatibilityHandler + client k8sclient.Client + config *rest.Config + decoder runtime.Decoder + customLabelDomain string + csiCompatibilityHandler CSICompatibilityHandler } // GetConfigChartValues returns the values for the config chart applied by the generic actuator. @@ -787,17 +785,18 @@ func (vp *valuesProvider) getControlPlaneChartValues(ctx context.Context, cpConf return nil, fmt.Errorf("unsupported storage CSI Driver: %s", storageCSIDriver) } - if vp.deployALBIngressController { - fmt.Println("deploying ALB Ingress Controller") - albcm, err := getSTACKITALBCMChartValues(cpConfig, cluster, infra, stackitCredentialsConfig, apiEndpoints, scaledDown, stackitRegion) + if DeploySTACKITApplicationLoadBalancer(cpConfig) { + // Currently only the ingress source is allowed and the validation does not allow to enable the ALB controller of no source is enabled. + // When adding support for GatewayAPI it is required to adopt the configuration of the ALB controller here. + albcm, err := getSTACKITApplicationLoadBalancerCMChartValues(cpConfig, cluster, infra, stackitCredentialsConfig, apiEndpoints, checksums, scaledDown, stackitRegion) if err != nil { return nil, err } - controlPlaneValues[openstack.STACKITALBControllerManagerName] = albcm + controlPlaneValues[openstack.STACKITApplicationLoadBalancerControllerName] = albcm } else { // NOTE: ensure deletion of ALB deployment, if disabled - if err := vp.deleteControlPlaneComponentsForGivenChart(ctx, cp.Namespace, openstack.STACKITALBControllerManagerName); err != nil { + if err := vp.deleteControlPlaneComponentsForGivenChart(ctx, cp.Namespace, openstack.STACKITApplicationLoadBalancerControllerName); err != nil { return nil, err } } @@ -1041,56 +1040,68 @@ func getCSIControllerChartValues(cluster *extensionscontroller.Cluster, userAgen return values } -func getSTACKITALBCMChartValues( - cpConfig *stackitv1alpha1.ControlPlaneConfig, +func getSTACKITApplicationLoadBalancerCMChartValues( + _ *stackitv1alpha1.ControlPlaneConfig, cluster *extensionscontroller.Cluster, infra *stackitv1alpha1.InfrastructureStatus, credentials *stackit.Credentials, apiEndpoints *stackitv1alpha1.APIEndpoints, + checksums map[string]string, scaledDown bool, stackitRegion string, ) (map[string]any, error) { - if !DeploySTACKITALB(cpConfig) { - return nil, nil - } - if credentials == nil { return nil, fmt.Errorf("no STACKIT credentials are provided in cluster %s", cluster.Shoot.Name) } - - config := map[string]any{ - "region": stackitRegion, - "stackitProjectID": credentials.ProjectID, + if infra == nil { + return map[string]any{}, fmt.Errorf("can't determine networkID from as InfrastructureStatus is nil in cluster %s", cluster.Shoot.Name) } - if infra != nil { - config["stackitNetworkID"] = infra.Networks.ID + globalSettings := map[string]any{ + "region": stackitRegion, + "projectId": credentials.ProjectID, } if apiEndpoints != nil { + endpoints := map[string]any{} if apiEndpoints.ApplicationLoadBalancer != nil { - config["applicationLBApiUrl"] = apiEndpoints.ApplicationLoadBalancer + endpoints["applicationLoadBalancerApi"] = apiEndpoints.ApplicationLoadBalancer } - if apiEndpoints.LoadBalancerCertificate != nil { - config["certificateApiUrl"] = *apiEndpoints.LoadBalancerCertificate + if apiEndpoints.ApplicationLoadBalancerCertificate != nil { + endpoints["applicationLoadBalancerCertificateApi"] = *apiEndpoints.ApplicationLoadBalancerCertificate } - if apiEndpoints.TokenEndpoint != nil { - config["tokenUrl"] = *apiEndpoints.TokenEndpoint + if len(endpoints) > 0 { + globalSettings["apiEndpoints"] = endpoints } } + config := map[string]any{ + "global": globalSettings, + "applicationLoadBalancer": map[string]any{ + "networkId": infra.Networks.ID, + }, + } + values := map[string]any{ "enabled": true, "replicas": extensionscontroller.GetControlPlaneReplicas(cluster, scaledDown, 1), "config": config, + "podAnnotations": map[string]any{ + "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: checksums[v1beta1constants.SecretNameCloudProvider], + "checksum/application-load-balancer-config": gardenerutils.ComputeChecksum(config), + }, + } + + if apiEndpoints != nil && apiEndpoints.TokenEndpoint != nil { + values["tokenEndpoint"] = *apiEndpoints.TokenEndpoint } return values, nil } -func DeploySTACKITALB(cpConfig *stackitv1alpha1.ControlPlaneConfig) bool { +func DeploySTACKITApplicationLoadBalancer(cpConfig *stackitv1alpha1.ControlPlaneConfig) bool { return ptr.Deref(cpConfig.ApplicationLoadBalancer, stackitv1alpha1.ApplicationLoadBalancerConfig{}).Enabled } diff --git a/pkg/controller/controlplane/valuesprovider_test.go b/pkg/controller/controlplane/valuesprovider_test.go index 6796e224..ce1cf564 100644 --- a/pkg/controller/controlplane/valuesprovider_test.go +++ b/pkg/controller/controlplane/valuesprovider_test.go @@ -65,9 +65,9 @@ func newTestScheme() *runtime.Scheme { return scheme } -func newTestValuesProvider(cl client.Client, scheme *runtime.Scheme, deployALB bool, customLabelDomain string) *valuesProvider { +func newTestValuesProvider(cl client.Client, scheme *runtime.Scheme, customLabelDomain string) *valuesProvider { mgr := &testutils.FakeManager{Scheme: scheme, Client: cl} - return NewValuesProvider(mgr, deployALB, customLabelDomain, new(noopCSICompatibilityHandler)).(*valuesProvider) + return NewValuesProvider(mgr, customLabelDomain, new(noopCSICompatibilityHandler)).(*valuesProvider) } func baseControlPlaneConfig() *stackitv1alpha1.ControlPlaneConfig { @@ -436,7 +436,7 @@ var _ = Describe("ValuesProvider fake client", func() { scheme = newTestScheme() c = fake.NewClientBuilder().WithScheme(scheme).Build() secretsManager = fakesecretsmanager.New(c, namespace) - vp = newTestValuesProvider(c, scheme, true, "kubernetes.io") + vp = newTestValuesProvider(c, scheme, "kubernetes.io") }) Describe("#GetConfigChartValues", func() { @@ -617,7 +617,7 @@ var _ = Describe("ValuesProvider fake client", func() { "tlsSecretName": stackitPodIdentityWebhookServerName, }, })) - Expect(values[openstack.STACKITALBControllerManagerName]).To(BeNil()) + Expect(values[openstack.STACKITApplicationLoadBalancerControllerName]).To(BeNil()) }) It("returns OpenStack CSI values when selected", func() { @@ -707,7 +707,7 @@ var _ = Describe("ValuesProvider fake client", func() { DescribeTable("propagates custom label domains", func(customLabelDomain string) { - vp = newTestValuesProvider(c, scheme, true, customLabelDomain) + vp = newTestValuesProvider(c, scheme, customLabelDomain) cp, cluster, providerSecret, _ := seedReadyControlPlane(ctx, c) values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) @@ -726,32 +726,47 @@ var _ = Describe("ValuesProvider fake client", func() { It("returns ALB controller values when enabled", func() { cp, cluster, providerSecret, _ := seedReadyControlPlane(ctx, c) cpConfig := baseControlPlaneConfig() - cpConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{Enabled: true} + cpConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{ + Enabled: true, + Ingress: &stackitv1alpha1.ApplicationLoadBalancerConfigIngress{ + Enabled: false, + }, + } cp.Spec.ProviderConfig.Raw = encode(cpConfig) values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(chartValues(values, openstack.STACKITALBControllerManagerName)).To(BeComparableTo(map[string]any{ + config := map[string]any{ + "global": map[string]any{ + "region": "eu01", + "projectId": "foo", + }, + "applicationLoadBalancer": map[string]any{ + "networkId": "network-acbd1234", + }, + } + + Expect(chartValues(values, openstack.STACKITApplicationLoadBalancerControllerName)).To(BeComparableTo(map[string]any{ "enabled": true, "replicas": 1, - "config": map[string]any{ - "region": "eu01", - "stackitProjectID": "foo", - "stackitNetworkID": "network-acbd1234", + "podAnnotations": map[string]any{ + "checksum/application-load-balancer-config": gardenerutils.ComputeChecksum(config), + "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: gardenerutils.ComputeChecksum(providerSecret.Data), }, + "config": config, })) }) - It("omits ALB controller values when the config disables ALB deployment", func() { - vp = newTestValuesProvider(c, scheme, false, "kubernetes.io") + It("omits ALB controller values when the alb is ALB deployment", func() { + vp = newTestValuesProvider(c, scheme, "kubernetes.io") cp, cluster, providerSecret, _ := seedReadyControlPlane(ctx, c) cpConfig := baseControlPlaneConfig() - cpConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{Enabled: true} + cpConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{Enabled: false} cp.Spec.ProviderConfig.Raw = encode(cpConfig) values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(values[openstack.STACKITALBControllerManagerName]).To(BeNil()) + Expect(values[openstack.STACKITApplicationLoadBalancerControllerName]).To(BeNil()) }) It("deletes legacy cleanup objects from the fake client", func() { @@ -858,7 +873,7 @@ var _ = Describe("ValuesProvider fake client", func() { }, }). Build() - interceptedProvider := newTestValuesProvider(interceptedClient, scheme, true, "kubernetes.io") + interceptedProvider := newTestValuesProvider(interceptedClient, scheme, "kubernetes.io") apiURL, apiToken, err := interceptedProvider.checkEmergencyLoadBalancerAccess(ctx, secretKey) Expect(err).To(MatchError(expectedErr)) diff --git a/pkg/controller/healthcheck/add.go b/pkg/controller/healthcheck/add.go index 4ab43580..abb74407 100644 --- a/pkg/controller/healthcheck/add.go +++ b/pkg/controller/healthcheck/add.go @@ -31,20 +31,28 @@ import ( var ( defaultSyncPeriod = time.Second * 30 // DefaultAddOptions are the default DefaultAddArgs for AddToManager. - DefaultAddOptions = healthcheck.DefaultAddArgs{ - HealthCheckConfig: healthcheckconfig.HealthCheckConfig{ - SyncPeriod: metav1.Duration{Duration: defaultSyncPeriod}, - ShootRESTOptions: &healthcheckconfig.RESTOptions{ - QPS: new(float32(100)), - Burst: new(130), + DefaultAddOptions = AddOptions{ + DefaultAddArgs: healthcheck.DefaultAddArgs{ + HealthCheckConfig: healthcheckconfig.HealthCheckConfig{ + SyncPeriod: metav1.Duration{Duration: defaultSyncPeriod}, + ShootRESTOptions: &healthcheckconfig.RESTOptions{ + QPS: new(float32(100)), + Burst: new(130), + }, }, }, } ) +// AddOptions are options to apply when adding the STACKIT healthcheck controller to the manager. +type AddOptions struct { + // DefaultAddArgs + healthcheck.DefaultAddArgs +} + // RegisterHealthChecks registers health checks for each extension resource // HealthChecks are grouped by extension (e.g worker), extension.type (e.g aws) and Health Check Type (e.g ShootControlPlaneHealthy) -func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts healthcheck.DefaultAddArgs) error { +func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts AddOptions) error { healthchecks := []healthcheck.ConditionTypeToHealthCheck{ { ConditionType: string(gardencorev1beta1.ShootControlPlaneHealthy), @@ -75,16 +83,11 @@ func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts healthc HealthCheck: general.NewSeedDeploymentHealthChecker(controlplane.CSIStackitPrefix + "-" + openstack.CSISnapshotControllerName), PreCheckFunc: checkCSISTACKIT, }, - } - - if controlplane.DeployALBIngressController { - healthchecks = append(healthchecks, - healthcheck.ConditionTypeToHealthCheck{ - ConditionType: string(gardencorev1beta1.ShootControlPlaneHealthy), - HealthCheck: general.NewSeedDeploymentHealthChecker(openstack.STACKITALBControllerManagerName), - PreCheckFunc: checkALB, - }, - ) + { + ConditionType: string(gardencorev1beta1.ShootControlPlaneHealthy), + HealthCheck: general.NewSeedDeploymentHealthChecker(openstack.STACKITApplicationLoadBalancerControllerName), + PreCheckFunc: checkALB, + }, } if err := healthcheck.DefaultRegistration( @@ -93,7 +96,7 @@ func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts healthc func() client.ObjectList { return &extensionsv1alpha1.ControlPlaneList{} }, func() extensionsv1alpha1.Object { return &extensionsv1alpha1.ControlPlane{} }, mgr, - opts, + opts.DefaultAddArgs, nil, healthchecks, sets.New[gardencorev1beta1.ConditionType](), @@ -107,7 +110,7 @@ func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts healthc func() client.ObjectList { return &extensionsv1alpha1.WorkerList{} }, func() extensionsv1alpha1.Object { return &extensionsv1alpha1.Worker{} }, mgr, - opts, + opts.DefaultAddArgs, nil, []healthcheck.ConditionTypeToHealthCheck{{ ConditionType: string(gardencorev1beta1.ShootEveryNodeReady), @@ -173,7 +176,7 @@ func checkALB(_ context.Context, client client.Client, _ client.Object, clusterO return false } - return controlplane.DeploySTACKITALB(cpConfig) + return controlplane.DeploySTACKITApplicationLoadBalancer(cpConfig) } // AddToManager adds a controller with the default Options. diff --git a/pkg/openstack/types.go b/pkg/openstack/types.go index 6b38156b..25c73e80 100644 --- a/pkg/openstack/types.go +++ b/pkg/openstack/types.go @@ -73,8 +73,8 @@ const ( CloudControllerManagerName = "cloud-controller-manager" // STACKITCloudControllerManagerName is a constant for the name of the CloudController deployed by the worker controller. (stackit) STACKITCloudControllerManagerName = "stackit-cloud-controller-manager" - // STACKITALBControllerManagerName is a constant for the name of the ALB CloudController. (stackit) - STACKITALBControllerManagerName = "stackit-alb-controller-manager" + // STACKITApplicationLoadBalancerControllerName is a constant for the name of the ALB CloudController. (stackit) + STACKITApplicationLoadBalancerControllerName = "stackit-application-load-balancer-controller" // CSIDiskDriverTopologyKey is the label on persistent volumes that represents availability by zone. // See https://github.com/kubernetes/cloud-provider-openstack/blob/master/examples/cinder-csi-plugin/topology/example.yaml // See https://gitlab.cern.ch/cloud/cloud-provider-openstack/-/blob/release-1.19/docs/using-cinder-csi-plugin.md#enable-topology-aware-dynamic-provisioning-for-cinder-volumes From db9e1d95173e769b731f52d2d5289604d814a96e Mon Sep 17 00:00:00 2001 From: Maximilian Geberl Date: Wed, 15 Jul 2026 08:11:20 +0200 Subject: [PATCH 2/4] Add tests to validation --- pkg/admission/validator/shoot_test.go | 2 +- .../stackit/validation/controlplane_test.go | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/pkg/admission/validator/shoot_test.go b/pkg/admission/validator/shoot_test.go index b63549e2..051a6994 100644 --- a/pkg/admission/validator/shoot_test.go +++ b/pkg/admission/validator/shoot_test.go @@ -49,7 +49,7 @@ var _ = Describe("NamespacedCloudProfile Validator", func() { Client: fakeClient, Scheme: scheme, } - shootValidator = validator.NewShootValidator(fakeManager) + shootValidator = validator.NewShootValidator(fakeManager, true) infrastructureConfig = v1alpha1.InfrastructureConfig{ FloatingPoolName: "floating-pool", diff --git a/pkg/apis/stackit/validation/controlplane_test.go b/pkg/apis/stackit/validation/controlplane_test.go index 13108e4e..324f2901 100644 --- a/pkg/apis/stackit/validation/controlplane_test.go +++ b/pkg/apis/stackit/validation/controlplane_test.go @@ -58,6 +58,46 @@ var _ = Describe("ControlPlaneConfig validation", func() { })), )) }) + + It("should fail with no controller enabled for application load balancer", func() { + controlPlane.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{ + Enabled: true, + Ingress: &stackitv1alpha1.ApplicationLoadBalancerConfigIngress{ + Enabled: false, + }, + } + Expect(ValidateControlPlaneConfig(controlPlane, "", true, nilPath)).To(ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("applicationLoadBalancer"), + })), + )) + }) + + It("should fail with application load balancer and allow application load balancer controller on false", func() { + controlPlane.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{ + Enabled: true, + Ingress: &stackitv1alpha1.ApplicationLoadBalancerConfigIngress{ + Enabled: true, + }, + } + Expect(ValidateControlPlaneConfig(controlPlane, "", false, nilPath)).To(ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("applicationLoadBalancer.enabled"), + })), + )) + }) + + It("should succeed with application load balancer correctly configured", func() { + controlPlane.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{ + Enabled: true, + Ingress: &stackitv1alpha1.ApplicationLoadBalancerConfigIngress{ + Enabled: true, + }, + } + Expect(ValidateControlPlaneConfig(controlPlane, "", true, nilPath).ToAggregate()).To(Succeed()) + }) }) Describe("#ValidateControlPlaneConfigUpdate", func() { From 0979497e20eecfa457f4db499856c2ffcad44d62 Mon Sep 17 00:00:00 2001 From: Maximilian Geberl Date: Wed, 15 Jul 2026 08:13:52 +0200 Subject: [PATCH 3/4] Add missing pdb and configmap --- pkg/controller/controlplane/valuesprovider.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/controller/controlplane/valuesprovider.go b/pkg/controller/controlplane/valuesprovider.go index 1fbbe678..886ce250 100644 --- a/pkg/controller/controlplane/valuesprovider.go +++ b/pkg/controller/controlplane/valuesprovider.go @@ -211,6 +211,8 @@ var ( // stackit-application-load-balancer-controller {Type: &appsv1.Deployment{}, Name: openstack.STACKITApplicationLoadBalancerControllerName}, {Type: &vpaautoscalingv1.VerticalPodAutoscaler{}, Name: openstack.STACKITApplicationLoadBalancerControllerName}, + {Type: &policyv1.PodDisruptionBudget{}, Name: openstack.STACKITApplicationLoadBalancerControllerName}, + {Type: &corev1.ConfigMap{}, Name: openstack.STACKITApplicationLoadBalancerControllerName}, }, }, { From b0c6c839d6f4b34b374c810c2c32ff49227132b3 Mon Sep 17 00:00:00 2001 From: Maximilian Geberl Date: Wed, 15 Jul 2026 08:15:35 +0200 Subject: [PATCH 4/4] Fix wrong namings --- .../stackit-application-load-balancer-controller/values.yaml | 2 +- pkg/admission/validator/shoot.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml index 14d889bc..eb6dac91 100644 --- a/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml +++ b/charts/internal/seed-controlplane/charts/stackit-application-load-balancer-controller/values.yaml @@ -13,7 +13,7 @@ metricsPort: 9090 podAnnotations: {} podLabels: {} images: - stackit-alb-controller-manager: image:tag + stackit-application-load-balancer-controller: image:tag vpa: resourcePolicy: maxAllowed: diff --git a/pkg/admission/validator/shoot.go b/pkg/admission/validator/shoot.go index 10fa264e..04e28eea 100644 --- a/pkg/admission/validator/shoot.go +++ b/pkg/admission/validator/shoot.go @@ -49,7 +49,7 @@ func (s *shoot) Validate(_ context.Context, newObj, oldObj client.Object) error allErrs := field.ErrorList{} - allErrs = append(allErrs, stackitvalidation.ValidateControlPlaneConfig(cpConfig, shoot.Spec.Kubernetes.Version, s.allowApplicationLoadBalancerController, field.NewPath("spec").Child("provider").Child("controlPlaneConfig").Child("controlPlaneConfig"))...) + allErrs = append(allErrs, stackitvalidation.ValidateControlPlaneConfig(cpConfig, shoot.Spec.Kubernetes.Version, s.allowApplicationLoadBalancerController, field.NewPath("spec").Child("provider").Child("controlPlaneConfig"))...) allErrs = append(allErrs, stackitvalidation.ValidateInfrastructureConfig(infraConfig, ptr.Deref(shoot.Spec.Networking, core.Networking{}).Nodes, field.NewPath("spec").Child("provider").Child("infrastructureConfig"))...)