生产级完整案例:高可用WordPress网站

这是一个整合了你之前学过的所有知识点的完整生产级案例,将部署一个真正可以对外提供服务的高可用WordPress网站。通过这个案例,你会把之前零散的知识点串联起来,真正理解如何用Terraform构建一个完整的企业级应用。

一、整体架构设计

我们将部署一个标准的三层架构,所有核心服务都部署在私网子网,只有负载均衡器暴露在公网:

互联网
  ↓
ALB应用负载均衡器(跨两个可用区)
  ↓
┌─────────────────────────────────────────────────────────────┐
│                      VPC (10.0.0.0/16)                      │
│  ┌─────────────────────┐        ┌─────────────────────┐     │
│  │    可用区eu-west-1a │        │    可用区eu-west-1b │     │
│  │ ┌─────────────────┐ │        │ ┌─────────────────┐ │     │
│  │ │   公网子网1     │ │        │ │   公网子网2     │ │     │
│  │ │ 10.0.1.0/24     │ │        │ │ 10.0.2.0/24     │ │     │
│  │ │                 │ │        │ │                 │ │     │
│  │ │ NAT网关1        │ │        │ │ NAT网关2        │ │     │
│  │ └─────────────────┘ │        │ └─────────────────┘ │     │
│  │          ↓          │        │          ↓          │     │
│  │ ┌─────────────────┐ │        │ ┌─────────────────┐ │     │
│  │ │   应用子网1     │ │        │ │   应用子网2     │ │     │
│  │ │ 10.0.4.0/24     │ │        │ │ 10.0.5.0/24     │ │     │
│  │ │                 │ │        │ │                 │ │     │
│  │ │ WordPress实例1  │ │        │ │ WordPress实例2  │ │     │
│  │ │ (Auto Scaling)  │ │        │ │ (Auto Scaling)  │ │     │
│  │ └─────────────────┘ │        │ └─────────────────┘ │     │
│  │          ↓          │        │          ↓          │     │
│  │ ┌─────────────────┐ │        │ ┌─────────────────┐ │     │
│  │ │   数据子网1     │ │        │ │   数据子网2     │ │     │
│  │ │ 10.0.8.0/24     │ │        │ │ 10.0.9.0/24     │ │     │
│  │ │                 │ │        │ │                 │ │     │
│  │ │ RDS主节点       │ │        │ │ RDS备用节点     │ │     │
│  │ │ (多可用区)      │ │        │ │ (自动故障转移)  │ │     │
│  │ └─────────────────┘ │        │ └─────────────────┘ │     │
│  └─────────────────────┘        └─────────────────────┘     │
└─────────────────────────────────────────────────────────────┘

核心特性
✅ 完全网络分层:公网层、应用层、数据层
✅ 双NAT网关高可用:每个可用区一个NAT网关
✅ 应用层自动扩缩容:根据CPU使用率自动调整实例数量
✅ 数据库多可用区:自动故障转移,RTO约1-2分钟
✅ 应用层无状态:所有数据存在数据库,实例可以随时创建和销毁
✅ 最小权限安全:所有核心服务都在私网,只有ALB暴露在公网


二、完整文件结构

wordpress-terraform/
├── versions.tf         # 版本约束
├── provider.tf         # AWS提供商配置
├── variables.tf        # 变量定义
├── outputs.tf          # 输出值
├── locals.tf           # 本地变量
├── vpc.tf              # VPC网络架构
├── nat.tf              # NAT网关
├── securitygroup.tf    # 安全组
├── rds.tf              # RDS数据库
├── alb.tf              # ALB应用负载均衡器
├── autoscaling.tf      # Auto Scaling自动扩缩容
└── userdata.sh         # CloudInit初始化脚本

三、逐文件详细解析

1. versions.tf(版本约束)

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

2. provider.tf(AWS配置)

provider "aws" {
  region = var.aws_region
}

3. variables.tf(变量定义)

variable "aws_region" {
  description = "AWS区域"
  type        = string
  default     = "eu-west-1"
}

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

