aws cloudformation 理解常见资源的部署和使用

参考

cloudformation是aws的iac工具,以下简称cfn

环境搭建——cfn命令行工具

创建堆栈

aws cloudformation create-stack --stack-name testtemp \
    --template-body file://testtemp.yaml
    # --parameters ParameterKey=KeyPairName,ParameterValue=TestKey

删除堆栈

aws cloudformation delete-stack \
    --stack-name testtemp

查看堆栈event

aws cloudformation describe-stack-events \
    --stack-name  \
    --max-items 2

切换格式,json和yaml的转换

pip install cfn-flip
cfn-flip --version
cfn-flip example_parameter.json example_parameter.yaml

方便起见可以编写脚本将常用测试命令整合在一起,只需要修改参数即可快速重复测试模板

#!/bin/bash
WEBURL=$2
FILE_NAME=resources.yaml
BUCKET_NAME=zhaojiew-test
FILEPATH=cfn
BUCKET_FILEPATH=s3://$BUCKET_NAME/$FILEPATH/
STACK_NAME=aws
CHANGESET_NAME=awsset
ROLE_ARN=arn:aws-cn:iam::xxxxxxxx:role/MyCloudfotmationRole

echo STACK_NAME   "====>" $STACK_NAME
echo FILE_NAME    "====>" $FILE_NAME 
echo ROLE_ARN     "====>" $ROLE_ARN 
echo BUCKET_NAME  "====>" $BUCKET_NAME
echo FILEPATH     "====>" $FILEPATH

case $1 in
    "create")
        echo " =================== create stack ===================";
        aws cloudformation create-stack --stack-name $STACK_NAME\
            --template-body file://$FILE_NAME \
	        --role-arn $ROLE_ARN \
            --capabilities CAPABILITY_IAM
            # --tags Key=who,Value=justtest \
            # --parameters ParameterKey=InstanceType,ParameterValue=t2.small \
            #              ParameterKey=SubnetIDs,ParameterValue=SubnetID1\\,SubnetID2
    ;;
    "urlcreate")
        echo " =================== create stack from url ===================";
        aws s3 cp $FILE_NAME $BUCKET_FILEPATH
        TEMPURL=https://$BUCKET_NAME.s3.cn-north-1.amazonaws.com.cn/$FILEPATH/$FILE_NAME
        echo $TEMPURL
        aws cloudformation create-stack --stack-name $STACK_NAME\
            --template-url $TEMPURL \
	        --role-arn $ROLE_ARN 
            # --tags Key=who,Value=justtest \
            # --parameters ParameterKey=InstanceType,ParameterValue=t2.small \
            #              ParameterKey=SubnetIDs,ParameterValue=SubnetID1\\,SubnetID2
    ;;
    "delete")
        echo " =================== delete stack ==================="
        aws cloudformation delete-stack --stack-name $STACK_NAME
    ;;
   "update")
        echo " =================== update stack ==================="
        aws cloudformation update-stack --stack-name $STACK_NAME\
            --template-body file://$FILE_NAME \
	        --role-arn $ROLE_ARN \
            --capabilities CAPABILITY_NAMED_IAM
    ;;
    "updateset")
        echo " =================== create stackset ==================="
        aws cloudformation create-change-set \
            --stack-name $STACK_NAME \
            --change-set-name $CHANGESET_NAME \
            --template-body file://$FILE_NAME\
             --capabilities CAPABILITY_IAM
    ;;
    "event")
        echo " =================== describe stack event ==================="
        aws cloudformation describe-stack-events --stack-name aws --max-items 5 \
            --query 'StackEvents[].{CStatus: ResourceStatus,Rreason: ResourceStatusReason}' --output table 
    ;;
    "download")
        echo " =================== download external url ==================="
        wget -nc $WEBURL -O $FILE_NAME
        echo "file name is =====>>>>> " ${WEBURL##*/}
    ;;
    "upload")
        echo " =================== upload file to bucket ==================="
        aws s3 cp $FILE_NAME $BUCKET_FILEPATH
        echo https://$BUCKET_NAME.s3.cn-north-1.amazonaws.com.cn/$FILEPATH/$FILE_NAME
    ;;
    "valid")
        echo " =================== validate templaet ==================="
        aws cloudformation validate-template --template-body file://$FILE_NAME
    ;;
    *)
        echo "Input Args Error..."
    ;;
