Skip to content

CloudFormation

📋 概述

AWS CloudFormation是亚马逊提供的基础设施即代码服务,允许使用JSON或YAML模板来定义和部署AWS资源。它提供了声明式的方式来管理整个AWS基础设施栈。

🎯 学习目标

  • 掌握CloudFormation的核心概念和架构
  • 学会编写和组织CloudFormation模板
  • 了解栈管理和更新策略
  • 掌握Node.js应用的AWS基础设施部署

📚 CloudFormation核心概念

模板结构

yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Node.js应用基础设施模板'

# 输入参数
Parameters:
  Environment:
    Type: String
    Default: 'dev'
    AllowedValues: ['dev', 'staging', 'prod']

# 映射关系
Mappings:
  EnvironmentMap:
    dev:
      InstanceType: t3.micro
      MinSize: 1
      MaxSize: 2
    prod:
      InstanceType: t3.medium
      MinSize: 2
      MaxSize: 10

# 条件判断
Conditions:
  IsProd: !Equals [!Ref Environment, 'prod']
  CreateReadReplica: !Equals [!Ref Environment, 'prod']

# 资源定义
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16

# 输出值
Outputs:
  VPCId:
    Description: 'VPC ID'
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}-VPC-ID'

内置函数

yaml
# 常用内置函数示例
Resources:
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !FindInMap [EnvironmentMap, !Ref Environment, InstanceType]
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds:
        - !Ref WebSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          echo "Environment: ${Environment}" > /tmp/env.txt
          echo "Stack: ${AWS::StackName}" >> /tmp/env.txt
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-web-server'
        - Key: Environment
          Value: !Ref Environment

🛠 Node.js应用完整基础设施

主模板

yaml
# main-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Complete Node.js Application Infrastructure'

Parameters:
  Environment:
    Type: String
    Default: 'dev'
    AllowedValues: ['dev', 'staging', 'prod']
    Description: 'Environment name'
  
  ProjectName:
    Type: String
    Default: 'nodejs-app'
    Description: 'Project name for resource naming'
  
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: 'EC2 Key Pair for SSH access'
  
  DBPassword:
    Type: String
    NoEcho: true
    MinLength: 8
    Description: 'Database password'
  
  SSLCertificateArn:
    Type: String
    Default: ''
    Description: 'SSL Certificate ARN (optional)'
  
  DomainName:
    Type: String
    Default: ''
    Description: 'Domain name (optional)'

Mappings:
  EnvironmentMap:
    dev:
      InstanceType: t3.micro
      DBInstanceClass: db.t3.micro
      MinSize: 1
      MaxSize: 2
      DesiredCapacity: 1
      AllocatedStorage: 20
    staging:
      InstanceType: t3.small
      DBInstanceClass: db.t3.small
      MinSize: 1
      MaxSize: 3
      DesiredCapacity: 2
      AllocatedStorage: 50
    prod:
      InstanceType: t3.medium
      DBInstanceClass: db.t3.medium
      MinSize: 2
      MaxSize: 10
      DesiredCapacity: 3
      AllocatedStorage: 100

Conditions:
  IsProd: !Equals [!Ref Environment, 'prod']
  HasSSLCertificate: !Not [!Equals [!Ref SSLCertificateArn, '']]
  HasDomainName: !Not [!Equals [!Ref DomainName, '']]
  CreateReadReplica: !Equals [!Ref Environment, 'prod']