variable "public_subnet_cidrs" {
  description = "公网子网CIDR列表"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "app_subnet_cidrs" {
  description = "应用子网CIDR列表"
  type        = list(string)
  default     = ["10.0.4.0/24", "10.0.5.0/24"]
}

variable "db_subnet_cidrs" {
  description = "数据库子网CIDR列表"
  type        = list(string)
  default     = ["10.0.8.0/24", "10.0.9.0/24"]
}

variable "db_username" {
  description = "数据库管理员用户名"
  type        = string
  default     = "wpadmin"
}

variable "db_password" {
  description = "数据库管理员密码"
  type        = string
  sensitive   = true  # 标记为敏感,不会在输出中显示
}

variable "instance_type" {
  description = "应用服务器实例类型"
  type        = string
  default     = "t3.micro"
}

variable "min_instances" {
  description = "最小应用服务器数量"
  type        = number
  default     = 2
}

variable "max_instances" {
  description = "最大应用服务器数量"
  type        = number
  default     = 4
}

4. locals.tf(本地变量)

locals {
  # 公共标签,所有资源都使用
  common_tags = {
    Project     = "wordpress"
    Environment = "production"
    ManagedBy   = "terraform"
  }

  # 可用区列表
  availability_zones = ["eu-west-1a", "eu-west-1b"]
}

5. vpc.tf(VPC网络架构)

# 创建VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = merge(local.common_tags, {
    Name = "wordpress-vpc"
  })
}

# 创建公网子网
resource "aws_subnet" "public" {
  count                   = length(var.public_subnet_cidrs)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = local.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = merge(local.common_tags, {
    Name = "wordpress-public-${count.index + 1}"
  })
}

# 创建应用子网(私网)
resource "aws_subnet" "app" {
  count                   = length(var.app_subnet_cidrs)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.app_subnet_cidrs[count.index]
  availability_zone       = local.availability_zones[count.index]
  map_public_ip_on_launch = false

  tags = merge(local.common_tags, {
    Name = "wordpress-app-${count.index + 1}"
  })
}

# 创建数据库子网(私网)
resource "aws_subnet" "db" {
  count                   = length(var.db_subnet_cidrs)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.db_subnet_cidrs[count.index]
  availability_zone       = local.availability_zones[count.index]
  map_public_ip_on_launch = false

  tags = merge(local.common_tags, {
    Name = "wordpress-db-${count.index + 1}"
  })
}

# 创建Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = merge(local.common_tags, {
    Name = "wordpress-igw"
  })
}

# 公网路由表
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 = "wordpress-public-rt"
  })
}

# 关联公网子网到公网路由表
resource "aws_route_table_association" "public" {
  count           = length(aws_subnet.public)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

6. nat.tf(双NAT网关高可用)

# 为每个可用区创建一个EIP和NAT网关
resource "aws_eip" "nat" {
  count = length(aws_subnet.public)
  vpc   = true

  tags = merge(local.common_tags, {
    Name = "wordpress-nat-eip-${count.index + 1}"
  })
}

resource "aws_nat_gateway" "main" {
  count         = length(aws_subnet.public)
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
  depends_on    = [aws_internet_gateway.main]

  tags = merge(local.common_tags, {
    Name = "wordpress-nat-${count.index + 1}"
  })
}

# 应用子网路由表(指向本可用区的NAT网关)
resource "aws_route_table" "app" {
  count  = length(aws_subnet.app)
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main[count.index].id
  }

  tags = merge(local.common_tags, {
    Name = "wordpress-app-rt-${count.index + 1}"
  })
}

# 关联应用子网到应用路由表
resource "aws_route_table_association" "app" {
  count           = length(aws_subnet.app)
  subnet_id      = aws_subnet.app[count.index].id
  route_table_id = aws_route_table.app[count.index].id
}

# 数据库子网路由表(指向本可用区的NAT网关)
resource "aws_route_table" "db" {
  count  = length(aws_subnet.db)
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main[count.index].id
  }

  tags = merge(local.common_tags, {
    Name = "wordpress-db-rt-${count.index + 1}"
  })
}

# 关联数据库子网到数据库路由表
resource "aws_route_table_association" "db" {
  count           = length(aws_subnet.db)
  subnet_id      = aws_subnet.db[count.index].id
  route_table_id = aws_route_table.db[count.index].id
}

关键设计:每个可用区的应用子网和数据库子网都指向本可用区的NAT网关。这样即使一个可用区故障,另一个可用区的流量仍然可以正常上网。