esac

cfn基础——顶级字段

cfn顶级字段,只有resource是必须的,关于cloutformation的模板剖析

AWSTemplateFormatVersion: 'version date' (optional) # version of the CloudFormation template. Only accepted value is '2010-09-09'
Description: 'String' (optional) # a text description of the Cloudformation template
Metadata: 'template metadata' (optional) # objects that provide additional information about the template
Parameters: 'set of parameters' (optional) # a set of inputs used to customize the template
Rules: 'set of rules' (optional) # a set of rules to validate the parameters provided at deployment/update
Mappings: 'set of mappings' (optional) # a mapping of keys and associated values
Conditions: 'set of conditions' (optional) # conditions that control whether certain resources are created
Transform: 'set of transforms' (optional) # for serverless applications
Resources: 'set of resources' (required) # a components of your infrastructure
Hooks: 'set of hooks' (optional) # Used for ECS Blue/Green Deployments
Outputs: 'set of outputs' (optional) # values that are returned whenever you view your stack's properties

堆栈是cfn模板的部署,单个cfn模板可以创建多个stack

如果堆栈创建失败会回滚,删除所有已创建的资源。如果无法删除则会保留资源直到能够成功删除堆栈

创建堆栈——cfn 权限

通过iam权限控制cfn访问,控制台使用cfn需要比cli和api更多的额外权限,例如上传文件到s3(每个区域都有默认的cf模板的存储桶),列出下拉参数所需的desctibe*权限。可见如果通过cli创建堆栈,实际上并不会将文件上传到s3桶中

cfn创建资源的api调用来自cfn的ip地址,因此不要使用aws:SourceIp条件键进行限制

通过--role-arn可以指定cfn可以使用的角色,cfn的所有堆栈操作都通过该角色完成。如果不指定,则使用之前关联过的角色。如果没有可用角色,则会从用户凭证生成临时会话

aws cloudformation create-stack --stack-name testtemp \
	--role-arn arn:aws-cn:iam::xxxxxxx:role/MyCloudfotmationRole
    --template-body file://testtemp.yaml

Amazon CloudFormation will use this role for all stack operations. Other users that have permissions to operate on this stack will be able to use this role, even if they don’t have permission to pass it. Ensure that this role grants least privilege.

这意味着如果其他用户有权访问堆栈,即使cfn的角色没有传递,其他用户也能通过cfn间接使用该角色

创建资源——简单创建s3桶

简单s3 bucket 模板

#testtemp.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CloudFormation workshop - Template and stack
Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

使用cli创建堆栈

aws cloudformation create-stack --stack-name temptest \
    --template-body file://temptest.yaml

内置函数——具备profile的ec2实例

由于该行为会创建iam 角色,因此在cli命令和console中要进行确认 --capabilities CAPABILITY_IAM

