Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ override.tf.json

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
*tfplan*

# Ignore CLI configuration files
.terraformrc
Expand Down
42 changes: 42 additions & 0 deletions src/config/hub-and-spoke.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,40 @@ connectivity = {
# allowed_network_ranges = ["0.0.0.0/0"]
# }

# platform_kubernetes = {
# "eu01" = {
# region = "eu01"
# network = {
# mode = "sna"
# }
# cluster = {
# name = "pltfmk8s"
# kubernetes_version_min = "1.35"
# node_pools = [
# {
# name = "small-a"
# machine_type = "g3i.4"
# minimum = 2
# maximum = 2
# availability_zones = ["eu01-1"]
# },
# {
# name = "small-b"
# machine_type = "g3i.4"
# minimum = 2
# maximum = 2
# availability_zones = ["eu01-2"]
# }
# ]
# }
#
# # Defaults to disabled. Set true to enable encrypted storage class setup.
# encrypted_volumes = {
# enabled = false
# }
# }
# }

###############
## SANDBOXES ##
###############
Expand All @@ -112,6 +146,14 @@ landing_zones = {
# Set corporate = true for network area connectivity, false for public internet
corporate = true
network_prefix_length = 24

# Optional: create namespace service in central platform Kubernetes cluster
# namespace_service = {
# enabled = true
# namespace = "data-prod"
# dns_subdomain = "app"
# secretsmanager = true
# }
}

# Public landing zone — no network area, uses STACKIT's default public networking
Expand Down
32 changes: 32 additions & 0 deletions src/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,37 @@ module "devops" {
allowed_network_ranges = var.devops.allowed_network_ranges
}

#########################
## PLATFORM KUBERNETES ##
#########################

module "platform_kubernetes" {
source = "./modules/platform-kubernetes"
for_each = var.platform_kubernetes

owner_email = var.owner_email
naming_pattern = "${var.company_code}-pltfm-k8s-${each.value.region}"
parent_container_id = module.governance.folder_container_ids["platform"]
labels = var.labels
region = each.value.region
role_assignments = each.value.role_assignments
cluster = each.value.cluster
observability = each.value.observability
encrypted_volumes = each.value.encrypted_volumes
debug_bastion = each.value.debug_bastion

network = {
mode = each.value.network.mode
sna_network_area_id = each.value.network.sna_network_area_id != null ? each.value.network.sna_network_area_id : try(module.connectivity[0].network_area_id, null)
}

dns = {
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)]))
}
}

