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.