Exziled
Exziled

Reputation: 493

Cloudformation nested stacks without rollback root stack

I currently have a "master.yaml" template that runs "service-a.yaml" and "service-b.yaml" then "service-c.yaml" which relies on outputs from service-a and service-b.

Is there a way to break this nested stack into multiple nested stacks? That way when something deep inside "service-c" fails it doesn't cause a rollback all the way up the chain? I want to kick off A+B in parallel and then C when they are finished in an automated fashion.

I could have a master.yaml which builds "service-a" and "service-b" then manually kick off "service-c" when they're done but I would like to automate this somehow?

Upvotes: 0

Views: 729

Answers (1)

Biplob Biswas
Biplob Biswas

Reputation: 1891

You can create a stack with Codebuild project and Codepipeline (Basically performing CI/CD) to trigger one stack after the other and thus each stack would fail and roll back separately.

For example the cloudformation template would have a Codebuld project as follows

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        ComputeType: BUILD_GENERAL1_LARGE
        Image: aws/codebuild/python:3.6.5
        Type: LINUX_CONTAINER
        EnvironmentVariables:
        - Name: bucket
          Value: !Ref ArtifactStoreBucket
          Type: PLAINTEXT
        - Name: prefix
          Value: build
          Type: PLAINTEXT
      Name: !Ref AWS::StackName
      ServiceRole: !Ref CodeBuildRole
      Source:
        Type: CODEPIPELINE
        BuildSpec: stack/buildspec.yaml
      Tags:
      - Key: owner
        Value: !Ref StackOwner
      - Key: task
        Value: !Ref RepositoryName

In the buildspec.yaml file, you can package the cloudfromation templates as follows:

  - aws cloudformation package --template-file master.yaml
                               --s3-bucket $bucket --s3-prefix $prefix
                               --output-template-file master-template.yaml

  - aws cloudformation package --template-file service-a.yaml
                               --s3-bucket $bucket --s3-prefix $prefix
                               --output-template-file service-a-template.yaml

And finally, a codepipeline stage which links all together. For example in the below-provided snippet, you can have source code triggered by codecommit. So every push to the repository would build your pipeline automatically.

  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref ArtifactStoreBucket
        Type: S3
      DisableInboundStageTransitions: []
      Name: !Sub "${AWS::StackName}"
      RoleArn: !GetAtt [PipelineRole, Arn]
      Stages:
      # Stage 1 - CodeUpdate Stage
      - Name: CodeUpdate
        Actions:
        - Name: SourceCodeUpdate
          ActionTypeId:
            Category: Source
            Owner: AWS
            Version: '1'
            Provider: CodeCommit
          OutputArtifacts:
          - Name: SourceCode
          Configuration:
            PollForSourceChanges: 'false'
            RepositoryName: !Ref RepositoryName
            BranchName: !Ref BranchName
          RunOrder: '1'
      # Stage 2 - Build Stage
      - Name: Build
        Actions:
        - Name: UpdateLambda
          ActionTypeId:
            Category: Build
            Owner: AWS
            Version: '1'
            Provider: CodeBuild
          InputArtifacts:
          - Name: SourceCode
          OutputArtifacts:
          - Name: BuildArtifact
          Configuration:
            ProjectName: !Ref 'CodeBuildProject'
          RunOrder: '1'
      # Stage 3 - Build master stack
      - Name: MasterSetup
        Actions:
        - Name: CreateMasterChangeset
          ActionTypeId:
            Category: Deploy
            Owner: AWS
            Version: '1'
            Provider: CloudFormation
          InputArtifacts:
          - Name: BuildArtifact
          Configuration:
            ActionMode: CHANGE_SET_REPLACE
            StackName: !Sub "${AWS::StackName}-master"
            ChangeSetName: !Sub "${AWS::StackName}-master-update"
            RoleArn: !GetAtt [CFNRole, Arn]
            TemplatePath: BuildArtifact::master-template.yaml
            Capabilities: CAPABILITY_IAM
            ParameterOverrides: !Sub
            - |
              {
                "MasterStack": "${w}",
                "StackOwner": "${x}",
                "Task": "${y}"
              }
            - {
              w: !Sub '${AWS::StackName}',
              x: !Sub '${StackOwner}',
              y: !Sub '${RepositoryName}'
            }
          RunOrder: '1'
        - Name: ExecuteMasterChangeset
          ActionTypeId:
            Category: Deploy
            Owner: AWS
            Version: '1'
            Provider: CloudFormation
          Configuration:
            ActionMode: CHANGE_SET_EXECUTE
            StackName: !Sub "${AWS::StackName}-master"
            ChangeSetName: !Sub "${AWS::StackName}-master-update"
          RunOrder: '2'
      # Stage 4 - Build service-a stack
      - Name: ServiceASetup
        Actions:
        - Name: CreateServiceAChangeset
          ActionTypeId:
            Category: Deploy
            Owner: AWS
            Version: '1'
            Provider: CloudFormation
          InputArtifacts:
          - Name: BuildArtifact
          Configuration:
            ActionMode: CHANGE_SET_REPLACE
            StackName: !Sub "${AWS::StackName}-service-a"
            ChangeSetName: !Sub "${AWS::StackName}-service-a-update"
            RoleArn: !GetAtt [CFNRole, Arn]
            TemplatePath: BuildArtifact::service-a-template.yaml
            Capabilities: CAPABILITY_IAM
            ParameterOverrides: !Sub
            - |
              {
                "MasterStack": "${w}",
                "StackOwner": "${x}",
                "Task": "${y}"
              }
            - {
              w: !Sub '${AWS::StackName}',
              x: !Sub '${StackOwner}',
              y: !Sub '${RepositoryName}'
            }
          RunOrder: '1'
        - Name: ExecuteServiceAChangeset
          ActionTypeId:
            Category: Deploy
            Owner: AWS
            Version: '1'
            Provider: CloudFormation
          Configuration:
            ActionMode: CHANGE_SET_EXECUTE
            StackName: !Sub "${AWS::StackName}-service-a"
            ChangeSetName: !Sub "${AWS::StackName}-service-a-update"
          RunOrder: '2'

If you want to have stacks executing in parallel, you can add more than 1 stack in each stage.

Obviously you need to setup the roles and buckets yourself and this should give you basic idea how to get started.

For more information, you can read up more about codepipeline as follows: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-cd-pipeline.html https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html

Upvotes: 1

Related Questions