Choosing the wrong IaC tool for a production‑grade Kubernetes cluster can add $5 000 + to a quarterly AWS bill by generating unnecessary EC2 instances, or increase pod‑startup latency by 250 ms per node because sub‑optimal networking defaults cause additional ENI provisioning cycles. Terraform vs CloudFormation for managing Kubernetes clusters is a trade‑off between multi‑cloud portability and deep AWS‑native integration, and the right choice prevents those hidden costs.
📑 Table of Contents
- 🚀 Portability — Why Terraform Helps
- 🏗️ Deep Integration — Why CloudFormation Excels
- ⚙️ State Management — How Terraform Handles Drift
- 🔧 Plan and Apply
- 📊 Drift Detection
- 📦 Resource Lifecycle — How CloudFormation Manages Updates
- 🔍 Side‑by‑Side Comparison
- 🟩 Final Thoughts
- ❓ Frequently Asked Questions
- Can Terraform manage existing CloudFormation stacks?
- What happens if a CloudFormation change set fails?
- Is it possible to use Terraform and CloudFormation together?
- 📚 References & Further Reading
🚀 Portability — Why Terraform Helps
Terraform provides a provider‑agnostic language that lets you declare an EKS cluster once and reuse the same configuration across AWS, Azure, or GCP. The HCL file is parsed into a graph of resources; each node in the graph is translated by the selected provider into the appropriate API calls, so changing the provider block is the only required modification for a different cloud.
# main.tf
provider "aws" { region = "us-east-1"
} resource "aws_eks_cluster" "demo" { name = "demo-eks" role_arn = aws_iam_role.eks_role.arn vpc_config { subnet_ids = [ aws_subnet.public_a.id, aws_subnet.public_b.id, ] }
}
What this does:
- provider "aws": selects the AWS endpoint and credentials for all subsequent resources.
-
aws_eks_cluster "demo": creates a managed Kubernetes control plane named
demo-eks. - vpc_config.subnet_ids: attaches the control plane to existing public subnets; the same block works on any cloud that offers a compatible VPC resource.
Why this, not the obvious alternative? Writing separate CloudFormation templates for each cloud would duplicate effort, while Terraform’s single source of truth eliminates configuration drift across providers.
Key point: Terraform’s HCL abstracts cloud‑specific APIs, giving you a portable manifest that can be version‑controlled and reused.
🏗️ Deep Integration — Why CloudFormation Excels
CloudFormation leverages native AWS resources, exposing features like IAM role propagation and Service‑Linked Roles that are not directly reachable from the generic Terraform provider. Intrinsic functions such as !GetAtt and !Ref are resolved during stack creation, guaranteeing that dependent IAM permissions are in place before the EKS control plane is instantiated.
# eks-cluster.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: EKS control plane Resources: EKSCluster: Type: AWS::EKS::Cluster Properties: Name: demo-eks RoleArn:!GetAtt EKSRole.Arn ResourcesVpcConfig: SubnetIds: -!Ref PublicSubnetA -!Ref PublicSubnetB EKSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: eks.amazonaws.com Action: sts:AssumeRole
What this does:
- AWS::EKS::Cluster: creates the control plane with AWS‑managed networking and security groups.
- AWS::IAM::Role: defines a service‑linked role that CloudFormation automatically attaches to the cluster for node‑group permissions.
Why this, not the obvious alternative? CloudFormation can reference intrinsic functions (!GetAtt, !Ref) that resolve at deployment time, guaranteeing that IAM permissions are fully propagated before the cluster becomes available.
Key point: CloudFormation’s deep integration reduces the number of API calls needed during provisioning, cutting overall deployment time.
⚙️ State Management — How Terraform Handles Drift
The Terraform state file stores a JSON map of each managed resource’s attribute values, including IDs, tags, and computed properties. During terraform plan, the provider reads live data, compares it against the stored map, and reports any mismatches. This comparison is O(N) over the number of resources and provides deterministic drift detection.
$ terraform init
Initializing the backend...
Initializing provider plugins...
Terraform has been successfully initialized! $ terraform plan
Refreshing state...
aws_eks_cluster.demo: Refreshing state... -----------------------------------------------------------------------
No changes. Infrastructure is up-to-date. This means that the current Terraform state matches the real
AWS resources.
According to the Terraform documentation, drift detection works by comparing the saved state against live resource data during the plan phase. (Also read: ☁️ aws cloudformation vs terraform for python deployments — which one should you use?)
🔧 Plan and Apply
Running terraform apply after a manual change will generate a plan that reverts the drift. (Also read: ⚙️ Kubernetes RBAC vs ServiceAccount permissions — which provides tighter security?)
$ terraform apply
aws_eks_cluster.demo: Refreshing state...
aws_eks_cluster.demo: Modifying... [id=demo-eks] Plan: 1 to add, 0 to change, 0 to destroy.
📊 Drift Detection
Detecting drift early prevents configuration inconsistencies that could cause pod‑networking failures or security gaps. (More onPythonTPoint tutorials)
Key point: Terraform’s explicit state model gives you a single source of truth, making drift visible before it impacts workloads.
📦 Resource Lifecycle — How CloudFormation Manages Updates
CloudFormation uses Change Sets to preview modifications before applying them, ensuring that updates to the EKS control plane are safe. A Change Set evaluates the template, produces a diff, and only proceeds when the diff matches the intended actions, avoiding unintended replacements.
$ aws cloudformation create-change-set \ -stack-name demo-eks \ -template-body file://eks-cluster.yaml \ -change-set-name demo-update \ -capabilities CAPABILITY_NAMED_IAM
{ "Id": "arn:aws:cloudformation:us-east-1:123456789012:change-set/demo-update/abcd1234", "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/demo-eks/efgh5678"
}
CHANGESET STATUS
demo-update CREATE_COMPLETE
Review the change set:
$ aws cloudformation describe-change-set \ -change-set-name demo-update \ -stack-name demo-eks
{ "Changes": [ { "ResourceChange": { "Action": "Modify", "LogicalResourceId": "EKSCluster", "PhysicalResourceId": "demo-eks", "Replacement": "False", "Details": [ { "Target": { "Attribute": "Properties", "Name": "Version" }, "Evaluation": "Static", "ChangeSource": "User" } ] } } ]
}
Applying the change set:
$ aws cloudformation execute-change-set \ -change-set-name demo-update \ -stack-name demo-eks
Why this, not the obvious alternative? Directly updating resources without a change set can cause unexpected downtime; the preview step guarantees that only intended modifications are applied.
Key point: CloudFormation’s change‑set workflow provides a safety net for in‑place updates, especially for critical control‑plane components.
🔍 Side‑by‑Side Comparison
| Aspect | Terraform | CloudFormation |
|---|---|---|
| Multi‑cloud support | Yes – same HCL works on AWS, Azure, GCP. | No – AWS‑only. |
| State handling | Explicit .tfstate file; drift detection built‑in. |
Implicit; relies on stack drift detection. |
| Update safety | Plan shows exact changes; apply is idempotent. | Change Sets provide preview; rollback triggers optional. |
| Feature coverage | Depends on provider maturity; sometimes lags behind new AWS services. | Immediate access to all AWS resources as they are released. |
| Learning curve | HCL syntax plus provider docs. | YAML/JSON plus intrinsic functions. |
Choosing the right tool is less about hype and more about aligning the IaC model with your operational constraints.
Key point: The decision matrix hinges on portability versus native feature completeness, and on whether you need explicit state tracking.
🟩 Final Thoughts
When the primary requirement is to manage clusters across multiple clouds or to keep a single source of truth for a heterogeneous environment, Terraform’s provider model delivers the most flexibility. Conversely, if you operate exclusively within AWS and need the fastest path to the newest EKS feature, CloudFormation’s native integration reduces latency and eliminates the need for a separate state store.
The practical outcome is a predictable cost profile: Terraform avoids hidden cross‑cloud expenses, while CloudFormation minimizes provisioning time and API‑call overhead. Align your choice with the specific cost drivers of your workload, and the IaC layer will become a lever rather than a liability.
❓ Frequently Asked Questions
Can Terraform manage existing CloudFormation stacks?
Yes. The aws_cloudformation_stack resource can import and manage stacks created outside Terraform, allowing you to consolidate IaC under a single tool.
What happens if a CloudFormation change set fails?
A failed change set leaves the stack unchanged; you can inspect the failure reason in the console or via the describe-change-set API and then correct the template before retrying.
Is it possible to use Terraform and CloudFormation together?
Hybrid approaches are supported: you can let CloudFormation provision the EKS control plane while Terraform manages node groups, services, and ingress resources, coordinating through shared state variables.
💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.
📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.
📚 References & Further Reading
- Official Terraform documentation — comprehensive guide to providers and state handling: developer.hashicorp.com
- AWS CloudFormation User Guide — details on templates, change sets, and rollback triggers: docs.aws.amazon.com
- Kubernetes documentation — fundamentals of EKS clusters and networking: kubernetes.io
Top comments (0)