Resources:
  # VPC网络
  VPCStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: './nested-templates/vpc.yaml'
      Parameters:
        Environment: !Ref Environment
        ProjectName: !Ref ProjectName

  # 安全组
  SecurityStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: './nested-templates/security-groups.yaml'
      Parameters:
        Environment: !Ref Environment
        ProjectName: !Ref ProjectName
        VPCId: !GetAtt VPCStack.Outputs.VPCId

  # 数据库
  DatabaseStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: './nested-templates/database.yaml'
      Parameters:
        Environment: !Ref Environment
        ProjectName: !Ref ProjectName
        VPCId: !GetAtt VPCStack.Outputs.VPCId
        PrivateSubnet1: !GetAtt VPCStack.Outputs.PrivateSubnet1
        PrivateSubnet2: !GetAtt VPCStack.Outputs.PrivateSubnet2
        DatabaseSecurityGroup: !GetAtt SecurityStack.Outputs.DatabaseSecurityGroup
        DBInstanceClass: !FindInMap [EnvironmentMap, !Ref Environment, DBInstanceClass]
        AllocatedStorage: !FindInMap [EnvironmentMap, !Ref Environment, AllocatedStorage]
        DBPassword: !Ref DBPassword

  # 缓存
  CacheStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: './nested-templates/cache.yaml'
      Parameters:
        Environment: !Ref Environment
        ProjectName: !Ref ProjectName
        VPCId: !GetAtt VPCStack.Outputs.VPCId
        PrivateSubnet1: !GetAtt VPCStack.Outputs.PrivateSubnet1
        PrivateSubnet2: !GetAtt VPCStack.Outputs.PrivateSubnet2
        CacheSecurityGroup: !GetAtt SecurityStack.Outputs.CacheSecurityGroup

  # 应用服务器
  ApplicationStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: './nested-templates/application.yaml'
      Parameters:
        Environment: !Ref Environment
        ProjectName: !Ref ProjectName
        VPCId: !GetAtt VPCStack.Outputs.VPCId
        PublicSubnet1: !GetAtt VPCStack.Outputs.PublicSubnet1
        PublicSubnet2: !GetAtt VPCStack.Outputs.PublicSubnet2
        PrivateSubnet1: !GetAtt VPCStack.Outputs.PrivateSubnet1
        PrivateSubnet2: !GetAtt VPCStack.Outputs.PrivateSubnet2
        WebSecurityGroup: !GetAtt SecurityStack.Outputs.WebSecurityGroup
        ALBSecurityGroup: !GetAtt SecurityStack.Outputs.ALBSecurityGroup
        InstanceType: !FindInMap [EnvironmentMap, !Ref Environment, InstanceType]
        MinSize: !FindInMap [EnvironmentMap, !Ref Environment, MinSize]
        MaxSize: !FindInMap [EnvironmentMap, !Ref Environment, MaxSize]
        DesiredCapacity: !FindInMap [EnvironmentMap, !Ref Environment, DesiredCapacity]
        KeyPairName: !Ref KeyPairName
        DatabaseEndpoint: !GetAtt DatabaseStack.Outputs.DatabaseEndpoint
        CacheEndpoint: !GetAtt CacheStack.Outputs.CacheEndpoint
        SSLCertificateArn: !Ref SSLCertificateArn

  # 监控
  MonitoringStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: './nested-templates/monitoring.yaml'
      Parameters:
        Environment: !Ref Environment
        ProjectName: !Ref ProjectName
        LoadBalancerFullName: !GetAtt ApplicationStack.Outputs.LoadBalancerFullName
        AutoScalingGroupName: !GetAtt ApplicationStack.Outputs.AutoScalingGroupName

Outputs:
  LoadBalancerDNS:
    Description: 'Load Balancer DNS Name'
    Value: !GetAtt ApplicationStack.Outputs.LoadBalancerDNS
    Export:
      Name: !Sub '${AWS::StackName}-LoadBalancer-DNS'

  DatabaseEndpoint:
    Description: 'Database Endpoint'
    Value: !GetAtt DatabaseStack.Outputs.DatabaseEndpoint
    Export:
      Name: !Sub '${AWS::StackName}-Database-Endpoint'

  VPCId:
    Description: 'VPC ID'
    Value: !GetAtt VPCStack.Outputs.VPCId
    Export:
      Name: !Sub '${AWS::StackName}-VPC-ID'

VPC嵌套模板

yaml
# nested-templates/vpc.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC and Networking Resources'

Parameters:
  Environment:
    Type: String
  ProjectName:
    Type: String

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-vpc'
        - Key: Environment
          Value: !Ref Environment

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-igw'

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-public-subnet-1'

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-public-subnet-2'

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.3.0/24
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-subnet-1'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.4.0/24
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-subnet-2'

  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-nat-eip-1'

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-nat-eip-2'

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-nat-1'

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-nat-2'

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-public-routes'

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-routes-1'

  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-routes-2'

  DefaultPrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

