mcpField
C
cloudformation

cloudformation

github.com/bagelhole云与 DevOps1.2k311 次安装23 周前更新
代码仓库

关于 cloudformation

cloudformation 是使用原生 CloudFormation 模板部署和管理 AWS 基础设施的技能,涵盖可重复的多环境部署、安全更新、漂移检测和嵌套堆栈组织。

主要功能

  1. 01验证和检查模板
  2. 02部署参数化堆栈
  3. 03通过变更集预览更新
  4. 04检测并修复配置漂移
  5. 05用嵌套堆栈组织基础设施
  6. 06查看堆栈事件并排查故障

适用场景

  1. 01在开发、预发布和生产环境部署相同基础设施
  2. 02在更新堆栈前审查拟议变更
  3. 03查找导致配置漂移的手动改动
  4. 04将大型基础设施定义拆分为嵌套堆栈

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill cloudformation

安装后在 Claude Code 中输入/cloudformation确认已加载。

附带文件references/cfn-syntax.md

详细介绍

CloudFormation

使用原生 CloudFormation 模板、变更集、嵌套堆栈和偏差检测部署 AWS 基础设施。

何时使用此技能

  • 使用 AWS 原生基础设施即代码部署 AWS 资源
  • 创建可重复使用、参数化的基础设施模板
  • 使用同一模板管理多环境部署(dev、staging、prod)
  • 借助变更集和回滚保护实现安全部署
  • 检测并修复配置偏差
  • 将大型基础设施拆分为嵌套堆栈
  • 在堆栈之间导出和导入值

前置条件

  • 已安装并配置 AWS CLI v2
  • IAM 权限:cloudformation:*,以及模板中所有资源所需的权限
  • (可选)已安装 cfn-lint,用于验证模板(pip install cfn-lint)
  • 用于存储大于 51,200 字节的模板的 S3 存储桶

模板结构

AWSTemplateFormatVersion: '2010-09-09'
Description: Production web application infrastructure

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: { default: "Environment" }
        Parameters: [Environment, InstanceType]
      - Label: { default: "Network" }
        Parameters: [VpcId, SubnetIds]

Parameters:
  Environment:
    Type: String
    AllowedValues: [dev, staging, prod]
    Default: dev

  InstanceType:
    Type: String
    Default: t3.micro
    AllowedValues: [t3.micro, t3.small, t3.medium, t3.large]

  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC to deploy into

  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Description: Subnets for the application

Conditions:
  IsProd: !Equals [!Ref Environment, prod]
  CreateReadReplica: !Equals [!Ref Environment, prod]

Mappings:
  RegionAMI:
    us-east-1:
      AL2023: ami-0abcdef1234567890
    us-west-2:
      AL2023: ami-0fedcba9876543210

Resources:
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub '${Environment}-web-sg'
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub '${Environment}-web-sg'

  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub '${Environment}-web'
      LaunchTemplateData:
        ImageId: !FindInMap [RegionAMI, !Ref 'AWS::Region', AL2023]
        InstanceType: !If [IsProd, t3.large, !Ref InstanceType]
        MetadataOptions:
          HttpTokens: required
        SecurityGroupIds:
          - !Ref SecurityGroup

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub '${Environment}-web-asg'
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MinSize: !If [IsProd, 2, 1]
      MaxSize: !If [IsProd, 10, 3]
      DesiredCapacity: !If [IsProd, 4, 1]
      VPCZoneIdentifier: !Ref SubnetIds
      TargetGroupARNs:
        - !Ref TargetGroup
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      Tags:
        - Key: Name
          Value: !Sub '${Environment}-web'
          PropagateAtLaunch: true
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: !If [IsProd, 2, 0]
        MaxBatchSize: 1
        PauseTime: PT5M
        WaitOnResourceSignals: true
        SuspendProcesses:
          - HealthCheck
          - ReplaceUnhealthy
          - AZRebalance
          - AlarmNotification
          - ScheduledActions

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${Environment}-web-tg'
      Port: 8080
      Protocol: HTTP
      VpcId: !Ref VpcId
      TargetType: instance
      HealthCheckPath: /health
      HealthCheckIntervalSeconds: 30
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3

Outputs:
  SecurityGroupId:
    Description: Web security group ID
    Value: !Ref SecurityGroup
    Export:
      Name: !Sub '${Environment}-WebSecurityGroup'

  AutoScalingGroupName:
    Description: ASG name
    Value: !Ref AutoScalingGroup
    Export:
      Name: !Sub '${Environment}-WebASG'