在模板中使用了内置函数ref引用其他资源,可以在output中验证

  • 如果引用资源会返回物理id(对特定资源例如ec2实例会返回实例id
  • 如果是参数会返回参数值

policy策略有两种写法,内联和托管策略,以下为用户创建的托管策略

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  myEC2Instance:
    Type: AWS::EC2::Instance
    Version: '2009-05-15'
    Properties:
      ImageId: ami-0ab68f4313c5aff87
      InstanceType: t2.micro
      Monitoring: 'true'
      DisableApiTermination: 'false'
      IamInstanceProfile:
        !Ref RootInstanceProfile
  RootRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com.cn
          Action:
          - sts:AssumeRole
      Path: "/"
  RolePolicies:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: s3readonly
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action: 
          - "s3:Get*"
          - "s3:List*"
          Resource: "*"
      Roles:
      - !Ref RootRole
  RootInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
      - !Ref RootRole

Outputs:
  InstanceId:
    Description: Instance ID of the instance you create
    Value: !Ref myEC2Instance
  RoleID:
    Description: role ID of the instance you create
    Value: !Ref RootRole

伪参数——打包lambda获取ssm参数

使用伪参数获取堆栈id,区域和账号等信息,例如以下使用AWS::Partition获取中国区分区为aws-cn

意味着可以动态适配不同账号和区域的环境进行堆栈部署。

创建lambada函数使用zip对inline function进行打包,大小不超过4MB

使用内置函数sub对伪参数进行替换,sub可以分为有mapping和无mapping两种,对于伪参数的用法为无map。

sub函数中使用${paramater}能间接获取refFn::GetAttr等函数的调用结果

$(!parameter)将原样解析不进行替换

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Pseudo parameters (uksb-1q9p31idr).

Parameters:
  DatabaseUsername:
    Description: Value to be used with the dbUsername SSM parameter. The default value is set to 'alice', which users can override when creating a CloudFormation stack.
    Type: String
    Default: alice
    AllowedPattern: ^[a-z0-9]{5,12}$

  S3BucketNamePrefix:
    Description: The prefix to use for your S3 bucket
    Type: String
    Default: my-demo-bucket
    AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$
    ConstraintDescription: Bucket name prefix can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-).
    MinLength: 3

Resources:
  BasicParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: dbUsername
      Type: String
      Value: !Ref DatabaseUsername
      Description: SSM Parameter for database username.

  DemoRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: ssm-least-privilege
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: ssm:GetParameter
                Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${BasicParameter}'

  DemoLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.lambda_handler
      Role: !GetAtt DemoRole.Arn
      Runtime: python3.8
      Code:
        ZipFile: |
          import boto3
          client = boto3.client('ssm')
          def lambda_handler(event, context):
              response = client.get_parameter(Name='dbUsername')
              print(f'SSM dbUsername parameter value: {response["Parameter"]["Value"]}')

  DemoBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${S3BucketNamePrefix}-${AWS::Region}-${AWS::AccountId}'

映射——动态指定实例类型

map是个键值对,通过Fn::FindInMap(简写为!FindInMap)获取对应键的值

以下获取ec2实例的id实际是对应键EnvironmentType.Test.InstanceType 的值而已

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Mappings (uksb-1q9p31idr).

Parameters:
  EnvironmentType:
    Description: 'Specify the Environment type of the stack.'
    Type: String
    Default: Test
    AllowedValues:
      - Test
      - Prod
    ConstraintDescription: 'Specify either Test or Prod.'
    
Mappings:
  EnvironmentToInstanceType:
    Test:
      InstanceType: t2.micro
    Prod:
      InstanceType: t2.small

Resources:
  WebServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0ab68f4313c5aff87
      InstanceType: !FindInMap [EnvironmentToInstanceType, !Ref EnvironmentType, InstanceType]

输出——带eip的ec2实例

可以从cloudformation资源和属性参考中查找对应资源的返回值信息,可以查看refgetattr分别能够获取的值

getattrrefsub的区别在于

  • ref返回特定参数(参数值)和资源(物理id/资源id/资源名称)的值
  • getattr返回特定资源的属性值
  • 能够通过${}简介使用以上两者
AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Outputs (uksb-1q9p31idr).
    
Resources:
  WebServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-xxxxxx
      InstanceType: t2.micro

  WebServerEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId: !Ref WebServerInstance

Outputs:
  WebServerPublicDNS:
    Description: Public DNS of EC2 instance
    Value: !GetAtt WebServerInstance.PublicDnsName

  WebServerElasticIP:
    Description: Elastic IP assigned to EC2
    Value: !Ref WebServerEIP

返回值——带策略s3桶和带安全组ec2实例

创建ec2实例时,可以指定从ssm参数中获取最新的ami

每个ami都拥有公开的ssm参数空间,如下

# 获取公共ami列表
aws ssm get-parameters-by-path --path "/aws/service/ami-amazon-linux-latest"
# 使用公共ssm参数查询ami
aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

