为应用程序创建资源

在前一页中创建了安全资源后,我们现在将能够部署所需的资源来托管我们的应用程序。 我们将创建一个RDS MySQL数据库、一个负载均衡器、一个ASG、一个EFS文件系统、一个S3存储桶,以及下面所示的许多其他资源:

创建资源

  • aws_db_subnet_group - 在可用性区域内为数据库创建子网组
  • random_password - 创建一个随机密码
  • aws_secretsmanager_secret - 创建一个Secrets Manager条目
  • aws_secretsmanager_secret_version - 为Secrets Manager条目创建一个值
  • aws_db_instance - 创建一个MySQL数据库
  • aws_efs_file_system - 创建一个EFS卷
  • aws_efs_mount_target - 创建一个配置,将EFS卷挂载到一组实例
  • aws_instance - 创建一个引导Wordpress应用程序的EC2
  • aws_ami_copy - 创建正在使用的Amazon Machine Image (AMI)的副本
  • aws_lb - 为Wordpress Web应用程序创建一个应用程序负载均衡器(ALB)
  • aws_launch_template - 创建一个托管Wordpress Web应用程序的启动模板
  • aws_autoscaling_group - 创建一个自动缩放组,用于扩展Wordpress Web应用程序实例
  • aws_autoscaling_policy - 创建一个自动缩放策略
  • aws_cloudwatch_metric_alarm - 创建一个CloudWatch指标报警
  • aws_lb_target_group - 创建一个目标组,用于附加负载均衡器以供Wordpress Web应用程序使用
  • aws_lb_listener - 为ALB创建一个负载均衡器侦听器
  • tls_private_key - 创建一个TLS私钥
  • tls_self_signed_cert - 使用私钥创建一个公共TLS证书
  • aws_acm_certificate - 使用TLS公共证书和私钥创建一个ACM证书条目
  • aws_s3_bucket - 创建一个私有S3存储桶
  • aws_s3_bucket_public_access_block - 创建一个公共访问块,禁用私有S3存储桶的公共访问
  • aws_s3_object - S3的一个对象,通常表示类似文件的数据
  • aws_cloudfront_distribution - 为Wordpress Web应用程序创建一个CloudFront分发
  • aws_cloudfront_cache_policy - 为Wordpress Web应用程序创建一个CloudFront缓存策略

terraform文件夹中打开main.tf, 附加以下内容并保存文件

# RDS
resource "aws_db_subnet_group" "mission_db_group" {  # 创建RDS数据库子网组
  name       = "${var.namespace}-db-group"
  subnet_ids = values(aws_subnet.private)[*].id  # 使用私有子网

  tags = {
    Name = "${var.namespace}-db-group"
  }
}