堆栈操作

# Validate a template
aws cloudformation validate-template --template-body file://template.yaml

# Lint with cfn-lint (catches more issues)
cfn-lint template.yaml

# Create a stack
aws cloudformation create-stack \
  --stack-name production-web \
  --template-body file://template.yaml \
  --parameters \
    ParameterKey=Environment,ParameterValue=prod \
    ParameterKey=VpcId,ParameterValue=vpc-abc123 \
    ParameterKey=SubnetIds,ParameterValue="subnet-aaa\\,subnet-bbb" \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
  --tags Key=Environment,Value=production Key=Team,Value=platform \
  --enable-termination-protection \
  --on-failure ROLLBACK

# Wait for stack creation
aws cloudformation wait stack-create-complete --stack-name production-web

# Describe stack status and outputs
aws cloudformation describe-stacks \
  --stack-name production-web \
  --query "Stacks[0].{Status:StackStatus,Outputs:Outputs}" \
  --output table

# List stack resources
aws cloudformation list-stack-resources --stack-name production-web \
  --query "StackResourceSummaries[].{Logical:LogicalResourceId,Physical:PhysicalResourceId,Type:ResourceType,Status:ResourceStatus}" \
  --output table

# Delete a stack
aws cloudformation delete-stack --stack-name dev-web
aws cloudformation wait stack-delete-complete --stack-name dev-web

变更集(安全更新)

# Create a change set to preview changes before applying
aws cloudformation create-change-set \
  --stack-name production-web \
  --change-set-name update-instance-type \
  --template-body file://template.yaml \
  --parameters \
    ParameterKey=Environment,ParameterValue=prod \
    ParameterKey=InstanceType,ParameterValue=t3.large \
    ParameterKey=VpcId,UsePreviousValue=true \
    ParameterKey=SubnetIds,UsePreviousValue=true \
  --capabilities CAPABILITY_IAM

# Describe the change set to review planned changes
aws cloudformation describe-change-set \
  --stack-name production-web \
  --change-set-name update-instance-type \
  --query "Changes[].{Action:ResourceChange.Action,Resource:ResourceChange.LogicalResourceId,Type:ResourceChange.ResourceType,Replacement:ResourceChange.Replacement}" \
  --output table

# Execute the change set (apply changes)
aws cloudformation execute-change-set \
  --stack-name production-web \
  --change-set-name update-instance-type

# Wait for update
aws cloudformation wait stack-update-complete --stack-name production-web

# Delete a change set without applying
aws cloudformation delete-change-set \
  --stack-name production-web \
  --change-set-name update-instance-type

偏差检测

# Start drift detection
DRIFT_ID=$(aws cloudformation detect-stack-drift \
  --stack-name production-web \
  --query 'StackDriftDetectionId' --output text)

# Check drift detection status
aws cloudformation describe-stack-drift-detection-status \
  --stack-drift-detection-id $DRIFT_ID

# View drifted resources
aws cloudformation describe-stack-resource-drifts \
  --stack-name production-web \
  --stack-resource-drift-status-filters MODIFIED DELETED \
  --query "StackResourceDrifts[].{Resource:LogicalResourceId,Status:StackResourceDriftStatus,Differences:PropertyDifferences}" \
  --output table

# Detect drift on a specific resource
aws cloudformation detect-stack-resource-drift \
  --stack-name production-web \
  --logical-resource-id SecurityGroup

嵌套堆栈

父模板:

AWSTemplateFormatVersion: '2010-09-09'
Description: Parent stack - full application

Parameters:
  Environment:
    Type: String
    AllowedValues: [dev, staging, prod]

Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-cfn-templates/network.yaml
      Parameters:
        Environment: !Ref Environment
        VpcCidr: "10.0.0.0/16"
      Tags:
        - Key: Environment
          Value: !Ref Environment

  DatabaseStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: NetworkStack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-cfn-templates/database.yaml
      Parameters:
        Environment: !Ref Environment
        VpcId: !GetAtt NetworkStack.Outputs.VpcId
        SubnetIds: !GetAtt NetworkStack.Outputs.PrivateSubnetIds

  AppStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: [NetworkStack, DatabaseStack]
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-cfn-templates/app.yaml
      Parameters:
        Environment: !Ref Environment
        VpcId: !GetAtt NetworkStack.Outputs.VpcId
        SubnetIds: !GetAtt NetworkStack.Outputs.PrivateSubnetIds
        DbEndpoint: !GetAtt DatabaseStack.Outputs.Endpoint