模板如下

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Resource Return Values Lab (uksb-1q9p31idr).

Parameters:
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
    
Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      Tags:
        - Key: Purpose
          Value: AWS CloudFormation Workshop

  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - s3:*
            Effect: Deny
            Resource:
              - !GetAtt S3Bucket.Arn
              - !Sub '${S3Bucket.Arn}/*'
            Principal: '*'
            Condition:
              Bool:
                aws:SecureTransport: false
	
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: t2.micro
      SecurityGroups:
        - !Ref InstanceSecurityGroup

  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      
Outputs:
  S3BucketDomainName:
    Description: IPv4 DNS name of the bucket.
    Value: !GetAtt S3Bucket.DomainName
    
  InstanceID:
    Description: The ID of the launched instance
    Value: !Ref Ec2Instance

  PublicIP:
    Description: Public IP of the launched instance
    Value: !GetAtt Ec2Instance.PublicIp

  SecurityGroupId:
    Description: ID of the security group created
    Value: !GetAtt InstanceSecurityGroup.GroupId

开启ssm和使用userdata——定制ec2实例

开启ssm控制台登录功能的ec2实例

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Session manager (uksb-1q9p31idr).

Resources:
  SSMIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws-cn:iam::aws:policy/AmazonSSMManagedInstanceCore

  WebServerInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref SSMIAMRole

  WebServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref WebServerInstanceProfile
      ImageId: ami-0ab68f4313c5aff87
      InstanceType: t2.micro


Outputs:
  WebServerPublicDNS:
    Description: Public DNS of EC2 instance
    Value: !GetAtt WebServerInstance.PublicDnsName

使用userdata,userdata需要通过内置函数base64进行编码

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - User data (uksb-1q9p31idr).

Resources:

  WebServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref WebServerInstanceProfile
      ImageId: ami-xxxxxx
      InstanceType: t2.micro
      UserData: !Base64 |
        #!/bin/bash
        yum update -y
        yum install -y httpd php
        systemctl start httpd
        systemctl enable httpd
        usermod -a -G apache ec2-user
        chown -R ec2-user:apache /var/www
        chmod 2775 /var/www
        find /var/www -type d -exec chmod 2775 {} \;
        find /var/www -type f -exec chmod 0664 {} \;
        cat << 'EOF' > /var/www/html/index.php
          <!DOCTYPE html>
          <html>
          <body>
            <center>
              <?php
              # Get the instance ID from meta-data and store it in the $instance_id variable
              $url = "http://169.254.169.254/latest/meta-data/instance-id";
              $instance_id = file_get_contents($url);
              # Get the instance's availability zone from metadata and store it in the $zone variable
              $url = "http://169.254.169.254/latest/meta-data/placement/availability-zone";
              $zone = file_get_contents($url);
              ?>
              <h2>EC2 Instance ID: <?php echo $instance_id ?></h2>
              <h2>Availability Zone: <?php echo $zone ?></h2>
            </center>
          </body>
          </html>
        EOF

helper脚本——监听cfn资源变动

helper脚本能够在原来的模板上对应用程序进行微调(不需要重新部署和创建堆栈资源),默认helper可以不需要凭证,请求会被限制在堆栈中的实例中

帮助脚本比较难理解一共有4个

  • cfn-init,通过获取stack元数据对实例进行配置,包括安装包,创建文件和启动服务
  • cfn-signal,当ec2创建和配置完毕向cfn发送信号。当结合creationpolicy和asg的WaitOnResourceSignals策略,会形成类似waitgroup的机制,暂停执行堆栈操作,收到指定数量的信号之后继续执行堆栈行为。因此signal程序可以发送信号给cfn
  • cfn-get-metadata,获取cfn元数据,可以手动获取看看和cfn中的配置一致
