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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 11 additions & 31 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-name>,gateway.envoyproxy.io/owning-gateway-namespace=<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.
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.
218 changes: 2 additions & 216 deletions src/_landing-zone-kubernetes.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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" {
Expand All @@ -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

Expand Down Expand Up @@ -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."
}
}
}
1 change: 1 addition & 0 deletions src/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/modules/connectivity/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
time = {
source = "hashicorp/time"
Expand Down
2 changes: 1 addition & 1 deletion src/modules/debug-bastion/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
}
}
2 changes: 1 addition & 1 deletion src/modules/devops/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
}
}
2 changes: 1 addition & 1 deletion src/modules/governance/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
time = {
source = "hashicorp/time"
Expand Down
2 changes: 1 addition & 1 deletion src/modules/landing-zone/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
time = {
source = "hashicorp/time"
Expand Down
2 changes: 1 addition & 1 deletion src/modules/management/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
time = {
source = "hashicorp/time"
Expand Down
2 changes: 1 addition & 1 deletion src/modules/namespace-service-demo/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
grafana = {
source = "grafana/grafana"
Expand Down
5 changes: 3 additions & 2 deletions src/modules/platform-kubernetes/3-cluster.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/modules/platform-kubernetes/terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "0.106.0"
version = "0.113.0"
}
time = {
source = "hashicorp/time"
Expand Down
Loading
Loading