cfn-modules / docs

Rapid CloudFormation: Modular, production ready, open source.
https://github.com/cfn-modules
Apache License 2.0
260 stars 40 forks source link

Using Multiple Stacks #14

Open benwaine opened 5 years ago

benwaine commented 5 years ago

You might want to use this module without embedding it as a nested stack because you want to share the VPC stack with many other CloudFormation stacks.

Once the stack is created, you can use the stack name (in this case vpc) as the value for the VpcModule parameter in other cfn-modules.

These lines above from the VPC module README seem to imply that it's possible to create multiple stacks and feed the exports of one stack into the another.

I'm trying to do this with the idea that my vpc and alerting stacks will be referenced from multiple applications stacks.

After successfully creating the vpc and alerting stacks following the instructions in these docs, I attempt to create an application stack like so:

---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Stay Nimble API Application Stack'
Resources:

....

  Alb:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        VpcModule: 'sn-vpc'
        AlertingModule: 'sn-alerting' # optional
        Scheme: 'internet-facing'
        IdleTimeoutInSeconds: '60' # optional
      TemplateURL: './node_modules/@cfn-modules/alb/module.yml'

...

I get the following error when creating the application stack:

Embedded stack arn:aws:cloudformation:eu-west-2:357864412476:stack/sn-api-Alb-1830BJ8NFPN8E/9a619a80-5477-11e9-b51c-0ad01a6c5eea was not successfully created: No export named sn-alerting-Arn found

Looking at the alerting stack, there are no exports.

> aws cloudformation describe-stacks --stack-name=sn-alerting

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:357864412476:stack/sn-alerting/aa3e3800-5472-11e9-bc38-06afee4bb078",
            "StackName": "sn-alerting",
            "ChangeSetId": "arn:aws:cloudformation:eu-west-2:357864412476:changeSet/awscli-cloudformation-package-deploy-1554118706/b49bfd6d-fa00-4760-af0c-c18dc88db238",
            "Description": "Stay Nimble Alerting Stack",
            "CreationTime": "2019-04-01T11:38:27.093Z",
            "LastUpdatedTime": "2019-04-01T11:38:32.419Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

Looking at the nested stack created by my alerting stack I see exports including the expected ARN.


> aws cloudformation describe-stacks --stack-name=sn-alerting-Alerting-1VKQQLE2KMQ26

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:357864412476:stack/sn-alerting-Alerting-1VKQQLE2KMQ26/b04d9470-5472-11e9-9aec-02d140453e24",
            "StackName": "sn-alerting-Alerting-1VKQQLE2KMQ26",
            "Description": "cfn-modules: Alerting",
            "Parameters": [
                {
                    "ParameterKey": "Email",
                    "ParameterValue": ""
                },
                {
                    "ParameterKey": "FallbackEmail",
                    "ParameterValue": ""
                },
                {
                    "ParameterKey": "HttpsEndpoint",
                    "ParameterValue": ""
                },
                {
                    "ParameterKey": "HttpEndpoint",
                    "ParameterValue": ""
                }
            ],
            "CreationTime": "2019-04-01T11:38:37.624Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": true,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "ModuleId",
                    "OutputValue": "alerting"
                },
                {
                    "OutputKey": "ModuleVersion",
                    "OutputValue": "1.0.1"
                },
                {
                    "OutputKey": "Arn",
                    "OutputValue": "arn:aws:sns:eu-west-2:357864412476:sn-alerting-Alerting-1VKQQLE2KMQ26-Topic-1JTN2NUAX7RAV",
                    "ExportName": "sn-alerting-Alerting-1VKQQLE2KMQ26-Arn"
                },
                {
                    "OutputKey": "StackName",
                    "OutputValue": "sn-alerting-Alerting-1VKQQLE2KMQ26"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "ParentId": "arn:aws:cloudformation:eu-west-2:357864412476:stack/sn-alerting/aa3e3800-5472-11e9-bc38-06afee4bb078",
            "RootId": "arn:aws:cloudformation:eu-west-2:357864412476:stack/sn-alerting/aa3e3800-5472-11e9-bc38-06afee4bb078",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

Is the design pattern I'm attempting possible? Have I missed something in terms of how to identify the stack in "child stacks"?

michaelwittig commented 5 years ago

Hi @benwaine your alerting stack seems to be named sn-alerting-Alerting-1VKQQLE2KMQ26 not sn-alerting. You can create a normal (not nested) CloudFormation stack based on cfn-modules/alerting, name it sn-alerting and your idea should work.

Let me know if this works for you.

ambsw-technology commented 4 years ago

We reuse modules like this by creating exports. For example, this AlertingModule is shared by all of our (dedicated-to-client) environments for our APEX application:

Resources:
  AlertingModule:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        Email: 'team@org.com' # optional
        HttpEndpoint: 'http://org.com/webhook' # optional
        HttpsEndpoint: 'https://org.com/webhook' # optional
        FallbackEmail: 'user@org.net' # optional
      TemplateURL: './node_modules/@cfn-modules/alerting/module.yml'
Outputs:
  AlertingModule:
    Description: 'The stack name of the alerting module.'
    Value: !GetAtt 'AlertingModule.Outputs.StackName'
    Export:
      Name: 'APEX-AlertingModule'

This lets us import the template elsewhere without having any relation between the two files:

  AlbModule:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        AlertingModule: !ImportValue 'APEX-AlertingModule'
        VpcModule: !GetAtt 'VpcModule.Outputs.StackName'
        Scheme: 'internal'
      TemplateURL: './node_modules/@cfn-modules/alb/module.yml'
Outputs:
  EnvironmentAlbModule:
    Description: 'Environment ALB module.'
    Value: !GetAtt 'AlbModule.Outputs.StackName'
    Export:
      Name: !Sub 'APEX-${Environment}-AlbModule'

This example also exports the ALB in a narrower namespace since it's specific to a single client. When we want to build on that client's ALB, you need to use a slightly more verbose import...

  NlbAlbForward443:
    Type: 'AWS::CloudFormation::Stack'
    Properties:
      Parameters:
        VpcModule: {'Fn::ImportValue': !Sub 'APEX-${Environment}-VpcModule'}
        AlbModule: {'Fn::ImportValue': !Sub 'APEX-${Environment}-AlbModule'}
        NlbModule: !GetAtt 'NlbModule.Outputs.StackName'
        KmsKeyModule: {'Fn::ImportValue': !Sub 'APEX-${Environment}-KmsKeyModule'}
        Port: '443'
      TemplateURL: './apex-env-network-port.yaml'

As you can see, the VPC is also client-specific. The NLB is defined inline (in this same file). This particular template implements the lambda-based forwarding logic from an AWS Blog post.