sudo /opt/aws/bin/cfn-get-metadata  --region cn-north-1 --stack aws -r WebServerInstance
{
    "AWS::CloudFormation::Init": {
        "config": {
            "files": {
                "/etc/cfn/cfn-hup.conf": {
                    "mode": "256",
                    "owner": "root",
                    "content": "[main]\nstack=arn:aws-cn:cloudformation:cn-north-1:xxxxxxx:stack/aws/aa931910-6ee0-11ed-9669-0e2f05fc8512\nregion=cn-north-1\ninterval=1\n",
                    "group": "root"
                },
                "/var/www/html/index.php": {
                    "mode": "420",
                    "owner": "apache",
                    "content": "<!DOCTYPE html>\n<html>\n<body>\n  <center>\n    <h2>EC2 Instance ID: <?php echo hello world ?></h2>\n  </center>\n</body>\n</html>\n",
                    "group": "apache"
                },
                "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
                    "content": "[cfn-auto-reloader-hook]\ntriggers=post.update\npath=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\naction=/opt/aws/bin/cfn-init --stack aws --resource WebServerInstance --region cn-north-1\nrunas=root\n"
                }
            },
            "services": {
                "sysvinit": {
                    "cfn-hup": {
                        "files": [
                            "/etc/cfn/cfn-hup.conf",
                            "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
                        ],
                        "ensureRunning": "true",
                        "enabled": "true"
                    },
                    "httpd": {
                        "ensureRunning": "true",
                        "enabled": "true"
                    }
                }
            },
            "packages": {
                "yum": {
                    "php": [],
                    "httpd": []
                }
            }
        }
    }
}
  • cfn-hup,后台进程检测cfn元数据的变更,配置文件为cfn-hup.conf,钩子文件在hooks.d

以下将模板精简之后可以看到在WebServerInstance资源中主要分为MetadataProperties两部分

  • Metadata是对cfn资源元数据的设置,通过init对实例进行配置,分为packages, groups, users, sources, files, commands, services,当调用/opt/aws/bin/cfn-init时会执行全部配置

  • 在实例内部安装aws-cfn-bootstrap,在al2类型实例上已经预装

  • cfn-hup后台启动并读取配置文件,指定检查元数据间隔interval为1分钟,定期调用钩子。可以检查cfn资源,cfn资源物理id和cfn元数据。action指定检测到更改后执行的行为,实际上就是个shell脚本,下面的配置表明重新执行了cfn-init文件。此时如果在更改集中修改了ec2实例元数据中的php脚本则会触发更改刷新网页内容

    [cfn-auto-reloader-hook]
    triggers=post.update
    path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init
    action=/opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource WebServerInstance --region 
    runas=root
    
  • cfn-signal使用cfn-init的返回结果$?进行调用,此时如果实例配置失败则堆栈会回滚

  • 创建WebServerInstance资源使用了CreationPolicy策略,此时只有ec2实例使用cfn-signal发送信号之后,才会完成ec2实例资源的创建。指定需要收到信号数量为1,超时时间为10分钟
    资源策略

      WebServerInstance:
        CreationPolicy:
          ResourceSignal:
            Count: 1
            Timeout: PT10M
    
  • cfn-signal可以和资源策略相结合,但是只有部分资源支持CreationPolicy。同样指定UpdatePolicy配置WaitOnResourceSignals可以在更新asg时触发信号等待,确保asg中的每个实例都已经配置完成

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Helper scripts (uksb-1q9p31idr).

Resources:

  WebServerInstance:
    CreationPolicy:
      ResourceSignal:
        Count: 1
        Timeout: PT10M
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          packages:
            yum:
              httpd: []
              php: []
          files:
            /var/www/html/index.php:
              content: |
                <!DOCTYPE html>
                <html>
                <body>
                  <center>
                    <h2>EC2 Instance ID: <?php echo helloworld ?></h2>
                  </center>
                </body>
                </html>
              mode: 000644
              owner: apache
              group: apache
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
                interval=1
              mode: 000400
              owner: root
              group: root
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
                runas=root
          services:
            sysvinit:
              httpd:
                enabled: true
                ensureRunning: true
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
    Properties:
      ImageId: ami-02c8191b43515df27
      KeyName: "temp-key"
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
      InstanceType: t2.micro
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash -xe
          # Update aws-cfn-bootstrap to the latest
          yum install -y aws-cfn-bootstrap
          # Call cfn-init script to install files and packages
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
          # Call cfn-signal script to send a signal with exit code
          /opt/aws/bin/cfn-signal --exit-code $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}

