找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1526

积分

0

好友

222

主题
发表于 7 天前 | 查看: 20| 回复: 0

1.1 背景介绍

新项目启动时,搭建VPC、子网、安全组和NAT网关等基础设施往往是首要任务。过去依赖云控制台进行手动点击配置,单个环境就需要耗费半小时,若需部署开发、测试、预生产和生产四套环境,总计耗时可能超过两小时。更棘手的是,手工操作极易出错,不同环境间常存在细微的配置差异,导致在排查问题时才发现“开发环境与生产环境的子网CIDR竟然不同”。

Terraform 作为 HashiCorp 推出的基础设施即代码(IaC)工具,允许您用代码定义云资源,实现版本控制、代码复用与一键部署。本文将分享如何使用 Terraform 快速部署一套标准化的多环境 VPC 网络架构,将原本需要半天的工作压缩至30分钟内完成。

1.2 技术特点

  • 声明式定义:只需描述“期望的最终状态”,而非具体操作步骤。
  • 多云支持:同一套代码可管理 AWS、阿里云、腾讯云等多个云平台。
  • 状态管理:自动追踪资源状态,支持增量式更新与配置漂移检测。
  • 模块化:代码高度可复用,一套模板即可部署多套环境。

1.3 适用场景

  • 快速搭建:为新项目快速、一致地搭建多套隔离的网络环境。
  • 环境标准化:统一并固化现有环境配置,消除手动变更导致的配置漂移。
  • 灾备演练:一键搭建完整的灾备或演练环境。
  • 自动化测试:为网络架构变更提供可重复的自动化测试和验证流程。

1.4 环境要求

组件 版本要求 说明
Terraform 1.5+ 推荐使用最新稳定版
云账号 AWS/阿里云/腾讯云 需具备创建网络资源的足够权限
操作系统 Windows/macOS/Linux Terraform 支持全平台
Git 2.x 用于版本控制(可选但推荐)

二、详细步骤

2.1 准备工作

2.1.1 安装 Terraform
# macOS(使用 Homebrew)
brew install terraform

# Linux(Ubuntu/Debian)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# Windows(使用 Chocolatey)
choco install terraform

# 验证安装
terraform version
2.1.2 配置云账号凭证

AWS 配置

# 方法一:配置文件
mkdir -p ~/.aws
cat > ~/.aws/credentials << EOF
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY
EOF

# 方法二:环境变量
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
export AWS_DEFAULT_REGION="ap-northeast-1"

阿里云配置

# 环境变量方式
export ALICLOUD_ACCESS_KEY="YOUR_ACCESS_KEY"
export ALICLOUD_SECRET_KEY="YOUR_SECRET_KEY"
export ALICLOUD_REGION="cn-hangzhou"
2.1.3 创建项目目录结构
mkdir -p terraform-vpc-project
cd terraform-vpc-project

# 推荐的目录结构
mkdir -p modules/vpc
mkdir -p environments/{dev,staging,prod}

# 创建基本文件
touch modules/vpc/{main.tf,variables.tf,outputs.tf}
touch environments/dev/{main.tf,terraform.tfvars}
touch environments/staging/{main.tf,terraform.tfvars}
touch environments/prod/{main.tf,terraform.tfvars}

2.2 编写 VPC 模块

2.2.1 模块主配置(以 AWS 为例)
# modules/vpc/main.tf
# AWS VPC 模块 - 包含 VPC、子网、路由表、NAT 网关等

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# 获取可用区信息
data "aws_availability_zones" "available" {
  state = "available"
}

# 本地变量
locals {
  azs = slice(data.aws_availability_zones.available.names, 0, var.az_count)

  # 生成子网 CIDR
  public_subnets = [
    for i, az in local.azs :
    cidrsubnet(var.vpc_cidr, var.subnet_newbits, i)
  ]

  private_subnets = [
    for i, az in local.azs :
    cidrsubnet(var.vpc_cidr, var.subnet_newbits, i + var.az_count)
  ]

  # 通用标签
  common_tags = merge(var.tags, {
    Environment = var.environment
    ManagedBy   = "terraform"
  })
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-vpc"
  })
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-igw"
  })
}