Outputs:
  VPCId:
    Description: 'VPC ID'
    Value: !Ref VPC

  PublicSubnet1:
    Description: 'Public Subnet 1 ID'
    Value: !Ref PublicSubnet1

  PublicSubnet2:
    Description: 'Public Subnet 2 ID'
    Value: !Ref PublicSubnet2

  PrivateSubnet1:
    Description: 'Private Subnet 1 ID'
    Value: !Ref PrivateSubnet1

  PrivateSubnet2:
    Description: 'Private Subnet 2 ID'
    Value: !Ref PrivateSubnet2

应用服务器嵌套模板

yaml
# nested-templates/application.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Application Servers and Load Balancer'

Parameters:
  Environment:
    Type: String
  ProjectName:
    Type: String
  VPCId:
    Type: String
  PublicSubnet1:
    Type: String
  PublicSubnet2:
    Type: String
  PrivateSubnet1:
    Type: String
  PrivateSubnet2:
    Type: String
  WebSecurityGroup:
    Type: String
  ALBSecurityGroup:
    Type: String
  InstanceType:
    Type: String
  MinSize:
    Type: Number
  MaxSize:
    Type: Number
  DesiredCapacity:
    Type: Number
  KeyPairName:
    Type: String
  DatabaseEndpoint:
    Type: String
  CacheEndpoint:
    Type: String
  SSLCertificateArn:
    Type: String

Conditions:
  HasSSLCertificate: !Not [!Equals [!Ref SSLCertificateArn, '']]