条件判断——有条件地创建ec2的挂载卷

根据参数不同指定资源的创建与否,还可以根据条件指定属性和输出值

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Conditions at resource level (uksb-1q9p31idr).

Parameters:
  EnvType:
    Description: Specify the Environment type of the stack.
    Type: String
    AllowedValues:
      - test
      - prod
    Default: test
    ConstraintDescription: Specify either test or prod.

Conditions:
  IsProduction: !Equals
    - !Ref EnvType
    - prod

Resources:
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !If [IsProduction, t2.small, t2.micro]

  MountPoint:
    Type: AWS::EC2::VolumeAttachment
    Properties:
      InstanceId: !Ref EC2Instance
      VolumeId: !Ref Volume
      Device: /dev/sdh
    Condition: IsProduction

  Volume:
    Type: AWS::EC2::Volume
    Properties:
      Size: 2
      AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
      Encrypted: true
    Condition: IsProduction
    
Outputs:
  VolumeId:
    Value: !Ref Volume
    Condition: IsProduction

资源依赖——指定资源创建的现后顺序

在cfn中资源的依赖可以分为

  • 显式依赖,使用dependon参数
  • 隐式依赖,使用refgetattr参数

在没有依赖的情况下,cfn创建资源的方式时并行的。下面的sg隐式依赖ingressrule,sns显式依赖s3桶

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Resource Dependencies Lab with Ref and Fn::GetAtt Intrinsic Functions (uksb-1q9p31idr).

Resources:

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Example Security Group

  SecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !GetAtt SecurityGroup.GroupId
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      CidrIp: 0.0.0.0/0

  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:

  SNSTopic:
    Type: AWS::SNS::Topic
    DependsOn: S3Bucket
    Properties:

动态引用——设置lamda获取secret和parameter

提前在ssm中创建参数

aws ssm put-parameter \
    --name "/golden-images/amazon-linux-2" \
    --value YOUR_AMI_ID \
    --type "String" \
    --region YOUR_REGION

在cfn中动态引用参数,固定写法

Resources:
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      AvailabilityZone: !Select ["0", !GetAZs ""]
      InstanceType: t2.micro
      ImageId: '{{resolve:ssm:/golden-images/amazon-linux-2}}'
   
  DatabaseConnParams:
    Type: AWS::SecretsManager::Secret
    Properties:
      Description: Database Connection Parameters.
      Name: DatabaseConnParams
      SecretString: !Sub |
        {
          "RDS_HOSTNAME": "${Database.Endpoint.Address}",
          "RDS_PORT": "${Database.Endpoint.Port}",
          "RDS_USERNAME": "${DBUsername}",
          "RDS_PASSWORD": "${DBPassword}"
        }
  FunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Path: /

  HelloWorldFunction:
    Type: AWS::Lambda::Function
    Properties:
      Role: !GetAtt FunctionExecutionRole.Arn
      Handler: index.handler
      Environment:
        Variables:
          RDS_HOSTNAME: '{{resolve:secretsmanager:DatabaseConnParams:SecretString:RDS_HOSTNAME}}'
          RDS_PORT: '{{resolve:secretsmanager:DatabaseConnParams:SecretString:RDS_PORT}}'
      Runtime: python3.7
      Code:
        ZipFile: |
          import os
          def handler(event, context):
              RDS_HOSTNAME=os.getenv('RDS_HOSTNAME')
              RDS_PORT=os.getenv('RDS_PORT')
              return "Database: {}:{}".format(RDS_HOSTNAME,RDS_PORT)

嵌套堆栈——将模板化整为零

所谓嵌套堆栈就是将原本cfn模板中的资源单独分离出来,便于复用和灵活组合。使用时只需要在root template中进行引用即可。

