From b4e3ceef1ee685f788135afac4260fdf86d8f0aa Mon Sep 17 00:00:00 2001 From: Shiwam Jaiswal Date: Mon, 9 Jun 2025 21:21:32 +0530 Subject: [PATCH 1/5] feat(example): move example to example dir --- examples/simple/main.tf | 379 ++++++++++++------- outputs.tf => examples/simple/outputs.tf | 2 +- examples/simple/terraform.tfvars.example | 32 -- variables.tf => examples/simple/variables.tf | 28 +- versions.tf => examples/simple/versions.tf | 6 +- main.tf | 268 ------------- 6 files changed, 269 insertions(+), 446 deletions(-) rename outputs.tf => examples/simple/outputs.tf (98%) delete mode 100644 examples/simple/terraform.tfvars.example rename variables.tf => examples/simple/variables.tf (95%) rename versions.tf => examples/simple/versions.tf (79%) delete mode 100644 main.tf diff --git a/examples/simple/main.tf b/examples/simple/main.tf index f9e09d4..392713d 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -1,25 +1,3 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - google = { - source = "hashicorp/google" - version = ">= 6.0" - } - kubernetes = { - source = "hashicorp/kubernetes" - version = "~> 2.0" - } - helm = { - source = "hashicorp/helm" - version = "~> 2.0" - } - random = { - source = "hashicorp/random" - version = "~> 3.0" - } - } -} provider "google" { project = var.project_id @@ -30,158 +8,291 @@ provider "google" { data "google_client_config" "default" {} provider "kubernetes" { - host = "https://${module.materialize.gke_cluster.endpoint}" + host = "https://${module.gke.cluster_endpoint}" token = data.google_client_config.default.access_token - cluster_ca_certificate = base64decode(module.materialize.gke_cluster.ca_certificate) + cluster_ca_certificate = base64decode(module.gke.cluster_ca_certificate) } provider "helm" { kubernetes { - host = "https://${module.materialize.gke_cluster.endpoint}" + host = "https://${module.gke.cluster_endpoint}" token = data.google_client_config.default.access_token - cluster_ca_certificate = base64decode(module.materialize.gke_cluster.ca_certificate) + cluster_ca_certificate = base64decode(module.gke.cluster_ca_certificate) } } -module "materialize" { - # Referencing the root module directory: - source = "../.." - - # Alternatively, you can use the GitHub source URL: - # source = "github.com/MaterializeInc/terraform-google-materialize?ref=v0.1.0" - project_id = var.project_id - region = var.region - prefix = var.prefix +locals { + common_labels = merge(var.labels, { + managed_by = "terraform" + module = "materialize" + }) - network_config = { - subnet_cidr = "10.0.0.0/20" - pods_cidr = "10.48.0.0/14" - services_cidr = "10.52.0.0/20" + # Disk support configuration + disk_config = { + install_openebs = var.enable_disk_support ? lookup(var.disk_support_config, "install_openebs", true) : false + run_disk_setup_script = var.enable_disk_support ? lookup(var.disk_support_config, "run_disk_setup_script", true) : false + local_ssd_count = lookup(var.disk_support_config, "local_ssd_count", 1) + create_storage_class = var.enable_disk_support ? lookup(var.disk_support_config, "create_storage_class", true) : false + openebs_version = lookup(var.disk_support_config, "openebs_version", "4.2.0") + openebs_namespace = lookup(var.disk_support_config, "openebs_namespace", "openebs") + storage_class_name = lookup(var.disk_support_config, "storage_class_name", "openebs-lvm-instance-store-ext4") + storage_class_provisioner = "local.csi.openebs.io" + storage_class_parameters = { + storage = "lvm" + fsType = "ext4" + volgroup = "instance-store-vg" + } } +} - database_config = { - tier = "db-custom-2-4096" - version = "POSTGRES_15" - password = random_password.pass.result - } +module "networking" { + source = "../../modules/networking" - labels = { - environment = "simple" - example = "true" - } + project_id = var.project_id + region = var.region + prefix = var.prefix + subnet_cidr = var.network_config.subnet_cidr + pods_cidr = var.network_config.pods_cidr + services_cidr = var.network_config.services_cidr +} - install_materialize_operator = true - operator_version = var.operator_version - orchestratord_version = var.orchestratord_version +module "gke" { + source = "../../modules/gke" - install_cert_manager = var.install_cert_manager - use_self_signed_cluster_issuer = var.use_self_signed_cluster_issuer + depends_on = [module.networking] - # Once the operator is installed, you can define your Materialize instances here. - materialize_instances = var.materialize_instances + project_id = var.project_id + region = var.region + prefix = var.prefix + network_name = module.networking.network_name + subnet_name = module.networking.subnet_name - providers = { - google = google - kubernetes = kubernetes - helm = helm - } -} + node_count = var.gke_config.node_count + machine_type = var.gke_config.machine_type + disk_size_gb = var.gke_config.disk_size_gb + min_nodes = var.gke_config.min_nodes + max_nodes = var.gke_config.max_nodes -variable "project_id" { - description = "GCP Project ID" - type = string -} + # Disk support configuration + enable_disk_setup = local.disk_config.run_disk_setup_script + local_ssd_count = local.disk_config.local_ssd_count + install_openebs = local.disk_config.install_openebs + openebs_namespace = local.disk_config.openebs_namespace + openebs_version = local.disk_config.openebs_version + disk_setup_image = var.disk_setup_image -variable "region" { - description = "GCP Region" - type = string - default = "us-central1" + namespace = var.namespace + labels = local.common_labels } -variable "prefix" { - description = "Used to prefix the names of the resources" - type = string - default = "mz-simple" -} -resource "random_password" "pass" { +resource "random_password" "database_password" { length = 20 special = false } -output "gke_cluster" { - description = "GKE cluster details" - value = module.materialize.gke_cluster - sensitive = true -} +module "database" { + source = "../../modules/database" -output "service_accounts" { - description = "Service account details" - value = module.materialize.service_accounts -} + depends_on = [ + module.networking, + ] -output "connection_strings" { - description = "Connection strings for metadata and persistence backends" - value = module.materialize.connection_strings - sensitive = true -} + database_name = var.database_config.db_name + database_user = var.database_config.username + + project_id = var.project_id + region = var.region + prefix = var.prefix + network_id = module.networking.network_id + + tier = var.database_config.tier + db_version = var.database_config.version + password = random_password.database_password.result -output "load_balancer_details" { - description = "Details of the Materialize instance load balancers." - value = module.materialize.load_balancer_details + labels = local.common_labels } -variable "operator_version" { - description = "Version of the Materialize operator to install" - type = string - default = null +module "storage" { + source = "../../modules/storage" + + project_id = var.project_id + region = var.region + prefix = var.prefix + service_account = module.gke.workload_identity_sa_email + versioning = var.storage_bucket_versioning + version_ttl = var.storage_bucket_version_ttl + + labels = local.common_labels } -output "network" { - description = "Network details" - value = module.materialize.network +module "certificates" { + source = "../../modules/certificates" + + install_cert_manager = var.install_cert_manager + cert_manager_install_timeout = var.cert_manager_install_timeout + cert_manager_chart_version = var.cert_manager_chart_version + use_self_signed_cluster_issuer = var.use_self_signed_cluster_issuer && length(var.materialize_instances) > 0 + cert_manager_namespace = var.cert_manager_namespace + name_prefix = var.prefix + + depends_on = [ + module.gke, + ] } -variable "orchestratord_version" { - description = "Version of the Materialize orchestrator to install" - type = string - default = null +module "operator" { + source = "github.com/MaterializeInc/terraform-helm-materialize?ref=v0.1.15" + + count = var.install_materialize_operator ? 1 : 0 + + install_metrics_server = var.install_metrics_server + + depends_on = [ + module.gke, + module.database, + module.storage, + module.certificates, + ] + + namespace = var.namespace + environment = var.prefix + operator_version = var.operator_version + operator_namespace = var.operator_namespace + + helm_values = local.merged_helm_values + + instances = local.instances + + // For development purposes, you can use a local Helm chart instead of fetching it from the Helm repository + use_local_chart = var.use_local_chart + helm_chart = var.helm_chart + + providers = { + kubernetes = kubernetes + helm = helm + } } -variable "materialize_instances" { - description = "List of Materialize instances to be created." - type = list(object({ - name = string - namespace = optional(string) - database_name = string - create_database = optional(bool, true) - create_load_balancer = optional(bool, true) - internal_load_balancer = optional(bool, true) - environmentd_version = optional(string) - cpu_request = optional(string, "1") - memory_request = optional(string, "1Gi") - memory_limit = optional(string, "1Gi") - in_place_rollout = optional(bool, false) - request_rollout = optional(string) - force_rollout = optional(string) - balancer_memory_request = optional(string, "256Mi") - balancer_memory_limit = optional(string, "256Mi") - balancer_cpu_request = optional(string, "100m") - license_key = optional(string) - environmentd_extra_args = optional(list(string), []) - })) - default = [] +module "load_balancers" { + source = "../../modules/load_balancers" + + for_each = { for idx, instance in local.instances : instance.name => instance if lookup(instance, "create_load_balancer", false) } + + instance_name = each.value.name + namespace = module.operator[0].materialize_instances[each.value.name].namespace + resource_id = module.operator[0].materialize_instance_resource_ids[each.value.name] + internal = each.value.internal_load_balancer + + depends_on = [ + module.operator, + module.gke, + ] } -variable "install_cert_manager" { - description = "Whether to install cert-manager." - type = bool - default = true +locals { + default_helm_values = { + observability = { + podMetrics = { + enabled = true + } + } + operator = { + image = var.orchestratord_version == null ? {} : { + tag = var.orchestratord_version + }, + cloudProvider = { + type = "gcp" + region = var.region + providers = { + gcp = { + enabled = true + } + } + } + } + storage = var.enable_disk_support ? { + storageClass = { + create = local.disk_config.create_storage_class + name = local.disk_config.storage_class_name + provisioner = local.disk_config.storage_class_provisioner + parameters = local.disk_config.storage_class_parameters + } + } : {} + tls = (var.use_self_signed_cluster_issuer && length(var.materialize_instances) > 0) ? { + defaultCertificateSpecs = { + balancerdExternal = { + dnsNames = [ + "balancerd", + ] + issuerRef = { + name = module.certificates.cluster_issuer_name + kind = "ClusterIssuer" + } + } + consoleExternal = { + dnsNames = [ + "console", + ] + issuerRef = { + name = module.certificates.cluster_issuer_name + kind = "ClusterIssuer" + } + } + internal = { + issuerRef = { + name = module.certificates.cluster_issuer_name + kind = "ClusterIssuer" + } + } + } + } : {} + } + + merged_helm_values = merge(local.default_helm_values, var.helm_values) } -variable "use_self_signed_cluster_issuer" { - description = "Whether to install and use a self-signed ClusterIssuer for TLS. To work around limitations in Terraform, this will be treated as `false` if no materialize instances are defined." - type = bool - default = true +locals { + instances = [ + for instance in var.materialize_instances : { + name = instance.name + namespace = instance.namespace + database_name = instance.database_name + create_database = instance.create_database + create_load_balancer = instance.create_load_balancer + internal_load_balancer = instance.internal_load_balancer + environmentd_version = instance.environmentd_version + + environmentd_extra_args = instance.environmentd_extra_args + + metadata_backend_url = format( + "postgres://%s:%s@%s:5432/%s?sslmode=disable", + var.database_config.username, + urlencode(var.database_config.password), + module.database.private_ip, + coalesce(instance.database_name, instance.name) + ) + + persist_backend_url = format( + "s3://%s:%s@%s/materialize?endpoint=%s®ion=%s", + module.storage.hmac_access_id, + local.encoded_secret, + module.storage.bucket_name, + local.encoded_endpoint, + var.region + ) + + license_key = instance.license_key + + cpu_request = instance.cpu_request + memory_request = instance.memory_request + memory_limit = instance.memory_limit + + # Rollout options + in_place_rollout = instance.in_place_rollout + request_rollout = instance.request_rollout + force_rollout = instance.force_rollout + } + ] } diff --git a/outputs.tf b/examples/simple/outputs.tf similarity index 98% rename from outputs.tf rename to examples/simple/outputs.tf index 0202443..b298707 100644 --- a/outputs.tf +++ b/examples/simple/outputs.tf @@ -49,7 +49,7 @@ locals { metadata_backend_url = format( "postgres://%s:%s@%s:5432/%s?sslmode=disable", var.database_config.username, - var.database_config.password, + random_password.database_password.result, module.database.private_ip, var.database_config.db_name ) diff --git a/examples/simple/terraform.tfvars.example b/examples/simple/terraform.tfvars.example deleted file mode 100644 index 61b82e1..0000000 --- a/examples/simple/terraform.tfvars.example +++ /dev/null @@ -1,32 +0,0 @@ -project_id = "enter-your-gcp-project-id" -prefix = "enter-a-prefix" // e.g., mz-simple, my-mz-demo -region = "us-central1" - -# Network configuration -network_config = { - subnet_cidr = "10.0.0.0/20" - pods_cidr = "10.48.0.0/14" - services_cidr = "10.52.0.0/20" -} - -# Once the operator is installed, you can define your Materialize instances here. -# Uncomment the following block (or provide your own instances) to configure them: - -# materialize_instances = [ -# { -# name = "analytics" -# namespace = "materialize-environment" -# database_name = "analytics_db" -# cpu_request = "2" -# memory_request = "4Gi" -# memory_limit = "4Gi" -# }, -# { -# name = "demo" -# namespace = "materialize-environment" -# database_name = "demo_db" -# cpu_request = "2" -# memory_request = "4Gi" -# memory_limit = "4Gi" -# } -# ] diff --git a/variables.tf b/examples/simple/variables.tf similarity index 95% rename from variables.tf rename to examples/simple/variables.tf index 3707b26..11faded 100644 --- a/variables.tf +++ b/examples/simple/variables.tf @@ -22,6 +22,11 @@ variable "network_config" { pods_cidr = string services_cidr = string }) + default = { + subnet_cidr = "10.0.0.0/20" + pods_cidr = "10.48.0.0/14" + services_cidr = "10.52.0.0/20" + } } variable "gke_config" { @@ -47,14 +52,15 @@ variable "database_config" { type = object({ tier = optional(string, "db-custom-2-4096") version = optional(string, "POSTGRES_15") - password = string username = optional(string, "materialize") db_name = optional(string, "materialize") }) - validation { - condition = var.database_config.password != null - error_message = "database_config.password must be provided" + default = { + tier = "db-custom-2-4096" + version = "POSTGRES_15" + username = "materialize" + db_name = "materialize" } } @@ -95,12 +101,6 @@ variable "helm_values" { default = {} } -variable "orchestratord_version" { - description = "Version of the Materialize orchestrator to install" - type = string - default = null -} - variable "materialize_instances" { description = "Configuration for Materialize instances" type = list(object({ @@ -144,9 +144,17 @@ variable "materialize_instances" { } } + variable "operator_version" { description = "Version of the Materialize operator to install" type = string + default = "v25.1.12" # META: helm-chart version + nullable = false +} + +variable "orchestratord_version" { + description = "Version of the Materialize orchestrator to install" + type = string default = null } diff --git a/versions.tf b/examples/simple/versions.tf similarity index 79% rename from versions.tf rename to examples/simple/versions.tf index e495fd7..f43e900 100644 --- a/versions.tf +++ b/examples/simple/versions.tf @@ -14,5 +14,9 @@ terraform { source = "hashicorp/helm" version = "~> 2.0" } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } } -} +} \ No newline at end of file diff --git a/main.tf b/main.tf deleted file mode 100644 index bfded57..0000000 --- a/main.tf +++ /dev/null @@ -1,268 +0,0 @@ -locals { - common_labels = merge(var.labels, { - managed_by = "terraform" - module = "materialize" - }) - - # Disk support configuration - disk_config = { - install_openebs = var.enable_disk_support ? lookup(var.disk_support_config, "install_openebs", true) : false - run_disk_setup_script = var.enable_disk_support ? lookup(var.disk_support_config, "run_disk_setup_script", true) : false - local_ssd_count = lookup(var.disk_support_config, "local_ssd_count", 1) - create_storage_class = var.enable_disk_support ? lookup(var.disk_support_config, "create_storage_class", true) : false - openebs_version = lookup(var.disk_support_config, "openebs_version", "4.2.0") - openebs_namespace = lookup(var.disk_support_config, "openebs_namespace", "openebs") - storage_class_name = lookup(var.disk_support_config, "storage_class_name", "openebs-lvm-instance-store-ext4") - storage_class_provisioner = "local.csi.openebs.io" - storage_class_parameters = { - storage = "lvm" - fsType = "ext4" - volgroup = "instance-store-vg" - } - } -} - -module "networking" { - source = "./modules/networking" - - project_id = var.project_id - region = var.region - prefix = var.prefix - subnet_cidr = var.network_config.subnet_cidr - pods_cidr = var.network_config.pods_cidr - services_cidr = var.network_config.services_cidr -} - -module "gke" { - source = "./modules/gke" - - depends_on = [module.networking] - - project_id = var.project_id - region = var.region - prefix = var.prefix - network_name = module.networking.network_name - subnet_name = module.networking.subnet_name - - node_count = var.gke_config.node_count - machine_type = var.gke_config.machine_type - disk_size_gb = var.gke_config.disk_size_gb - min_nodes = var.gke_config.min_nodes - max_nodes = var.gke_config.max_nodes - - # Disk support configuration - enable_disk_setup = local.disk_config.run_disk_setup_script - local_ssd_count = local.disk_config.local_ssd_count - install_openebs = local.disk_config.install_openebs - openebs_namespace = local.disk_config.openebs_namespace - openebs_version = local.disk_config.openebs_version - disk_setup_image = var.disk_setup_image - - namespace = var.namespace - labels = local.common_labels -} - -module "database" { - source = "./modules/database" - - depends_on = [ - module.networking, - ] - - database_name = var.database_config.db_name - database_user = var.database_config.username - - project_id = var.project_id - region = var.region - prefix = var.prefix - network_id = module.networking.network_id - - tier = var.database_config.tier - db_version = var.database_config.version - password = var.database_config.password - - labels = local.common_labels -} - -module "storage" { - source = "./modules/storage" - - project_id = var.project_id - region = var.region - prefix = var.prefix - service_account = module.gke.workload_identity_sa_email - versioning = var.storage_bucket_versioning - version_ttl = var.storage_bucket_version_ttl - - labels = local.common_labels -} - -module "certificates" { - source = "./modules/certificates" - - install_cert_manager = var.install_cert_manager - cert_manager_install_timeout = var.cert_manager_install_timeout - cert_manager_chart_version = var.cert_manager_chart_version - use_self_signed_cluster_issuer = var.use_self_signed_cluster_issuer && length(var.materialize_instances) > 0 - cert_manager_namespace = var.cert_manager_namespace - name_prefix = var.prefix - - depends_on = [ - module.gke, - ] -} - -module "operator" { - source = "github.com/MaterializeInc/terraform-helm-materialize?ref=v0.1.15" - - count = var.install_materialize_operator ? 1 : 0 - - install_metrics_server = var.install_metrics_server - - depends_on = [ - module.gke, - module.database, - module.storage, - module.certificates, - ] - - namespace = var.namespace - environment = var.prefix - operator_version = var.operator_version - operator_namespace = var.operator_namespace - - helm_values = local.merged_helm_values - - instances = local.instances - - // For development purposes, you can use a local Helm chart instead of fetching it from the Helm repository - use_local_chart = var.use_local_chart - helm_chart = var.helm_chart - - providers = { - kubernetes = kubernetes - helm = helm - } -} - -module "load_balancers" { - source = "./modules/load_balancers" - - for_each = { for idx, instance in local.instances : instance.name => instance if lookup(instance, "create_load_balancer", false) } - - instance_name = each.value.name - namespace = module.operator[0].materialize_instances[each.value.name].namespace - resource_id = module.operator[0].materialize_instance_resource_ids[each.value.name] - internal = each.value.internal_load_balancer - - depends_on = [ - module.operator, - module.gke, - ] -} - -locals { - default_helm_values = { - observability = { - podMetrics = { - enabled = true - } - } - operator = { - image = var.orchestratord_version == null ? {} : { - tag = var.orchestratord_version - }, - cloudProvider = { - type = "gcp" - region = var.region - providers = { - gcp = { - enabled = true - } - } - } - } - storage = var.enable_disk_support ? { - storageClass = { - create = local.disk_config.create_storage_class - name = local.disk_config.storage_class_name - provisioner = local.disk_config.storage_class_provisioner - parameters = local.disk_config.storage_class_parameters - } - } : {} - tls = (var.use_self_signed_cluster_issuer && length(var.materialize_instances) > 0) ? { - defaultCertificateSpecs = { - balancerdExternal = { - dnsNames = [ - "balancerd", - ] - issuerRef = { - name = module.certificates.cluster_issuer_name - kind = "ClusterIssuer" - } - } - consoleExternal = { - dnsNames = [ - "console", - ] - issuerRef = { - name = module.certificates.cluster_issuer_name - kind = "ClusterIssuer" - } - } - internal = { - issuerRef = { - name = module.certificates.cluster_issuer_name - kind = "ClusterIssuer" - } - } - } - } : {} - } - - merged_helm_values = merge(local.default_helm_values, var.helm_values) -} - -locals { - instances = [ - for instance in var.materialize_instances : { - name = instance.name - namespace = instance.namespace - database_name = instance.database_name - create_database = instance.create_database - create_load_balancer = instance.create_load_balancer - internal_load_balancer = instance.internal_load_balancer - environmentd_version = instance.environmentd_version - - environmentd_extra_args = instance.environmentd_extra_args - - metadata_backend_url = format( - "postgres://%s:%s@%s:5432/%s?sslmode=disable", - var.database_config.username, - urlencode(var.database_config.password), - module.database.private_ip, - coalesce(instance.database_name, instance.name) - ) - - persist_backend_url = format( - "s3://%s:%s@%s/materialize?endpoint=%s®ion=%s", - module.storage.hmac_access_id, - local.encoded_secret, - module.storage.bucket_name, - local.encoded_endpoint, - var.region - ) - - license_key = instance.license_key - - cpu_request = instance.cpu_request - memory_request = instance.memory_request - memory_limit = instance.memory_limit - - # Rollout options - in_place_rollout = instance.in_place_rollout - request_rollout = instance.request_rollout - force_rollout = instance.force_rollout - } - ] -} From c10098eb6dd1ec7a0bd341a642f771a523fd1285 Mon Sep 17 00:00:00 2001 From: Shiwam Jaiswal Date: Wed, 11 Jun 2025 19:25:24 +0530 Subject: [PATCH 2/5] feat(nat): make subnet private and add nat gateway to access internet --- examples/simple/grant-permissions.sh | 29 +++++++++++++++++++++ examples/simple/main.tf | 2 +- examples/simple/variables.tf | 2 +- modules/gke/main.tf | 4 +++ modules/networking/main.tf | 39 +++++++++++++++++++++++++++- modules/networking/outputs.tf | 10 +++++++ 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100755 examples/simple/grant-permissions.sh diff --git a/examples/simple/grant-permissions.sh b/examples/simple/grant-permissions.sh new file mode 100755 index 0000000..07cda4f --- /dev/null +++ b/examples/simple/grant-permissions.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Script to grant required IAM permissions to fix Terraform permission errors +# Replace PROJECT_ID and USER_EMAIL with your actual values + +PROJECT_ID= +USER_EMAIL= + +echo "Granting required IAM permissions to $USER_EMAIL for project $PROJECT_ID..." + +# Grant Service Account Admin role for IAM operations +echo "Granting Service Account Admin role..." +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="user:$USER_EMAIL" \ + --role="roles/iam.serviceAccountAdmin" + +# Alternative: Grant specific workload identity permission (less permissive) +echo "Granting Workload Identity User role..." +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="user:$USER_EMAIL" \ + --role="roles/iam.workloadIdentityUser" + +# Grant Container Admin role for full cluster access +echo "Granting Container Admin role for GKE cluster management..." +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="user:$USER_EMAIL" \ + --role="roles/container.admin" + +echo "Permissions granted! You should now be able to run terraform apply successfully." diff --git a/examples/simple/main.tf b/examples/simple/main.tf index 392713d..a296642 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -269,7 +269,7 @@ locals { metadata_backend_url = format( "postgres://%s:%s@%s:5432/%s?sslmode=disable", var.database_config.username, - urlencode(var.database_config.password), + urlencode(random_password.database_password.result), module.database.private_ip, coalesce(instance.database_name, instance.name) ) diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index 11faded..c1e4441 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -43,7 +43,7 @@ variable "gke_config" { machine_type = "n2-highmem-8" disk_size_gb = 100 min_nodes = 1 - max_nodes = 2 + max_nodes = 5 } } diff --git a/modules/gke/main.tf b/modules/gke/main.tf index d194f01..2cea48a 100644 --- a/modules/gke/main.tf +++ b/modules/gke/main.tf @@ -93,6 +93,10 @@ resource "google_container_node_pool" "primary_nodes" { max_node_count = var.max_nodes } + network_config { + enable_private_nodes = true + } + node_config { machine_type = var.machine_type disk_size_gb = var.disk_size_gb diff --git a/modules/networking/main.tf b/modules/networking/main.tf index fad6509..6664169 100644 --- a/modules/networking/main.tf +++ b/modules/networking/main.tf @@ -2,14 +2,15 @@ resource "google_compute_network" "vpc" { name = "${var.prefix}-network" auto_create_subnetworks = false project = var.project_id + mtu = 1460 # Optimized for GKE lifecycle { create_before_destroy = true prevent_destroy = false } - } + resource "google_compute_route" "default_route" { name = "${var.prefix}-default-route" project = var.project_id @@ -34,6 +35,7 @@ resource "google_compute_subnetwork" "subnet" { region = var.region private_ip_google_access = true + purpose = "PRIVATE" secondary_ip_range { range_name = "pods" @@ -45,6 +47,41 @@ resource "google_compute_subnetwork" "subnet" { ip_cidr_range = var.services_cidr } + lifecycle { + create_before_destroy = true + } + +} + +# Cloud Router for NAT Gateway +resource "google_compute_router" "router" { + name = "${var.prefix}-router" + project = var.project_id + region = var.region + network = google_compute_network.vpc.name + + bgp { + asn = 64514 + } +} + +# Cloud NAT for outbound internet access from private nodes +resource "google_compute_router_nat" "nat" { + name = "${var.prefix}-nat" + project = var.project_id + router = google_compute_router.router.name + region = var.region + nat_ip_allocate_option = "AUTO_ONLY" + source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" + + log_config { + enable = true + filter = "ERRORS_ONLY" + } + + lifecycle { + create_before_destroy = true + } } resource "google_compute_global_address" "private_ip_address" { diff --git a/modules/networking/outputs.tf b/modules/networking/outputs.tf index 58a79ba..425b663 100644 --- a/modules/networking/outputs.tf +++ b/modules/networking/outputs.tf @@ -22,3 +22,13 @@ output "private_vpc_connection" { description = "The private VPC connection" value = google_service_networking_connection.private_vpc_connection } + +output "router_name" { + description = "The name of the Cloud Router" + value = google_compute_router.router.name +} + +output "nat_name" { + description = "The name of the Cloud NAT" + value = google_compute_router_nat.nat.name +} From 1e56bd5d37e31d0d763cbdfeea28370cbbad4554 Mon Sep 17 00:00:00 2001 From: Shiwam Jaiswal Date: Thu, 12 Jun 2025 19:26:06 +0530 Subject: [PATCH 3/5] feat(nodes): decouple node pool+disk setup, openebs and gke --- examples/simple/main.tf | 42 +++-- modules/gke/main.tf | 342 ---------------------------------- modules/gke/variables.tf | 69 +------ modules/nodepool/main.tf | 309 ++++++++++++++++++++++++++++++ modules/nodepool/outputs.tf | 19 ++ modules/nodepool/variables.tf | 94 ++++++++++ modules/openebs/main.tf | 36 ++++ modules/openebs/outputs.tf | 24 +++ modules/openebs/variables.tf | 23 +++ 9 files changed, 536 insertions(+), 422 deletions(-) create mode 100644 modules/nodepool/main.tf create mode 100644 modules/nodepool/outputs.tf create mode 100644 modules/nodepool/variables.tf create mode 100644 modules/openebs/main.tf create mode 100644 modules/openebs/outputs.tf create mode 100644 modules/openebs/variables.tf diff --git a/examples/simple/main.tf b/examples/simple/main.tf index a296642..cdb582b 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -67,25 +67,43 @@ module "gke" { prefix = var.prefix network_name = module.networking.network_name subnet_name = module.networking.subnet_name + namespace = var.namespace +} - node_count = var.gke_config.node_count + +module "nodepool" { + source = "../../modules/nodepool" + depends_on = [module.gke] + + nodepool_name = "${var.prefix}-node-pool" + region = var.region + enable_private_nodes = true + cluster_name = module.gke.cluster_name + project_id = var.project_id + node_count = var.gke_config.node_count + min_nodes = var.gke_config.min_nodes + max_nodes = var.gke_config.max_nodes machine_type = var.gke_config.machine_type disk_size_gb = var.gke_config.disk_size_gb - min_nodes = var.gke_config.min_nodes - max_nodes = var.gke_config.max_nodes + service_account_email = module.gke.service_account_email + labels = local.common_labels - # Disk support configuration + disk_setup_image = var.disk_setup_image enable_disk_setup = local.disk_config.run_disk_setup_script - local_ssd_count = local.disk_config.local_ssd_count - install_openebs = local.disk_config.install_openebs - openebs_namespace = local.disk_config.openebs_namespace - openebs_version = local.disk_config.openebs_version - disk_setup_image = var.disk_setup_image - - namespace = var.namespace - labels = local.common_labels } +module "openebs" { + source = "../../modules/openebs" + depends_on = [ + module.gke, + module.nodepool + ] + + install_openebs = local.disk_config.install_openebs + create_namespace = true + openebs_namespace = local.disk_config.openebs_namespace + openebs_version = local.disk_config.openebs_version +} resource "random_password" "database_password" { length = 20 diff --git a/modules/gke/main.tf b/modules/gke/main.tf index 2cea48a..b81411f 100644 --- a/modules/gke/main.tf +++ b/modules/gke/main.tf @@ -1,24 +1,3 @@ -locals { - node_labels = merge( - var.labels, - { - "materialize.cloud/disk" = var.enable_disk_setup ? "true" : "false" - "workload" = "materialize-instance" - }, - var.enable_disk_setup ? { - "materialize.cloud/disk-config-required" = "true" - } : {} - ) - - node_taints = var.enable_disk_setup ? [ - { - key = "disk-unconfigured" - value = "true" - effect = "NO_SCHEDULE" - } - ] : [] -} - resource "google_service_account" "gke_sa" { project = var.project_id account_id = "${var.prefix}-gke-sa" @@ -78,61 +57,6 @@ resource "google_container_cluster" "primary" { } } -resource "google_container_node_pool" "primary_nodes" { - provider = google - - name = "${var.prefix}-node-pool" - location = var.region - cluster = google_container_cluster.primary.name - project = var.project_id - - node_count = var.node_count - - autoscaling { - min_node_count = var.min_nodes - max_node_count = var.max_nodes - } - - network_config { - enable_private_nodes = true - } - - node_config { - machine_type = var.machine_type - disk_size_gb = var.disk_size_gb - - labels = local.node_labels - - dynamic "taint" { - for_each = local.node_taints - content { - key = taint.value.key - value = taint.value.value - effect = taint.value.effect - } - } - - service_account = google_service_account.gke_sa.email - - oauth_scopes = [ - "https://www.googleapis.com/auth/cloud-platform" - ] - - local_nvme_ssd_block_config { - local_ssd_count = var.local_ssd_count - } - - workload_metadata_config { - mode = "GKE_METADATA" - } - } - - lifecycle { - create_before_destroy = true - prevent_destroy = false - } -} - resource "google_service_account_iam_binding" "workload_identity" { depends_on = [ google_service_account.workload_identity_sa, @@ -144,269 +68,3 @@ resource "google_service_account_iam_binding" "workload_identity" { "serviceAccount:${var.project_id}.svc.id.goog[${var.namespace}/orchestratord]" ] } - -resource "kubernetes_namespace" "openebs" { - count = var.install_openebs ? 1 : 0 - - metadata { - name = var.openebs_namespace - } - - depends_on = [ - google_container_cluster.primary, - google_container_node_pool.primary_nodes - ] -} - -resource "helm_release" "openebs" { - count = var.install_openebs ? 1 : 0 - - name = "openebs" - namespace = var.openebs_namespace - repository = "https://openebs.github.io/openebs" - chart = "openebs" - version = var.openebs_version - - set { - name = "engines.replicated.mayastor.enabled" - value = "false" - } - - # Unable to continue with install: CustomResourceDefinition "volumesnapshotclasses.snapshot.storage.k8s.io" - # in namespace "" exists and cannot be imported into the current release - # https://github.com/openebs/website/pull/506 - set { - name = "openebs-crds.csi.volumeSnapshots.enabled" - value = "false" - } - - depends_on = [ - google_container_cluster.primary, - google_container_node_pool.primary_nodes, - kubernetes_namespace.openebs - ] -} - -resource "kubernetes_namespace" "disk_setup" { - count = var.enable_disk_setup ? 1 : 0 - - metadata { - name = "disk-setup" - labels = { - "app.kubernetes.io/managed-by" = "terraform" - "app.kubernetes.io/part-of" = "materialize" - } - } - - depends_on = [ - google_container_node_pool.primary_nodes - ] -} - -resource "kubernetes_daemonset" "disk_setup" { - count = var.enable_disk_setup ? 1 : 0 - - metadata { - name = "disk-setup" - namespace = kubernetes_namespace.disk_setup[0].metadata[0].name - labels = { - "app.kubernetes.io/managed-by" = "terraform" - "app.kubernetes.io/part-of" = "materialize" - "app" = "disk-setup" - } - } - - spec { - selector { - match_labels = { - app = "disk-setup" - } - } - - template { - metadata { - labels = { - app = "disk-setup" - } - } - - spec { - security_context { - run_as_non_root = false - run_as_user = 0 - fs_group = 0 - seccomp_profile { - type = "RuntimeDefault" - } - } - - affinity { - node_affinity { - required_during_scheduling_ignored_during_execution { - node_selector_term { - match_expressions { - key = "materialize.cloud/disk" - operator = "In" - values = ["true"] - } - } - } - } - } - - toleration { - key = "disk-unconfigured" - operator = "Exists" - effect = "NoSchedule" - } - - # Use host network and PID namespace - host_network = true - host_pid = true - - init_container { - name = "disk-setup" - image = var.disk_setup_image - command = ["/usr/local/bin/configure-disks.sh"] - args = ["--cloud-provider", "gcp"] - resources { - limits = { - memory = "128Mi" - } - requests = { - memory = "128Mi" - cpu = "50m" - } - } - - security_context { - privileged = true - run_as_user = 0 - } - - env { - name = "NODE_NAME" - value_from { - field_ref { - field_path = "spec.nodeName" - } - } - } - - volume_mount { - name = "dev" - mount_path = "/dev" - } - - volume_mount { - name = "host-root" - mount_path = "/host" - } - - } - - init_container { - name = "taint-removal" - image = var.disk_setup_image - command = ["/usr/local/bin/remove-taint.sh"] - resources { - limits = { - memory = "64Mi" - } - requests = { - memory = "64Mi" - cpu = "10m" - } - } - security_context { - run_as_user = 0 - } - env { - name = "NODE_NAME" - value_from { - field_ref { - field_path = "spec.nodeName" - } - } - } - } - - container { - name = "pause" - image = "gcr.io/google_containers/pause:3.2" - - resources { - limits = { - memory = "8Mi" - } - requests = { - memory = "8Mi" - cpu = "1m" - } - } - - security_context { - allow_privilege_escalation = false - read_only_root_filesystem = true - run_as_non_root = true - run_as_user = 65534 - } - - } - - volume { - name = "dev" - host_path { - path = "/dev" - } - } - - volume { - name = "host-root" - host_path { - path = "/" - } - } - - service_account_name = kubernetes_service_account.disk_setup[0].metadata[0].name - } - } - } -} - -resource "kubernetes_service_account" "disk_setup" { - count = var.enable_disk_setup ? 1 : 0 - metadata { - name = "disk-setup" - namespace = kubernetes_namespace.disk_setup[0].metadata[0].name - } -} - -resource "kubernetes_cluster_role" "disk_setup" { - count = var.enable_disk_setup ? 1 : 0 - metadata { - name = "disk-setup" - } - rule { - api_groups = [""] - resources = ["nodes"] - verbs = ["get", "patch"] - } -} - -resource "kubernetes_cluster_role_binding" "disk_setup" { - count = var.enable_disk_setup ? 1 : 0 - metadata { - name = "disk-setup" - } - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "ClusterRole" - name = kubernetes_cluster_role.disk_setup[0].metadata[0].name - } - subject { - kind = "ServiceAccount" - name = kubernetes_service_account.disk_setup[0].metadata[0].name - namespace = kubernetes_namespace.disk_setup[0].metadata[0].name - } -} diff --git a/modules/gke/variables.tf b/modules/gke/variables.tf index 9e3b680..a2f063b 100644 --- a/modules/gke/variables.tf +++ b/modules/gke/variables.tf @@ -23,74 +23,7 @@ variable "subnet_name" { type = string } -variable "node_count" { - description = "Number of nodes in the GKE cluster" - type = number -} - -variable "machine_type" { - description = "Machine type for GKE nodes" - type = string -} - -variable "disk_size_gb" { - description = "Size of the disk attached to each node" - type = number -} - -variable "min_nodes" { - description = "Minimum number of nodes in the node pool" - type = number -} - -variable "max_nodes" { - description = "Maximum number of nodes in the node pool" - type = number -} - variable "namespace" { - description = "Kubernetes namespace for Materialize" - type = string -} - -variable "labels" { - description = "Labels to apply to resources" - type = map(string) - default = {} -} - -# Disk setup variables -variable "enable_disk_setup" { - description = "Whether to enable the local NVMe SSD disks setup script for NVMe storage" - type = bool - default = true -} - -variable "local_ssd_count" { - description = "Number of local NVMe SSDs to attach to each node. In GCP, each disk is 375GB. For Materialize, you need to have a 1:2 ratio of disk to memory. If you have 8 CPUs and 64GB of memory, you need 128GB of disk. This means you need at least 1 local NVMe SSD. If you go with a larger machine type, you can increase the number of local NVMe SSDs." - type = number - default = 1 -} - -variable "install_openebs" { - description = "Whether to install OpenEBS for NVMe storage" - type = bool - default = true -} - -variable "openebs_namespace" { - description = "Namespace for OpenEBS components" - type = string - default = "openebs" -} - -variable "openebs_version" { - description = "Version of OpenEBS Helm chart to install" - type = string - default = "4.2.0" -} - -variable "disk_setup_image" { - description = "Docker image for the disk setup script" + description = "The namespace where the GKE cluster will be created" type = string } diff --git a/modules/nodepool/main.tf b/modules/nodepool/main.tf new file mode 100644 index 0000000..2bd61e5 --- /dev/null +++ b/modules/nodepool/main.tf @@ -0,0 +1,309 @@ +locals { + node_taints = var.enable_disk_setup ? [ + { + key = "disk-unconfigured" + value = "true" + effect = "NO_SCHEDULE" + } + ] : [] + + node_labels = merge( + var.labels, + { + "materialize.cloud/disk" = var.enable_disk_setup ? "true" : "false" + "workload" = "materialize-instance" + }, + var.enable_disk_setup ? { + "materialize.cloud/disk-config-required" = "true" + } : {} + ) + + disk_setup_name = "disk-setup" + + disk_setup_labels = merge( + var.labels, + { + "app" = local.disk_setup_name + } + ) +} + +resource "google_container_node_pool" "primary_nodes" { + provider = google + + name = "${var.nodepool_name}" + location = var.region + cluster = var.cluster_name + project = var.project_id + + node_count = var.node_count + + autoscaling { + min_node_count = var.min_nodes + max_node_count = var.max_nodes + } + + network_config { + enable_private_nodes = var.enable_private_nodes + } + + node_config { + machine_type = var.machine_type + disk_size_gb = var.disk_size_gb + + labels = local.node_labels + + dynamic "taint" { + for_each = local.node_taints + content { + key = taint.value.key + value = taint.value.value + effect = taint.value.effect + } + } + + service_account = var.service_account_email + + oauth_scopes = [ + "https://www.googleapis.com/auth/cloud-platform" + ] + + local_nvme_ssd_block_config { + local_ssd_count = var.local_ssd_count + } + + workload_metadata_config { + mode = "GKE_METADATA" + } + } + + lifecycle { + create_before_destroy = true + prevent_destroy = false + } +} + + +resource "kubernetes_namespace" "disk_setup" { + count = var.enable_disk_setup ? 1 : 0 + + metadata { + name = local.disk_setup_name + labels = local.disk_setup_labels + } + + depends_on = [ + google_container_node_pool.primary_nodes + ] +} + +resource "kubernetes_daemonset" "disk_setup" { + count = var.enable_disk_setup ? 1 : 0 + depends_on = [ + kubernetes_namespace.disk_setup + ] + + metadata { + name = local.disk_setup_name + namespace = kubernetes_namespace.disk_setup[0].metadata[0].name + labels = local.disk_setup_labels + } + + spec { + selector { + match_labels = { + app = local.disk_setup_name + } + } + + template { + metadata { + labels = local.disk_setup_labels + } + + spec { + security_context { + run_as_non_root = false + run_as_user = 0 + fs_group = 0 + seccomp_profile { + type = "RuntimeDefault" + } + } + + affinity { + node_affinity { + required_during_scheduling_ignored_during_execution { + node_selector_term { + match_expressions { + key = "materialize.cloud/disk" + operator = "In" + values = ["true"] + } + } + } + } + } + + toleration { + key = local.node_taints[0].key + operator = "Exists" + effect = "NoSchedule" + } + + # Use host network and PID namespace + host_network = true + host_pid = true + + init_container { + name = local.disk_setup_name + image = var.disk_setup_image + command = ["/usr/local/bin/configure-disks.sh"] + args = ["--cloud-provider", "gcp"] + resources { + limits = { + memory = "128Mi" + } + requests = { + memory = "128Mi" + cpu = "50m" + } + } + + security_context { + privileged = true + run_as_user = 0 + } + + env { + name = "NODE_NAME" + value_from { + field_ref { + field_path = "spec.nodeName" + } + } + } + + volume_mount { + name = "dev" + mount_path = "/dev" + } + + volume_mount { + name = "host-root" + mount_path = "/host" + } + + } + + init_container { + name = "taint-removal" + image = var.disk_setup_image + command = ["/usr/local/bin/remove-taint.sh"] + resources { + limits = { + memory = "64Mi" + } + requests = { + memory = "64Mi" + cpu = "10m" + } + } + security_context { + run_as_user = 0 + } + env { + name = "NODE_NAME" + value_from { + field_ref { + field_path = "spec.nodeName" + } + } + } + } + + container { + name = "pause" + image = "gcr.io/google_containers/pause:3.2" + + resources { + limits = { + memory = "8Mi" + } + requests = { + memory = "8Mi" + cpu = "1m" + } + } + + security_context { + allow_privilege_escalation = false + read_only_root_filesystem = true + run_as_non_root = true + run_as_user = 65534 + } + + } + + volume { + name = "dev" + host_path { + path = "/dev" + } + } + + volume { + name = "host-root" + host_path { + path = "/" + } + } + + service_account_name = kubernetes_service_account.disk_setup[0].metadata[0].name + } + } + } +} + +resource "kubernetes_service_account" "disk_setup" { + count = var.enable_disk_setup ? 1 : 0 + depends_on = [ + kubernetes_namespace.disk_setup + ] + metadata { + name = local.disk_setup_name + namespace = kubernetes_namespace.disk_setup[0].metadata[0].name + } +} + +resource "kubernetes_cluster_role" "disk_setup" { + count = var.enable_disk_setup ? 1 : 0 + depends_on = [ + kubernetes_namespace.disk_setup + ] + metadata { + name = local.disk_setup_name + } + rule { + api_groups = [""] + resources = ["nodes"] + verbs = ["get", "patch"] + } +} + +resource "kubernetes_cluster_role_binding" "disk_setup" { + count = var.enable_disk_setup ? 1 : 0 + metadata { + name = local.disk_setup_name + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = kubernetes_cluster_role.disk_setup[0].metadata[0].name + } + subject { + kind = "ServiceAccount" + name = kubernetes_service_account.disk_setup[0].metadata[0].name + namespace = kubernetes_namespace.disk_setup[0].metadata[0].name + } +} diff --git a/modules/nodepool/outputs.tf b/modules/nodepool/outputs.tf new file mode 100644 index 0000000..fecc25f --- /dev/null +++ b/modules/nodepool/outputs.tf @@ -0,0 +1,19 @@ +output "node_pool_name" { + description = "The name of the node pool" + value = google_container_node_pool.primary_nodes.name +} + +output "node_pool_id" { + description = "The ID of the node pool" + value = google_container_node_pool.primary_nodes.id +} + +output "instance_group_urls" { + description = "List of instance group URLs for the node pool" + value = google_container_node_pool.primary_nodes.instance_group_urls +} + +output "node_count" { + description = "The current number of nodes in the pool" + value = google_container_node_pool.primary_nodes.node_count +} diff --git a/modules/nodepool/variables.tf b/modules/nodepool/variables.tf new file mode 100644 index 0000000..9a9781d --- /dev/null +++ b/modules/nodepool/variables.tf @@ -0,0 +1,94 @@ +variable "nodepool_name" { + description = "The name of the node pool" + type = string +} + +variable "region" { + description = "The region where the cluster is located" + type = string +} + +variable "enable_disk_setup" { + description = "Whether to enable disk setup" + type = bool + default = true +} + +variable "cluster_name" { + description = "The name of the GKE cluster" + type = string +} + +variable "project_id" { + description = "The GCP project ID" + type = string +} + +variable "node_count" { + description = "The number of nodes in the node pool" + type = number + default = 3 +} + +variable "min_nodes" { + description = "The minimum number of nodes in the autoscaling group" + type = number + default = 1 +} + +variable "max_nodes" { + description = "The maximum number of nodes in the autoscaling group" + type = number + default = 10 +} + +variable "machine_type" { + description = "The machine type for the nodes" + type = string + default = "e2-medium" +} + +variable "disk_size_gb" { + description = "The disk size in GB for each node" + type = number + default = 100 +} + +variable "labels" { + description = "Labels to apply to the nodes" + type = map(string) + default = {} +} + +variable "taints" { + description = "Taints to apply to the nodes" + type = list(object({ + key = string + value = string + effect = string + })) + default = [] +} + +variable "service_account_email" { + description = "The email of the service account to use for the nodes" + type = string +} + +variable "local_ssd_count" { + description = "The number of local SSD disks to attach to each node" + type = number + default = 0 +} + +variable "enable_private_nodes" { + description = "Whether to enable private nodes" + type = bool + default = true +} + +variable "disk_setup_image" { + description = "Docker image for the disk setup script" + type = string + default = "materialize/ephemeral-storage-setup-image:v0.1.1" +} diff --git a/modules/openebs/main.tf b/modules/openebs/main.tf new file mode 100644 index 0000000..fdc9df5 --- /dev/null +++ b/modules/openebs/main.tf @@ -0,0 +1,36 @@ + + +resource "kubernetes_namespace" "openebs" { + count = var.install_openebs && var.create_namespace ? 1 : 0 + + metadata { + name = var.openebs_namespace + } +} + +resource "helm_release" "openebs" { + count = var.install_openebs ? 1 : 0 + + name = "openebs" + namespace = var.openebs_namespace + repository = "https://openebs.github.io/openebs" + chart = "openebs" + version = var.openebs_version + + set { + name = "engines.replicated.mayastor.enabled" + value = "false" + } + + # Unable to continue with install: CustomResourceDefinition "volumesnapshotclasses.snapshot.storage.k8s.io" + # in namespace "" exists and cannot be imported into the current release + # https://github.com/openebs/website/pull/506 + set { + name = "openebs-crds.csi.volumeSnapshots.enabled" + value = "false" + } + + depends_on = [ + kubernetes_namespace.openebs + ] +} diff --git a/modules/openebs/outputs.tf b/modules/openebs/outputs.tf new file mode 100644 index 0000000..be94294 --- /dev/null +++ b/modules/openebs/outputs.tf @@ -0,0 +1,24 @@ +output "openebs_namespace" { + description = "The namespace where OpenEBS is installed" + value = var.install_openebs ? var.openebs_namespace : null +} + +output "openebs_installed" { + description = "Whether OpenEBS is installed" + value = var.install_openebs +} + +output "helm_release_name" { + description = "The name of the OpenEBS Helm release" + value = var.install_openebs ? helm_release.openebs[0].name : null +} + +output "helm_release_version" { + description = "The version of the installed OpenEBS Helm chart" + value = var.install_openebs ? var.openebs_version : null +} + +output "helm_release_status" { + description = "The status of the OpenEBS Helm release" + value = var.install_openebs ? helm_release.openebs[0].status : null +} \ No newline at end of file diff --git a/modules/openebs/variables.tf b/modules/openebs/variables.tf new file mode 100644 index 0000000..cd2631e --- /dev/null +++ b/modules/openebs/variables.tf @@ -0,0 +1,23 @@ +variable "install_openebs" { + description = "Whether to install OpenEBS or not" + type = bool + default = true +} + +variable "openebs_namespace" { + description = "The namespace where OpenEBS will be installed" + type = string + default = "openebs" +} + +variable "create_namespace" { + description = "Whether to create the namespace where OpenEBS will be installed" + type = bool + default = true +} + +variable "openebs_version" { + description = "The version of OpenEBS Helm chart to install" + type = string + default = "3.9.0" +} From b7f0e095100edd349d66bd41ddbd20b9af5785f2 Mon Sep 17 00:00:00 2001 From: Shiwam Jaiswal Date: Mon, 16 Jun 2025 21:20:07 +0530 Subject: [PATCH 4/5] feat(bugs): fix dependency bugs related with nodepool and fix local_ssd_count issue --- examples/simple/main.tf | 4 ++++ modules/nodepool/variables.tf | 16 +++------------- modules/openebs/variables.tf | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/examples/simple/main.tf b/examples/simple/main.tf index cdb582b..25bb07b 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -90,6 +90,7 @@ module "nodepool" { disk_setup_image = var.disk_setup_image enable_disk_setup = local.disk_config.run_disk_setup_script + local_ssd_count = local.disk_config.local_ssd_count } module "openebs" { @@ -157,6 +158,7 @@ module "certificates" { depends_on = [ module.gke, + module.nodepool, ] } @@ -169,6 +171,7 @@ module "operator" { depends_on = [ module.gke, + module.nodepool, module.database, module.storage, module.certificates, @@ -206,6 +209,7 @@ module "load_balancers" { depends_on = [ module.operator, module.gke, + module.nodepool, ] } diff --git a/modules/nodepool/variables.tf b/modules/nodepool/variables.tf index 9a9781d..0512499 100644 --- a/modules/nodepool/variables.tf +++ b/modules/nodepool/variables.tf @@ -9,7 +9,7 @@ variable "region" { } variable "enable_disk_setup" { - description = "Whether to enable disk setup" + description = "Whether to enable the local NVMe SSD disks setup script for NVMe storage" type = bool default = true } @@ -60,25 +60,15 @@ variable "labels" { default = {} } -variable "taints" { - description = "Taints to apply to the nodes" - type = list(object({ - key = string - value = string - effect = string - })) - default = [] -} - variable "service_account_email" { description = "The email of the service account to use for the nodes" type = string } variable "local_ssd_count" { - description = "The number of local SSD disks to attach to each node" + description = "Number of local NVMe SSDs to attach to each node. In GCP, each disk is 375GB. For Materialize, you need to have a 1:2 ratio of disk to memory. If you have 8 CPUs and 64GB of memory, you need 128GB of disk. This means you need at least 1 local NVMe SSD. If you go with a larger machine type, you can increase the number of local NVMe SSDs." type = number - default = 0 + default = 1 } variable "enable_private_nodes" { diff --git a/modules/openebs/variables.tf b/modules/openebs/variables.tf index cd2631e..2117ee7 100644 --- a/modules/openebs/variables.tf +++ b/modules/openebs/variables.tf @@ -1,5 +1,5 @@ variable "install_openebs" { - description = "Whether to install OpenEBS or not" + description = "Whether to install OpenEBS for NVMe storage" type = bool default = true } From e617a53b91d02abb2b0bd171df9b84f5e5af19b7 Mon Sep 17 00:00:00 2001 From: Shiwam Jaiswal Date: Tue, 17 Jun 2025 20:20:21 +0530 Subject: [PATCH 5/5] feat(materialize): add operator and materialize-instance modules + related changes --- examples/simple/main.tf | 179 ++++++---------------- examples/simple/outputs.tf | 36 ++--- examples/simple/variables.tf | 46 +----- modules/certificates/variables.tf | 1 + modules/load_balancers/variables.tf | 1 + modules/materialize-instance/main.tf | 99 ++++++++++++ modules/materialize-instance/outputs.tf | 24 +++ modules/materialize-instance/variables.tf | 122 +++++++++++++++ modules/materialize-instance/versions.tf | 14 ++ modules/operator/main.tf | 131 ++++++++++++++++ modules/operator/outputs.tf | 14 ++ modules/operator/variables.tf | 98 ++++++++++++ 12 files changed, 567 insertions(+), 198 deletions(-) create mode 100644 modules/materialize-instance/main.tf create mode 100644 modules/materialize-instance/outputs.tf create mode 100644 modules/materialize-instance/variables.tf create mode 100644 modules/materialize-instance/versions.tf create mode 100644 modules/operator/main.tf create mode 100644 modules/operator/outputs.tf create mode 100644 modules/operator/variables.tf diff --git a/examples/simple/main.tf b/examples/simple/main.tf index 25bb07b..536adb8 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -44,6 +44,26 @@ locals { volgroup = "instance-store-vg" } } + + metadata_backend_url = format( + "postgres://%s:%s@%s:5432/%s?sslmode=disable", + var.database_config.username, + random_password.database_password.result, + module.database.private_ip, + var.database_config.db_name + ) + + encoded_endpoint = urlencode("https://storage.googleapis.com") + encoded_secret = urlencode(module.storage.hmac_secret) + + persist_backend_url = format( + "s3://%s:%s@%s/materialize?endpoint=%s®ion=%s", + module.storage.hmac_access_id, + local.encoded_secret, + module.storage.bucket_name, + local.encoded_endpoint, + var.region + ) } module "networking" { @@ -152,7 +172,7 @@ module "certificates" { install_cert_manager = var.install_cert_manager cert_manager_install_timeout = var.cert_manager_install_timeout cert_manager_chart_version = var.cert_manager_chart_version - use_self_signed_cluster_issuer = var.use_self_signed_cluster_issuer && length(var.materialize_instances) > 0 + use_self_signed_cluster_issuer = var.install_materialize_instance cert_manager_namespace = var.cert_manager_namespace name_prefix = var.prefix @@ -163,11 +183,12 @@ module "certificates" { } module "operator" { - source = "github.com/MaterializeInc/terraform-helm-materialize?ref=v0.1.15" - count = var.install_materialize_operator ? 1 : 0 + source = "../../modules/operator" - install_metrics_server = var.install_metrics_server + name_prefix = var.prefix + use_self_signed_cluster_issuer = var.install_materialize_instance + region = var.region depends_on = [ module.gke, @@ -176,145 +197,39 @@ module "operator" { module.storage, module.certificates, ] - - namespace = var.namespace - environment = var.prefix - operator_version = var.operator_version - operator_namespace = var.operator_namespace - - helm_values = local.merged_helm_values - - instances = local.instances - - // For development purposes, you can use a local Helm chart instead of fetching it from the Helm repository - use_local_chart = var.use_local_chart - helm_chart = var.helm_chart - - providers = { - kubernetes = kubernetes - helm = helm - } } -module "load_balancers" { - source = "../../modules/load_balancers" +module "materialize_instance" { + count = var.install_materialize_instance ? 1 : 0 - for_each = { for idx, instance in local.instances : instance.name => instance if lookup(instance, "create_load_balancer", false) } - - instance_name = each.value.name - namespace = module.operator[0].materialize_instances[each.value.name].namespace - resource_id = module.operator[0].materialize_instance_resource_ids[each.value.name] - internal = each.value.internal_load_balancer + source = "../../modules/materialize-instance" + instance_name = "main" + instance_namespace = "materialize-environment" + metadata_backend_url = local.metadata_backend_url + persist_backend_url = local.persist_backend_url depends_on = [ - module.operator, module.gke, + module.database, + module.storage, + module.networking, + module.certificates, + module.operator, module.nodepool, + module.openebs, ] } -locals { - default_helm_values = { - observability = { - podMetrics = { - enabled = true - } - } - operator = { - image = var.orchestratord_version == null ? {} : { - tag = var.orchestratord_version - }, - cloudProvider = { - type = "gcp" - region = var.region - providers = { - gcp = { - enabled = true - } - } - } - } - storage = var.enable_disk_support ? { - storageClass = { - create = local.disk_config.create_storage_class - name = local.disk_config.storage_class_name - provisioner = local.disk_config.storage_class_provisioner - parameters = local.disk_config.storage_class_parameters - } - } : {} - tls = (var.use_self_signed_cluster_issuer && length(var.materialize_instances) > 0) ? { - defaultCertificateSpecs = { - balancerdExternal = { - dnsNames = [ - "balancerd", - ] - issuerRef = { - name = module.certificates.cluster_issuer_name - kind = "ClusterIssuer" - } - } - consoleExternal = { - dnsNames = [ - "console", - ] - issuerRef = { - name = module.certificates.cluster_issuer_name - kind = "ClusterIssuer" - } - } - internal = { - issuerRef = { - name = module.certificates.cluster_issuer_name - kind = "ClusterIssuer" - } - } - } - } : {} - } +module "load_balancers" { + count = var.install_materialize_instance ? 1 : 0 - merged_helm_values = merge(local.default_helm_values, var.helm_values) -} + source = "../../modules/load_balancers" -locals { - instances = [ - for instance in var.materialize_instances : { - name = instance.name - namespace = instance.namespace - database_name = instance.database_name - create_database = instance.create_database - create_load_balancer = instance.create_load_balancer - internal_load_balancer = instance.internal_load_balancer - environmentd_version = instance.environmentd_version - - environmentd_extra_args = instance.environmentd_extra_args - - metadata_backend_url = format( - "postgres://%s:%s@%s:5432/%s?sslmode=disable", - var.database_config.username, - urlencode(random_password.database_password.result), - module.database.private_ip, - coalesce(instance.database_name, instance.name) - ) - - persist_backend_url = format( - "s3://%s:%s@%s/materialize?endpoint=%s®ion=%s", - module.storage.hmac_access_id, - local.encoded_secret, - module.storage.bucket_name, - local.encoded_endpoint, - var.region - ) - - license_key = instance.license_key - - cpu_request = instance.cpu_request - memory_request = instance.memory_request - memory_limit = instance.memory_limit - - # Rollout options - in_place_rollout = instance.in_place_rollout - request_rollout = instance.request_rollout - force_rollout = instance.force_rollout - } + instance_name = "main" + namespace = "materialize-environment" + resource_id = module.materialize_instance[0].instance_resource_id + + depends_on = [ + module.materialize_instance, ] } diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index b298707..ce0cf34 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -45,28 +45,6 @@ output "service_accounts" { } } -locals { - metadata_backend_url = format( - "postgres://%s:%s@%s:5432/%s?sslmode=disable", - var.database_config.username, - random_password.database_password.result, - module.database.private_ip, - var.database_config.db_name - ) - - encoded_endpoint = urlencode("https://storage.googleapis.com") - encoded_secret = urlencode(module.storage.hmac_secret) - - persist_backend_url = format( - "s3://%s:%s@%s/materialize?endpoint=%s®ion=%s", - module.storage.hmac_access_id, - local.encoded_secret, - module.storage.bucket_name, - local.encoded_endpoint, - var.region - ) -} - output "connection_strings" { description = "Formatted connection strings for Materialize" value = { @@ -82,8 +60,18 @@ output "operator" { namespace = module.operator[0].operator_namespace release_name = module.operator[0].operator_release_name release_status = module.operator[0].operator_release_status - instances = module.operator[0].materialize_instances - instance_resource_ids = module.operator[0].materialize_instance_resource_ids + } : null +} + +output "materialize_instance" { + description = "Materialize instance details" + sensitive = true + value = var.install_materialize_instance ? { + name = module.materialize_instance[0].instance_name + namespace = module.materialize_instance[0].instance_namespace + resource_id = module.materialize_instance[0].instance_resource_id + metadata_backend_url = module.materialize_instance[0].metadata_backend_url + persist_backend_url = module.materialize_instance[0].persist_backend_url } : null } diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index c1e4441..82d622c 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -101,50 +101,12 @@ variable "helm_values" { default = {} } -variable "materialize_instances" { - description = "Configuration for Materialize instances" - type = list(object({ - name = string - namespace = optional(string) - database_name = string - create_database = optional(bool, true) - create_load_balancer = optional(bool, true) - internal_load_balancer = optional(bool, true) - environmentd_version = optional(string) - cpu_request = optional(string, "1") - memory_request = optional(string, "1Gi") - memory_limit = optional(string, "1Gi") - in_place_rollout = optional(bool, false) - request_rollout = optional(string) - force_rollout = optional(string) - balancer_memory_request = optional(string, "256Mi") - balancer_memory_limit = optional(string, "256Mi") - balancer_cpu_request = optional(string, "100m") - license_key = optional(string) - environmentd_extra_args = optional(list(string), []) - })) - default = [] - - validation { - condition = alltrue([ - for instance in var.materialize_instances : - instance.request_rollout == null || - can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", instance.request_rollout)) - ]) - error_message = "Request rollout must be a valid UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - } - - validation { - condition = alltrue([ - for instance in var.materialize_instances : - instance.force_rollout == null || - can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", instance.force_rollout)) - ]) - error_message = "Force rollout must be a valid UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - } +variable "install_materialize_instance" { + description = "Whether to install the Materialize instance. Default is false as it requires the Kubernetes cluster to be created first." + type = bool + default = false } - variable "operator_version" { description = "Version of the Materialize operator to install" type = string diff --git a/modules/certificates/variables.tf b/modules/certificates/variables.tf index 2de3f2f..914a8d1 100644 --- a/modules/certificates/variables.tf +++ b/modules/certificates/variables.tf @@ -6,6 +6,7 @@ variable "install_cert_manager" { variable "use_self_signed_cluster_issuer" { description = "Whether to install and use a self-signed ClusterIssuer for TLS. Due to limitations in Terraform, this may not be enabled before the cert-manager CRDs are installed." type = bool + default = false } variable "cert_manager_namespace" { diff --git a/modules/load_balancers/variables.tf b/modules/load_balancers/variables.tf index 65d451d..1bbbc8c 100644 --- a/modules/load_balancers/variables.tf +++ b/modules/load_balancers/variables.tf @@ -16,4 +16,5 @@ variable "resource_id" { variable "internal" { description = "Whether the load balancer is internal to the VPC." type = bool + default = true } diff --git a/modules/materialize-instance/main.tf b/modules/materialize-instance/main.tf new file mode 100644 index 0000000..adfad29 --- /dev/null +++ b/modules/materialize-instance/main.tf @@ -0,0 +1,99 @@ +# Create a namespace for this Materialize instance +resource "kubernetes_namespace" "instance" { + count = var.create_namespace ? 1 : 0 + + metadata { + name = var.instance_namespace + } +} + +# Create the Materialize instance using the kubernetes_manifest resource +resource "kubernetes_manifest" "materialize_instance" { + field_manager { + # force field manager conflicts to be overridden + name = "terraform" + force_conflicts = true + } + + manifest = { + apiVersion = "materialize.cloud/v1alpha1" + kind = "Materialize" + metadata = { + name = var.instance_name + namespace = var.instance_namespace + } + spec = { + environmentdImageRef = "materialize/environmentd:${var.environmentd_version}" + backendSecretName = "${var.instance_name}-materialize-backend" + inPlaceRollout = var.in_place_rollout + requestRollout = var.request_rollout + forceRollout = var.force_rollout + + environmentdExtraEnv = length(var.environmentd_extra_env) > 0 ? [{ + name = "MZ_SYSTEM_PARAMETER_DEFAULT" + value = join(";", [ + for item in var.environmentd_extra_env : + "${item.name}=${item.value}" + ]) + }] : null + + environmentdExtraArgs = length(var.environmentd_extra_args) > 0 ? var.environmentd_extra_args : null + + environmentdResourceRequirements = { + limits = { + memory = var.memory_limit + } + requests = { + cpu = var.cpu_request + memory = var.memory_request + } + } + balancerdResourceRequirements = { + limits = { + memory = var.balancer_memory_limit + } + requests = { + cpu = var.balancer_cpu_request + memory = var.balancer_memory_request + } + } + } + } + + depends_on = [ + kubernetes_secret.materialize_backend, + kubernetes_namespace.instance, + ] +} + +# Create a secret with connection information for the Materialize instance +resource "kubernetes_secret" "materialize_backend" { + metadata { + name = "${var.instance_name}-materialize-backend" + namespace = var.instance_namespace + } + + data = { + metadata_backend_url = var.metadata_backend_url + persist_backend_url = var.persist_backend_url + license_key = var.license_key == null ? "" : var.license_key + } + + depends_on = [ + kubernetes_namespace.instance + ] +} + +# Retrieve the resource ID of the Materialize instance +data "kubernetes_resource" "materialize_instance" { + api_version = "materialize.cloud/v1alpha1" + kind = "Materialize" + metadata { + name = var.instance_name + namespace = var.instance_namespace + } + + depends_on = [ + kubernetes_manifest.materialize_instance + ] +} diff --git a/modules/materialize-instance/outputs.tf b/modules/materialize-instance/outputs.tf new file mode 100644 index 0000000..b94360b --- /dev/null +++ b/modules/materialize-instance/outputs.tf @@ -0,0 +1,24 @@ +output "instance_name" { + description = "Name of the Materialize instance" + value = var.instance_name +} + +output "instance_namespace" { + description = "Namespace of the Materialize instance" + value = var.instance_namespace +} + +output "instance_resource_id" { + description = "Resource ID of the Materialize instance" + value = data.kubernetes_resource.materialize_instance.object.status.resourceId +} + +output "metadata_backend_url" { + description = "Metadata backend URL used by the Materialize instance" + value = var.metadata_backend_url +} + +output "persist_backend_url" { + description = "Persist backend URL used by the Materialize instance" + value = var.persist_backend_url +} diff --git a/modules/materialize-instance/variables.tf b/modules/materialize-instance/variables.tf new file mode 100644 index 0000000..493d890 --- /dev/null +++ b/modules/materialize-instance/variables.tf @@ -0,0 +1,122 @@ +variable "instance_name" { + description = "Name of the Materialize instance" + type = string +} + +variable "create_namespace" { + description = "Whether to create the Kubernetes namespace. Set to false if the namespace already exists." + type = bool + default = true +} + +variable "instance_namespace" { + description = "Kubernetes namespace for the instance." + type = string +} + +variable "metadata_backend_url" { + description = "PostgreSQL connection URL for metadata backend" + type = string + sensitive = true +} + +variable "persist_backend_url" { + description = "Object storage connection URL for persist backend" + type = string +} + +variable "license_key" { + description = "Materialize license key" + type = string + default = null + sensitive = true +} + +# Environmentd Configuration +variable "environmentd_version" { + description = "Version of environmentd to use" + type = string + default = "v0.130.13" # META: mz version +} + +variable "environmentd_extra_env" { + description = "Extra environment variables for environmentd" + type = list(object({ + name = string + value = string + })) + default = [] +} + +variable "environmentd_extra_args" { + description = "Extra command line arguments for environmentd" + type = list(string) + default = [] +} + +# Resource Requirements +variable "cpu_request" { + description = "CPU request for environmentd" + type = string + default = "1" +} + +variable "memory_request" { + description = "Memory request for environmentd" + type = string + default = "1Gi" +} + +variable "memory_limit" { + description = "Memory limit for environmentd" + type = string + default = "1Gi" +} + +# Rollout Configuration +variable "in_place_rollout" { + description = "Whether to perform in-place rollouts" + type = bool + default = true +} + +variable "request_rollout" { + description = "UUID to request a rollout" + type = string + default = "00000000-0000-0000-0000-000000000001" + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.request_rollout)) + error_message = "Request rollout must be a valid UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + } +} + +variable "force_rollout" { + description = "UUID to force a rollout" + type = string + default = "00000000-0000-0000-0000-000000000001" + + validation { + condition = can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.force_rollout)) + error_message = "Force rollout must be a valid UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + } +} + +# Balancer Resource Requirements +variable "balancer_memory_request" { + description = "Memory request for balancer" + type = string + default = "256Mi" +} + +variable "balancer_memory_limit" { + description = "Memory limit for balancer" + type = string + default = "256Mi" +} + +variable "balancer_cpu_request" { + description = "CPU request for balancer" + type = string + default = "100m" +} diff --git a/modules/materialize-instance/versions.tf b/modules/materialize-instance/versions.tf new file mode 100644 index 0000000..ba8641a --- /dev/null +++ b/modules/materialize-instance/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.0" + } + } +} diff --git a/modules/operator/main.tf b/modules/operator/main.tf new file mode 100644 index 0000000..ca4bff5 --- /dev/null +++ b/modules/operator/main.tf @@ -0,0 +1,131 @@ + + +locals { + default_helm_values = { + image = var.orchestratord_version == null ? {} : { + tag = var.orchestratord_version + }, + observability = { + podMetrics = { + enabled = true + } + } + operator = { + cloudProvider = { + type = "gcp" + region = var.region + providers = { + gcp = { + enabled = true + } + } + } + } + storage = var.enable_disk_support ? { + storageClass = { + create = local.disk_config.create_storage_class + name = local.disk_config.storage_class_name + provisioner = local.disk_config.storage_class_provisioner + parameters = local.disk_config.storage_class_parameters + } + } : {} + tls = var.use_self_signed_cluster_issuer ? { + defaultCertificateSpecs = { + balancerdExternal = { + dnsNames = [ + "balancerd", + ] + issuerRef = { + name = "${var.name_prefix}-root-ca" + kind = "ClusterIssuer" + } + } + consoleExternal = { + dnsNames = [ + "console", + ] + issuerRef = { + name = "${var.name_prefix}-root-ca" + kind = "ClusterIssuer" + } + } + internal = { + issuerRef = { + name = "${var.name_prefix}-root-ca" + kind = "ClusterIssuer" + } + } + } + } : {} + } + + # Requires OpenEBS to be installed + disk_config = { + create_storage_class = var.enable_disk_support ? lookup(var.disk_support_config, "create_storage_class", true) : false + storage_class_name = lookup(var.disk_support_config, "storage_class_name", "openebs-lvm-instance-store-ext4") + storage_class_provisioner = "local.csi.openebs.io" + storage_class_parameters = { + storage = "lvm" + fsType = "ext4" + volgroup = "instance-store-vg" + } + } +} + +resource "kubernetes_namespace" "materialize" { + metadata { + name = var.operator_namespace + } +} + +resource "kubernetes_namespace" "monitoring" { + metadata { + name = var.monitoring_namespace + } +} + +resource "helm_release" "materialize_operator" { + name = var.name_prefix + namespace = kubernetes_namespace.materialize.metadata[0].name + + repository = var.use_local_chart ? null : var.helm_repository + chart = var.helm_chart + version = var.use_local_chart ? null : var.operator_version + + values = [ + yamlencode(merge(local.default_helm_values, var.helm_values)) + ] + + depends_on = [kubernetes_namespace.materialize] +} + +# Install the metrics-server for monitoring +# Required for the Materialize Console to display cluster metrics +# Defaults to false because GKE provides metrics-server by default +# Enable this when metrics collection is disabled in the cluster +# https://cloud.google.com/kubernetes-engine/docs/how-to/configure-metrics +# TODO: we should rather rely on GKE metrics-server instead of installing our own, confirm with team +resource "helm_release" "metrics_server" { +count = var.install_metrics_server ? 1 : 0 + +name = "${var.name_prefix}-metrics-server" +namespace = kubernetes_namespace.monitoring.metadata[0].name +repository = "https://kubernetes-sigs.github.io/metrics-server/" +chart = "metrics-server" +version = var.metrics_server_version + +# Common configuration values +set { + name = "args[0]" + value = "--kubelet-insecure-tls" +} + +set { + name = "metrics.enabled" + value = "true" +} + +depends_on = [ + kubernetes_namespace.monitoring +] +} \ No newline at end of file diff --git a/modules/operator/outputs.tf b/modules/operator/outputs.tf new file mode 100644 index 0000000..e1073f9 --- /dev/null +++ b/modules/operator/outputs.tf @@ -0,0 +1,14 @@ +output "operator_namespace" { + description = "Namespace where the operator is installed" + value = kubernetes_namespace.materialize.metadata[0].name +} + +output "operator_release_name" { + description = "Helm release name of the operator" + value = helm_release.materialize_operator.name +} + +output "operator_release_status" { + description = "Status of the helm release" + value = helm_release.materialize_operator.status +} \ No newline at end of file diff --git a/modules/operator/variables.tf b/modules/operator/variables.tf new file mode 100644 index 0000000..da82237 --- /dev/null +++ b/modules/operator/variables.tf @@ -0,0 +1,98 @@ +variable "name_prefix" { + description = "Prefix for all resource names (replaces separate namespace and environment variables)" + type = string +} + +variable "operator_version" { + description = "Version of the Materialize operator to install" + type = string + default = "v25.1.12" # META: helm-chart version + nullable = false +} + +variable "orchestratord_version" { + description = "Version of the Materialize orchestrator to install" + type = string + default = null +} + +variable "helm_repository" { + description = "Repository URL for the Materialize operator Helm chart. Leave empty if using local chart." + type = string + default = "https://materializeinc.github.io/materialize/" +} + +variable "helm_chart" { + description = "Chart name from repository or local path to chart. For local charts, set the path to the chart directory." + type = string + default = "materialize-operator" +} + +variable "use_local_chart" { + description = "Whether to use a local chart instead of one from a repository" + type = bool + default = false +} + +variable "helm_values" { + description = "Values to pass to the Helm chart" + type = any + default = {} +} + +variable "operator_namespace" { + description = "Namespace for the Materialize operator" + type = string + default = "materialize" +} + +variable "monitoring_namespace" { + description = "Namespace for monitoring resources" + type = string + default = "monitoring" +} + +variable "metrics_server_version" { + description = "Version of metrics-server to install" + type = string + default = "3.12.2" +} + +variable "install_metrics_server" { + description = "Whether to install the metrics-server" + type = bool + default = false +} + +variable "region" { + description = "Region/Zone for the operator Helm values." + type = string + default = "us-central1" +} + +variable "use_self_signed_cluster_issuer" { + description = "Whether to use a self-signed cluster issuer for cert-manager." + type = bool + default = false +} + +variable "enable_disk_support" { + description = "Enable disk support for Materialize using OpenEBS and NVMe instance storage. When enabled, this configures OpenEBS, runs the disk setup script for NVMe devices, and creates appropriate storage classes." + type = bool + default = true +} + +variable "disk_support_config" { + description = "Advanced configuration for disk support (only used when enable_disk_support = true)" + type = object({ + create_storage_class = optional(bool, true) + storage_class_name = optional(string, "openebs-lvm-instance-store-ext4") + storage_class_provisioner = optional(string, "local.csi.openebs.io") + storage_class_parameters = optional(object({ + storage = optional(string, "lvm") + fsType = optional(string, "ext4") + volgroup = optional(string, "instance-store-vg") + }), {}) + }) + default = {} +}