Resources:
  # IAM角色
  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-${Environment}-instance-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: S3Access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                Resource: !Sub '${S3Bucket}/*'
              - Effect: Allow
                Action:
                  - s3:ListBucket
                Resource: !Ref S3Bucket
        - PolicyName: SecretsManagerAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Ref ApplicationSecrets

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub '${ProjectName}-${Environment}-instance-profile'
      Roles:
        - !Ref InstanceRole

  # S3存储桶
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-${Environment}-app-bucket-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-app-bucket'
        - Key: Environment
          Value: !Ref Environment

  # Secrets Manager
  ApplicationSecrets:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-app-secrets'
      Description: 'Application secrets'
      SecretString: !Sub |
        {
          "DATABASE_URL": "postgresql://postgres:${DBPassword}@${DatabaseEndpoint}:5432/nodejs_app",
          "REDIS_URL": "redis://${CacheEndpoint}:6379",
          "JWT_SECRET": "${JWTSecret}",
          "SESSION_SECRET": "${SessionSecret}"
        }
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-secrets'
        - Key: Environment
          Value: !Ref Environment

  # 启动模板
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub '${ProjectName}-${Environment}-launch-template'
      LaunchTemplateData:
        ImageId: !Ref LatestAmiId
        InstanceType: !Ref InstanceType
        KeyName: !Ref KeyPairName
        SecurityGroupIds:
          - !Ref WebSecurityGroup
        IamInstanceProfile:
          Arn: !GetAtt InstanceProfile.Arn
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            yum update -y
            yum install -y docker amazon-cloudwatch-agent
            
            # 启动Docker
            systemctl start docker
            systemctl enable docker
            usermod -a -G docker ec2-user
            
            # 安装Node.js
            curl -sL https://rpm.nodesource.com/setup_18.x | bash -
            yum install -y nodejs
            
            # 创建应用目录
            mkdir -p /opt/nodejs-app
            cd /opt/nodejs-app
            
            # 从S3下载应用代码
            aws s3 cp s3://${S3Bucket}/app.tar.gz /tmp/app.tar.gz
            tar -xzf /tmp/app.tar.gz -C /opt/nodejs-app
            
            # 获取应用密钥
            aws secretsmanager get-secret-value \
              --secret-id ${ApplicationSecrets} \
              --region ${AWS::Region} \
              --query SecretString --output text > /opt/nodejs-app/.env
            
            # 安装依赖
            npm ci --only=production
            
            # 创建systemd服务
            cat << EOF > /etc/systemd/system/nodejs-app.service
            [Unit]
            Description=Node.js Application
            After=network.target
            
            [Service]
            Type=simple
            User=ec2-user
            WorkingDirectory=/opt/nodejs-app
            EnvironmentFile=/opt/nodejs-app/.env
            ExecStart=/usr/bin/node server.js
            Restart=always
            RestartSec=5
            
            [Install]
            WantedBy=multi-user.target
            EOF
            
            # 启动应用
            systemctl daemon-reload
            systemctl enable nodejs-app
            systemctl start nodejs-app
            
            # 配置CloudWatch Agent
            cat << EOF > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
            {
              "logs": {
                "logs_collected": {
                  "files": {
                    "collect_list": [
                      {
                        "file_path": "/var/log/nodejs-app.log",
                        "log_group_name": "/aws/ec2/${ProjectName}-${Environment}",
                        "log_stream_name": "{instance_id}"
                      }
                    ]
                  }
                }
              },
              "metrics": {
                "namespace": "NodeJS/Application",
                "metrics_collected": {
                  "cpu": {
                    "measurement": ["cpu_usage_idle", "cpu_usage_iowait"]
                  },
                  "disk": {
                    "measurement": ["used_percent"],
                    "metrics_collection_interval": 60,
                    "resources": ["*"]
                  },
                  "mem": {
                    "measurement": ["mem_used_percent"]
                  }
                }
              }
            }
            EOF
            
            # 启动CloudWatch Agent
            /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
              -a fetch-config \
              -m ec2 \
              -s \
              -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
        TagSpecifications:
          - ResourceType: instance
            Tags:
              - Key: Name
                Value: !Sub '${ProjectName}-${Environment}-instance'
              - Key: Environment
                Value: !Ref Environment

  # Auto Scaling Group
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub '${ProjectName}-${Environment}-asg'
      VPCZoneIdentifier:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MinSize: !Ref MinSize
      MaxSize: !Ref MaxSize
      DesiredCapacity: !Ref DesiredCapacity
      TargetGroupARNs:
        - !Ref TargetGroup
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-asg'
          PropagateAtLaunch: false
        - Key: Environment
          Value: !Ref Environment
          PropagateAtLaunch: true
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: 1
        MaxBatchSize: 2
        PauseTime: PT5M
        WaitOnResourceSignals: true
        SuspendProcesses:
          - HealthCheck
          - ReplaceUnhealthy
          - AZRebalance
          - AlarmNotification
          - ScheduledActions

  # Application Load Balancer
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-alb'
      Scheme: internet-facing
      Type: application
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-alb'
        - Key: Environment
          Value: !Ref Environment

  # Target Group
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-tg'
      Port: 3000
      Protocol: HTTP
      VpcId: !Ref VPCId
      HealthCheckPath: /health
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 5
      TargetType: instance
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-tg'
        - Key: Environment
          Value: !Ref Environment

  # HTTP Listener
  HTTPListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  # HTTPS Listener (条件性创建)
  HTTPSListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Condition: HasSSLCertificate
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !Ref SSLCertificateArn

  # Auto Scaling Policies
  ScaleUpPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref AutoScalingGroup
      Cooldown: 300
      ScalingAdjustment: 1

  ScaleDownPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref AutoScalingGroup
      Cooldown: 300
      ScalingAdjustment: -1

  # CloudWatch Alarms
  CPUAlarmHigh:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub '${ProjectName}-${Environment}-cpu-high'
      AlarmDescription: 'CPU utilization is too high'
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300
      EvaluationPeriods: 2
      Threshold: 70
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref ScaleUpPolicy

  CPUAlarmLow:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub '${ProjectName}-${Environment}-cpu-low'
      AlarmDescription: 'CPU utilization is too low'
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300
      EvaluationPeriods: 2
      Threshold: 20
      ComparisonOperator: LessThanThreshold
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      AlarmActions:
        - !Ref ScaleDownPolicy

  # CloudWatch Log Group
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/ec2/${ProjectName}-${Environment}'
      RetentionInDays: 30

Parameters:
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  DBPassword:
    Type: String
    NoEcho: true

  JWTSecret:
    Type: String
    Default: !Sub '${AWS::StackName}-jwt-secret-${AWS::AccountId}'
    NoEcho: true

  SessionSecret:
    Type: String
    Default: !Sub '${AWS::StackName}-session-secret-${AWS::AccountId}'
    NoEcho: true

Outputs:
  LoadBalancerDNS:
    Description: 'Load Balancer DNS Name'
    Value: !GetAtt ApplicationLoadBalancer.DNSName

  LoadBalancerFullName:
    Description: 'Load Balancer Full Name'
    Value: !GetAtt ApplicationLoadBalancer.LoadBalancerFullName

  AutoScalingGroupName:
    Description: 'Auto Scaling Group Name'
    Value: !Ref AutoScalingGroup

  S3BucketName:
    Description: 'S3 Bucket Name'
    Value: !Ref S3Bucket