例如创建一个root堆栈,引用vpc,ec2和iam的堆栈

在这里插入图片描述
对于需要引用的堆栈资源需要使用AWS::CloudFormation::Stack类型

以下模板创建stack资源,并指定了模板所在的s3 url和初始参数。stack资源之间相互引用output,通过Fn: : GetAtt 将值从子堆栈传递到根堆栈

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Nested stacks - Root template (uksb-1q9p31idr).

Parameters:
  S3BucketName:
    Type: String

  AvailabilityZones:
    Type: List<AWS::EC2::AvailabilityZone::Name>

  VPCName:
    Type: String
    Default: cfn-workshop-vpc

  VPCCidr:
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1Cidr:
    Type: String
    Default: 10.0.0.0/24

  PublicSubnet2Cidr:
    Type: String
    Default: 10.0.1.0/24

Resources:
  VpcStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub https://${S3BucketName}.s3.amazonaws.com.cn/vpc.yaml
      TimeoutInMinutes: 20
      Parameters:
        AvailabilityZones: !Join
          - ','
          - !Ref AvailabilityZones
        VPCCidr: !Ref VPCCidr
        VPCName: !Ref VPCName
        PublicSubnet1Cidr: !Ref PublicSubnet1Cidr
        PublicSubnet2Cidr: !Ref PublicSubnet2Cidr

  IamStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub https://${S3BucketName}.s3.amazonaws.com.cn/iam.yaml
      TimeoutInMinutes: 10

  EC2Stack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub https://${S3BucketName}.s3.amazonaws.com.cn/ec2.yaml
      TimeoutInMinutes: 20
      Parameters:
        EnvironmentType: Dev
        VpcId: !GetAtt VpcStack.Outputs.VpcId
        SubnetId: !GetAtt VpcStack.Outputs.PublicSubnet1
        WebServerInstanceProfile: !GetAtt IamStack.Outputs.WebServerInstanceProfile

Outputs:
  WebsiteURL:
    Value: !GetAtt EC2Stack.Outputs.WebsiteURL

vpc/ec2和iam的堆栈模板如同往常一样,由于在参数中使用ref进行了引用,实际上形成了隐式依赖关系

创建嵌套堆栈需要开启权限CAPABILITY_AUTO_EXPAND

If you want to create a stack from a stack template that contains macros and nested stacks, you must create the stack directly from the template using this capability.

开启nested视图后可以看到标记
在这里插入图片描述
删除嵌套堆需要删除root堆栈,否则会导致不一致

Deleting this stack will delete all stack resources. Resources will be deleted according to their DeletionPolicy.
It is recommended to delete through the root stack
Deleting a nested stack may result in an unstable state where the nested stack is out-of-sync with its root stack.

分层堆栈——堆栈间互通有无

嵌套堆栈的问题在于无法创建一对多关系,因为所有资源都在root中进行维护,本质上其实还是一个堆栈。分层堆栈就是创建多个堆栈,并实现跨堆栈的资源引用。

在这里插入图片描述
创建iam堆栈,并export instance profile

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  SSMIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  WebServerInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref SSMIAMRole

Outputs:
  WebServerInstanceProfile:
    Value: !Ref WebServerInstanceProfile
    Export:
      Name: cfn-workshop-WebServerInstanceProfile

导出的值可以在控制台看到
在这里插入图片描述

创建ec2并引用iam堆栈的instance profile

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  WebServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      SubnetId: sub-xxxx
      IamInstanceProfile: !ImportValue cfn-workshop-WebServerInstanceProfile
      ImageId: ami-xxxx
      InstanceType: t2.micro

打包和部署

我们在之前部署过lambda资源以及嵌套堆栈。lambda使用zip的方式将code打包,嵌套堆栈则是提前将模板上传到s3中进行引用,如果这样的组件很多工作量会非常大。

使用cfn打包功能可以简化步骤

package-and-deploy
├── infrastructure.template
└── lambda/
    ├── lambda_function.py
    └── requirements.txt

查看程序

