Posted on :: 2728 Words :: Tags: , ,

Core Terraform Workflow

Bạn đã học HCL syntax, variables, và state management rồi đó. Giờ mình sẽ nói về workflow thực tế - chu trình hàng ngày bạn sẽ dùng mỗi khi làm việc với infrastructure.

Đây là Part 6 of 12 trong series Terraform tutorial. Đến cuối bài, bạn sẽ biết những commands cần thiết, khi nào dùng chúng, và cách debug khi có vấn đề xảy ra.

📦 Code Examples

Repository: terraform-hcl-tutorial-series This Part: Part 6 - Workflow Examples

Get working example:

git clone https://github.com/khuongdo/terraform-hcl-tutorial-series.git
cd terraform-hcl-tutorial-series
git checkout part-06
cd examples/part-06-workflow/

# Practice the workflow
terraform init
terraform fmt
terraform validate
terraform plan

The Terraform Lifecycle

Mỗi infrastructure change đều follow pattern này:

Write → Init → Format → Validate → Plan → Apply → Destroy
   ↑______________________________________________|
              (Repeat for changes)

Mỗi step có một nhiệm vụ riêng. Skip một cái là bạn sẽ biết tại sao nó tồn tại theo cách khó nhằn đó.

StepWhat It DoesWhat Breaks If You Skip It
WriteDefine what you wantNothing to deploy
InitDownload providers, set up backendCommands fail với "not initialized"
FormatClean up spacing và indentationMessy diffs, annoying code reviews
ValidateCheck syntax trước khi runErrors during plan/apply
PlanPreview changesSurprises, accidental deletions
ApplyActually create stuffNo infrastructure
DestroyTear it all downOrphaned resources, surprise bills

Giờ mình sẽ đi qua từng bước nhé.


Step 1: Write Configuration

Bắt đầu bằng cách viết những gì bạn muốn. Đây là một basic EC2 instance setup:

# main.tf
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name        = "${var.project_name}-web-server"
    Environment = var.environment
  }
}
# variables.tf
variable "aws_region" {
  description = "AWS region for resources"
  type        = string
  default     = "us-east-1"
}

variable "ami_id" {
  description = "AMI ID for EC2 instance"
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "project_name" {
  description = "Project name for resource tagging"
  type        = string
}

variable "environment" {
  description = "Environment (dev/staging/prod)"
  type        = string
}
# outputs.tf
output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web.id
}

output "public_ip" {
  description = "Public IP address of the instance"
  value       = aws_instance.web.public_ip
}

Step 2: Initialize the Project

Command: terraform init

Đây luôn là command đầu tiên bạn chạy. Nó sẽ:

  1. Download provider plugins (AWS, GCP, whatever bạn đang dùng)
  2. Set up backend (nơi state được stored)
  3. Tạo .terraform directory
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)

Terraform has been successfully initialized!

Khi nào chạy init lại:

  • Added provider mới
  • Changed backend configuration
  • Fresh Git clone
  • Deleted .terraform/ directory

Checkpoint!

terraform init an toàn để run nhiều lần. Nếu bạn không chắc có cần hay không, cứ chạy thôi.


Step 3: Format Your Code

Command: terraform fmt

Cái này auto-format .tf files của bạn theo Terraform standard style:

$ terraform fmt
main.tf
variables.tf

What it fixes:

  • Indentation (2 spaces)
  • Spacing around =
  • Alignment
  • Trailing whitespace

Before formatting:

resource "aws_instance" "web"{
ami="ami-12345"
  instance_type =    "t3.micro"
    tags={
Name="web-server"
  }
}

After terraform fmt:

resource "aws_instance" "web" {
  ami           = "ami-12345"
  instance_type = "t3.micro"
  tags = {
    Name = "web-server"
  }
}

Bạn có thể add cái này vào Git pre-commit hook để không ai push messy code:

#!/bin/bash
# .git/hooks/pre-commit
terraform fmt -check -recursive
if [ $? -ne 0 ]; then
  echo "Error: Terraform files not formatted. Run 'terraform fmt'"
  exit 1
fi

Step 4: Validate Configuration

Command: terraform validate

Cái này check syntax errors và missing required fields mà không đụng đến cloud APIs:

$ terraform validate
Success! The configuration is valid.

What it catches:

  • Bad HCL syntax
  • Missing required arguments
  • Invalid resource references
  • Type mismatches

Example validation failure:

resource "aws_instance" "web" {
  ami = var.ami_id
  # Oops, quên instance_type
}
$ terraform validate