resource "random_password" "default" {  # 生成随机密码
  length           = 25
  special          = false
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "aws_secretsmanager_secret" "db" {  # 在Secrets Manager中创建密钥
  name_prefix             = "${var.namespace}-secret-db-"
  description             = "Password to the RDS"
  recovery_window_in_days = 7  # 删除保护期为7天
}

resource "aws_secretsmanager_secret_version" "db" {  # 存储密钥版本
  secret_id     = aws_secretsmanager_secret.db.id
  secret_string = random_password.default.result  # 存储随机生成的密码
}

resource "aws_db_instance" "wp_mysql" {  # 创建RDS MySQL实例
  identifier = "${var.namespace}-db"

  allocated_storage      = 20  # 分配20GB存储空间
  engine                 = local.rds.engine  # MySQL数据库引擎
  engine_version         = local.rds.engine_version
  instance_class         = local.rds.instance_class  # 实例类型
  db_name                = local.rds.db_name  # 数据库名称
  username               = local.rds.username  # 数据库用户名
  password               = aws_secretsmanager_secret_version.db.secret_string  # 使用生成的密码
  db_subnet_group_name   = aws_db_subnet_group.mission_db_group.name  # 使用创建的子网组
  vpc_security_group_ids = [aws_security_group.db.id]  # 应用安全组
  multi_az               = true  # 启用多可用区部署
  skip_final_snapshot    = true  # 删除时跳过最终快照

  tags = {
    Name = "${var.namespace}-db"
  }
}

# EFS
resource "aws_efs_file_system" "mission_app" {  # 创建EFS文件系统
  creation_token = "${var.namespace}-efs"
  encrypted      = true  # 启用加密

  tags = {
    Name = "${var.namespace}-efs"
  }
}

resource "aws_efs_mount_target" "mission_app_targets" {  # 创建EFS挂载点
  count = length(local.vpc.azs)  # 在每个可用区创建挂载点

  file_system_id  = aws_efs_file_system.mission_app.id
  subnet_id       = aws_subnet.private_ingress[count.index].id  # 部署在私有入口子网
  security_groups = [aws_security_group.nfs.id]  # 应用NFS安全组
}

resource "aws_instance" "staging_app" {  # 创建暂存应用服务器

  lifecycle {
    prevent_destroy = false
    ignore_changes  = [iam_instance_profile, tags, tags_all]  # 忽略这些属性的变更
  }

  ami                         = data.aws_ami.linux.image_id  # 使用Amazon Linux AMI
  instance_type               = local.vm.instance_type
  subnet_id                   = aws_subnet.private_ingress[0].id  # 部署在第一个私有入口子网
  user_data_replace_on_change = true

  user_data = templatefile("${path.module}/userdata/staging-efs.sh", {  # 使用用户数据脚本配置实例
    region        = data.aws_region.current.name,
    efs_id        = aws_efs_file_system.mission_app.id
    db_name       = aws_db_instance.wp_mysql.db_name
    db_username   = aws_db_instance.wp_mysql.username
    db_password   = aws_db_instance.wp_mysql.password
    db_host       = aws_db_instance.wp_mysql.address
    DOMAIN_NAME   = aws_cloudfront_distribution.mission_app.domain_name
    demo_username = local.demo.admin.username
    demo_password = local.demo.admin.password
    demo_email    = local.demo.admin.email
  })

  iam_instance_profile   = aws_iam_instance_profile.app.name  # 使用应用IAM配置文件
  availability_zone      = data.aws_availability_zones.available.names[0]  # 部署在第一个可用区
  vpc_security_group_ids = [aws_security_group.app.id]  # 应用安全组

  metadata_options {  # 配置实例元数据选项
    http_endpoint               = "enabled"
    http_put_response_hop_limit = 1
    http_tokens                 = "required"  # 要求使用IMDSv2
    instance_metadata_tags      = "enabled"
  }

  root_block_device {  # 配置根卷
    delete_on_termination = true
    encrypted             = true  # 启用加密
  }

  tags = {
    Name = format("${var.namespace}-staging_app-%s", element(data.aws_availability_zones.available.names, 0))
  }

  depends_on = [aws_s3_object.mission_app-private_key, aws_s3_object.mission_app-public_key]  # 依赖S3对象创建
}

resource "aws_ami_copy" "mission_app_ami" {  # 复制AMI
  name              = "Amazon Linux 2 Image"
  description       = "A copy of ${data.aws_ami.linux.image_id} - ${data.aws_ami.linux.description}"
  source_ami_id     = data.aws_ami.linux.image_id
  source_ami_region = data.aws_region.current.name

  tags = {
    Name               = "${var.namespace}-ami"
    Description        = data.aws_ami.linux.description
    "Creation Date"    = data.aws_ami.linux.creation_date
    "Deprecation Time" = data.aws_ami.linux.deprecation_time
  }
}

# Application Load Balancer
resource "aws_lb" "mission_app" {  # 创建应用负载均衡器
  name               = "${var.namespace}-alb"
  internal           = false  # 面向公网
  load_balancer_type = "application"
  subnets            = values(aws_subnet.private)[*].id  # 部署在私有子网

  tags = {
    Name = "${var.namespace}-lb"
  }

  security_groups = [aws_security_group.app.id]  # 应用安全组
}

# Launch configuration
resource "aws_launch_template" "mission_app_lc" {  # 创建启动模板
  name_prefix = "${var.namespace}-mission_app_iac_lc-"
  image_id    = aws_ami_copy.mission_app_ami.id  # 使用复制的AMI

  instance_requirements {  # 实例要求配置
    memory_mib {
      min = local.vm.instance_requirements.memory_mib.min
    }
    vcpu_count {
      min = local.vm.instance_requirements.vcpu_count.min
    }

    allowed_instance_types = ["m*"]  # 允许使用m系列实例
    instance_generations   = local.vm.instance_requirements.instance_generations
  }

  ebs_optimized          = true  # 启用EBS优化
  vpc_security_group_ids = [aws_security_group.app.id]  # 应用安全组

  iam_instance_profile {
    name = aws_iam_instance_profile.web_hosting.name  # 使用Web托管IAM配置文件
  }

  block_device_mappings {  # 块设备映射配置
    device_name = "/dev/xvda"

    ebs {
      delete_on_termination = true
      encrypted             = true  # 启用加密
    }
  }

  monitoring {
    enabled = true  # 启用详细监控
  }

  metadata_options {  # 配置实例元数据选项
    http_endpoint               = "enabled"
    http_put_response_hop_limit = 1
    http_tokens                 = "required"  # 要求使用IMDSv2
    instance_metadata_tags      = "enabled"
  }

  user_data = base64encode(templatefile("${path.module}/userdata/staging-wordpress.sh", {  # 使用用户数据脚本配置WordPress
    region    = data.aws_region.current.name,
    efs_id    = aws_efs_file_system.mission_app.id,
    s3_bucket = aws_s3_bucket.mission_app.bucket
  }))

  update_default_version = true  # 更新默认版本
}

resource "aws_autoscaling_group" "mission_app_asg" {  # 创建自动扩展组
  name     = "${var.namespace}-asg-mission_app"
  min_size = 2  # 最小实例数
  max_size = 8  # 最大实例数

  vpc_zone_identifier = values(aws_subnet.private_ingress)[*].id  # 部署在私有入口子网
  target_group_arns   = [aws_lb_target_group.mission_app.arn]  # 关联目标组

  mixed_instances_policy {  # 混合实例策略
    launch_template {
      launch_template_specification {
        launch_template_id = aws_launch_template.mission_app_lc.id
        version            = "$Latest"  # 使用最新版本
      }
    }
  }

  lifecycle {
    create_before_destroy = true  # 先创建后销毁
  }

  tag {
    key                 = "Name"
    value               = "${var.namespace}-asg-mission_app"
    propagate_at_launch = true  # 标签传播到实例
  }

  depends_on = [aws_instance.staging_app, aws_db_instance.wp_mysql]  # 依赖暂存实例和数据库
}

resource "aws_autoscaling_policy" "mission_app_scale_out_policy" {  # 创建扩容策略
  name                   = "${var.namespace}-out-policy"
  scaling_adjustment     = 4  # 每次增加4个实例
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300  # 冷却时间300秒
  autoscaling_group_name = aws_autoscaling_group.mission_app_asg.name
}

# ASG Cloudwatch policy
resource "aws_cloudwatch_metric_alarm" "mission_app_scale_out_alarm" {  # 创建扩容告警
  alarm_name          = "${var.namespace}-out-alarm"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "60"  # 60秒评估一次
  statistic           = "Average"
  threshold           = "30"  # CPU使用率超过30%触发

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.mission_app_asg.name
  }

  alarm_description = "This metric monitors ec2 cpu utilization for Mission App ASG"
  alarm_actions     = [aws_autoscaling_policy.mission_app_scale_out_policy.arn]  # 触发扩容策略
}

