This article is a continuation of the “Terraform Phoenix Cycle.” The core concept remains the same: the underlying Infrastructure as Code (IaC) layer is constant, while the Kubernetes (K8s) workload layer becomes interchangeable. In this model, K8s modules are deployed based on specific operational needs; once their task is complete, the module is destroyed, only to be resurrected when the next requirement arises.

We will explore three specific modules in this guide:

  • DDoS Telemetry Collection: Capturing traffic anomaly metadata within an isolated validation environment.

  • Traffic Shaping: Managing traffic flow and quality at the edge.

  • Proxy Caching: Reducing backend load and improving response efficiency for repeated requests.

These modules are intentionally lightweight so the orchestration and lifecycle mechanics remain easy to observe and test, the “Swappable Model” is designed to support much more complex logic. All examples will continue to use the Terraform, Minikube, and Miniblue.io stack for local portability and rapid testing.


Note: To keep the module swap process clean inside main.tf, we will use a variable-driven workload selector.

For simplicity, this example uses a local dynamic module path. In larger production environments, explicit module declarations or orchestration pipelines are generally preferred for stronger validation, dependency tracking, and lifecycle control.

variable "active_module" {
  description = "Select which workload module to deploy"
  type        = string
  default     = "ddos"

  validation {
    condition     = contains(["ddos", "shaping", "cache"], var.active_module)
    error_message = "Valid modules are: ddos, shaping, cache."
  }
}

##########################################################
# Swap Logic
##########################################################
module "workload" {
  source = "./modules/${var.active_module}"
}

The active workload can then be changed directly through terraform.tfvars:

filename: terraform.tfvars

active_module = "ddos"
cluster_name  = "phoenix-lab"

Restructuring the Terraform Layout

To implement the Module Swap Model cleanly, each Kubernetes workload should be isolated as an independent Terraform module while the infrastructure underlay remains static.

This allows:

  • reusable infrastructure provisioning
  • isolated workload testing
  • controlled lifecycle teardown
  • workload-specific manifests and automation
  • simpler operational debugging

Each workload module contains:

  • Terraform logic
  • Kubernetes manifests
  • optional validation scripts
  • cleanup procedures

File Structure


terraform-module-swap/
├── main.tf
├── providers.tf
├── variables.tf
├── terraform.tfvars
├── outputs.tf
│
├── modules/
│   ├── ddos/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   ├── scripts/
│   │   │   ├── validate.sh
│   │   │   └── cleanup.sh
│   │   └── k8s-manifests/
│   │       ├── collector-deployment.yaml
│   │       ├── collector-service.yaml
│   │       ├── rate-limit-config.yaml
│   │       └── telemetry-configmap.yaml
│   │
│   ├── shaping/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   ├── scripts/
│   │   │   ├── validate.sh
│   │   │   └── cleanup.sh
│   │   └── k8s-manifests/
│   │       ├── traffic-manager.yaml
│   │       ├── shaping-config.yaml
│   │       └── ingress-policy.yaml
│   │
│   └── cache/
│       ├── main.tf
│       ├── variables.tf
│       ├── outputs.tf
│       ├── scripts/
│       │   ├── validate.sh
│       │   └── cleanup.sh
│       └── k8s-manifests/
│           ├── cache-configmap.yaml
│           ├── cache-deployment.yaml
│           ├── cache-service.yaml
│           └── ingress.yaml
│
├── scripts/
│   ├── create_cluster.sh
│   ├── destroy_cluster.sh
│   ├── healthcheck.sh
│   └── teardown_guard.sh
│
└── docs/
    ├── architecture.md
    ├── lifecycle.md
    └── module-swap-flow.md

Root Folder

For other file check Terraform Phoenix Cycle

Filename: main.tf


##########################################################
# STEP 1: Static Infrastructure Underlay
# This layer remains unchanged regardless of workload
##########################################################

resource "null_resource" "base_infra" {

  provisioner "local-exec" {
    command = "bash scripts/create_cluster.sh ${var.cluster_name}"
  }

  provisioner "local-exec" {
    when    = destroy
    command = "bash scripts/destroy_cluster.sh ${var.cluster_name}"
  }
}

##########################################################
# STEP 2: Swappable Kubernetes Workload Layer
##########################################################

module "workload" {

  source = "./modules/${var.active_module}"

  depends_on = [
    null_resource.base_infra
  ]

  cluster_name = var.cluster_name
}

This is from the original Terraform Phoenix Cycle

##########################################################
# STEP 1
# Create Kubernetes cluster
##########################################################

resource "null_resource" "create_cluster" {

  provisioner "local-exec" 
	{
    command = "bash scripts/create_cluster.sh ${var.cluster_name}"
	}
}

##########################################################
# STEP 2
# Deploy backend application
##########################################################

resource "null_resource" "deploy_backend" {

  depends_on = [null_resource.create_cluster]

  provisioner "local-exec" {
    command = <<EOT
		kubectl apply -f k8s/backend-deployment.yaml
		kubectl apply -f k8s/backend-service.yaml
	EOT
  }
}

##########################################################
# STEP 3
# Deploy reverse proxy
##########################################################

