正在加载,请稍候…

Terraform State Management: Remote State, State Locking, Workspaces, and Modular IaC Best Practices

Master Terraform state management for production infrastructure: configure S3/GCS remote backends with DynamoDB locking, use workspaces for environment isolation, and organize infrastructure with reusable modules.

Terraform State Management: Remote State, State Locking, Workspaces, and Modular IaC Best Practices

Terraform state is the source of truth for your infrastructure. Mismanaged state leads to configuration drift, resource duplication, and deployment conflicts. This guide covers production-grade state management patterns, from configuring remote backends to organizing complex infrastructure with reusable modules.

Why State Management Matters

Terraform state tracks the mapping between your configuration and the real-world resources it manages. Without proper state management:

  • Team members overwrite each other's changes
  • State gets corrupted during interrupted applies
  • Sensitive values persist in plain text locally
  • Multiple environments share state causing cross-contamination

Remote State Backends

S3 Backend with DynamoDB Locking

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/networking/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    kms_key_id     = "arn:aws:kms:us-east-1:123456789012:key/your-key-id"

    # DynamoDB table for state locking
    dynamodb_table = "terraform-state-locks"
  }
}

Create the S3 bucket and DynamoDB table using a bootstrap configuration:

# bootstrap/main.tf
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state"
  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_s3_bucket_versioning" "state_versioning" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "state_encryption" {
  bucket = aws_s3_bucket.terraform_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}

resource "aws_dynamodb_table" "state_locks" {
  name         = "terraform-state-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

GCS Backend (Google Cloud)

terraform {
  backend "gcs" {
    bucket  = "my-terraform-state"
    prefix  = "production/networking"
  }
}

GCS provides built-in object versioning and uses Cloud Storage's native locking mechanism.

State Locking Deep Dive

State locking prevents concurrent operations from corrupting state.

Manual Lock/Unlock

# Force-unlock a stuck lock (use with caution)
terraform force-unlock LOCK_ID

# Show current state
terraform state list

# Show specific resource state
terraform state show aws_instance.web_server

Lock Timeout Configuration

terraform {
  backend "s3" {
    # ... bucket config ...
    dynamodb_table = "terraform-state-locks"
  }
}
# Apply with custom lock timeout
terraform apply -lock-timeout=300s

Workspaces for Environment Isolation

Workspaces allow a single Terraform configuration to manage multiple environments with separate state files.

Basic Workspace Operations

# Create workspaces
terraform workspace new staging
terraform workspace new production

# Switch workspace
terraform workspace select production

# List workspaces
terraform workspace list

# Show current workspace
terraform workspace show

Workspace-Aware Configuration

locals {
  environment = terraform.workspace
  
  instance_type = {
    staging    = "t3.micro"
    production = "t3.xlarge"
  }
  
  min_capacity = {
    staging    = 1
    production = 3
  }
}

resource "aws_autoscaling_group" "app" {
  min_size = local.min_capacity[local.environment]
  
  launch_template {
    id = aws_launch_template.app.id
    version = "$Latest"
  }
}

resource "aws_instance" "bastion" {
  instance_type = local.instance_type[local.environment]
  
  tags = {
    Name        = "bastion-${local.environment}"
    Environment = local.environment
  }
}

Backend Key per Workspace

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "env:/WORKSPACE/network/terraform.tfstate"
    region = "us-east-1"
  }
}

Terraform substitutes WORKSPACE with the current workspace name automatically.

Modular Infrastructure

Module Structure

infrastructure/
  modules/
    vpc/
      main.tf
      variables.tf
      outputs.tf
      versions.tf
    eks/
      main.tf
      variables.tf
      outputs.tf
    rds/
      main.tf
      variables.tf
      outputs.tf
  environments/
    staging/
      main.tf
      terraform.tfvars
    production/
      main.tf
      terraform.tfvars

VPC Module

# modules/vpc/variables.tf
variable "cidr_block" {
  description = "VPC CIDR block"
  type        = string

  validation {
    condition     = can(cidrnetmask(var.cidr_block))
    error_message = "Must be a valid CIDR block."
  }
}

variable "availability_zones" {
  description = "List of AZs"
  type        = list(string)
}

variable "environment" {
  type = string
}

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_subnet" "private" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.cidr_block, 4, count.index)
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.environment}-private-${var.availability_zones[count.index]}"
    Type = "private"
  }
}

# modules/vpc/outputs.tf
output "vpc_id" {
  value       = aws_vpc.main.id
  description = "VPC ID"
}

output "private_subnet_ids" {
  value       = aws_subnet.private[*].id
  description = "List of private subnet IDs"
}

Using Modules

# environments/production/main.tf
module "vpc" {
  source = "../../modules/vpc"

  cidr_block         = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
  environment        = "production"
}

module "eks" {
  source = "../../modules/eks"

  cluster_name    = "production-cluster"
  vpc_id          = module.vpc.vpc_id
  subnet_ids      = module.vpc.private_subnet_ids
  node_count      = 3
  node_type       = "m5.xlarge"
}

module "rds" {
  source = "../../modules/rds"

  identifier     = "production-db"
  engine         = "postgres"
  engine_version = "16.2"
  instance_class = "db.r6g.xlarge"
  vpc_id         = module.vpc.vpc_id
  subnet_ids     = module.vpc.private_subnet_ids
}

Remote State Data Sources

Share state between configurations:

# Read VPC outputs from another state file
data "terraform_remote_state" "networking" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "production/networking/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_ids[0]
}

State Migration

Importing Existing Resources

# Import an existing EC2 instance
terraform import aws_instance.web i-1234567890abcdef0

# Import with resource address in module
terraform import module.eks.aws_eks_cluster.main my-cluster

Moving Resources in State

# Move resource to new address (no destroy/recreate)
terraform state mv aws_instance.old_name aws_instance.new_name

# Move resource into a module
terraform state mv aws_security_group.app module.app.aws_security_group.main

Removing Resources from State

# Remove without destroying (manage outside Terraform)
terraform state rm aws_instance.legacy

Sensitive Values in State

variable "db_password" {
  type      = string
  sensitive = true
}

output "db_connection_string" {
  value     = "postgresql://admin:${var.db_password}@${aws_db_instance.main.endpoint}/app"
  sensitive = true  # prevents printing in console output
}

Even with sensitive = true, values are stored in plaintext in state. Always use encrypted remote backends.

Drift Detection

# Check for drift without applying
terraform plan -detailed-exitcode
# Exit code 0: no changes
# Exit code 1: error
# Exit code 2: changes present

# Refresh state to match reality
terraform refresh

Integrate into CI:

- name: Detect drift
  run: |
    terraform init
    terraform plan -detailed-exitcode
  continue-on-error: false

Conclusion

Terraform state management is the foundation of reliable infrastructure as code. Remote backends with locking prevent race conditions and data loss. Workspaces provide clean environment separation. Modular configurations enable code reuse across projects and teams. Remote state data sources allow loose coupling between infrastructure components. With these patterns, your IaC scales from a single developer to an entire platform engineering team without friction.