Outputs:
  VpcId:
    Value: !GetAtt NetworkStack.Outputs.VpcId
  AppUrl:
    Value: !GetAtt AppStack.Outputs.LoadBalancerDNS
# Package nested templates (uploads local references to S3)
aws cloudformation package \
  --template-file parent.yaml \
  --s3-bucket my-cfn-templates \
  --output-template-file packaged.yaml

# Deploy the packaged template
aws cloudformation deploy \
  --template-file packaged.yaml \
  --stack-name production-app \
  --parameter-overrides Environment=prod \
  --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND \
  --tags Environment=production

内置函数参考

# Ref - reference a parameter or resource
SecurityGroupId: !Ref SecurityGroup

# GetAtt - get an attribute of a resource
SecurityGroupArn: !GetAtt SecurityGroup.GroupId

# Sub - string substitution
BucketName: !Sub '${Environment}-${AWS::AccountId}-data'

# Join - concatenate strings
PolicyArn: !Join ['', ['arn:aws:iam::', !Ref 'AWS::AccountId', ':policy/MyPolicy']]

# Select - pick from a list
FirstSubnet: !Select [0, !Ref SubnetIds]

# Split - split a string
FirstPart: !Select [0, !Split ['-', !Ref 'AWS::StackName']]

# If - conditional value
InstanceSize: !If [IsProd, t3.large, t3.micro]

# Equals - condition definition
Conditions:
  IsProd: !Equals [!Ref Environment, prod]

# ImportValue - cross-stack reference
VpcId: !ImportValue production-VpcId

# Cidr - generate CIDR blocks
Subnets: !Cidr [!GetAtt VPC.CidrBlock, 6, 8]

# GetAZs - list availability zones
AZ: !Select [0, !GetAZs '']

堆栈策略(防止意外替换)

# Apply a stack policy that prevents replacement of the database
aws cloudformation set-stack-policy \
  --stack-name production-web \
  --stack-policy-body '{
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "Update:*",
        "Principal": "*",
        "Resource": "*"
      },
      {
        "Effect": "Deny",
        "Action": "Update:Replace",
        "Principal": "*",
        "Resource": "LogicalResourceId/Database"
      },
      {
        "Effect": "Deny",
        "Action": "Update:Delete",
        "Principal": "*",
        "Resource": "LogicalResourceId/Database"
      }
    ]
  }'

堆栈事件和调试

# View stack events (most recent first)
aws cloudformation describe-stack-events \
  --stack-name production-web \
  --query "StackEvents[?ResourceStatus=='CREATE_FAILED' || ResourceStatus=='UPDATE_FAILED'].{Time:Timestamp,Resource:LogicalResourceId,Status:ResourceStatus,Reason:ResourceStatusReason}" \
  --output table

# Continue a rollback that is stuck
aws cloudformation continue-update-rollback \
  --stack-name production-web \
  --resources-to-skip SecurityGroup

# Cancel an in-progress update
aws cloudformation cancel-update-stack --stack-name production-web

# Get template from an existing stack
aws cloudformation get-template \
  --stack-name production-web \
  --template-stage Processed \
  --query TemplateBody \
  --output text > current-template.yaml

故障排除

问题原因解决方法
IAM 资源出现 CREATE_FAILED缺少 CAPABILITY_IAM添加 --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
堆栈卡在 UPDATE_ROLLBACK_FAILED 状态资源无法回滚使用 continue-update-rollback 和 --resources-to-skip
嵌套堆栈失败模板 URL 错误或 S3 访问被拒绝使用 aws cloudformation package 上传;检查存储桶策略
循环依赖错误两个资源相互引用使用第三个资源打破循环,或使用 DependsOn
检测到偏差在 CloudFormation 之外进行了手动更改重新应用模板,或更新模板以匹配当前状态
变更集显示没有更改模板和参数相同检查差异;确认更改是否仅涉及参数
模板验证错误YAML 语法错误或资源属性无效运行 cfn-lint;对照文档检查属性名称
导出名称已存在另一个堆栈使用了相同的导出名称使用唯一的导出名称,例如 !Sub '${AWS::StackName}-Name'
删除失败——资源正在使用堆栈外部的依赖资源先移除依赖项;检查安全组引用

相关技能

评论

支持 Markdown