resource "aws_autoscaling_policy" "mission_app_scale_in_policy" {  # 创建缩容策略
  name                   = "${var.namespace}-in-policy"
  scaling_adjustment     = -1  # 每次减少1个实例
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 180  # 冷却时间180秒
  autoscaling_group_name = aws_autoscaling_group.mission_app_asg.name
}

# ASG Cloudwatch policy
resource "aws_cloudwatch_metric_alarm" "mission_app_scale_in_alarm" {  # 创建缩容告警
  alarm_name          = "${var.namespace}-in-alarm"
  comparison_operator = "LessThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "60"  # 60秒评估一次
  statistic           = "Average"
  threshold           = "20"  # CPU使用率低于20%触发

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.mission_app_asg.name
  }

  alarm_description = "This metric monitors ec2 cpu utilization for Mission App ASG"
  alarm_actions     = [aws_autoscaling_policy.mission_app_scale_in_policy.arn]  # 触发缩容策略
}

resource "aws_lb_target_group" "mission_app" {  # 创建负载均衡器目标组
  name     = "${var.namespace}-lb-tg"
  port     = 443  # HTTPS端口
  protocol = "HTTPS"
  vpc_id   = aws_vpc.default.id

  stickiness {  # 配置会话粘性
    type            = "lb_cookie"
    cookie_duration = 1800  # Cookie有效期1800秒
    enabled         = true
  }

  health_check {  # 配置健康检查
    healthy_threshold   = 3
    unhealthy_threshold = 10
    timeout             = 5
    interval            = 10
    path                = "/"
    port                = 443
    protocol            = "HTTPS"
  }
}

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

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

resource "aws_lb_listener_rule" "redirect_cloudfront_to_http" {  # 创建CloudFront到HTTP的重定向规则
  listener_arn = aws_lb_listener.mission_app_http.arn
  priority     = 100  # 优先级100

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.mission_app.arn
  }

  condition {
    http_header {
      http_header_name = "X-Request"
      values           = [aws_lb.mission_app.dns_name]
    }
  }
}

resource "aws_lb_listener_rule" "redirect_http_to_https" {  # 创建HTTP到HTTPS的重定向规则
  listener_arn = aws_lb_listener.mission_app_http.arn
  priority     = 200  # 优先级200

  action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"  # 永久重定向
    }
  }

  condition {
    host_header {
      values = [aws_lb.mission_app.dns_name]
    }
  }
}