7. securitygroup.tf(安全组,最小权限原则)

# ALB安全组:只允许80端口从互联网访问
resource "aws_security_group" "alb" {
  name        = "wordpress-alb"
  description = "Allow HTTP access from internet"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.common_tags
}

# 应用服务器安全组:只允许来自ALB的80端口和SSH访问
resource "aws_security_group" "app" {
  name        = "wordpress-app"
  description = "Allow HTTP from ALB and SSH from anywhere"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # 生产环境请改为你的办公IP
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.common_tags
}

# 数据库安全组:只允许来自应用服务器的3306端口访问
resource "aws_security_group" "db" {
  name        = "wordpress-db"
  description = "Allow MySQL access from app servers"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.common_tags
}

安全设计亮点

  • 数据库的3306端口完全不开放给互联网,只允许来自应用服务器安全组的流量
  • 应用服务器的80端口只允许来自ALB的流量,用户无法直接访问应用服务器
  • 所有流量必须经过ALB转发,形成了一个安全的多层防御体系

8. rds.tf(RDS数据库多可用区)

# 创建RDS子网组
resource "aws_db_subnet_group" "wordpress" {
  name       = "wordpress-db-subnet-group"
  subnet_ids = aws_subnet.db[*].id

  tags = local.common_tags
}

# 创建RDS参数组
resource "aws_db_parameter_group" "wordpress" {
  name   = "wordpress-parameter-group"
  family = "mysql8.0"

  parameter {
    name  = "character_set_server"
    value = "utf8mb4"
  }

  parameter {
    name  = "collation_server"
    value = "utf8mb4_unicode_ci"
  }

  tags = local.common_tags
}

# 创建RDS数据库实例(多可用区)
resource "aws_db_instance" "wordpress" {
  identifier              = "wordpress-db"
  engine                  = "mysql"
  engine_version          = "8.0"
  instance_class          = "db.t3.small"
  allocated_storage       = 20
  max_allocated_storage   = 100
  db_name                 = "wordpress"
  username                = var.db_username
  password                = var.db_password
  db_subnet_group_name    = aws_db_subnet_group.wordpress.name
  parameter_group_name    = aws_db_parameter_group.wordpress.name
  vpc_security_group_ids  = [aws_security_group.db.id]
  multi_az                = true  # 启用多可用区高可用
  storage_type            = "gp3"
  storage_encrypted       = true  # 启用存储加密
  backup_retention_period = 7
  skip_final_snapshot     = false
  final_snapshot_identifier = "wordpress-db-final-snapshot"

  tags = local.common_tags
}

生产级配置

  • 启用多可用区:自动故障转移,保证数据库高可用
  • 启用存储加密:加密数据库静态数据
  • 启用自动存储扩展:当存储使用率达到80%时自动扩容
  • 保留7天自动备份:可以恢复到过去7天内的任意时间点
  • 删除时创建最终快照:防止误删数据

9. alb.tf(ALB应用负载均衡器)

# 创建ALB
resource "aws_lb" "wordpress" {
  name               = "wordpress-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id

  enable_deletion_protection = false

  tags = local.common_tags
}

# 创建目标组
resource "aws_lb_target_group" "wordpress" {
  name     = "wordpress-target-group"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
  target_type = "instance"

  health_check {
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }

  tags = local.common_tags
}

# 创建监听器
resource "aws_lb_listener" "wordpress" {
  load_balancer_arn = aws_lb.wordpress.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.wordpress.arn
  }
}

10. autoscaling.tf(Auto Scaling自动扩缩容)

# 创建启动模板
resource "aws_launch_template" "wordpress" {
  name_prefix   = "wordpress-launch-template"
  image_id      = "ami-0c55b159cbfafe1f0"  # Ubuntu 20.04 LTS (eu-west-1)
  instance_type = var.instance_type
  key_name      = "mykey"  # 替换为你的密钥对名称

  security_group_ids = [aws_security_group.app.id]

  # CloudInit初始化脚本
  user_data = base64encode(templatefile("userdata.sh", {
    DB_HOST     = aws_db_instance.wordpress.endpoint
    DB_NAME     = aws_db_instance.wordpress.db_name
    DB_USER     = aws_db_instance.wordpress.username
    DB_PASSWORD = aws_db_instance.wordpress.password
  }))

  tag_specifications {
    resource_type = "instance"
    tags = merge(local.common_tags, {
      Name = "wordpress-app-server"
    })
  }
}