###############
## SANDBOXES ##
###############
Expand Down Expand Up @@ -98,5 +129,6 @@ module "landing_zone" {
role_assignments = each.value.role_assignments
network_prefix_length = each.value.network_prefix_length
custom_roles = each.value.custom_roles
observability = each.value.observability
firewall_next_hop_ip = var.connectivity != null && var.connectivity.firewall != null ? module.connectivity[0].firewall_next_hop_ip : null # if firewall is enabled, pass the next hop IP to the landing zones for route configuration
}
2 changes: 1 addition & 1 deletion src/modules/connectivity/5-firewall.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resource "stackit_image" "firewall" {

project_id = stackit_resourcemanager_project.this.project_id
name = var.firewall.name
local_file_path = "./firewall-image.qcow2"
local_file_path = var.firewall != null ? "${path.root}/firewall-image.qcow2" : null
disk_format = "qcow2"
min_disk_size = 16
min_ram = 2
Expand Down
12 changes: 12 additions & 0 deletions src/modules/landing-zone/8-observability.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
###################
## OBSERVABILITY ##
###################

resource "stackit_observability_instance" "this" {
count = var.observability.enabled ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
name = var.observability.name != null ? var.observability.name : "${var.naming_pattern}-obs"
Comment thread
lweberru marked this conversation as resolved.
Outdated
plan_name = var.observability.plan_name
acl = var.observability.acl
}
31 changes: 31 additions & 0 deletions src/modules/landing-zone/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,35 @@ output "connected_network_area_id" {
output "landing_zone_type" {
description = "The type of the landing zone, either 'corporate' or 'public'."
value = var.corporate ? "corporate" : "public"
}

output "secretsmanager_instance_id" {
description = "The ID of the landing zone Secrets Manager instance."
value = stackit_secretsmanager_instance.this.instance_id
}

output "observability_instance_id" {
description = "The optional observability instance ID in the landing zone project."
value = var.observability.enabled ? stackit_observability_instance.this[0].instance_id : null
}

output "observability_grafana_url" {
description = "The Grafana URL of the optional landing zone observability instance."
value = var.observability.enabled ? stackit_observability_instance.this[0].grafana_url : null
}

output "observability_grafana_admin_user" {
description = "The initial Grafana admin user of the optional landing zone observability instance."
value = var.observability.enabled ? stackit_observability_instance.this[0].grafana_initial_admin_user : null
}

output "observability_grafana_admin_password" {
Comment thread
lweberru marked this conversation as resolved.
description = "The initial Grafana admin password of the optional landing zone observability instance."
value = var.observability.enabled ? stackit_observability_instance.this[0].grafana_initial_admin_password : null
sensitive = true
}

output "observability_metrics_push_url" {
description = "The Prometheus remote-write URL of the optional landing zone observability instance."
value = var.observability.enabled ? stackit_observability_instance.this[0].metrics_push_url : null
}
11 changes: 11 additions & 0 deletions src/modules/landing-zone/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,15 @@ variable "secretsmanager_acls" {
type = list(string)
description = "List of ACL rules for the Secrets Manager instance. Set to empty list for no ACLs or null to skip Secrets Manager creation."
default = []
}

variable "observability" {
type = object({
enabled = optional(bool, false)
plan_name = optional(string, "Observability-Starter-EU01")
acl = optional(list(string), [])
name = optional(string, null)
})
description = "Optional observability instance configuration in the landing zone project."
default = {}
}
22 changes: 22 additions & 0 deletions src/modules/platform-kubernetes/1-project.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
locals {
project_labels = merge(
{ "region" = var.region },
var.network.mode == "sna" && var.network.sna_network_area_id != null ? { "networkArea" = var.network.sna_network_area_id } : {},
var.labels
)
}

resource "stackit_resourcemanager_project" "this" {
parent_container_id = var.parent_container_id
name = var.project_name != null ? var.project_name : var.naming_pattern
owner_email = var.owner_email
labels = length(local.project_labels) > 0 ? local.project_labels : null
}

resource "stackit_authorization_project_role_assignment" "this" {
for_each = { for assignment in var.role_assignments : "${assignment.role}-${assignment.subject}" => assignment }

resource_id = stackit_resourcemanager_project.this.project_id
role = each.value.role
subject = each.value.subject
}
12 changes: 12 additions & 0 deletions src/modules/platform-kubernetes/2-dns-zones.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
locals {
Comment thread
lweberru marked this conversation as resolved.
Outdated
dns_extension_zones = distinct(compact(var.dns.zones))
}

resource "stackit_dns_zone" "ske_extension" {
for_each = var.dns.create_zones ? { for zone in local.dns_extension_zones : zone => zone } : {}

project_id = stackit_resourcemanager_project.this.project_id
name = each.value
dns_name = each.value
contact_email = var.owner_email
}
8 changes: 8 additions & 0 deletions src/modules/platform-kubernetes/2-network-area-membership.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "time_sleep" "wait_for_network_area_membership" {
count = var.network.mode == "sna" ? 1 : 0
Comment thread
lweberru marked this conversation as resolved.
Outdated

# Allow backend propagation after project label updates before SKE SNA validation.
create_duration = "30s"

depends_on = [stackit_resourcemanager_project.this]
}
8 changes: 8 additions & 0 deletions src/modules/platform-kubernetes/2-observability.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "stackit_observability_instance" "this" {
count = var.observability.enabled ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
name = var.observability.name != null ? var.observability.name : "${var.naming_pattern}-obs"
plan_name = var.observability.plan_name
acl = var.observability.acl
}
8 changes: 8 additions & 0 deletions src/modules/platform-kubernetes/2-sna-network.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "stackit_network" "sna" {
Comment thread
lweberru marked this conversation as resolved.
Outdated
count = local.use_sna ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
name = "${var.cluster.name}-sna"
ipv4_prefix_length = var.network.sna_network_prefix_length
routed = true
}
82 changes: 82 additions & 0 deletions src/modules/platform-kubernetes/3-cluster.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
locals {
use_sna = lower(var.network.mode) == "sna"
Comment thread
lweberru marked this conversation as resolved.
Outdated

effective_observability_instance_id = var.observability.enabled ? stackit_observability_instance.this[0].instance_id : null
Comment thread
lweberru marked this conversation as resolved.
Outdated
effective_dns_zones = var.dns.create_zones ? sort([
for zone in values(stackit_dns_zone.ske_extension) : zone.dns_name
]) : local.dns_extension_zones
default_node_pools = [
Comment thread
lweberru marked this conversation as resolved.
Outdated
{
name = "ha-a"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["${var.region}-1"]
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {}
},
{
name = "ha-b"
machine_type = "g3i.4"
minimum = 2
maximum = 2
availability_zones = ["${var.region}-2"]
volume_size = 20
volume_type = "storage_premium_perf1"
os_name = "flatcar"
labels = {}
}
]
effective_node_pools = length(var.cluster.node_pools) > 0 ? var.cluster.node_pools : local.default_node_pools
}

resource "stackit_ske_cluster" "this" {
project_id = stackit_resourcemanager_project.this.project_id
region = var.region
name = var.cluster.name
depends_on = [time_sleep.wait_for_network_area_membership, stackit_dns_zone.ske_extension]
Comment thread
lweberru marked this conversation as resolved.
Outdated
kubernetes_version_min = var.cluster.kubernetes_version_min
node_pools = local.effective_node_pools

maintenance = {
Comment thread
lweberru marked this conversation as resolved.
Outdated
enable_kubernetes_version_updates = var.cluster.maintenance.enable_kubernetes_version_updates
enable_machine_image_version_updates = var.cluster.maintenance.enable_machine_image_version_updates
start = var.cluster.maintenance.start
end = var.cluster.maintenance.end
}

network = local.use_sna ? {
id = stackit_network.sna[0].network_id
control_plane = {
access_scope = "SNA"
}
} : {
id = null
control_plane = {
access_scope = "PUBLIC"
}
}

extensions = {
observability = {
enabled = var.observability.enabled
instance_id = local.effective_observability_instance_id
}
dns = {
enabled = var.dns.enabled && length(local.effective_dns_zones) > 0
zones = local.effective_dns_zones
}
}
}

resource "stackit_ske_kubeconfig" "this" {
project_id = stackit_resourcemanager_project.this.project_id
region = var.region
cluster_name = stackit_ske_cluster.this.name

refresh = true
expiration = 7200
refresh_before = 1800
}
50 changes: 50 additions & 0 deletions src/modules/platform-kubernetes/4-encrypted-volumes.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
data "stackit_service_accounts" "ske_internal" {
count = var.encrypted_volumes.enabled ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
email_suffix = "@ske.sa.stackit.cloud"

depends_on = [stackit_ske_cluster.this]
}

resource "stackit_kms_keyring" "this" {
count = var.encrypted_volumes.enabled ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
display_name = "${var.naming_pattern}-${var.encrypted_volumes.kms_keyring_name}"
}

resource "stackit_kms_key" "this" {
count = var.encrypted_volumes.enabled ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
keyring_id = stackit_kms_keyring.this[0].keyring_id
display_name = "${var.naming_pattern}-${var.encrypted_volumes.kms_key_name}"
protection = "software"
algorithm = "aes_256_gcm"
purpose = "symmetric_encrypt_decrypt"
}

resource "stackit_service_account" "kms_manager" {
count = var.encrypted_volumes.enabled ? 1 : 0

project_id = stackit_resourcemanager_project.this.project_id
# STACKIT service account names are limited to 20 characters.
name = "${substr(var.naming_pattern, 0, 8)}-kms-mgr"
}

resource "stackit_authorization_project_role_assignment" "kms_admin" {
count = var.encrypted_volumes.enabled ? 1 : 0

resource_id = stackit_resourcemanager_project.this.project_id
role = "kms.admin"
subject = stackit_service_account.kms_manager[0].email
}

resource "stackit_authorization_service_account_role_assignment" "ske_impersonation" {
count = var.encrypted_volumes.enabled ? 1 : 0

resource_id = stackit_service_account.kms_manager[0].service_account_id
role = "user"
subject = data.stackit_service_accounts.ske_internal[0].items[0].email
}
Loading
Loading