# Certificates
resource "tls_private_key" "mission_app" {  # 创建私钥
  algorithm = "RSA"
}

resource "tls_self_signed_cert" "mission_app" {  # 创建自签名证书
  private_key_pem = tls_private_key.mission_app.private_key_pem

  validity_period_hours = 8760  # 有效期一年

  # 在证书过期前3小时内运行Terraform时生成新证书
  early_renewal_hours = 3

  # 服务器SSL证书的合理用途
  allowed_uses = [
    "key_encipherment",
    "digital_signature",
    "server_auth",
  ]

  dns_names = [  # 证书的DNS名称
    aws_lb.mission_app.dns_name
  ]

  subject {  # 证书主题信息
    common_name         = aws_lb.mission_app.dns_name
    organization        = "Amazon Web Services"
    organizational_unit = "WWPS ProServe"
    country             = "USA"
    locality            = "San Diego"
  }
}

resource "aws_acm_certificate" "mission_app_private" {  # 导入证书到ACM
  private_key      = tls_private_key.mission_app.private_key_pem
  certificate_body = tls_self_signed_cert.mission_app.cert_pem
}

resource "aws_s3_bucket" "mission_app" {  # 创建S3存储桶
  bucket_prefix = "${var.namespace}-mission-app-"

  tags = {
    Description = "Used to store items"
  }
}

resource "aws_s3_bucket_public_access_block" "mission_app" {  # 配置S3存储桶公共访问
  bucket = aws_s3_bucket.mission_app.bucket

  block_public_acls       = true  # 阻止公共访问
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_object" "mission_app-private_key" {  # 上传私钥到S3
  bucket = aws_s3_bucket.mission_app.bucket

  key     = "server.key"
  content = tls_private_key.mission_app.private_key_pem
}

resource "aws_s3_object" "mission_app-public_key" {  # 上传公钥到S3
  bucket = aws_s3_bucket.mission_app.bucket

  key     = "server.crt"
  content = tls_self_signed_cert.mission_app.cert_pem
}

# Application Load Balancer (finishing touch)
resource "aws_lb_listener" "mission_app_https" {  # 创建HTTPS监听器
  load_balancer_arn = aws_lb.mission_app.arn
  port              = "443"
  protocol          = "HTTPS"

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

  certificate_arn = aws_acm_certificate.mission_app_private.arn  # 使用ACM证书
}

resource "aws_cloudfront_distribution" "mission_app" {  # 创建CloudFront分发
  enabled = true
  comment = "${var.namespace} - frontend site for mission app"

  origin {  # 配置源站
    domain_name = aws_lb.mission_app.dns_name
    origin_id   = aws_lb.mission_app.dns_name
    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "http-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }

    custom_header {
      name  = "X-Request"
      value = aws_lb.mission_app.dns_name
    }
  }

  default_cache_behavior {  # 配置默认缓存行为
    allowed_methods        = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods         = ["GET", "HEAD", "OPTIONS"]
    target_origin_id       = aws_lb.mission_app.dns_name
    viewer_protocol_policy = "redirect-to-https"  # 重定向到HTTPS
    cache_policy_id        = aws_cloudfront_cache_policy.default.id
  }

  # https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
  restrictions {  # 配置地理限制
    geo_restriction {
      restriction_type = "none"  # 不限制
      locations        = []
    }
  }

  viewer_certificate {  # 配置查看器证书
    cloudfront_default_certificate = true  # 使用默认证书
  }
}

resource "aws_cloudfront_cache_policy" "default" {  # 创建CloudFront缓存策略
  name        = "${var.namespace}-default-policy"
  comment     = "Default Policy"
  default_ttl = 50  # 默认TTL 50秒
  max_ttl     = 100  # 最大TTL 100秒
  min_ttl     = 1  # 最小TTL 1秒

  parameters_in_cache_key_and_forwarded_to_origin {  # 配置缓存键参数
    cookies_config {
      cookie_behavior = "all"  # 包含所有Cookie
    }
    headers_config {
      header_behavior = "whitelist"
      headers {
        items = ["X-Request"]  # 白名单头部
      }
    }
    query_strings_config {
      query_string_behavior = "all"  # 包含所有查询字符串
    }
  }
}

再运行一次terraform init

运行命令 terraform apply 来应用部署

上面大概要20min创建完毕

检查资源

Terraform 为以下 AWS 服务创建了资源,在控制台上检查:

  • EC2
  • EFS
  • RDS
  • CloudWatch
  • S3
  • ACM
  • ELB