上一篇学习了 Nested Stack,现在看看另外一种方式来调用其他的stack。假设在一个大公司里面,有不同的部门,如果要配置一系列的服务,我们需要配置一个很大的template 文件,但是我们可以通过 cross stack的方式,把一个大的template分割为很多小的template,然后在对应的地方export/import 对应的值就好了。

下面通过一个实例来说明,创建两个stack,第一个创建我们的VPC,路由表,Internet网关,安全组等等;第二个stack会调用第一个stack的结果,然后在这个基础上创建一个新的web server

模板文件可以在这个链接下载
https://github.com/natonic/CloudFormation-Deep-Dive/tree/master/Labs/CrossStack

下面是我们的第一个template 文件


{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "AWS CloudFormation Sample Template VPC_with_PublicIPs_And_DNS: Sample template that creates a VPC with DNS and public IPs enabled. Note that you are billed for the AWS resources that you use when you create a stack from this template.",
  "Resources" : {
    "VPC" : {
      "Type" : "AWS::EC2::VPC",
      "Properties" : {
        "EnableDnsSupport" : "true",
        "EnableDnsHostnames" : "true",
        "CidrBlock" : "10.0.0.0/16"
      }
    },
    "PublicSubnet" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : "10.0.0.0/24"
      }
    },
    "InternetGateway" : {
      "Type" : "AWS::EC2::InternetGateway"
    },
    "VPCGatewayAttachment" : {
       "Type" : "AWS::EC2::VPCGatewayAttachment",
       "Properties" : {
         "VpcId" : { "Ref" : "VPC" },
         "InternetGatewayId" : { "Ref" : "InternetGateway" }
       }
    },
    "PublicRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" }
      }
    },
    "PublicRoute" : {
      "Type" : "AWS::EC2::Route",
      "DependsOn" : "VPCGatewayAttachment",
      "Properties" : {
        "RouteTableId" : { "Ref" : "PublicRouteTable" },
        "DestinationCidrBlock" : "0.0.0.0/0",
        "GatewayId" : { "Ref" : "InternetGateway" }
      }
    },
    "PublicSubnetRouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },
    "PublicSubnetNetworkAclAssociation" : {
      "Type" : "AWS::EC2::SubnetNetworkAclAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet" },
        "NetworkAclId" : { "Fn::GetAtt" : ["VPC", "DefaultNetworkAcl"] }
      }
    },
    "WebServerSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP ingress",
        "VpcId" : { "Ref" : "VPC" },
        "SecurityGroupIngress" : [ { 
          "IpProtocol" : "tcp",
          "FromPort" : "80",  
          "ToPort" : "80",
          "CidrIp" : "0.0.0.0/0"},
          {
            "CidrIp": "0.0.0.0/0",
            "FromPort": 443,
            "IpProtocol": "tcp",
            "ToPort": 443
          },
          {
            "FromPort": 443,
            "IpProtocol": "tcp",
            "ToPort": 443
          }
         ]
      }
    }
  },
  "Outputs" : {
    "VPCId" : {
      "Description" : "VPC ID",
      "Value" :  { "Ref" : "VPC" },
      "Export" : { "Name" : {"Fn::Sub": "${AWS::StackName}-VPCID" }}
    },
    "PublicSubnet" : {
      "Description" : "The subnet ID to use for public web servers",
      "Value" :  { "Ref" : "PublicSubnet" },
      "Export" : { "Name" : {"Fn::Sub": "${AWS::StackName}-SubnetID" }}
    },
    "WebServerSecurityGroup" : {
      "Description" : "The security group ID to use for public web servers",
      "Value" :  { "Fn::GetAtt" : ["WebServerSecurityGroup", "GroupId"] },
      "Export" : { "Name" : {"Fn::Sub": "${AWS::StackName}-SecurityGroupID" }}
    }
  }
}

下面是第二个template 文件

