部署CloudFormation template

接下来我们将添加想要部署的CloudFormation模板:

mkdir -p ./cloudformation
touch ./cloudformation/ec2-bastion.yml

ec2-bastion.yml内容如下:

---
AWSTemplateFormatVersion: "2010-09-09"
Description: EC2 bastion for latest AWS Linux 2 EC2 deployment

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "EC2 Configuration"
        Parameters:
          - pTagNameValue
          - pOperatingSystem
          - pInstanceType
          - pVolumeSize
          - pEbsDeleteOnTermination
      - Label:
          default: "Network Configuration"
        Parameters:
          - pVpc
          - pSubnet
    ParameterLabels:
      pOperatingSystem:
        default: "Operating System"
      pInstanceType:
        default: "Instance Type"
      pTagNameValue:
        default: "EC2 Name"
      pVolumeSize:
        default: "Volume Size"
      pEbsDeleteOnTermination:
        default: "Delete EBS Volume on Termination"
      pSubnet:
        default: "Subnet"
      pVpc:
        default: "VPC"

Parameters:
  pSubnet:
    Description: The subnet to launch the instance in to. It must be part of the VPC chosen above.
    Type: AWS::EC2::Subnet::Id
  pVpc:
    Description: The VPC to launch the EC2 instance in to.
    Type: AWS::EC2::VPC::Id
  pOperatingSystem:
    Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
    Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs"
  pInstanceType:
    Description: Desired Instance Size
    Type: String
    Default: m4.large
    AllowedValues:
      - m4.large
      - t3.small
      - t3.medium
      - t3.large
      - t3.xlarge
  pTagNameValue:
    Description: "Required: Enter the tag name you'd like applied to the instance. Tag Name gives the name to the EC2 instance."
    Type: String
    MinLength: 1
    Default: "myBastion"
  pVolumeSize:
    Description:
      Enter the number of GBs you want your volume to be. The minimum value
      is 8 GBs
    Type: Number
    Default: 50
    MinValue: 8
  pEbsDeleteOnTermination:
    Description: "Specify if the EBS volume should be deleted if EC2 is deleted."
    Type: String
    Default: true
    AllowedValues:
      - true
      - false

Rules:
  SubnetInVPC:
    Assertions:
      - Assert: !EachMemberIn
          - !ValueOfAll
            - AWS::EC2::Subnet::Id
            - VpcId
          - !RefAll "AWS::EC2::VPC::Id"
        AssertDescription: All subnets must in the VPC

Resources:
  rSecurityGroupDefault:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub "Default SG for SC Product ${pTagNameValue} "
      VpcId: !Ref pVpc
      SecurityGroupEgress:
        - Description: Outbound unrestricted traffic
          IpProtocol: "-1"
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Ref pTagNameValue

  rLinuxEc2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref pOperatingSystem
      Monitoring: false
      InstanceType: !Ref pInstanceType
      EbsOptimized: true
      SourceDestCheck: true
      SubnetId: !Ref pSubnet
      SecurityGroupIds:
        - !Ref rSecurityGroupDefault
      BlockDeviceMappings:
        - DeviceName: "/dev/xvda"
          Ebs:
            VolumeSize: !Ref pVolumeSize
            DeleteOnTermination: !Ref pEbsDeleteOnTermination
      Tags:
        - Key: Name
          Value: !Ref pTagNameValue
      UserData:
        Fn::Base64:
          yum update -y

  ## Instance Profiles
  ## EC2 IAM Roles
  rEc2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName:  !Sub "ec2-role-${AWS::StackName}"
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ec2.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore'
      - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy'

  rec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub "ec2-profile-${AWS::StackName}"
      Path: /
      Roles:
        - !Ref rEc2Role

Outputs:
  oLinuxEc2InstanceId:
    Description: Resource ID of the newly created EC2 instance
    Value: !Ref rLinuxEc2

  oLinuxEc2PrivateIP:
    Description: Private IP Address for EC2
    Value: !GetAtt rLinuxEc2.PrivateIp

提交到Github仓库:

git add -A
git commit -m "init feat"
git push -u origin feat-init

image-20231111222024829

修复Compliance问题

第一次提交后,查看Github的Actions页面,会发现它通过了build测试,但没有通过compliance:

image-20231111222104630

打开compliance检查,会发现它没有通过以下两项检查:

  • EC2_INSTANCE_PROFILE_ATTACHED:EC2上没有绑定instance profile
  • EC2_INSTANCE_DETAILED_MONITORING_ENABLED:EC2没有开启detailed monitoring

image-20231111222740490

为了修复第一个问题,在原来的代码上加上如下部分,为EC2实例绑定instance profile:

IamInstanceProfile: !Ref rec2InstanceProfile

image-20231111223014804

重新提交代码:

git add -A
git commit -m "fix instance profile"
git push -u origin feat-init

会发现新的提交现在依然有问题,但只有EC2_INSTANCE_DETAILED_MONITORING_ENABLED问题:

image-20231111223133301

添加Rule Suppression

上一节对功能分支 feat-init 的最后一次推送仍然存在合规性失败; 但这是由于Detailed Monitoring属性设置不正确造成的。 但是我们并不希望启用详细监控,在这种情况下合规性要求不适用。 现在需要抑制 rEc2Linux 资源上的规则,以允许其通过所有合规性测试。

ec2-bastion.yml上添加以下规则:

    Metadata:
      guard:
        SuppressedRules:
          - 'EC2_INSTANCE_DETAILED_MONITORING_ENABLED'

image-20231111223324450

重新提交代码:

git add -A
git commit -m "suppress detailed monitoring"
git push -u origin feat-init

Detailed Monitoring合规检查已被抑制,现在buildcomplicance检查均已通过:

image-20231111223446273

现在是时候继续执行后续步骤,来创建Pull request、合并分支和触发自动部署了。

合并分支触发部署

在创建deploy.yml的时候,我们指定了它只对main分支生效,所以前面在执行合规性检查时并没有触发deploy.yml里的命令:

image-20231111223703046

上一节我们的功能分支测试已全部通过,现在将更新的代码合并到我们的主分支中。

所有代码都应经过pull / merge request请求,这有助于促进审查并记录将部署到我们的 AWS 账户中的更改。 一旦 Pull-Request 获得批准,GitHub 部署操作将被触发并部署我们的 cloudformation 模板。

在github的Pull requests页面点击Compare & pull request

image-20231111223757993

main分支从feat-init分支拉取最新的更改,设置Assignee(由于当前只有自己在开发,设置成自己;在实际业务中建议设置为另外的开发者),点击Create pull request:

image-20231111223957346

将分支合并:

image-20231111224047402

合并pull request后,会触发deploy.yml中的指令,在AWS上部署这个CloudFormation模板:

image-20231111224126887

等一段时间后,部署成功。在Github的Actions页面中也能看到相关信息:

image-20231112151409317

总结

本章我们创建了一个简单的 git workflow,可自动执行所有CloudFormation代码的合规性测试以及在主分支代码上进行部署。