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/2] 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/2] 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)