cat lambda_function.py
from datetime import datetime
from pytz import timezone, utc
def handler(event, context):
    payload = event["time_zone"]
    message = "Current date/time in TimeZone *{}* is: {}".format(
        payload, _timezone(payload)
    )

    return {"message": message}
def _timezone(time_zone):
    utc_now = utc.localize(datetime.utcnow())
    compare_to_utc = utc_now.astimezone(timezone(time_zone))

    return compare_to_utc.strftime("%Y-%m-%d %H:%M")
    
cat requirements.txt
pytz==2021.3

模板如下

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS CloudFormation workshop - Package and deploy (uksb-1q9p31idr).

Resources:
  LambdaBasicExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  PythonFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: cfn-workshop-python-function
      Description: Python Function to return specific TimeZone time
      Runtime: python3.8
      Role: !GetAtt LambdaBasicExecutionRole.Arn
      Handler: lambda_function.handler
      Code: lambda/

对模板打包

aws cloudformation package \
  -         -template-file ./infrastructure.template \
            --s3-bucket "${BUCKET_NAME}" \
            --s3-prefix "${FILEPATH}" \
            --output-template-file ./infrastructure-packaged.template

打包后的模板,bucketname中的prefix/9497159249ecec70572a11a73f15e601文件实际上就是lambda/目录的压缩包

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation workshop - Package and deploy (uksb-1q9p31idr).
Resources:
  LambdaBasicExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  PythonFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: cfn-workshop-python-function
      Description: Python Function to return specific TimeZone time
      Runtime: python3.8
      Role:
        Fn::GetAtt:
        - LambdaBasicExecutionRole
        - Arn
      Handler: lambda_function.handler
      Code:
        S3Bucket: buckname
        S3Key: prefix/9497159249ecec70572a11a73f15e601
Outputs:
  LambdaFunction:
    Description: AWS Lambda Python Function
    Value:
      Ref: PythonFunction

更新堆栈

堆栈的更新行为有以下几种

  • Update with No Interruption,
  • Updates with Some Interruption,
  • Replacement

AWS资源类型参考中介绍了每个属性的更新行为

使用更改集更新堆栈能够预览堆栈的变动并确认修改

偏差检测——找不同

cloudformation只根据在模板中显示声明的属性检测偏离。模板中没有的属性是不检查的

检测偏差的结果如下,可以看到前后模板的差异,以及修改和删除资源的细节
在这里插入图片描述
当偏移发生后,有两种方式解决

  • 手动将资源还原为之前的状态,再次执行偏差检测
  • 将cfn模板修改为当前实际状态对应的模板,然后更新堆栈
  • 如果已经删除资源或不希望中断和替换资源,则需要先将资源的deletepolicy设置为retain,从堆栈中排除。然后使用新模板部署,并将手动创建或之前保留的现有资源import到堆栈中

使用模块——复用和分享

使用模块可以将cfn的一些常见配置和资源打包成组件,方便传播和复用

模块的结构如下

  • 模板片段(template fragment ),定义使用模块的资源和关联信息
  • 模板模式(module schema ),模板片段的契约

安装工具

pip install cloudformation-cli

初始化模块

mkdir module
cd module
cfn init
Initializing new project
Do you want to develop a new resource(r) or a module(m) or a hook(h)?.
>> m
What's the name of your module type?
(<Organization>::<Service>::<Name>::MODULE)

新建文件以下三部分

  • fragments/: cfn模板片段,sample片段默认创建s3桶。我们可以将自己的cfn模板写在这里

  • .rpdk-config: 模块的配置文件

  • rpdk.log: 记录日志

提交模块

cfn submit

在控制台找到刚创建的module
在这里插入图片描述
使用模块,实际上托管资源就是官方提供的module

AWSTemplateFormatVersion: 2010-09-09

Resources:

  myS3Bucket:
    Type: CFNWORKSHOP::EC2::VPC::MODULE

删除module

aws cloudformation deregister-type --type MODULE --type-name CFNWORKSHOP::EC2::VPC::MODULE
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值