Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
terraform【部署与运维】Use this skill when provisioning infrastructure with Terraform — module structure, state management, remote backends, workspaces, CI/CD integration, policy as code. This skill enforces: environment directory structure, remote backends with state locking, semantic versioning for modules, no hardcoded values, sensitive output marking, plan review befor…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Define and enforce Terraform infrastructure provisioning patterns with module design, state management, and CI/CD integration.
User request includes: terraform, tf, iac, infrastructure as code, hcl, terraform module, terraform state, terraform workspace, terraform backend, terragrunt.
A markdown document containing:
Produce the artifact directly. No preamble, no postamble, no explanations. No filler, no hedging, no transitions. Strip articles a/an/the where unambiguous. Compress output — why use many token when few do trick.
4096 tokens
| Strategy | When | Pros | Cons |
|---|---|---|---|
| Directory per env | Production workloads, different configs | Clear separation, parallel applies | Config duplication |
| Workspaces | Dev/test, same config | DRY, minimal files | Accidental cross-env changes |
| Terragrunt | Multi-module dependencies | DRY, dependency resolution | Additional tooling, complexity |
Rule: Prefer directory per environment for production. Workspaces for dev/test only.
terraform/
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ ├── backend.hcl
│ │ └── provider.tf
│ ├── staging/
│ │ └── ...
│ └── development/
│ └── ...
├── modules/
│ ├── eks/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── rds/
│ │ └── ...
│ ├── vpc/
│ │ └── ...
│ └── iam/
│ └── ...
├── policies/
│ ├── required_tags.rego
│ ├── allowed_instance_types.rego
│ └── enforce_encryption.rego
├── scripts/
│ └── plan-apply.sh
├── versions.tf
├── terragrunt.hcl
└── README.md
# environments/production/backend.hcl
bucket = "tf-state-prod"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "tf-state-lock"
# environments/production/main.tf
terraform {
backend "s3" {
# Use -backend-config=backend.hcl for partial config
}
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
}
}
| Backend | State Locking | Encryption | Best For |
|---|---|---|---|
| S3 + DynamoDB | Yes (DynamoDB) | SSE-S3/KMS | AWS-native teams |
| GCS | Yes (native) | AES256/CMEK | GCP-native teams |
| AzureRM | Yes (Blob lease) | SSE | Azure-native teams |
| Terraform Cloud | Yes (native) | At rest + in transit | Team collaboration, remote runs |
| Consul | Yes (session) | TLS | Self-hosted, simple |
| local | No | No | Personal projects only |
# modules/vpc/main.tf
variable "name" {
description = "VPC name"
type = string
}
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
validation {
condition = can(cidrsubnet(var.cidr_block, 0, 0))
error_message = "Must be a valid CIDR block."
}
}
variable "azs" {
description = "Availability zones"
type = list(string)
}
variable "enable_nat_gateway" {
description = "Enable NAT gateway"
type = bool
default = true
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {}
}
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, {
Name = var.name
})
}
resource "aws_subnet" "public" {
count = length(var.azs)
vpc_id = aws_vpc.this.id
cidr_block = cidrsubnet(var.cidr_block, 8, count.index)
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index}"
Tier = "public"
})
}
resource "aws_subnet" "private" {
count = length(var.azs)
vpc_id = aws_vpc.this.id
cidr_block = cidrsubnet(var.cidr_block, 8, count.index + 100)
availability_zone = var.azs[count.index]
tags = merge(var.tags, {
Name = "${var.name}-private-${count.index}"
Tier = "private"
})
}
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.this.id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "Private subnet IDs"
value = aws_subnet.private[*].id
}
# environments/production/main.tf
module "vpc" {
source = "../../modules/vpc"
version = "1.2.0" # Module version pinning
name = "prod"
cidr_block = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
enable_nat_gateway = true
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
module "eks" {
source = "../../modules/eks"
cluster_name = "prod-cluster"
subnet_ids = module.vpc.private_subnet_ids
node_groups = {
general = {
instance_types = ["t3.medium"]
min_size = 2
max_size = 10
desired_size = 3
disk_size = 50
}
gpu = {
instance_types = ["g5.xlarge"]
min_size = 0
max_size = 5
desired_size = 0
disk_size = 200
}
}
tags = {
Environment = "production"
}
}
name: Terraform
on:
pull_request:
paths: ['terraform/**']
push:
branches: [main]
paths: ['terraform/**']
env:
TF_VERSION: 1.7.0
ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Init
run: |
terraform init \
-backend-config=environments/${{ env.ENV }}/backend.hcl \
-reconfigure
working-directory: terraform/environments/${{ env.ENV }}
- name: Validate
run: terraform validate
working-directory: terraform/environments/${{ env.ENV }}
- name: Check formatting
run: terraform fmt -check -recursive
- name: Security scan
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: terraform
plan:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Init
run: |
terraform init \
-backend-config=environments/${{ env.ENV }}/backend.hcl
working-directory: terraform/environments/${{ env.ENV }}
- name: Plan
id: plan
run: |
terraform plan \
-var-file=terraform.tfvars \
-out=tfplan \
-no-color
working-directory: terraform/environments/${{ env.ENV }}
- name: Post plan comment (PR only)
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const output = `${{ steps.plan.outputs.stdout }}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan (${process.env.ENV}) \n\`\`\`\n${output}\n\`\`\``
})
apply:
needs: plan
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production # Requires approval in GitHub Environments
steps:
- uses: actions/checkout@v4
- name: Init
run: |
terraform init \
-backend-config=environments/${{ env.ENV }}/backend.hcl
working-directory: terraform/environments/${{ env.ENV }}
- name: Apply
run: terraform apply tfplan
working-directory: terraform/environments/${{ env.ENV }}
# policies/enforce_encryption.rego
package terraform
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not resource.change.after.server_side_encryption_configuration
msg := sprintf("%s (%s) must have encryption enabled", [resource.type, resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
not resource.change.after.storage_encrypted
msg := sprintf("%s (%s) must have storage encrypted", [resource.type, resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type in ["aws_security_group", "aws_security_group_rule"]
resource.change.after.from_port == 0
resource.change.after.to_port == 65535
resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
msg := sprintf("%s (%s) has overly permissive ingress from 0.0.0.0/0", [resource.type, resource.address])
}
# Option 1: Terraform sensitive variables
variable "db_password" {
description = "Database admin password"
type = string
sensitive = true
}
# Option 2: Vault data source
data "vault_kv_secret_v2" "db" {
mount = "secret"
name = "production/database"
}
resource "aws_db_instance" "main" {
password = data.vault_kv_secret_v2.db.data["password"]
}
# Option 3: AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db" {
secret_id = "production/database"
}
resource "aws_db_instance" "main" {
password = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)["password"]
}
# Option 4: sops-encrypted vars
# Use `sops` to encrypt terraform.tfvars.sops
# Decode in pipeline: sops -d terraform.tfvars.sops > terraform.tfvars
# Import existing resources
terraform import aws_s3_bucket.my_bucket my-bucket-name
terraform import module.vpc.aws_vpc.this vpc-12345
# State list and show
terraform state list
terraform state show aws_s3_bucket.my_bucket
# State move (rename resource)
terraform state mv aws_s3_bucket.old aws_s3_bucket.new
# State removal
terraform state rm aws_s3_bucket.deleted
# State recovery
terraform state pull > backup.tfstate
# Edit manually, then:
terraform state push backup.tfstate
# Force unlock (DANGER: only when lock is orphaned)
terraform force-unlock LOCK_ID
# modules/rds/main.tf — Multi-engine RDS
variable "engine" {
description = "Database engine"
type = string
validation {
condition = contains(["postgres", "mysql", "aurora"], var.engine)
error_message = "Engine must be postgres, mysql, or aurora."
}
}
variable "performance_insights_enabled" {
description = "Enable Performance Insights"
type = bool
default = true
}
locals {
engine_port = {
postgres = 5432
mysql = 3306
aurora = 3306
}
port = local.engine_port[var.engine]
}
resource "aws_db_instance" "this" {
engine = var.engine
port = local.port
identifier = "${var.name}-${var.engine}"
performance_insights_enabled = var.performance_insights_enabled
performance_insights_retention_period = var.performance_insights_enabled ? 7 : 0
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
backup_retention_period = var.environment == "production" ? 30 : 7
}
terraform plan output must be reviewed before apply in CI.sensitive = true.terraform validate and tflint in CI before plan.ManagedBy: terraform for cost tracking.prevent_destroy lifecycle on critical resources.create_before_destroy for zero-downtime replacements.terraform plan -out=tfplan to lock the plan for apply.terraform apply without plan — uncontrolled changes.ignore_changes as a crutch — masks real drift.count instead of for_each — index-based references break on reorder.Hand off to devops/helm-patterns/SKILL.md for K8s application deployment. Hand off to devops/ansible/SKILL.md for post-provisioning configuration.