{
    "AWSTemplateFormatVersion" : "2010-09-09",

    "Description" : "AWS CloudFormation Cross-Stack Reference Sample Template: Demonstrates how to reference resources from a different stack. This template provisions an EC2 instance in an EC2 Security Group provisioned in a different stack. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

    "Parameters": {
      "NetworkStackName": {
        "Description": "Name of an active CloudFormation stack that contains the networking resources, such as the subnet and security group, that will be used in this stack.",
        "Type": "String",
        "MinLength" : 1,
        "MaxLength" : 255,
        "AllowedPattern" : "^[a-zA-Z][-a-zA-Z0-9]*$",
        "Default" : "SampleNetworkCrossStack"
      }
    },

    "Mappings" : {
      "AWSRegionArch2AMI" : {
        "us-east-1"        : {"PV64" : "ami-8ff710e2", "HVM64" : "ami-f5f41398", "HVMG2" : "ami-4afd1d27"},
        "us-west-2"        : {"PV64" : "ami-eff1028f", "HVM64" : "ami-d0f506b0", "HVMG2" : "ami-ee897b8e"},
        "us-west-1"        : {"PV64" : "ami-ac85fbcc", "HVM64" : "ami-6e84fa0e", "HVMG2" : "ami-69106909"},
        "eu-west-1"        : {"PV64" : "ami-23ab2250", "HVM64" : "ami-b0ac25c3", "HVMG2" : "ami-936de5e0"},
        "eu-central-1"     : {"PV64" : "ami-27c12348", "HVM64" : "ami-d3c022bc", "HVMG2" : "ami-8e7092e1"},
        "ap-northeast-1"   : {"PV64" : "ami-26160d48", "HVM64" : "ami-29160d47", "HVMG2" : "ami-91809aff"},
        "ap-northeast-2"   : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-cf32faa1", "HVMG2" : "NOT_SUPPORTED"},
        "ap-southeast-1"   : {"PV64" : "ami-f3dd0a90", "HVM64" : "ami-1ddc0b7e", "HVMG2" : "ami-3c30e75f"},
        "ap-southeast-2"   : {"PV64" : "ami-8f94b9ec", "HVM64" : "ami-0c95b86f", "HVMG2" : "ami-543d1137"},
        "sa-east-1"        : {"PV64" : "ami-e188018d", "HVM64" : "ami-fb890097", "HVMG2" : "NOT_SUPPORTED"},
        "cn-north-1"       : {"PV64" : "ami-77a46e1a", "HVM64" : "ami-05a66c68", "HVMG2" : "NOT_SUPPORTED"}
      }
    },

    "Resources" : {
      "WebServerInstance": {  
        "Type": "AWS::EC2::Instance",
        "Metadata" : {
          "AWS::CloudFormation::Init" : {
            "configSets" : {
              "All" : [ "ConfigureSampleApp" ]
            },

            "ConfigureSampleApp" : {
              "packages" : {
                "yum" : {
                  "httpd" : []
                }
              },

              "files" : {
                "/var/www/html/index.html" : {
                  "content" : { "Fn::Join" : ["\n", [
                    "<img src=\"https://s3.amazonaws.com/cloudformation-examples/cloudformation_graphic.png\" alt=\"AWS CloudFormation Logo\"/>",
                    "Congratulations, you have successfully launched the AWS CloudFormation sample."
                  ]]},
                  "mode"    : "000644",
                  "owner"   : "root",
                  "group"   : "root"
                }
              },

              "services" : {
                "sysvinit" : {
                  "httpd"    : { "enabled" : "true", "ensureRunning" : "true" }
                }
              }
            }
          }
        },
        "Properties": {
          "InstanceType"   : "t2.micro",
          "ImageId": { "Fn::FindInMap": [ "AWSRegionArch2AMI", { "Ref": "AWS::Region" } , "HVM64" ] },
          "NetworkInterfaces" : [{
            "GroupSet"                 : [{ "Fn::ImportValue" :  {"Fn::Sub": "${NetworkStackName}-SecurityGroupID" } }],
            "AssociatePublicIpAddress" : "true",
            "DeviceIndex"              : "0",
            "DeleteOnTermination"      : "true",
            "SubnetId"                 : { "Fn::ImportValue" : {"Fn::Sub": "${NetworkStackName}-SubnetID" } }
          }],
          "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
               "#!/bin/bash -xe\n",
               "yum update -y aws-cfn-bootstrap\n",

               "# Install the files and packages from the metadata\n",
               "/opt/aws/bin/cfn-init -v ",
               "         --stack ", { "Ref" : "AWS::StackName" },
               "         --resource WebServerInstance ",
               "         --configsets All ",
               "         --region ", { "Ref" : "AWS::Region" }, "\n",

               "# Signal the status from cfn-init\n",
               "/opt/aws/bin/cfn-signal -e $? ",
               "         --stack ", { "Ref" : "AWS::StackName" },
               "         --resource WebServerInstance ",
               "         --region ", { "Ref" : "AWS::Region" }, "\n"
          ]]}}        
        },
        "CreationPolicy" : {
          "ResourceSignal" : {
            "Timeout" : "PT5M"
          }
        }
      }
    },

    "Outputs" : {
      "URL" : {
        "Description" : "URL of the sample website",
        "Value" :  { "Fn::Join" : [ "", [ "http://", { "Fn::GetAtt" : [ "WebServerInstance", "PublicDnsName" ]}]]}
      }
    }
  }

首先创建第一个stack

AWS Cloudformation - Cross Stack 学习总结

给一个名字叫networkstack

AWS Cloudformation - Cross Stack 学习总结

创建成功

AWS Cloudformation - Cross Stack 学习总结

我们创建了以下的资源

AWS Cloudformation - Cross Stack 学习总结

看看他的output

AWS Cloudformation - Cross Stack 学习总结

看看他的export
AWS Cloudformation - Cross Stack 学习总结

接下来创建第二个stack

AWS Cloudformation - Cross Stack 学习总结

他的参数指向第一个stack的名字,这是为了import 数据

AWS Cloudformation - Cross Stack 学习总结

创建成功
AWS Cloudformation - Cross Stack 学习总结

看看output的URL

AWS Cloudformation - Cross Stack 学习总结

可以成功打开这个测试网页

AWS Cloudformation - Cross Stack 学习总结

最后,再试试删除我们的networkstack
AWS Cloudformation - Cross Stack 学习总结

删除失败,这是因为存在依赖关系,appstack需要import来自networkstack的vpc,sg等数据
AWS Cloudformation - Cross Stack 学习总结

如果我们先删除appstack,然后再删除network stack,那就没有问题了

整个流程就是如上所示。最后再次强调一下关键点:

我们在networkstack 的Output 进行export,这里通过全局变量替换掉stackname
AWS Cloudformation - Cross Stack 学习总结

同样的,我们在appstack 的 resource 部分 import 对应的值
AWS Cloudformation - Cross Stack 学习总结