# 创建Auto Scaling组
resource "aws_autoscaling_group" "wordpress" {
  name                      = "wordpress-asg"
  min_size                  = var.min_instances
  max_size                  = var.max_instances
  desired_capacity          = var.min_instances
  vpc_zone_identifier       = aws_subnet.app[*].id
  health_check_grace_period = 300
  health_check_type         = "ELB"

  launch_template {
    id      = aws_launch_template.wordpress.id
    version = "$Latest"
  }

  target_group_arns = [aws_lb_target_group.wordpress.arn]

  tag {
    key                 = "Name"
    value               = "wordpress-app-server"
    propagate_at_launch = true
  }
}

# 目标跟踪扩展策略:将平均CPU使用率维持在70%
resource "aws_autoscaling_policy" "cpu_tracking" {
  name                   = "wordpress-cpu-tracking"
  autoscaling_group_name = aws_autoscaling_group.wordpress.name
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = 70.0
  }
}

关键改进

  • 使用启动模板代替启动配置(AWS推荐)
  • 使用目标跟踪扩展策略代替简单扩展策略,更智能
  • 自动将数据库连接信息注入到User Data中,不需要手动配置

11. userdata.sh(CloudInit初始化脚本)

#!/bin/bash
set -ex

# 更新系统
apt-get update
apt-get upgrade -y

# 安装Apache、PHP和MySQL客户端
apt-get install -y apache2 php php-mysql mysql-client