# 公有子网
resource "aws_subnet" "public" {
  count = var.az_count
  vpc_id                  = aws_vpc.main.id
  cidr_block              = local.public_subnets[count.index]
  availability_zone       = local.azs[count.index]
  map_public_ip_on_launch = true
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-public-${local.azs[count.index]}"
    Type = "public"
  })
}

# 私有子网
resource "aws_subnet" "private" {
  count = var.az_count
  vpc_id            = aws_vpc.main.id
  cidr_block        = local.private_subnets[count.index]
  availability_zone = local.azs[count.index]
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-private-${local.azs[count.index]}"
    Type = "private"
  })
}

# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
  count  = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : var.az_count) : 0
  domain = "vpc"
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-nat-eip-${count.index + 1}"
  })
  depends_on = [aws_internet_gateway.main]
}

# NAT Gateway
resource "aws_nat_gateway" "main" {
  count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : var.az_count) : 0
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-nat-${count.index + 1}"
  })
  depends_on = [aws_internet_gateway.main]
}

# 公有子网路由表
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-public-rt"
  })
}

# 公有子网路由表关联
resource "aws_route_table_association" "public" {
  count = var.az_count
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# 私有子网路由表
resource "aws_route_table" "private" {
  count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : var.az_count) : 1
  vpc_id = aws_vpc.main.id
  dynamic "route" {
    for_each = var.enable_nat_gateway ? [1] : []
    content {
      cidr_block     = "0.0.0.0/0"
      nat_gateway_id = var.single_nat_gateway ? aws_nat_gateway.main[0].id : aws_nat_gateway.main[count.index].id
    }
  }
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-private-rt-${count.index + 1}"
  })
}

# 私有子网路由表关联
resource "aws_route_table_association" "private" {
  count = var.az_count
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = var.single_nat_gateway ? aws_route_table.private[0].id : aws_route_table.private[count.index].id
}

# 默认安全组
resource "aws_security_group" "default" {
  name        = "${var.project_name}-${var.environment}-default-sg"
  description = "Default security group for ${var.project_name} ${var.environment}"
  vpc_id      = aws_vpc.main.id

  # 允许 VPC 内部通信
  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [var.vpc_cidr]
    description = "Allow all traffic within VPC"
  }

  # 允许出站
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow all outbound traffic"
  }

  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-default-sg"
  })
}

# VPC Flow Logs(可选)
resource "aws_flow_log" "main" {
  count = var.enable_flow_logs ? 1 : 0
  iam_role_arn    = aws_iam_role.flow_logs[0].arn
  log_destination = aws_cloudwatch_log_group.flow_logs[0].arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.main.id
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-${var.environment}-flow-logs"
  })
}

resource "aws_cloudwatch_log_group" "flow_logs" {
  count = var.enable_flow_logs ? 1 : 0
  name              = "/aws/vpc-flow-logs/${var.project_name}-${var.environment}"
  retention_in_days = var.flow_logs_retention_days
  tags = local.common_tags
}

resource "aws_iam_role" "flow_logs" {
  count = var.enable_flow_logs ? 1 : 0
  name = "${var.project_name}-${var.environment}-flow-logs-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "vpc-flow-logs.amazonaws.com"
        }
      }
    ]
  })
  tags = local.common_tags
}

resource "aws_iam_role_policy" "flow_logs" {
  count = var.enable_flow_logs ? 1 : 0
  name = "${var.project_name}-${var.environment}-flow-logs-policy"
  role = aws_iam_role.flow_logs[0].id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
          "logs:DescribeLogGroups",
          "logs:DescribeLogStreams"
        ]
        Effect   = "Allow"
        Resource = "*"
      }
    ]
  })
}
2.2.2 变量定义
# modules/vpc/variables.tf
variable "project_name" {
  description = "项目名称,用于资源命名"
  type        = string
}

variable "environment" {
  description = "环境名称(dev/staging/prod)"
  type        = string
}

variable "vpc_cidr" {
  description = "VPC CIDR 块"
  type        = string
  default     = "10.0.0.0/16"
}

variable "az_count" {
  description = "使用的可用区数量"
  type        = number
  default     = 2
}

variable "subnet_newbits" {
  description = "子网 CIDR 的额外位数(用于 cidrsubnet 函数)"
  type        = number
  default     = 8
}