Error: Missing required argument

  on main.tf line 10, in resource "aws_instance" "web":
  10: resource "aws_instance" "web" {

The argument "instance_type" is required, but no definition was found.

Tip!

terraform validate chạy hoàn toàn locally - nó không bao giờ gọi API calls. Dùng nó sớm và thường xuyên nhé.


Step 5: Preview Changes (Plan)

Command: terraform plan

Đây là step quan trọng nhất. Nó show exactly những gì sẽ xảy ra trước khi bạn thay đổi bất cứ thứ gì:

$ terraform plan -var-file="dev.tfvars" -out=tfplan

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami                          = "ami-0c55b159cbfafe1f0"
      + instance_type                = "t3.micro"
      + id                           = (known after apply)
      + public_ip                    = (known after apply)
      + tags                         = {
          + "Environment" = "dev"
          + "Name"        = "myapp-web-server"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + instance_id = (known after apply)
  + public_ip   = (known after apply)

────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

Understanding plan symbols:

SymbolMeaningRisk Level
+Resource sẽ được createdLow
~Resource sẽ modified in-placeMedium
-/+Resource sẽ destroyed và recreatedHigh
-Resource sẽ destroyedCritical

Save plans cho safer applies:

# Generate plan file
terraform plan -out=tfplan

# Review the plan
terraform show tfplan

# Apply the exact plan
terraform apply tfplan

Tại sao phải save plans?

Không có -out, chạy terraform apply sẽ generate plan mới tại apply time. Nếu có gì đó thay đổi giữa plan và apply (drift, thay đổi của người khác), bạn có thể apply cái gì đó khác với cái bạn đã review.

Production Best Practice

Luôn dùng terraform plan -out=tfplan rồi terraform apply tfplan trong production. Đừng bao giờ chạy terraform apply mà không review saved plan trước.


Step 6: Apply Changes

Command: terraform apply

Cái này executes plan và creates/modifies/destroys infrastructure:

# Interactive apply (hỏi confirmation)
$ terraform apply -var-file="dev.tfvars"

# Auto-approve (chỉ dùng trong CI/CD thôi)
$ terraform apply -var-file="dev.tfvars" -auto-approve

# Apply saved plan (recommended)
$ terraform apply tfplan

Output:

aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Creation complete after 25s [id=i-0abcd1234efgh5678]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

instance_id = "i-0abcd1234efgh5678"
public_ip = "54.123.45.67"

What happens during apply:

  1. Terraform locks state file (prevents other runs)
  2. Gọi API calls đến cloud provider của bạn
  3. Updates terraform.tfstate với new resource IDs
  4. Unlocks state file
  5. Shows outputs

Khi apply fails mid-execution:

Nếu nó crashes (network error, API limit, timeout), state file reflects những gì actually được created. Fix issue đó và re-run terraform apply - Terraform sẽ picks up từ nơi nó dừng lại.


Step 7: Inspect Your Infrastructure

Sau khi apply, check những gì bạn đã tạo:

# List tất cả resources trong state
$ terraform state list
aws_instance.web

# Show detailed resource attributes
$ terraform state show aws_instance.web
# aws_instance.web:
resource "aws_instance" "web" {
    ami                          = "ami-0c55b159cbfafe1f0"
    instance_type                = "t3.micro"
    id                           = "i-0abcd1234efgh5678"
    public_ip                    = "54.123.45.67"
    tags                         = {
        "Environment" = "dev"
        "Name"        = "myapp-web-server"
    }
}

# View outputs lại
$ terraform output
instance_id = "i-0abcd1234efgh5678"
public_ip = "54.123.45.67"

# Get specific output value (cho scripts)
$ terraform output -raw public_ip
54.123.45.67

Step 8: Destroy Infrastructure

Command: terraform destroy

Khi bạn xong (dev environment, testing, etc.), tear it down:

$ terraform destroy -var-file="dev.tfvars"

Terraform will perform the following actions:

  # aws_instance.web will be destroyed
  - resource "aws_instance" "web" {
      - ami           = "ami-0c55b159cbfafe1f0" -> null
      - instance_type = "t3.micro" -> null
      - id            = "i-0abcd1234efgh5678" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.web: Destroying... [id=i-0abcd1234efgh5678]
aws_instance.web: Destruction complete after 35s

Destroy complete! Resources: 1 destroyed.

Targeted destruction (specific resource):

# Destroy chỉ web instance thôi
terraform destroy -target=aws_instance.web

Warning!

terraform destroy là irreversible. Luôn chạy terraform plan -destroy trước để preview những gì sẽ deleted. Trong production, protect critical resources với lifecycle { prevent_destroy = true }.


Debugging Terraform

Khi có lỗi, Terraform cung cấp detailed logging:

Enable Debug Logging

# Set log level (TRACE, DEBUG, INFO, WARN, ERROR)
export TF_LOG=DEBUG

# Save logs to file
export TF_LOG_PATH=terraform-debug.log

# Run Terraform command
terraform apply

# Disable logging
unset TF_LOG TF_LOG_PATH

Log levels:

LevelUse Case
TRACEMaximum verbosity - shows mọi API call
DEBUGDetailed debugging info
INFOStandard operational messages
WARNWarning messages only
ERRORErrors only

Common Debugging Scenarios

Problem: "Error: Provider configuration not present"

# Solution: Run init
terraform init

Problem: "Error: Inconsistent dependency lock file"

# Solution: Regenerate lock file
terraform init -upgrade

Problem: "Error: Backend initialization required"

# Solution: Migrate backend
terraform init -migrate-state

Problem: "Error: Resource already exists"

# Solution: Import existing resource
terraform import aws_instance.web i-1234567890abcdef0

Importing Existing Infrastructure

Có manually created resources? Import chúng vào Terraform:

Step 1: Write the resource configuration

# main.tf
resource "aws_instance" "legacy" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "legacy-server"
  }
}

Step 2: Import the resource

# Syntax: terraform import <resource_type>.<name> <cloud_resource_id>
$ terraform import aws_instance.legacy i-1234567890abcdef0

aws_instance.legacy: Importing from ID "i-1234567890abcdef0"...
aws_instance.legacy: Import prepared!
  Prepared aws_instance for import
aws_instance.legacy: Refreshing state... [id=i-1234567890abcdef0]

Import successful!

Step 3: Verify the import

$ terraform plan

No changes. Your infrastructure matches the configuration.

Nếu plan shows changes, update configuration của bạn để match với actual resource.

Tip!

Dùng terraform show sau import để see full resource configuration, rồi copy nó vào .tf files của bạn.


State Operations

Terraform state commands cho phép bạn inspect và manipulate state mà không đụng đến cloud resources:

List Resources

$ terraform state list
aws_instance.web
aws_security_group.web_sg
aws_vpc.main

Show Resource Details

$ terraform state show aws_instance.web
# aws_instance.web:
resource "aws_instance" "web" {
    ami           = "ami-0c55b159cbfafe1f0"
    id            = "i-0abcd1234efgh5678"
    instance_type = "t3.micro"
}

Move Resources (Refactoring)

Khi bạn rename resources trong code, update state để match:

# Renamed trong code: aws_instance.web → aws_instance.app_server
$ terraform state mv aws_instance.web aws_instance.app_server

Move "aws_instance.web" to "aws_instance.app_server"
Successfully moved 1 object(s).

Remove Resources from State

Remove resource khỏi Terraform management mà không destroying nó:

$ terraform state rm aws_instance.web

Removed aws_instance.web
Successfully removed 1 resource instance(s).

Instance vẫn còn tồn tại trong AWS, nhưng Terraform không còn manage nó nữa.


Advanced CLI Operations

Refresh State

Sync Terraform state với actual cloud resources:

$ terraform refresh

Khi nào dùng:

  • Có người made manual changes trong cloud console
  • Checking drift giữa state và reality

Note: Terraform tự động refreshes trong planapply, nên explicit refresh hiếm khi cần thiết.

Replace Resources

Force replacement của resource (destroy rồi recreate):

# Replace specific resource
$ terraform apply -replace=aws_instance.web

# Hữu ích khi:
# - Resource bị corrupted
# - Cần fresh instance ID
# - Testing disaster recovery

Targeted Apply

Apply changes chỉ cho specific resources thôi:

# Chỉ create/update web instance
$ terraform apply -target=aws_instance.web

# Multiple targets
$ terraform apply -target=aws_instance.web -target=aws_security_group.web_sg

Warning!

Dùng -target có thể tạo inconsistent state. Tránh trong production trừ khi recovering từ partial failures.


The Complete Workflow Script

Đây là production-ready workflow script:

#!/bin/bash
# deploy.sh - Safe Terraform deployment script

set -e  # Exit on error

ENVIRONMENT=${1:-dev}
VAR_FILE="${ENVIRONMENT}.tfvars"

echo "========================================="
echo "Deploying to: $ENVIRONMENT"
echo "========================================="

# Step 1: Initialize
echo "→ Initializing Terraform..."
terraform init

# Step 2: Format check
echo "→ Checking code formatting..."
terraform fmt -check -recursive
if [ $? -ne 0 ]; then
  echo "❌ Code not formatted. Run: terraform fmt -recursive"
  exit 1
fi

# Step 3: Validate
echo "→ Validating configuration..."
terraform validate

# Step 4: Generate plan
echo "→ Generating plan..."
terraform plan -var-file="$VAR_FILE" -out=tfplan

# Step 5: Show plan
echo "→ Plan generated. Review changes above."
read -p "Apply this plan? (yes/no): " CONFIRM

if [ "$CONFIRM" != "yes" ]; then
  echo "❌ Deployment cancelled."
  rm tfplan
  exit 0
fi

# Step 6: Apply plan
echo "→ Applying changes..."
terraform apply tfplan

# Cleanup
rm tfplan

echo "✅ Deployment complete!"
terraform output

Usage:

chmod +x deploy.sh
./deploy.sh dev      # Deploy to dev
./deploy.sh staging  # Deploy to staging
./deploy.sh prod     # Deploy to production

Checkpoint Questions

Test understanding của bạn:

  1. terraform init làm gì? Tại sao phải chạy nó trước plan hoặc apply?

  2. Khác biệt giữa terraform planterraform apply? Bạn có thể apply mà không planning không?

  3. Điều gì xảy ra nếu bạn chạy terraform apply hai lần mà không có code changes?

  4. Làm sao destroy chỉ một specific resource mà không tear down mọi thứ?

  5. Tại sao phải save plans với -out=tfplan? Nó giải quyết vấn đề gì?

  6. Khi nào bạn sẽ dùng terraform import? Bạn có thể import resources mà Terraform đã created không?

  7. Khác biệt giữa terraform state rmterraform destroy là gì?

Click để xem answers
  1. terraform init downloads provider plugins và initializes backend. Required trước mọi Terraform operations.

  2. plan previews changes mà không making them; apply executes changes. Bạn có thể apply mà không có explicit plan, nhưng nó risky (plan happens inline).

  3. Không có gì thay đổi. Terraform detects infrastructure matches desired state và performs no operations.

  4. terraform destroy -target=resource_type.name

  5. Saved plans đảm bảo changes bạn reviewed là exactly những gì được applied (không có drift giữa plan time và apply time).

  6. Dùng import để bring manually-created resources vào Terraform management. Yes, bạn có thể import Terraform-created resources nếu bạn lost state.

  7. state rm removes khỏi Terraform tracking nhưng leaves resource intact. destroy deletes actual cloud resource.


What's Next: Part 7 - Modules for Organization

Bạn đã master Terraform workflow rồi đó, nhưng điều gì xảy ra khi codebase của bạn grows đến hàng trăm resources? Copy cùng một VPC configuration across 10 projects? Manage dependencies giữa related resources?

Trong Part 7, mình sẽ giải quyết những problems này với Terraform modules - reusable components mà:

  • Eliminate code duplication
  • Enforce organizational standards
  • Enable team collaboration
  • Simplify complex infrastructure

Bạn sẽ học:

  • Tạo custom modules từ scratch
  • Dùng public modules từ Terraform Registry
  • Pass variables và outputs giữa modules
  • Version và publish internal modules
  • Structure large Terraform projects

Preview snippet (Part 7):

# Instead of repeating VPC code...
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "${var.project}-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = false
}

# Reference module outputs
resource "aws_instance" "web" {
  subnet_id = module.vpc.public_subnets[0]
}

Hẹn gặp bạn ở Part 7 nhé.


Series Navigation

  • Part 1: Why Infrastructure as Code?
  • Part 2: Setting Up Terraform (Coming soon)
  • Part 3: Your First Cloud Resource (Coming soon)
  • Part 4: HCL Fundamentals (Coming soon)
  • Part 5: Variables, Outputs & State (Coming soon)
  • Part 6: Core Terraform Workflow (You are here)
  • Part 7: Modules for Organization (Coming soon)
  • Part 8: Multi-Cloud Patterns (Coming soon)
  • Part 9: State Management & Team Workflows (Coming soon)
  • Part 10: Testing & Validation (Coming soon)
  • Part 11: Security & Secrets Management (Coming soon)
  • Part 12: Production Patterns & DevSecOps (Coming soon)

References


Điều hướng series:


Questions hoặc feedback? Drop a comment below hoặc kết nối trên LinkedIn.