resource "null_resource" "deploy_proxy" {

  depends_on = [null_resource.deploy_backend]

  provisioner "local-exec" {
    command = <<EOT
		kubectl apply -f k8s/reverse-proxy-configmap.yaml
		kubectl apply -f k8s/reverse-proxy-deployment.yaml
		kubectl apply -f k8s/reverse-proxy-service.yaml
	EOT
  }
}

##########################################################
# STEP 4
# Wait for reverse proxy readiness
##########################################################

resource "null_resource" "wait_proxy" {

  depends_on = [null_resource.deploy_proxy]

  provisioner "local-exec" {
    command = "bash scripts/wait_proxy_ready.sh"
  }
}

##########################################################
# STEP 5
# Validate infrastructure before collapse
##########################################################

resource "null_resource" "validate_destroy" {

  depends_on = [null_resource.wait_proxy]

  provisioner "local-exec" {
    command = "bash scripts/validate_destroy.sh"
  }
}

##########################################################
# STEP 6
# Collapse Kubernetes workloads
##########################################################

resource "null_resource" "collapse_k8s" {

  depends_on = [null_resource.validate_destroy]

  provisioner "local-exec" {
    command = "bash scripts/collapse_k8s.sh ${var.cluster_name}"
  }
}

filename: variables.tf


variable "cluster_name" {
  description = "Minikube cluster name"
  type        = string
  default     = "test-lab"
}

variable "active_module" {
  description = "Which workload module to deploy"
  type        = string
  default     = "ddos"
}

The Swappable Modules

DDoS Telemetry Module

filename: modules/ddos/main.tf


variable "cluster_name" {}

resource "null_resource" "deploy_ddos_collector" {

  provisioner "local-exec" {

    command = <<EOT

      echo "[*] Deploying telemetry collector..."

      kubectl apply -f modules/ddos/k8s-manifests/telemetry-configmap.yaml
      kubectl apply -f modules/ddos/k8s-manifests/rate-limit-config.yaml
      kubectl apply -f modules/ddos/k8s-manifests/collector-deployment.yaml
      kubectl apply -f modules/ddos/k8s-manifests/collector-service.yaml

      bash modules/ddos/scripts/validate.sh

      echo "[+] DDoS telemetry module active on ${var.cluster_name}"

    EOT
  }

  ##########################################################
  # Self-Destruct Lifecycle
  ##########################################################

  provisioner "local-exec" {

    when = destroy

    command = <<EOT

      echo "[*] Removing telemetry collector..."

      bash modules/ddos/scripts/cleanup.sh

      kubectl delete -f modules/ddos/k8s-manifests/collector-service.yaml || true
      kubectl delete -f modules/ddos/k8s-manifests/collector-deployment.yaml || true
      kubectl delete -f modules/ddos/k8s-manifests/rate-limit-config.yaml || true

      echo "[+] DDoS telemetry module destroyed"

    EOT
  }
}

Traffic Shaping Module

filename: modules/shaping/main.tf


variable "cluster_name" {}

resource "null_resource" "apply_shaping" {

  provisioner "local-exec" {

    command = <<EOT

      echo "[*] Applying traffic shaping policies..."

      kubectl apply -f modules/shaping/k8s-manifests/shaping-config.yaml
      kubectl apply -f modules/shaping/k8s-manifests/traffic-manager.yaml
      kubectl apply -f modules/shaping/k8s-manifests/ingress-policy.yaml

      minikube ssh -p ${var.cluster_name} \
      "sudo tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 400ms"

      bash modules/shaping/scripts/validate.sh

      echo "[+] Traffic shaping active"

    EOT
  }

  provisioner "local-exec" {

    when = destroy

    command = <<EOT

      echo "[*] Removing shaping policies..."

      minikube ssh -p ${var.cluster_name} \
      "sudo tc qdisc del dev eth0 root" || true

      bash modules/shaping/scripts/cleanup.sh

      kubectl delete -f modules/shaping/k8s-manifests/traffic-manager.yaml || true

      echo "[+] Traffic shaping removed"

    EOT
  }
}

Proxy Cache Module

filename: modules/cache/main.tf


variable "cluster_name" {}

resource "null_resource" "deploy_cache" {

  provisioner "local-exec" {

    command = <<EOT

      echo "[*] Deploying cache infrastructure..."

      kubectl apply -f modules/cache/k8s-manifests/cache-configmap.yaml
      kubectl apply -f modules/cache/k8s-manifests/cache-deployment.yaml
      kubectl apply -f modules/cache/k8s-manifests/cache-service.yaml
      kubectl apply -f modules/cache/k8s-manifests/ingress.yaml

      bash modules/cache/scripts/validate.sh

      echo "[+] Cache module active"

    EOT
  }

  provisioner "local-exec" {

    when = destroy

    command = <<EOT

      echo "[*] Removing cache infrastructure..."

      bash modules/cache/scripts/cleanup.sh

      kubectl delete -f modules/cache/k8s-manifests/ingress.yaml || true
      kubectl delete -f modules/cache/k8s-manifests/cache-service.yaml || true
      kubectl delete -f modules/cache/k8s-manifests/cache-deployment.yaml || true

      echo "[+] Cache module removed"

    EOT
  }
}

This shifts the “Phoenix Cycle” paradigm from a main.tf centric model to a module-centric model.