variable "enable_nat_gateway" {
  description = "是否创建 NAT Gateway"
  type        = bool
  default     = true
}

variable "single_nat_gateway" {
  description = "是否只使用一个 NAT Gateway(节省成本)"
  type        = bool
  default     = false
}

variable "enable_flow_logs" {
  description = "是否启用 VPC Flow Logs"
  type        = bool
  default     = false
}

variable "flow_logs_retention_days" {
  description = "Flow Logs 保留天数"
  type        = number
  default     = 30
}

variable "tags" {
  description = "额外的资源标签"
  type        = map(string)
  default     = {}
}
2.2.3 输出定义
# modules/vpc/outputs.tf
output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "vpc_cidr" {
  description = "VPC CIDR 块"
  value       = aws_vpc.main.cidr_block
}

output "public_subnet_ids" {
  description = "公有子网 ID 列表"
  value       = aws_subnet.public
  • .id } output "private_subnet_ids" {   description = "私有子网 ID 列表"   value       = aws_subnet.private
  • .id } output "public_subnet_cidrs" {   description = "公有子网 CIDR 列表"   value       = aws_subnet.public
  • .cidr_block } output "private_subnet_cidrs" {   description = "私有子网 CIDR 列表"   value       = aws_subnet.private
  • .cidr_block } output "nat_gateway_ids" {   description = "NAT Gateway ID 列表"   value       = aws_nat_gateway.main
  • .id } output "nat_gateway_public_ips" {   description = "NAT Gateway 公网 IP 列表"   value       = aws_eip.nat
  • .public_ip } output "default_security_group_id" {   description = "默认安全组 ID"   value       = aws_security_group.default.id } output "internet_gateway_id" {   description = "Internet Gateway ID"   value       = aws_internet_gateway.main.id } output "availability_zones" {   description = "使用的可用区列表"   value       = local.azs }
  • 2.3 配置各环境

    2.3.1 开发环境
    # environments/dev/main.tf
    terraform {
      required_version = ">= 1.5.0"
      # 后端配置(可选,推荐使用远程后端)
      # backend "s3" {
      #   bucket         = "your-terraform-state-bucket"
      #   key            = "dev/vpc/terraform.tfstate"
      #   region         = "ap-northeast-1"
      #   encrypt        = true
      #   dynamodb_table = "terraform-locks"
      # }
    }
    
    provider "aws" {
      region = var.aws_region
      default_tags {
        tags = {
          Project     = var.project_name
          Environment = "dev"
          ManagedBy   = "terraform"
        }
      }
    }
    
    module "vpc" {
      source = "../../modules/vpc"
    
      project_name       = var.project_name
      environment        = "dev"
      vpc_cidr           = var.vpc_cidr
      az_count           = 2
      enable_nat_gateway = true
      single_nat_gateway = true  # 开发环境用一个 NAT 节省成本
      enable_flow_logs   = false # 开发环境不需要 Flow Logs
    
      tags = {
        CostCenter = "development"
      }
    }
    
    # 输出
    output "vpc_id" {
      value = module.vpc.vpc_id
    }
    
    output "public_subnet_ids" {
      value = module.vpc.public_subnet_ids
    }
    
    output "private_subnet_ids" {
      value = module.vpc.private_subnet_ids
    }
    
    output "nat_gateway_ips" {
      value = module.vpc.nat_gateway_public_ips
    }
    # environments/dev/variables.tf
    variable "aws_region" {
      description = "AWS 区域"
      type        = string
      default     = "ap-northeast-1"
    }
    
    variable "project_name" {
      description = "项目名称"
      type        = string
    }
    
    variable "vpc_cidr" {
      description = "VPC CIDR"
      type        = string
    }
    # environments/dev/terraform.tfvars
    aws_region   = "ap-northeast-1"
    project_name = "myproject"
    vpc_cidr     = "10.1.0.0/16"
    2.3.2 生产环境
    # environments/prod/main.tf
    terraform {
      required_version = ">= 1.5.0"
      # 生产环境强烈建议使用远程后端
      # backend "s3" {
      #   bucket         = "your-terraform-state-bucket"
      #   key            = "prod/vpc/terraform.tfstate"
      #   region         = "ap-northeast-1"
      #   encrypt        = true
      #   dynamodb_table = "terraform-locks"
      # }
    }
    
    provider "aws" {
      region = var.aws_region
      default_tags {
        tags = {
          Project     = var.project_name
          Environment = "prod"
          ManagedBy   = "terraform"
        }
      }
    }
    
    module "vpc" {
      source = "../../modules/vpc"
    
      project_name       = var.project_name
      environment        = "prod"
      vpc_cidr           = var.vpc_cidr
      az_count           = 3  # 生产环境用 3 个可用区
      enable_nat_gateway = true
      single_nat_gateway = false  # 生产环境每个 AZ 一个 NAT
      enable_flow_logs   = true   # 生产环境启用 Flow Logs
      flow_logs_retention_days = 90
    
      tags = {
        CostCenter  = "production"
        Compliance  = "required"
      }
    }
    
    output "vpc_id" {
      value = module.vpc.vpc_id
    }
    
    output "public_subnet_ids" {
      value = module.vpc.public_subnet_ids
    }
    
    output "private_subnet_ids" {
      value = module.vpc.private_subnet_ids
    }
    
    output "nat_gateway_ips" {
      value = module.vpc.nat_gateway_public_ips
    }
    # environments/prod/terraform.tfvars
    aws_region   = "ap-northeast-1"
    project_name = "myproject"
    vpc_cidr     = "10.0.0.0/16"

    2.4 部署执行

    2.4.1 初始化和部署
    # 进入开发环境目录
    cd environments/dev
    
    # 初始化 Terraform
    terraform init
    
    # 检查配置格式
    terraform fmt -check
    
    # 验证配置
    terraform validate
    
    # 预览变更
    terraform plan -out=tfplan
    
    # 执行部署
    terraform apply tfplan
    
    # 查看输出
    terraform output
    2.4.2 部署生产环境
    # 进入生产环境目录
    cd environments/prod
    
    # 初始化
    terraform init
    
    # 预览(生产环境务必仔细检查)
    terraform plan -out=tfplan
    
    # 部署前再次确认
    terraform show tfplan
    
    # 执行部署
    terraform apply tfplan

    三、示例代码和配置

    3.1 完整项目结构

    terraform-vpc-project/
    ├── modules/
    │   └── vpc/
    │       ├── main.tf
    │       ├── variables.tf
    │       └── outputs.tf
    ├── environments/
    │   ├── dev/
    │   │   ├── main.tf
    │   │   ├── variables.tf
    │   │   └── terraform.tfvars
    │   ├── staging/
    │   │   ├── main.tf
    │   │   ├── variables.tf
    │   │   └── terraform.tfvars
    │   └── prod/
    │       ├── main.tf
    │       ├── variables.tf
    │       └── terraform.tfvars
    ├── scripts/
    │   └── deploy.sh
    └── README.md

    3.2 自动化部署脚本

    #!/bin/bash
    # scripts/deploy.sh
    # 多环境部署脚本
    set -e
    
    ENVIRONMENT=${1:-"dev"}
    ACTION=${2:-"plan"}
    
    # 颜色定义
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    NC='\033[0m'
    
    log_info() {
        echo -e "${GREEN}[INFO]${NC} $1"
    }
    
    log_warn() {
        echo -e "${YELLOW}[WARN]${NC} $1"
    }
    
    log_error() {
        echo -e "${RED}[ERROR]${NC} $1"
    }
    
    # 检查环境是否有效
    if [[ ! -d "environments/${ENVIRONMENT}" ]]; then
        log_error "环境 ${ENVIRONMENT} 不存在"
        echo "可用环境: $(ls environments/ | tr '\n' ' ')"
        exit 1
    fi
    
    cd "environments/${ENVIRONMENT}"
    
    log_info "当前环境: ${ENVIRONMENT}"
    log_info "执行操作: ${ACTION}"
    
    case ${ACTION} in
        init)
            log_info "初始化 Terraform..."
            terraform init
            ;;
        plan)
            log_info "生成执行计划..."
            terraform plan -out=tfplan
            ;;
        apply)
            if [[ ! -f "tfplan" ]]; then
                log_warn "未找到计划文件,先执行 plan..."
                terraform plan -out=tfplan
            fi
    
            if [[ "${ENVIRONMENT}" == "prod" ]]; then
                log_warn "即将部署到生产环境!"
                read -p "确认部署?(yes/no): " confirm
                if [[ "${confirm}" != "yes" ]]; then
                    log_info "部署已取消"
                    exit 0
                fi
            fi
    
            log_info "执行部署..."
            terraform apply tfplan
            rm -f tfplan
            ;;
        destroy)
            if [[ "${ENVIRONMENT}" == "prod" ]]; then
                log_error "禁止销毁生产环境!"
                exit 1
            fi
    
            log_warn "即将销毁 ${ENVIRONMENT} 环境的所有资源!"
            read -p "确认销毁?(yes/no): " confirm
            if [[ "${confirm}" == "yes" ]]; then
                terraform destroy
            else
                log_info "操作已取消"
            fi
            ;;
        output)
            terraform output
            ;;
        *)
            echo "用法: $0 <environment> <action>"
            echo "环境: dev, staging, prod"
            echo "操作: init, plan, apply, destroy, output"
            exit 1
            ;;
    esac
    
    log_info "操作完成"

    3.3 阿里云版本模块

    # modules/vpc-alicloud/main.tf
    # 阿里云 VPC 模块
    terraform {
      required_providers {
        alicloud = {
          source  = "aliyun/alicloud"
          version = "~> 1.200"
        }
      }
    }
    
    data "alicloud_zones" "available" {
      available_resource_creation = "VSwitch"
    }
    
    locals {
      azs = slice(data.alicloud_zones.available.zones
  • .id, 0, var.az_count)   common_tags = merge(var.tags, {     Environment = var.environment     ManagedBy   = "terraform"   }) } # VPC resource "alicloud_vpc" "main" {   vpc_name    = "${var.project_name}-${var.environment}-vpc"   cidr_block  = var.vpc_cidr   description = "VPC for ${var.project_name} ${var.environment}"   tags = local.common_tags } # 公有交换机(VSwitch) resource "alicloud_vswitch" "public" {   count = var.az_count   vpc_id       = alicloud_vpc.main.id   cidr_block   = cidrsubnet(var.vpc_cidr, var.subnet_newbits, count.index)   zone_id      = local.azs[count.index]   vswitch_name = "${var.project_name}-${var.environment}-public-${local.azs[count.index]}"   tags = merge(local.common_tags, {     Type = "public"   }) } # 私有交换机 resource "alicloud_vswitch" "private" {   count = var.az_count   vpc_id       = alicloud_vpc.main.id   cidr_block   = cidrsubnet(var.vpc_cidr, var.subnet_newbits, count.index + var.az_count)   zone_id      = local.azs[count.index]   vswitch_name = "${var.project_name}-${var.environment}-private-${local.azs[count.index]}"   tags = merge(local.common_tags, {     Type = "private"   }) } # NAT 网关 resource "alicloud_nat_gateway" "main" {   count = var.enable_nat_gateway ? 1 : 0   vpc_id               = alicloud_vpc.main.id   nat_gateway_name     = "${var.project_name}-${var.environment}-nat"   payment_type         = "PayAsYouGo"   vswitch_id           = alicloud_vswitch.public[0].id   nat_type             = "Enhanced"   internet_charge_type = "PayByLcu"   tags = local.common_tags } # EIP resource "alicloud_eip_address" "nat" {   count = var.enable_nat_gateway ? 1 : 0   address_name         = "${var.project_name}-${var.environment}-nat-eip"   bandwidth            = 100   internet_charge_type = "PayByTraffic"   tags = local.common_tags } # EIP 绑定到 NAT resource "alicloud_eip_association" "nat" {   count = var.enable_nat_gateway ? 1 : 0   allocation_id = alicloud_eip_address.nat[0].id   instance_id   = alicloud_nat_gateway.main[0].id   instance_type = "Nat" } # SNAT 条目(让私有子网访问公网) resource "alicloud_snat_entry" "main" {   count = var.enable_nat_gateway ? var.az_count : 0   snat_table_id     = alicloud_nat_gateway.main[0].snat_table_ids   source_vswitch_id = alicloud_vswitch.private[count.index].id   snat_ip           = alicloud_eip_address.nat[0].ip_address   depends_on = [alicloud_eip_association.nat] } # 安全组 resource "alicloud_security_group" "default" {   name        = "${var.project_name}-${var.environment}-default-sg"   description = "Default security group"   vpc_id      = alicloud_vpc.main.id   tags = local.common_tags } # 安全组规则 resource "alicloud_security_group_rule" "allow_vpc_ingress" {   type              = "ingress"   ip_protocol       = "all"   nic_type          = "intranet"   policy            = "accept"   port_range        = "-1/-1"   priority          = 1   security_group_id = alicloud_security_group.default.id   cidr_ip           = var.vpc_cidr   description       = "Allow all traffic within VPC" } resource "alicloud_security_group_rule" "allow_all_egress" {   type              = "egress"   ip_protocol       = "all"   nic_type          = "intranet"   policy            = "accept"   port_range        = "-1/-1"   priority          = 1   security_group_id = alicloud_security_group.default.id   cidr_ip           = "0.0.0.0/0"   description       = "Allow all outbound traffic" }
  • 四、最佳实践和注意事项

    4.1 最佳实践

    4.1.1 状态管理

    生产环境务必使用远程后端存储 Terraform 状态:

    # backend.tf - S3 后端配置
    terraform {
      backend "s3" {
        bucket         = "your-company-terraform-state"
        key            = "prod/vpc/terraform.tfstate"
        region         = "ap-northeast-1"
        encrypt        = true
        dynamodb_table = "terraform-state-locks"  # 防止并发操作
        # 可选:使用 assume role
        # role_arn       = "arn:aws:iam::ACCOUNT_ID:role/TerraformRole"
      }
    }
    # 创建 S3 bucket 和 DynamoDB table(只需执行一次)
    aws s3 mb s3://your-company-terraform-state --region ap-northeast-1
    aws s3api put-bucket-versioning \
        --bucket your-company-terraform-state \
        --versioning-configuration Status=Enabled
    aws dynamodb create-table \
        --table-name terraform-state-locks \
        --attribute-definitions AttributeName=LockID,AttributeType=S \
        --key-schema AttributeName=LockID,KeyType=HASH \
        --billing-mode PAY_PER_REQUEST
    4.1.2 敏感信息处理
    # 使用 sensitive 标记敏感输出
    output "nat_gateway_ips" {
      value     = module.vpc.nat_gateway_public_ips
      sensitive = false  # IP 可以公开
    }
    
    # 不要在代码中硬编码凭证
    # 错误示例:
    # provider "aws" {
    #   access_key = "AKIAXXXXXXXX"
    #   secret_key = "xxxxxxxx"
    # }
    # 正确做法:使用环境变量或 AWS 配置文件
    4.1.3 代码规范
    # 格式化代码
    terraform fmt -recursive
    
    # 验证配置
    terraform validate
    
    # 使用 tflint 检查最佳实践
    tflint --init
    tflint
    
    # 使用 tfsec 安全扫描
    tfsec .

    4.2 注意事项

    4.2.1 常见错误
    错误现象 原因分析 解决方案
    Error: No valid credential sources found 未配置云凭证 设置环境变量或配置文件
    Error: Error locking state 状态文件被锁定 检查是否有其他操作进行中,或手动解锁
    Error: CIDR block overlaps CIDR 冲突 检查 VPC 和子网的 CIDR 规划
    Error: limit exceeded 达到资源配额限制 申请提高配额或清理无用资源
    4.2.2 生产环境检查清单
    • [ ] 使用远程后端存储状态
    • [ ] 启用状态文件加密
    • [ ] 配置 DynamoDB 锁表
    • [ ] 代码已通过 terraform validate
    • [ ] 代码已通过安全扫描(tfsec)
    • [ ] plan 输出已经仔细审查
    • [ ] 有回滚方案
    • [ ] 通知相关团队
    4.2.3 CIDR 规划建议
    公司 VPC CIDR 规划示例:
    10.0.0.0/8 - 公司总网段
    ├── 10.0.0.0/16 - 生产环境
    │   ├── 10.0.0.0/24   - 公有子网 AZ-a
    │   ├── 10.0.1.0/24   - 公有子网 AZ-b
    │   ├── 10.0.2.0/24   - 公有子网 AZ-c
    │   ├── 10.0.10.0/24  - 私有子网 AZ-a
    │   ├── 10.0.11.0/24  - 私有子网 AZ-b
    │   └── 10.0.12.0/24  - 私有子网 AZ-c
    ├── 10.1.0.0/16 - 开发环境
    ├── 10.2.0.0/16 - 测试环境
    └── 10.3.0.0/16 - 预生产环境
    
    注意:
    - 不同环境使用不同的第二段
    - 预留足够的地址空间用于扩展
    - 避免与办公网络、VPN 网段冲突

    五、故障排查和监控

    5.1 故障排查

    5.1.1 常见问题排查

    问题一:terraform init 失败

    # 检查网络连接
    curl -I https://registry.terraform.io
    
    # 如果在国内,配置镜像
    # 创建 ~/.terraformrc
    cat > ~/.terraformrc << EOF
    provider_installation {
      network_mirror {
        url = "https://mirrors.aliyun.com/terraform/"
      }
    }
    EOF
    
    # 或者使用代理
    export HTTPS_PROXY="http://proxy:8080"
    terraform init

    问题二:状态文件损坏

    # 从远程后端拉取最新状态
    terraform state pull > terraform.tfstate.backup
    
    # 如果状态与实际不符,导入资源
    terraform import aws_vpc.main vpc-xxxxxxxx
    
    # 刷新状态
    terraform refresh

    问题三:资源漂移检测

    # 检测实际资源与状态的差异
    terraform plan -refresh-only
    
    # 如果有漂移,决定是更新状态还是恢复资源
    # 更新状态到实际值
    terraform apply -refresh-only
    # 或者重新应用配置恢复资源
    terraform apply
    5.1.2 调试模式
    # 启用详细日志
    export TF_LOG=DEBUG
    export TF_LOG_PATH=./terraform.log
    terraform plan
    
    # 查看日志
    tail -f terraform.log
    
    # 只针对特定 provider 调试
    export TF_LOG_PROVIDER=DEBUG

    5.2 成本监控

    5.2.1 使用 Infracost 估算成本
    # 安装 Infracost
    brew install infracost
    
    # 注册获取 API key
    infracost auth login
    
    # 估算成本
    infracost breakdown --path .
    
    # 对比不同配置的成本
    infracost diff --path . --compare-to terraform.tfstate
    
    # 生成 HTML 报告
    infracost output --path /tmp/infracost.json --format html > cost-report.html
    5.2.2 成本优化建议
    # 开发环境使用单个 NAT Gateway 节省成本
    module "vpc" {
      source = "../../modules/vpc"
      # ...
      enable_nat_gateway = true
      single_nat_gateway = true  # 每月节省约 $30-50/NAT
    }
    
    # 或者开发环境完全不用 NAT Gateway
    # 私有子网的实例通过 VPC Endpoint 访问 AWS 服务
    module "vpc" {
      source = "../../modules/vpc"
      enable_nat_gateway = false
    }
    
    # 添加 VPC Endpoints(更便宜且更安全)
    resource "aws_vpc_endpoint" "s3" {
      vpc_id            = module.vpc.vpc_id
      service_name      = "com.amazonaws.${var.region}.s3"
      vpc_endpoint_type = "Gateway"
      route_table_ids   = module.vpc.private_route_table_ids
    }

    5.3 CI/CD 集成

    # .github/workflows/terraform.yml
    name: Terraform CI/CD
    on:
      push:
        branches: [main]
        paths:
          - 'environments/**'
          - 'modules/**'
      pull_request:
        branches: [main]
    
    env:
      TF_VERSION: '1.5.0'
    
    jobs:
      validate:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Setup Terraform
            uses: hashicorp/setup-terraform@v2
            with:
              terraform_version: ${{ env.TF_VERSION }}
          - name: Terraform Format Check
            run: terraform fmt -check -recursive
          - name: Terraform Validate
            run: |
              for env in environments/*/; do
                echo "Validating $env"
                cd $env
                terraform init -backend=false
                terraform validate
                cd ../..
              done
    
      security-scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Run tfsec
            uses: aquasecurity/tfsec-action@v1.0.0
    
      plan:
        needs: [validate, security-scan]
        runs-on: ubuntu-latest
        if: github.event_name == 'pull_request'
        strategy:
          matrix:
            environment: [dev, staging]
        steps:
          - uses: actions/checkout@v3
          - name: Setup Terraform
            uses: hashicorp/setup-terraform@v2
            with:
              terraform_version: ${{ env.TF_VERSION }}
          - name: Configure AWS Credentials
            uses: aws-actions/configure-aws-credentials@v2
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: ap-northeast-1
          - name: Terraform Plan
            run: |
              cd environments/${{ matrix.environment }}
              terraform init
              terraform plan -no-color -out=tfplan
          - name: Comment Plan on PR
            uses: actions/github-script@v6
            with:
              script: |
                // 将 plan 输出添加到 PR 评论

    六、总结

    6.1 技术要点回顾

    • 模块化设计:一套核心代码支持多环境部署,极大减少了重复劳动。
    • 状态管理:务必为生产环境使用远程后端并启用锁机制,这是保障团队协作安全的基础。
    • 渐进式部署:遵循从开发(dev)到预生产(staging)再到生产(prod)的部署流程,降低风险。
    • 成本意识:在开发测试环境采用精简配置(如单NAT网关),在生产环境则保障高可用性,实现成本与稳定性的平衡。

    6.2 进阶学习方向

    1. Terraform 高级特性

      • 学习资源:HashiCorp Learn 官方教程
      • 实践建议:深入学习 workspace、data sources、dynamic blocks 和 provisioners。
    2. GitOps 与 DevOps 流程集成

      • 学习资源:Terraform Cloud、Atlantis 等工具文档。
      • 实践建议:实现代码提交(PR)时自动触发 terraform plan,合并后自动执行 terraform apply
    3. 多云与混合云管理

      • 学习资源:Terraform 针对 AWS、阿里云、Azure 等各云服务商的 Provider 文档。
      • 实践建议:尝试使用同一套 Terraform 代码管理跨越不同云服务商的基础设施。

    6.3 参考资料

    附录

    A. 命令速查表

    # 初始化
    terraform init
    terraform init -upgrade  # 升级 provider
    
    # 格式化
    terraform fmt
    terraform fmt -recursive
    
    # 验证
    terraform validate
    
    # 计划
    terraform plan
    terraform plan -out=tfplan
    terraform plan -target=aws_vpc.main  # 只计划特定资源
    
    # 应用
    terraform apply
    terraform apply tfplan
    terraform apply -auto-approve  # 自动确认(谨慎使用)
    
    # 销毁
    terraform destroy
    terraform destroy -target=aws_vpc.main  # 只销毁特定资源
    
    # 状态管理
    terraform state list
    terraform state show aws_vpc.main
    terraform state mv aws_vpc.main aws_vpc.new_name
    terraform state rm aws_vpc.main  # 从状态中移除(不删除实际资源)
    
    # 导入
    terraform import aws_vpc.main vpc-xxxxxxxx
    
    # 输出
    terraform output
    terraform output -json

    B. 资源命名规范

    {project}-{environment}-{resource_type}-{purpose}
    示例:
    myproject-prod-vpc
    myproject-prod-subnet-public-a
    myproject-prod-nat-1
    myproject-prod-sg-default
    myproject-prod-igw

    C. 术语表

    术语 英文 解释
    基础设施即代码 Infrastructure as Code (IaC) 用代码定义和管理基础设施的方法论
    状态文件 State File Terraform 用于记录和管理资源状态的JSON文件
    提供者 Provider 与特定云平台或服务API进行交互的Terraform插件
    模块 Module 封装好的、可复用的Terraform配置单元
    数据源 Data Source 用于从外部(如云平台)查询并获取信息的配置块
    输出 Output 用于将模块内资源的属性值暴露给外部的配置
    后端 Backend 定义Terraform状态文件存储位置和方式的配置
    漂移 Drift 实际基础设施的配置与Terraform代码中定义的状态不一致的情况



    上一篇:多活架构与跨地域容灾实战:详解两地三中心方案的设计与实现
    下一篇:Vue3开源CRM系统开发指南:基于NestJS与TDesign的全流程可视化客户管理
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2025-12-24 21:13 , Processed in 0.325908 second(s), 39 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2025 云栈社区.

    快速回复 返回顶部 返回列表