From a4b54c218ade3a90d8f4be5004449c137ed693c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:05:57 +0000 Subject: [PATCH 1/4] Initial plan From ac402d2a39183093c64caa02b88a2f5bec818b80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:12:04 +0000 Subject: [PATCH 2/4] feat: migrate SKE DNS from bridge to native gateway_api provider support - Bump stackit provider from 0.106.0 to 0.113.0 in all terraform.tf files - Add gateway_api = true to extensions.dns in platform-kubernetes cluster - Add gateway_api field to dns variable type in platform-kubernetes and root modules - Wire gateway_api through main.tf dns block - Remove temporary DNS bridge: helm_release.demo_envoy_gateway, kubernetes_manifest.landing_zone_gateway_class, kubernetes_manifest.landing_zone_sample_gateway, kubernetes_manifest.landing_zone_sample_http_route, data.kubernetes_resources.landing_zone_sample_gateway_service, stackit_dns_record_set.landing_zone_sample_gateway, local.sample_gateway_lb_endpoint_by_key - Update docs/getting-started.md to document native gateway_api path Closes #41 Co-authored-by: lweberru <20902204+lweberru@users.noreply.github.com> --- docs/getting-started.md | 42 +--- src/_landing-zone-kubernetes.tf | 218 +------------------ src/main.tf | 1 + src/modules/connectivity/terraform.tf | 2 +- src/modules/debug-bastion/terraform.tf | 2 +- src/modules/devops/terraform.tf | 2 +- src/modules/governance/terraform.tf | 2 +- src/modules/landing-zone/terraform.tf | 2 +- src/modules/management/terraform.tf | 2 +- src/modules/namespace-service-demo/main.tf | 2 +- src/modules/platform-kubernetes/3-cluster.tf | 5 +- src/modules/platform-kubernetes/terraform.tf | 2 +- src/modules/platform-kubernetes/variables.tf | 3 +- src/modules/sandboxes/terraform.tf | 2 +- src/terraform.tf | 2 +- src/variables.tf | 1 + 16 files changed, 30 insertions(+), 260 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 9cad7cd..81e50a9 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -334,41 +334,21 @@ What exactly fails over, and what was measured, is in [High availability](archit ### Kubernetes: DNS automation for Gateway API resources -For Gateway API resources (for example Envoy Gateway with `Gateway` + `HTTPRoute`), use DNS records directly via `stackit_dns_record_set` until native provider support for `extensions.dns.gatewayApi` is available. - -For the existing sample content in this repository (`landing_zone_sample_gateway` + `landing_zone_sample_http_route` in `src/_landing-zone-kubernetes.tf`), the DNS record is created automatically based on the Envoy Gateway LoadBalancer endpoint discovered via `kubernetes_resources`. - -Implementation pattern: +Native `extensions.dns.gateway_api` support is now available in the STACKIT Terraform provider (since v0.109.0). Enable it in your `platform_kubernetes` configuration: ```hcl -# Discover Envoy-managed LoadBalancer service endpoint for each sample gateway -data "kubernetes_resources" "landing_zone_sample_gateway_service" { - provider = kubernetes.platform - - api_version = "v1" - kind = "Service" - namespace = "envoy-gateway-system" - label_selector = "gateway.envoyproxy.io/owning-gateway-name=,gateway.envoyproxy.io/owning-gateway-namespace=" -} - -# Create A or CNAME record depending on endpoint type -resource "stackit_dns_record_set" "landing_zone_sample_gateway" { - project_id = module.landing_zone["corp-exmpl"].project_id - zone_id = module.landing_zone["corp-exmpl"].dns_zone_id - - name = "app.${module.landing_zone["corp-exmpl"].dns_zone_dns_name}" - type = local.endpoint.ip != null ? "A" : "CNAME" - ttl = 60 - - records = [coalesce(local.endpoint.ip, local.endpoint.hostname)] - - lifecycle { - precondition { - condition = local.endpoint.ip != null || local.endpoint.hostname != null - error_message = "Gateway load balancer endpoint is not available yet for DNS record creation." +platform_kubernetes = { + my-cluster = { + dns = { + enabled = true + gateway_api = true } + # ... } } ``` -This ensures a stable, Terraform-managed DNS path without external scripts until provider-native `gatewayApi` DNS extension support is available. \ No newline at end of file +With `gateway_api = true`, the SKE DNS extension configures ExternalDNS to handle Gateway API resources (`Gateway`, `HTTPRoute`) automatically. You no longer need to manage `stackit_dns_record_set` records or discover LoadBalancer endpoints manually via `kubernetes_resources`. + +> [!NOTE] +> The Gateway API CRDs must be installed in the cluster before enabling this option. ExternalDNS will be configured at the next cluster reconcile. \ No newline at end of file diff --git a/src/_landing-zone-kubernetes.tf b/src/_landing-zone-kubernetes.tf index 36e744b..4694439 100644 --- a/src/_landing-zone-kubernetes.tf +++ b/src/_landing-zone-kubernetes.tf @@ -8,8 +8,8 @@ # blocks direct Secret management so credentials have to come through the Secrets Manager. # # The sample workload behind sample_load is demo material, not part of the landing zone -# contract: a pod, a Gateway API route and a DNS record that together prove the path from -# the internet to a namespace works. Drop it once real workloads move in. +# contract: a pod and a Service that together prove the path from the internet to a +# namespace works. Drop it once real workloads move in. locals { secrets_enforcement_default_exempt_principals = [ @@ -87,13 +87,6 @@ locals { for key, value in local.landing_zone_namespace_services : key => value if value.secrets_enforcement.enabled } - - sample_gateway_lb_endpoint_by_key = { - for key, data in data.kubernetes_resources.landing_zone_sample_gateway_service : key => { - ip = try(one(data.objects).status.loadBalancer.ingress[0].ip, null) - hostname = try(one(data.objects).status.loadBalancer.ingress[0].hostname, null) - } - } } module "namespace_service_demo" { @@ -118,57 +111,6 @@ resource "helm_release" "kyverno" { cleanup_on_fail = true } -resource "helm_release" "demo_envoy_gateway" { - provider = helm.platform - count = length([for svc in values(local.landing_zone_namespace_services) : svc if svc.sample_load.enabled && svc.dns_fqdn != null]) > 0 ? 1 : 0 - - name = "lz-demo-envoy-gateway" - namespace = "envoy-gateway-system" - chart = "oci://docker.io/envoyproxy/gateway-helm" - create_namespace = true - wait = false - timeout = 600 - atomic = false - cleanup_on_fail = false - - set = [ - { - name = "deployment.type" - value = "Kubernetes" - }, - { - name = "service.type" - value = "LoadBalancer" - }, - ] -} - -resource "kubernetes_manifest" "landing_zone_gateway_class" { - provider = kubernetes.platform - count = length([for svc in values(local.landing_zone_namespace_services) : svc if svc.sample_load.enabled && svc.dns_fqdn != null]) > 0 ? 1 : 0 - - manifest = { - apiVersion = "gateway.networking.k8s.io/v1" - kind = "GatewayClass" - metadata = { - name = "eg" - } - spec = { - controllerName = "gateway.envoyproxy.io/gatewayclass-controller" - } - } - - computed_fields = [ - "metadata", - "spec", - "status", - ] - - depends_on = [ - helm_release.demo_envoy_gateway, - ] -} - resource "kubernetes_manifest" "landing_zone_secret_enforcement_policy" { provider = kubernetes.platform @@ -491,159 +433,3 @@ resource "kubernetes_service_v1" "landing_zone_sample_load" { } } -resource "kubernetes_manifest" "landing_zone_sample_gateway" { - provider = kubernetes.platform - - for_each = { - for key, value in local.landing_zone_namespace_services : key => value - if value.sample_load.enabled && value.dns_fqdn != null - } - - manifest = { - apiVersion = "gateway.networking.k8s.io/v1" - kind = "Gateway" - metadata = { - name = "${kubernetes_service_v1.landing_zone_sample_load[each.key].metadata[0].name}-gw" - namespace = kubernetes_namespace_v1.landing_zone[each.key].metadata[0].name - annotations = { - "external-dns.alpha.kubernetes.io/hostname" = each.value.dns_fqdn - } - labels = { - "app.kubernetes.io/name" = "sample-load" - "stackit.cloud/landing-zone" = each.key - "stackit.cloud/sample-load" = "true" - } - } - spec = { - gatewayClassName = "eg" - listeners = [ - { - name = "http" - protocol = "HTTP" - port = 80 - allowedRoutes = { - namespaces = { - from = "Same" - } - } - } - ] - } - } - - computed_fields = [ - "metadata", - "spec", - "status", - ] - - depends_on = [ - kubernetes_manifest.landing_zone_gateway_class, - helm_release.demo_envoy_gateway, - ] -} - -resource "kubernetes_manifest" "landing_zone_sample_http_route" { - provider = kubernetes.platform - - for_each = { - for key, value in local.landing_zone_namespace_services : key => value - if value.sample_load.enabled && value.dns_fqdn != null - } - - manifest = { - apiVersion = "gateway.networking.k8s.io/v1" - kind = "HTTPRoute" - metadata = { - name = "${kubernetes_service_v1.landing_zone_sample_load[each.key].metadata[0].name}-route" - namespace = kubernetes_namespace_v1.landing_zone[each.key].metadata[0].name - labels = { - "app.kubernetes.io/name" = "sample-load" - "stackit.cloud/landing-zone" = each.key - "stackit.cloud/sample-load" = "true" - } - } - spec = { - parentRefs = [ - { - name = kubernetes_manifest.landing_zone_sample_gateway[each.key].manifest.metadata.name - } - ] - rules = [ - { - matches = [ - { - path = { - type = "PathPrefix" - value = "/" - } - } - ] - backendRefs = [ - { - name = kubernetes_service_v1.landing_zone_sample_load[each.key].metadata[0].name - port = 80 - } - ] - } - ] - } - } - - computed_fields = [ - "metadata", - "spec", - "status", - ] - - depends_on = [ - kubernetes_manifest.landing_zone_sample_gateway, - ] -} - -data "kubernetes_resources" "landing_zone_sample_gateway_service" { - provider = kubernetes.platform - - for_each = { - for key, value in local.landing_zone_namespace_services : key => value - if value.sample_load.enabled && value.dns_fqdn != null - } - - api_version = "v1" - kind = "Service" - namespace = "envoy-gateway-system" - label_selector = "gateway.envoyproxy.io/owning-gateway-name=${kubernetes_manifest.landing_zone_sample_gateway[each.key].manifest.metadata.name},gateway.envoyproxy.io/owning-gateway-namespace=${kubernetes_namespace_v1.landing_zone[each.key].metadata[0].name}" - - depends_on = [ - kubernetes_manifest.landing_zone_sample_gateway, - ] -} - -resource "stackit_dns_record_set" "landing_zone_sample_gateway" { - for_each = { - for key, value in local.landing_zone_namespace_services : key => value - if value.sample_load.enabled && value.dns_fqdn != null - } - - project_id = module.landing_zone[each.key].project_id - zone_id = module.landing_zone[each.key].dns_zone_id - name = each.value.dns_fqdn - type = try(local.sample_gateway_lb_endpoint_by_key[each.key].ip, null) != null ? "A" : "CNAME" - ttl = 60 - records = [ - coalesce( - try(local.sample_gateway_lb_endpoint_by_key[each.key].ip, null), - try(local.sample_gateway_lb_endpoint_by_key[each.key].hostname, null), - ), - ] - - lifecycle { - precondition { - condition = ( - try(local.sample_gateway_lb_endpoint_by_key[each.key].ip, null) != null || - try(local.sample_gateway_lb_endpoint_by_key[each.key].hostname, null) != null - ) - error_message = "Gateway load balancer endpoint is not available yet for DNS record creation." - } - } -} diff --git a/src/main.tf b/src/main.tf index 47d2948..2e3ca8f 100644 --- a/src/main.tf +++ b/src/main.tf @@ -131,6 +131,7 @@ module "platform_kubernetes" { enabled = each.value.dns.enabled create_zones = each.value.dns.create_zones zones = length(each.value.dns.zones) > 0 ? each.value.dns.zones : compact(distinct([for lz in values(module.landing_zone) : try(lz.dns_zone_dns_name, null)])) + gateway_api = each.value.dns.gateway_api } } diff --git a/src/modules/connectivity/terraform.tf b/src/modules/connectivity/terraform.tf index a131f20..22f261d 100644 --- a/src/modules/connectivity/terraform.tf +++ b/src/modules/connectivity/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } time = { source = "hashicorp/time" diff --git a/src/modules/debug-bastion/terraform.tf b/src/modules/debug-bastion/terraform.tf index 3ebb3ce..8e098d4 100644 --- a/src/modules/debug-bastion/terraform.tf +++ b/src/modules/debug-bastion/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } } } diff --git a/src/modules/devops/terraform.tf b/src/modules/devops/terraform.tf index 233ba67..cb240e6 100644 --- a/src/modules/devops/terraform.tf +++ b/src/modules/devops/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } } } \ No newline at end of file diff --git a/src/modules/governance/terraform.tf b/src/modules/governance/terraform.tf index de61aad..f49622a 100644 --- a/src/modules/governance/terraform.tf +++ b/src/modules/governance/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } time = { source = "hashicorp/time" diff --git a/src/modules/landing-zone/terraform.tf b/src/modules/landing-zone/terraform.tf index 90cd055..eb134c6 100644 --- a/src/modules/landing-zone/terraform.tf +++ b/src/modules/landing-zone/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } time = { source = "hashicorp/time" diff --git a/src/modules/management/terraform.tf b/src/modules/management/terraform.tf index e7ee7cd..bb2bc21 100644 --- a/src/modules/management/terraform.tf +++ b/src/modules/management/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } time = { source = "hashicorp/time" diff --git a/src/modules/namespace-service-demo/main.tf b/src/modules/namespace-service-demo/main.tf index f69e0ff..83262bc 100644 --- a/src/modules/namespace-service-demo/main.tf +++ b/src/modules/namespace-service-demo/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } grafana = { source = "grafana/grafana" diff --git a/src/modules/platform-kubernetes/3-cluster.tf b/src/modules/platform-kubernetes/3-cluster.tf index 319c4fd..68ab9c5 100644 --- a/src/modules/platform-kubernetes/3-cluster.tf +++ b/src/modules/platform-kubernetes/3-cluster.tf @@ -32,8 +32,9 @@ resource "stackit_ske_cluster" "this" { instance_id = var.observability.enabled ? stackit_observability_instance.this[0].instance_id : null } dns = { - enabled = var.dns.enabled && length(local.effective_dns_zones) > 0 - zones = local.effective_dns_zones + enabled = var.dns.enabled && length(local.effective_dns_zones) > 0 + zones = local.effective_dns_zones + gateway_api = var.dns.gateway_api } } diff --git a/src/modules/platform-kubernetes/terraform.tf b/src/modules/platform-kubernetes/terraform.tf index 99abeb8..c18d875 100644 --- a/src/modules/platform-kubernetes/terraform.tf +++ b/src/modules/platform-kubernetes/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } time = { source = "hashicorp/time" diff --git a/src/modules/platform-kubernetes/variables.tf b/src/modules/platform-kubernetes/variables.tf index 366a3d8..6fc6336 100644 --- a/src/modules/platform-kubernetes/variables.tf +++ b/src/modules/platform-kubernetes/variables.tf @@ -58,8 +58,9 @@ variable "dns" { enabled = optional(bool, true) create_zones = optional(bool, true) zones = optional(list(string), []) + gateway_api = optional(bool, true) }) - description = "SKE DNS extension configuration. If create_zones is true, zones are created in the platform project before cluster creation." + description = "SKE DNS extension configuration. If create_zones is true, zones are created in the platform project before cluster creation. gateway_api enables Gateway API support for ExternalDNS." default = {} } diff --git a/src/modules/sandboxes/terraform.tf b/src/modules/sandboxes/terraform.tf index 233ba67..cb240e6 100644 --- a/src/modules/sandboxes/terraform.tf +++ b/src/modules/sandboxes/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } } } \ No newline at end of file diff --git a/src/terraform.tf b/src/terraform.tf index 1ce1df2..a29bb87 100644 --- a/src/terraform.tf +++ b/src/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.106.0" + version = "0.113.0" } kubernetes = { source = "hashicorp/kubernetes" diff --git a/src/variables.tf b/src/variables.tf index 93a8690..fbcc083 100644 --- a/src/variables.tf +++ b/src/variables.tf @@ -81,6 +81,7 @@ variable "platform_kubernetes" { enabled = optional(bool, true) create_zones = optional(bool, true) zones = optional(list(string), []) + gateway_api = optional(bool, true) }), {}) observability = optional(object({ enabled = optional(bool, true) From 64e59be5d60a0f748063ec025478c9c098a25a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Weberru=C3=9F?= Date: Tue, 22 Sep 2026 11:49:44 +0200 Subject: [PATCH 3/4] fix(ske): address gateway API review feedback --- docs/getting-started.md | 8 ++++---- src/README.md | 5 +---- src/modules/platform-kubernetes/3-cluster.tf | 2 +- src/modules/platform-kubernetes/README.md | 6 +++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 5ad4ca9..f72fe1f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -334,21 +334,21 @@ What exactly fails over, and what was measured, is in [High availability](archit ### Kubernetes: DNS automation for Gateway API resources -Native `extensions.dns.gateway_api` support is now available in the STACKIT Terraform provider (since v0.109.0). Enable it in your `platform_kubernetes` configuration: +Native `extensions.dns.gateway_api` support is available in the STACKIT Terraform provider since v0.109.0 and is enabled by default whenever the SKE DNS extension has at least one zone. Disable it explicitly if the cluster does not use Gateway API resources: ```hcl platform_kubernetes = { my-cluster = { dns = { enabled = true - gateway_api = true + gateway_api = false } # ... } } ``` -With `gateway_api = true`, the SKE DNS extension configures ExternalDNS to handle Gateway API resources (`Gateway`, `HTTPRoute`) automatically. You no longer need to manage `stackit_dns_record_set` records or discover LoadBalancer endpoints manually via `kubernetes_resources`. +With the default `gateway_api = true`, the SKE DNS extension configures ExternalDNS to handle Gateway API resources (`Gateway`, `HTTPRoute`) automatically. You no longer need to manage DNS records or discover LoadBalancer endpoints manually. > [!NOTE] -> The Gateway API CRDs must be installed in the cluster before enabling this option. ExternalDNS will be configured at the next cluster reconcile. +> The Gateway API CRDs must be installed in the cluster while this option is enabled. Set `gateway_api = false` until the CRDs are available; ExternalDNS will be configured at the next cluster reconcile after enabling it. diff --git a/src/README.md b/src/README.md index a27eda9..bff17b0 100644 --- a/src/README.md +++ b/src/README.md @@ -19,7 +19,6 @@ | ---- | ------- | | [helm.platform](#provider\_helm.platform) | 3.3.0 | | [kubernetes.platform](#provider\_kubernetes.platform) | 3.2.1 | -| [stackit](#provider\_stackit) | 0.114.0 | | [terraform](#provider\_terraform) | n/a | | [vault](#provider\_vault) | 5.11.0 | @@ -54,10 +53,8 @@ | [kubernetes_secret_v1.landing_zone_user_token](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/secret_v1) | resource | | [kubernetes_service_account_v1.landing_zone_user](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/service_account_v1) | resource | | [kubernetes_service_v1.landing_zone_sample_load](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/resources/service_v1) | resource | -| [stackit_dns_record_set.landing_zone_sample_gateway](https://registry.terraform.io/providers/stackitcloud/stackit/0.114.0/docs/resources/dns_record_set) | resource | | [terraform_data.firewall_api_bootstrap](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | | [vault_kv_secret_v2.firewall_api_credentials](https://registry.terraform.io/providers/hashicorp/vault/5.11.0/docs/resources/kv_secret_v2) | resource | -| [kubernetes_resources.landing_zone_sample_gateway_service](https://registry.terraform.io/providers/hashicorp/kubernetes/3.2.1/docs/data-sources/resources) | data source | ## Inputs @@ -83,7 +80,7 @@ | [organization\_id](#input\_organization\_id) | Container ID of the root organization. | `string` | n/a | yes | | [organization\_owners](#input\_organization\_owners) | List of organization owners. | `list(string)` | `[]` | no | | [owner\_email](#input\_owner\_email) | Email address of the owner. Required for STACKIT resource manager. | `string` | n/a | yes | -| [platform\_kubernetes](#input\_platform\_kubernetes) | Map of central, region-scoped platform Kubernetes deployments. Empty map skips deployment. |
map(object({
region = string
network = optional(object({
sna_enabled = optional(bool, false)
sna_network_area_id = optional(string, null)
firewall_next_hop_ip = optional(string, null)
sna_network_prefix_length = optional(number, 24)
}), {})
dns = optional(object({
enabled = optional(bool, true)
create_zones = optional(bool, true)
zones = optional(list(string), [])
}), {})
observability = optional(object({
enabled = optional(bool, true)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, null)
}), {})
encrypted_volumes = optional(object({
enabled = optional(bool, false)
storage_class_name = optional(string, "stackit-encrypted-premium")
kms_keyring_name = optional(string, "ske-volume-keyring")
kms_key_name = optional(string, "ske-volume-key")
kms_key_version = optional(string, "1")
}), {})
debug_bastion = optional(object({
enabled = optional(bool, false)
name = optional(string, null)
availability_zone = optional(string, null)
machine_type = optional(string, "g2i.1")
image_id = optional(string, "7b10e105-295b-4369-b6e0-567ec940a02b")
boot_volume_size = optional(number, 20)
ssh_public_key = optional(string, null)
ssh_public_key_path = optional(string, "~/.ssh/id_rsa.pub")
ssh_allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
assign_public_ip = optional(bool, true)
install_kubectl = optional(bool, true)
}), {})
role_assignments = optional(list(object({
role = string
subject = string
})), [])
cluster = object({
name = string
kubernetes_version_min = optional(string, null)
node_pools = optional(list(object({
name = string
machine_type = string
minimum = number
maximum = number
availability_zones = list(string)
allow_system_components = optional(bool, false)
volume_size = optional(number, 20)
volume_type = optional(string, "storage_premium_perf1")
os_name = optional(string, "flatcar")
labels = optional(map(string), {})
})), [
{
name = "system"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-1"]
allow_system_components = true
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "system"
}
},
{
name = "application"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-2"]
allow_system_components = false
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "application"
}
}
])
maintenance = optional(object({
enable_kubernetes_version_updates = optional(bool, true)
enable_machine_image_version_updates = optional(bool, true)
start = optional(string, "01:00:00Z")
end = optional(string, "02:00:00Z")
}), {})
})
}))
| `{}` | no | +| [platform\_kubernetes](#input\_platform\_kubernetes) | Map of central, region-scoped platform Kubernetes deployments. Empty map skips deployment. |
map(object({
region = string
network = optional(object({
sna_enabled = optional(bool, false)
sna_network_area_id = optional(string, null)
firewall_next_hop_ip = optional(string, null)
sna_network_prefix_length = optional(number, 24)
}), {})
dns = optional(object({
enabled = optional(bool, true)
create_zones = optional(bool, true)
zones = optional(list(string), [])
gateway_api = optional(bool, true)
}), {})
observability = optional(object({
enabled = optional(bool, true)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, null)
}), {})
encrypted_volumes = optional(object({
enabled = optional(bool, false)
storage_class_name = optional(string, "stackit-encrypted-premium")
kms_keyring_name = optional(string, "ske-volume-keyring")
kms_key_name = optional(string, "ske-volume-key")
kms_key_version = optional(string, "1")
}), {})
debug_bastion = optional(object({
enabled = optional(bool, false)
name = optional(string, null)
availability_zone = optional(string, null)
machine_type = optional(string, "g2i.1")
image_id = optional(string, "7b10e105-295b-4369-b6e0-567ec940a02b")
boot_volume_size = optional(number, 20)
ssh_public_key = optional(string, null)
ssh_public_key_path = optional(string, "~/.ssh/id_rsa.pub")
ssh_allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
assign_public_ip = optional(bool, true)
install_kubectl = optional(bool, true)
}), {})
role_assignments = optional(list(object({
role = string
subject = string
})), [])
cluster = object({
name = string
kubernetes_version_min = optional(string, null)
node_pools = optional(list(object({
name = string
machine_type = string
minimum = number
maximum = number
availability_zones = list(string)
allow_system_components = optional(bool, false)
volume_size = optional(number, 20)
volume_type = optional(string, "storage_premium_perf1")
os_name = optional(string, "flatcar")
labels = optional(map(string), {})
})), [
{
name = "system"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-1"]
allow_system_components = true
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "system"
}
},
{
name = "application"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-2"]
allow_system_components = false
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "application"
}
}
])
maintenance = optional(object({
enable_kubernetes_version_updates = optional(bool, true)
enable_machine_image_version_updates = optional(bool, true)
start = optional(string, "01:00:00Z")
end = optional(string, "02:00:00Z")
}), {})
})
}))
| `{}` | no | | [platform\_kubernetes\_kube\_config\_override](#input\_platform\_kubernetes\_kube\_config\_override) | Optional raw kubeconfig used by kubernetes/helm platform providers when no platform\_kubernetes module output is available. | `string` | `null` | no | | [region](#input\_region) | STACKIT region for regional resources. | `string` | `"eu01"` | no | | [rm\_folder\_parent\_id](#input\_rm\_folder\_parent\_id) | ID of the parent folder under which the resource manager folders will be created. If not provided, the resource manager folders will be created under the organization. | `string` | `null` | no | diff --git a/src/modules/platform-kubernetes/3-cluster.tf b/src/modules/platform-kubernetes/3-cluster.tf index 84f4f7c..d2acc8e 100644 --- a/src/modules/platform-kubernetes/3-cluster.tf +++ b/src/modules/platform-kubernetes/3-cluster.tf @@ -38,7 +38,7 @@ resource "stackit_ske_cluster" "this" { dns = { enabled = var.dns.enabled && length(local.effective_dns_zones) > 0 zones = local.effective_dns_zones - gateway_api = var.dns.gateway_api + gateway_api = var.dns.enabled && length(local.effective_dns_zones) > 0 ? var.dns.gateway_api : null } } diff --git a/src/modules/platform-kubernetes/README.md b/src/modules/platform-kubernetes/README.md index 6536c51..c109346 100644 --- a/src/modules/platform-kubernetes/README.md +++ b/src/modules/platform-kubernetes/README.md @@ -11,8 +11,8 @@ | Name | Version | | ---- | ------- | -| [stackit](#provider\_stackit) | 0.114.0 | -| [time](#provider\_time) | 0.14.1 | +| [stackit](#provider\_stackit) | 0.116.0 | +| [time](#provider\_time) | 0.14.2 | ## Modules @@ -47,7 +47,7 @@ | ---- | ----------- | ---- | ------- | :------: | | [cluster](#input\_cluster) | SKE cluster configuration. |
object({
name = string
kubernetes_version_min = optional(string, null)
node_pools = optional(list(object({
name = string
machine_type = string
minimum = number
maximum = number
availability_zones = list(string)
allow_system_components = optional(bool, false)
volume_size = optional(number, 20)
volume_type = optional(string, "storage_premium_perf1")
os_name = optional(string, "flatcar")
labels = optional(map(string), {})
})), [
{
name = "system"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-1"]
allow_system_components = true
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "system"
}
},
{
name = "application"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["eu01-2"]
allow_system_components = false
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {
"workload-role" = "application"
}
}
])
maintenance = optional(object({
enable_kubernetes_version_updates = optional(bool, true)
enable_machine_image_version_updates = optional(bool, true)
start = optional(string, "01:00:00Z")
end = optional(string, "02:00:00Z")
}), {})
})
| n/a | yes | | [debug\_bastion](#input\_debug\_bastion) | Optional debug bastion VM in the SNA network with SSH access to test SKE connectivity from inside the private network. |
object({
enabled = optional(bool, false)
name = optional(string, null)
availability_zone = optional(string, null)
machine_type = optional(string, "g2i.1")
image_id = optional(string, "7b10e105-295b-4369-b6e0-567ec940a02b")
boot_volume_size = optional(number, 20)
ssh_public_key = optional(string, null)
ssh_public_key_path = optional(string, "~/.ssh/id_rsa.pub")
ssh_allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
assign_public_ip = optional(bool, true)
install_kubectl = optional(bool, true)
})
| `{}` | no | -| [dns](#input\_dns) | SKE DNS extension configuration. If create\_zones is true, zones are created in the platform project before cluster creation. |
object({
enabled = optional(bool, true)
create_zones = optional(bool, true)
zones = optional(list(string), [])
})
| `{}` | no | +| [dns](#input\_dns) | SKE DNS extension configuration. If create\_zones is true, zones are created in the platform project before cluster creation. gateway\_api enables Gateway API support for ExternalDNS. |
object({
enabled = optional(bool, true)
create_zones = optional(bool, true)
zones = optional(list(string), [])
gateway_api = optional(bool, true)
})
| `{}` | no | | [encrypted\_volumes](#input\_encrypted\_volumes) | Optional encrypted volume setup for SKE via KMS and Kubernetes storage class. |
object({
enabled = optional(bool, false)
storage_class_name = optional(string, "stackit-encrypted-premium")
kms_keyring_name = optional(string, "ske-volume-keyring")
kms_key_name = optional(string, "ske-volume-key")
kms_key_version = optional(string, "1")
})
| `{}` | no | | [labels](#input\_labels) | Additional labels to apply to resources in this module. | `map(string)` | `{}` | no | | [naming\_pattern](#input\_naming\_pattern) | Naming prefix for resources in this module, e.g. myco-pltfm-k8s-eu01. | `string` | n/a | yes | From 9e7c75d3fd11f34bc878c1323b7dfbedc654feb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Weberru=C3=9F?= Date: Tue, 22 Sep 2026 11:57:11 +0200 Subject: [PATCH 4/4] docs: clarify Gateway API CRD installation --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index f72fe1f..e46057f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -351,4 +351,4 @@ platform_kubernetes = { With the default `gateway_api = true`, the SKE DNS extension configures ExternalDNS to handle Gateway API resources (`Gateway`, `HTTPRoute`) automatically. You no longer need to manage DNS records or discover LoadBalancer endpoints manually. > [!NOTE] -> The Gateway API CRDs must be installed in the cluster while this option is enabled. Set `gateway_api = false` until the CRDs are available; ExternalDNS will be configured at the next cluster reconcile after enabling it. +> The SKE DNS extension does not install Gateway API CRDs. The optional Envoy Gateway demo installs them automatically. For other Gateway API implementations, install compatible CRDs separately. Once the CRDs are available, ExternalDNS is configured during the next cluster reconcile; no additional OpenTofu apply is required.