# 下载并解压WordPress
cd /var/www/html
rm index.html
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
mv wordpress/* .
rm -rf wordpress latest.tar.gz

# 配置wp-config.php
cp wp-config-sample.php wp-config.php
sed -i "s/database_name_here/${DB_NAME}/g" wp-config.php
sed -i "s/username_here/${DB_USER}/g" wp-config.php
sed -i "s/password_here/${DB_PASSWORD}/g" wp-config.php
sed -i "s/localhost/${DB_HOST}/g" wp-config.php

# 设置文件权限
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html

# 启用Apache重写模块
a2enmod rewrite
systemctl restart apache2

作用:实例启动时自动完成所有软件安装和WordPress配置,不需要手动操作。新创建的实例会自动加入集群并开始处理流量。

12. outputs.tf(输出关键信息)

output "alb_dns_name" {
  description = "ALB DNS名称"
  value       = aws_lb.wordpress.dns_name
}

output "db_endpoint" {
  description = "RDS数据库端点"
  value       = aws_db_instance.wordpress.endpoint
  sensitive   = true
}

四、完整部署步骤

1. 准备工作

# 生成SSH密钥对
ssh-keygen -t rsa -b 2048 -f mykey -N ""

# 导入密钥对到AWS
aws ec2 import-key-pair --key-name mykey --public-key-material file://mykey.pub

# 设置数据库密码环境变量
export TF_VAR_db_password="your-strong-password-123"

2. 部署所有资源

terraform init
terraform plan
terraform apply

⚠️ 注意:RDS创建需要10-15分钟,请耐心等待。

3. 访问WordPress

部署完成后,Terraform会输出ALB的DNS名称:

Outputs:

alb_dns_name = "wordpress-alb-123456789.eu-west-1.elb.amazonaws.com"

在浏览器中输入这个地址,就可以看到WordPress的安装界面,按照提示完成安装即可。

4. 验证高可用

# 查看当前运行的实例
aws ec2 describe-instances --filters "Name=tag:Name,Values=wordpress-app-server" --query "Reservations[*].Instances[*].{ID:InstanceId, AZ:Placement.AvailabilityZone}"

# 手动终止一个实例(模拟故障)
aws ec2 terminate-instances --instance-ids <实例ID>

# 继续访问ALB,服务不会中断
# 大约5分钟后,Auto Scaling会自动创建一个新的实例替换被终止的实例

五、生产环境进一步优化

这个案例已经是一个非常不错的生产级架构,但还可以做以下优化:

  1. 配置HTTPS:使用ACM证书配置HTTPS,将HTTP流量重定向到HTTPS
  2. 添加CDN:使用CloudFront加速静态资源
  3. 添加Redis缓存:使用ElastiCache Redis加速数据库查询
  4. 配置S3存储:将WordPress的媒体文件存储在S3中
  5. 配置监控和告警:使用CloudWatch监控所有资源的性能和健康状态
  6. 配置备份策略:定期备份数据库和应用数据
  7. 启用WAF:使用AWS WAF防护Web攻击

六、总结

这个案例整合了你之前学过的所有知识点:

  • VPC网络分层与高可用设计
  • NAT网关与路由表配置
  • 安全组最小权限原则
  • RDS数据库多可用区部署
  • ALB负载均衡器与健康检查
  • Auto Scaling自动扩缩容
  • CloudInit自动初始化

通过这个案例,你应该已经掌握了如何用Terraform构建一个完整的生产级Web应用。这是一个非常重要的里程碑,意味着你已经可以独立完成企业级基础设施的设计和部署。


生产级通用高可用网站完整案例(全链路HTTPS+CDN+WAF)

我给你做一个最通用、最完善的现代Web应用架构,不绑定任何特定CMS,你可以直接把自己的前端/后端代码放进去用。这个架构是目前全球互联网公司的标准配置,包含了所有生产环境必备的组件:全链路HTTPS、CloudFront CDN加速、WAF Web防火墙、静态资源分离、自动扩缩容、多可用区高可用

一、最终生产级架构(四层架构)

互联网
  ↓
CloudFront CDN(全球加速 + WAF防护)
  ↓
ALB应用负载均衡器(HTTPS终结 + 流量分发)
  ↓
┌─────────────────────────────────────────────────────────────┐
│                      VPC (10.0.0.0/16)                      │
│  ┌─────────────────────┐        ┌─────────────────────┐     │
│  │    可用区eu-west-1a │        │    可用区eu-west-1b │     │
│  │ ┌─────────────────┐ │        │ ┌─────────────────┐ │     │
│  │ │   公网子网1     │ │        │ │   公网子网2     │ │     │
│  │ │ 10.0.1.0/24     │ │        │ │ 10.0.2.0/24     │ │     │
│  │ │                 │ │        │ │                 │ │     │
│  │ │ NAT网关1        │ │        │ │ NAT网关2        │ │     │
│  │ │ ALB节点1        │ │        │ │ ALB节点2        │ │     │
│  │ └─────────────────┘ │        │ └─────────────────┘ │     │
│  │          ↓          │        │          ↓          │     │
│  │ ┌─────────────────┐ │        │ ┌─────────────────┐ │     │
│  │ │   应用子网1     │ │        │ │   应用子网2     │ │     │
│  │ │ 10.0.4.0/24     │ │        │ │ 10.0.5.0/24     │ │     │
│  │ │                 │ │        │ │                 │ │     │
│  │ │ 应用实例1       │ │        │ │ 应用实例2       │ │     │
│  │ │ (Auto Scaling)  │ │        │ │ (Auto Scaling)  │ │     │
│  │ └─────────────────┘ │        │ └─────────────────┘ │     │
│  └─────────────────────┘        └─────────────────────┘     │
└─────────────────────────────────────────────────────────────┘
  ↓
S3存储桶(静态资源:JS/CSS/图片/视频)

核心生产级特性
全链路HTTPS:从用户浏览器到CDN到ALB全程加密
CloudFront全球CDN:静态资源全球加速,降低源站压力
AWS WAF防护:自动阻挡SQL注入、XSS、DDoS等常见Web攻击
静态动态分离:静态资源走S3+CDN,动态请求走ALB+应用服务器
应用层无状态:实例可以随时创建销毁,支持无限水平扩展
基于请求数的自动扩缩容:比CPU使用率更适合Web应用
零公网IP:所有应用服务器都在私网,完全不暴露给互联网
优雅关闭与滚动更新:更新应用时用户完全无感知
完整监控告警:自动监控请求数、延迟、错误率等核心指标


二、完整文件结构

production-website-terraform/
├── versions.tf         # 版本约束
├── provider.tf         # AWS提供商配置(双区域:eu-west-1 + us-east-1)
├── variables.tf        # 变量定义
├── outputs.tf          # 输出值
├── locals.tf           # 本地变量
├── vpc.tf              # VPC网络架构(双NAT高可用)
├── securitygroup.tf    # 安全组(最小权限)
├── acm.tf              # ACM SSL证书(HTTPS)
├── alb.tf              # ALB负载均衡器(HTTPS终结)
├── autoscaling.tf      # Auto Scaling自动扩缩容
├── s3.tf               # S3静态资源存储桶
├── cloudfront.tf       # CloudFront CDN配置
├── waf.tf              # AWS WAF Web防火墙
└── userdata.sh         # 应用初始化脚本

三、核心文件逐行解析

1. provider.tf(双区域配置,关键!)

注意:CloudFront使用的ACM证书必须在us-east-1区域申请,所以我们需要配置两个AWS Provider:

# 主区域:部署所有资源
provider "aws" {
  region = var.aws_region
  alias  = "main"
}

# us-east-1区域:仅用于申请CloudFront的ACM证书
provider "aws" {
  region = "us-east-1"
  alias  = "us-east-1"
}

2. variables.tf(核心变量)

variable "aws_region" {
  description = "主AWS区域"
  type        = string
  default     = "eu-west-1"
}

variable "domain_name" {
  description = "你的网站域名(如example.com)"
  type        = string
  default     = "your-domain.com"  # 替换成你自己的域名
}

variable "app_port" {
  description = "应用监听端口"
  type        = number
  default     = 3000
}

variable "min_instances" {
  description = "最小应用实例数"
  type        = number
  default     = 2
}

variable "max_instances" {
  description = "最大应用实例数"
  type        = number
  default     = 10
}

variable "target_requests_per_instance" {
  description = "每个实例每秒处理的目标请求数"
  type        = number
  default     = 100
}

3. acm.tf(ACM SSL证书,自动验证)

# 在us-east-1区域申请证书,用于CloudFront
resource "aws_acm_certificate" "cloudfront" {
  provider          = aws.us-east-1
  domain_name       = var.domain_name
  validation_method = "DNS"

  subject_alternative_names = [
    "www.${var.domain_name}"
  ]

  lifecycle {
    create_before_destroy = true
  }
}

# 自动添加DNS验证记录(需要你的域名在Route53托管)
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.cloudfront.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = aws_route53_zone.main.zone_id
}

# 等待证书验证完成
resource "aws_acm_certificate_validation" "cloudfront" {
  provider                = aws.us-east-1
  certificate_arn         = aws_acm_certificate.cloudfront.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

# 在主区域申请证书,用于ALB
resource "aws_acm_certificate" "alb" {
  provider          = aws.main
  domain_name       = var.domain_name
  validation_method = "DNS"
  subject_alternative_names = ["www.${var.domain_name}"]
  lifecycle { create_before_destroy = true }
}

resource "aws_route53_record" "alb_cert_validation" {
  # 同上,省略DNS验证记录
}

resource "aws_acm_certificate_validation" "alb" {
  certificate_arn = aws_acm_certificate.alb.arn
  validation_record_fqdns = [for record in aws_route53_record.alb_cert_validation : record.fqdn]
}

4. alb.tf(HTTPS ALB,自动重定向HTTP到HTTPS)

# 创建ALB
resource "aws_lb" "main" {
  name               = "website-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id
  enable_deletion_protection = true  # 防止误删

  tags = local.common_tags
}

# HTTP监听器:自动重定向到HTTPS
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.main.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

# HTTPS监听器
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"  # 现代TLS策略
  certificate_arn   = aws_acm_certificate.alb.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# 目标组
resource "aws_lb_target_group" "app" {
  name     = "app-target-group"
  port     = var.app_port
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
  target_type = "instance"
  deregistration_delay = 300  # 连接排空时间

  health_check {
    path                = "/health"  # 应用健康检查端点
    port                = "traffic-port"
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 15
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 3
  }

  tags = local.common_tags
}

5. autoscaling.tf(基于请求数的自动扩缩容)

# 启动模板
resource "aws_launch_template" "app" {
  name_prefix   = "app-launch-template"
  image_id      = "ami-0c55b159cbfafe1f0"  # Ubuntu 20.04 LTS
  instance_type = "t3.small"
  iam_instance_profile {
    name = aws_iam_instance_profile.app.name  # 授予S3访问权限
  }

  security_group_ids = [aws_security_group.app.id]
  user_data = base64encode(templatefile("userdata.sh", {
    S3_BUCKET = aws_s3_bucket.static.bucket
  }))

  tag_specifications {
    resource_type = "instance"
    tags = local.common_tags
  }
}

# Auto Scaling组
resource "aws_autoscaling_group" "app" {
  name                      = "app-asg"
  min_size                  = var.min_instances
  max_size                  = var.max_instances
  desired_capacity          = var.min_instances
  vpc_zone_identifier       = aws_subnet.app[*].id
  health_check_grace_period = 300
  health_check_type         = "ELB"

  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }

  target_group_arns = [aws_lb_target_group.app.arn]

  # 实例刷新配置:滚动更新
  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 50
    }
  }

  tag {
    key                 = "Name"
    value               = "app-server"
    propagate_at_launch = true
  }
}

# 目标跟踪扩展策略:基于ALB请求数
resource "aws_autoscaling_policy" "request_count" {
  name                   = "request-count-tracking"
  autoscaling_group_name = aws_autoscaling_group.app.name
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      resource_label         = "${aws_lb.main.arn}/${aws_lb_target_group.app.arn}"
    }
    target_value = var.target_requests_per_instance * 60  # 转换为每分钟请求数
  }
}

6. s3.tf(静态资源存储桶)

# 静态资源存储桶
resource "aws_s3_bucket" "static" {
  bucket = "${var.domain_name}-static-assets"
  acl    = "private"

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET"]
    allowed_origins = ["https://${var.domain_name}"]
    max_age_seconds = 3000
  }

  tags = local.common_tags
}

# 允许CloudFront访问S3
resource "aws_s3_bucket_policy" "static" {
  bucket = aws_s3_bucket.static.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Principal = { AWS = aws_cloudfront_origin_access_identity.main.iam_arn }
        Action    = "s3:GetObject"
        Resource  = "${aws_s3_bucket.static.arn}/*"
      }
    ]
  })
}

7. cloudfront.tf(CloudFront CDN配置)

# 源访问身份:允许CloudFront访问私有S3桶
resource "aws_cloudfront_origin_access_identity" "main" {
  comment = "CloudFront OAI for ${var.domain_name}"
}

# CloudFront分发
resource "aws_cloudfront_distribution" "main" {
  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  price_class         = "PriceClass_100"  # 北美+欧洲,成本最低

  # 动态请求源:ALB
  origin {
    domain_name = aws_lb.main.dns_name
    origin_id   = "ALB-${var.domain_name}"

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }

  # 静态资源源:S3
  origin {
    domain_name = aws_s3_bucket.static.bucket_regional_domain_name
    origin_id   = "S3-${var.domain_name}"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.main.cloudfront_access_identity_path
    }
  }

  # 默认缓存行为:转发到ALB
  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = "ALB-${var.domain_name}"

    forwarded_values {
      query_string = true
      cookies { forward = "all" }
      headers {
        items = ["Host", "User-Agent", "Accept"]
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 0
    max_ttl                = 0
  }

  # 静态资源缓存行为:直接从S3返回
  ordered_cache_behavior {
    path_pattern     = "/static/*"
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = "S3-${var.domain_name}"

    forwarded_values {
      query_string = false
      cookies { forward = "none" }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 86400  # 缓存1天
    default_ttl            = 86400
    max_ttl                = 31536000
  }

  # SSL配置
  viewer_certificate {
    acm_certificate_arn = aws_acm_certificate.cloudfront.arn
    ssl_support_method  = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  # 关联WAF
  web_acl_id = aws_wafv2_web_acl.main.arn

  tags = local.common_tags
}

8. waf.tf(AWS WAF Web防火墙)

# WAF Web ACL
resource "aws_wafv2_web_acl" "main" {
  name        = "website-waf-acl"
  description = "WAF for ${var.domain_name}"
  scope       = "CLOUDFRONT"  # 关联到CloudFront

  # AWS托管规则组:核心规则集(阻挡90%的常见攻击)
  rule {
    name     = "AWSManagedRulesCommonRuleSet"
    priority = 1

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesCommonRuleSet"
      sampled_requests_enabled   = true
    }
  }

  # AWS托管规则组:SQL注入防护
  rule {
    name     = "AWSManagedRulesSQLiRuleSet"
    priority = 2

    override_action { none {} }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesSQLiRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesSQLiRuleSet"
      sampled_requests_enabled   = true
    }
  }

  # 默认动作:允许
  default_action {
    allow {}
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "website-waf"
    sampled_requests_enabled   = true
  }
}

9. userdata.sh(应用初始化脚本)

这是一个通用的Node.js应用示例,你可以替换成任何语言的应用:

#!/bin/bash
set -ex

# 更新系统
apt-get update
apt-get upgrade -y

# 安装Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs

# 创建应用目录
mkdir -p /opt/app
cd /opt/app

# 下载你的应用代码(这里用一个简单的示例)
cat > app.js << EOF
const express = require('express');
const app = express();
const port = ${app_port};

app.get('/', (req, res) => {
  res.send(\`
    <h1>Hello from Production Website!</h1>
    <p>Served by instance: \${require('os').hostname()}</p>
    <p>Static resource: <img src="/static/logo.png" alt="Logo"></p>
  \`);
});

app.get('/health', (req, res) => {
  res.status(200).send('OK');
});

app.listen(port, () => {
  console.log(\`App listening on port \${port}\`);
});
EOF

# 安装依赖并启动应用
npm init -y
npm install express
npm install -g pm2

pm2 start app.js
pm2 startup
pm2 save

10. outputs.tf(关键输出)

output "cloudfront_domain" {
  description = "CloudFront CDN域名"
  value       = aws_cloudfront_distribution.main.domain_name
}

output "alb_dns_name" {
  description = "ALB DNS名称"
  value       = aws_lb.main.dns_name
}

output "s3_static_bucket" {
  description = "静态资源S3存储桶"
  value       = aws_s3_bucket.static.bucket
}

四、完整部署步骤

1. 前置准备

  1. 注册一个域名(如example.com)
  2. 将域名的DNS托管到AWS Route53
  3. 配置AWS CLI认证

2. 部署所有资源

# 初始化
terraform init

# 预览资源
terraform plan

# 部署(大约需要20-30分钟,主要是CloudFront和RDS)
terraform apply

3. 配置DNS

部署完成后,Terraform会输出CloudFront的域名:

Outputs:

cloudfront_domain = "d1234567890.cloudfront.net"

在Route53中添加一条CNAME记录,将你的域名指向这个CloudFront域名。

4. 上传静态资源

# 上传静态资源到S3
aws s3 sync ./static s3://${var.domain_name}-static-assets/static/

5. 访问网站

在浏览器中输入你的域名(https://your-domain.com),就可以看到网站了。


五、如何部署你自己的应用

只需要修改userdata.sh脚本,替换成你自己的应用部署步骤即可:

  1. 安装你的应用所需的依赖(如Python、Java、Go等)
  2. 下载你的应用代码(从Git、S3或其他地方)
  3. 配置应用的环境变量(数据库连接、API密钥等)
  4. 启动应用并配置开机自启

六、生产环境成本估算(eu-west-1区域)

组件 规格 月成本(美元)
ALB 标准 ~22
CloudFront 1TB流量 ~85
WAF 标准 ~12
EC2 2个t3.small ~36
NAT网关 2个 ~66
S3 10GB存储 + 1TB流量 ~3
Route53 1个托管区域 ~0.5
总计 ~224.5美元/月

成本优化建议

  • 开发环境可以关闭CloudFront和WAF,直接使用ALB
  • 开发环境可以使用1个NAT网关
  • 使用Spot实例代替按需实例,可节省70%的EC2成本
  • 配置CloudFront缓存策略,减少回源流量

七、下一步优化方向

  1. 添加数据库层:集成RDS MySQL/PostgreSQL多可用区数据库
  2. 添加缓存层:集成ElastiCache Redis,加速数据库查询
  3. 配置CI/CD:使用GitHub Actions实现应用的自动部署
  4. 添加监控告警:配置CloudWatch告警,当错误率超过阈值时发送通知
  5. 配置日志收集:将应用日志和访问日志收集到CloudWatch Logs
  6. 配置备份策略:定期备份数据库和S3数据

需要我帮你补充数据库层和Redis缓存层的代码,或者帮你写一个GitHub Actions自动部署脚本吗?

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