🚀 部署和管理

部署脚本

bash
#!/bin/bash
# deploy-cloudformation.sh

set -e

# 配置变量
STACK_NAME=${1:-"nodejs-app-dev"}
ENVIRONMENT=${2:-"dev"}
TEMPLATE_FILE=${3:-"main-template.yaml"}
PARAMETERS_FILE=${4:-"parameters-${ENVIRONMENT}.json"}

echo "🚀 部署CloudFormation栈: $STACK_NAME"
echo "环境: $ENVIRONMENT"
echo "模板: $TEMPLATE_FILE"
echo "参数: $PARAMETERS_FILE"

# 验证模板
echo "🔍 验证CloudFormation模板..."
aws cloudformation validate-template --template-body file://$TEMPLATE_FILE

# 检查栈是否存在
if aws cloudformation describe-stacks --stack-name $STACK_NAME > /dev/null 2>&1; then
    echo "📝 更新现有栈..."
    
    # 创建变更集
    CHANGE_SET_NAME="changeset-$(date +%Y%m%d-%H%M%S)"
    
    aws cloudformation create-change-set \
        --stack-name $STACK_NAME \
        --change-set-name $CHANGE_SET_NAME \
        --template-body file://$TEMPLATE_FILE \
        --parameters file://$PARAMETERS_FILE \
        --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
    
    echo "⏳ 等待变更集创建完成..."
    aws cloudformation wait change-set-create-complete \
        --stack-name $STACK_NAME \
        --change-set-name $CHANGE_SET_NAME
    
    # 显示变更
    echo "📋 变更预览:"
    aws cloudformation describe-change-set \
        --stack-name $STACK_NAME \
        --change-set-name $CHANGE_SET_NAME \
        --query 'Changes[].{Action:Action,ResourceType:ResourceChange.ResourceType,LogicalId:ResourceChange.LogicalResourceId}' \
        --output table
    
    # 确认执行
    read -p "确认执行变更?(y/N): " confirm
    if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
        aws cloudformation execute-change-set \
            --stack-name $STACK_NAME \
            --change-set-name $CHANGE_SET_NAME
        
        echo "⏳ 等待栈更新完成..."
        aws cloudformation wait stack-update-complete --stack-name $STACK_NAME
    else
        echo "取消变更"
        aws cloudformation delete-change-set \
            --stack-name $STACK_NAME \
            --change-set-name $CHANGE_SET_NAME
        exit 0
    fi
else
    echo "🆕 创建新栈..."
    aws cloudformation create-stack \
        --stack-name $STACK_NAME \
        --template-body file://$TEMPLATE_FILE \
        --parameters file://$PARAMETERS_FILE \
        --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
        --on-failure ROLLBACK
    
    echo "⏳ 等待栈创建完成..."
    aws cloudformation wait stack-create-complete --stack-name $STACK_NAME
fi

# 显示输出
echo "📊 栈输出:"
aws cloudformation describe-stacks \
    --stack-name $STACK_NAME \
    --query 'Stacks[0].Outputs' \
    --output table

echo "✅ 部署完成!"

参数文件示例

json
// parameters-dev.json
[
  {
    "ParameterKey": "Environment",
    "ParameterValue": "dev"
  },
  {
    "ParameterKey": "ProjectName",
    "ParameterValue": "nodejs-app"
  },
  {
    "ParameterKey": "KeyPairName",
    "ParameterValue": "my-key-pair"
  },
  {
    "ParameterKey": "DBPassword",
    "ParameterValue": "MySecurePassword123!"
  },
  {
    "ParameterKey": "SSLCertificateArn",
    "ParameterValue": ""
  },
  {
    "ParameterKey": "DomainName",
    "ParameterValue": ""
  }
]
json
// parameters-prod.json
[
  {
    "ParameterKey": "Environment",
    "ParameterValue": "prod"
  },
  {
    "ParameterKey": "ProjectName",
    "ParameterValue": "nodejs-app"
  },
  {
    "ParameterKey": "KeyPairName",
    "ParameterValue": "prod-key-pair"
  },
  {
    "ParameterKey": "DBPassword",
    "ParameterValue": "ProductionSecurePassword123!"
  },
  {
    "ParameterKey": "SSLCertificateArn",
    "ParameterValue": "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
  },
  {
    "ParameterKey": "DomainName",
    "ParameterValue": "api.example.com"
  }
]

🔧 高级功能

自定义资源

yaml
# Custom Resource for Application Deployment
ApplicationDeployment:
  Type: AWS::CloudFormation::CustomResource
  Properties:
    ServiceToken: !GetAtt DeploymentFunction.Arn
    S3Bucket: !Ref S3Bucket
    ApplicationVersion: !Ref ApplicationVersion
    Environment: !Ref Environment

DeploymentFunction:
  Type: AWS::Lambda::Function
  Properties:
    FunctionName: !Sub '${ProjectName}-${Environment}-deployment'
    Runtime: python3.9
    Handler: index.handler
    Role: !GetAtt DeploymentFunctionRole.Arn
    Code:
      ZipFile: |
        import boto3
        import cfnresponse
        import json
        
        def handler(event, context):
            try:
                s3 = boto3.client('s3')
                
                if event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
                    # 下载并部署应用
                    bucket = event['ResourceProperties']['S3Bucket']
                    version = event['ResourceProperties']['ApplicationVersion']
                    
                    # 部署逻辑
                    print(f"Deploying version {version} to {bucket}")
                    
                    cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                        'DeploymentId': f"deploy-{version}",
                        'Status': 'Deployed'
                    })
                    
                elif event['RequestType'] == 'Delete':
                    # 清理逻辑
                    cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                    
            except Exception as e:
                print(f"Error: {str(e)}")
                cfnresponse.send(event, context, cfnresponse.FAILED, {})

DeploymentFunctionRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    Policies:
      - PolicyName: S3Access
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
                - s3:PutObject
              Resource: !Sub '${S3Bucket}/*'

栈间依赖

yaml
# 使用跨栈引用
DatabaseEndpoint:
  Type: String
  Default: !ImportValue 
    Fn::Sub: '${DatabaseStackName}-Database-Endpoint'

# 或使用嵌套栈输出
DatabaseEndpoint: !GetAtt DatabaseStack.Outputs.DatabaseEndpoint

📊 监控和日志

CloudWatch集成

yaml
# CloudWatch Dashboard
CloudWatchDashboard:
  Type: AWS::CloudWatch::Dashboard
  Properties:
    DashboardName: !Sub '${ProjectName}-${Environment}-dashboard'
    DashboardBody: !Sub |
      {
        "widgets": [
          {
            "type": "metric",
            "properties": {
              "metrics": [
                ["AWS/ApplicationELB", "RequestCount", "LoadBalancer", "${ApplicationLoadBalancer.LoadBalancerFullName}"],
                ["AWS/ApplicationELB", "TargetResponseTime", "LoadBalancer", "${ApplicationLoadBalancer.LoadBalancerFullName}"],
                ["AWS/ApplicationELB", "HTTPCode_Target_4XX_Count", "LoadBalancer", "${ApplicationLoadBalancer.LoadBalancerFullName}"],
                ["AWS/ApplicationELB", "HTTPCode_Target_5XX_Count", "LoadBalancer", "${ApplicationLoadBalancer.LoadBalancerFullName}"]
              ],
              "period": 300,
              "stat": "Sum",
              "region": "${AWS::Region}",
              "title": "ALB Metrics"
            }
          },
          {
            "type": "metric",
            "properties": {
              "metrics": [
                ["AWS/EC2", "CPUUtilization", "AutoScalingGroupName", "${AutoScalingGroup}"],
                ["AWS/EC2", "NetworkIn", "AutoScalingGroupName", "${AutoScalingGroup}"],
                ["AWS/EC2", "NetworkOut", "AutoScalingGroupName", "${AutoScalingGroup}"]
              ],
              "period": 300,
              "stat": "Average",
              "region": "${AWS::Region}",
              "title": "EC2 Metrics"
            }
          }
        ]
      }

📝 总结

AWS CloudFormation为Node.js应用提供了强大的基础设施管理能力:

  • 声明式配置:清晰描述AWS资源和依赖关系
  • 版本控制:基础设施代码可以版本化管理
  • 回滚能力:自动回滚失败的部署
  • 嵌套栈:模块化和可重用的模板设计
  • AWS集成:与AWS服务深度集成

通过合理的模板设计和参数化配置,可以实现高效、可维护的AWS基础设施管理。

🔗 相关资源