如何使用CloudFormation构建 VPC?

如果要为基础设施用例构建单独VPC模板,则可以使用Parameters、Conditions、Mapping和Outputs让现有模板更加灵活。

正文

关键要点

  • 通过使用参数和条件,CloudFormation模板可以变得更灵活、更强大。
  • 映射可用于从查找表中根据条件选择值。
  • 输出可用于为其他栈指定特定的栈资源。
  • 团队可以使用导出的输出作为团队间的通信媒介。

在本系列文章的第一部分,我们探讨了如何使用基础设施即代码(特别是CloudFormation)来创建和维护AWS VPC。我们创建的CloudFormation模板提供了简单、可重用的组件,我们可以用它创建简单VPC。

但是,这个模板还不够灵活。我们希望可以有一个这样的模板,可以用它为开发、测试和生产环境构建具有不同数量子网的VPC。我们想要一些在需要进行演示或POC时能够用于创建公共子网的东西,或者,我们可能希望使用NAT实例而不是NAT网关。

我们可以通过使用参数、条件、映射和输出让现有的模板变得更灵活,而不是为各种情况创建单独的模板。这是本系列的第二篇文章,所以你应该已经阅读了第一篇文章,并对模板已经很熟悉了。在这篇文章中,我将从增强原始模板开始说起。

本文涉及的CloudFormation模板可以在GitHub上找到,读者可以下载、修改和使用它。

可变数量的子网/可用区域

可用区域:AWS已经让在给定地区内利用多个可用区域(AZ)变得更容易,而且成本更低。简单地说,你可以将可用区域视为一个巨大的独立数据中心。给定地区内的AZ通过高速、低延迟的私有链接相互连接。它们彼此足够接近,支持同步通信,但相隔得又足够远,可以减轻自然灾害、停电等事件所带来的影响。确切地说,我们并不知道究竟相隔多远,但其实也没必要知道。

以最低的成本实现基本的高可用性是使用两个AZ。有时候单个AZ更适合简单的情况,例如演示或POC。其他时候需要三个AZ来获得略微改善的高可用性。所以,接下来让我们来调整模板,让它支持可变的AZ数量。

使用第一篇文章中的模板,在“Resources”部分上方添加以下内容。

Parameters:  NumberOfAZs:    Type: Number    AllowedValues:    - 1    - 2    - 3    Default: 2    Description:  How many Availability Zones do you wish to utilize?

YAML基础知识:YAML使用双空格缩进表示层次结构(没有制表符!)。短划线“-”表示“序列”,即属于同一组的多个值。参数通常位于资源上方,但从技术上讲,它们可以被放在模板中的任何位置。

NumberOfAZs:这个条目定义了模板的输入参数。在AWS管理控制台中使用这个模板创建资源栈时,UI将提示输入“NumberOfAZs”,输入框旁边是描述内容。因为我们提供了“AllowedValues”,所以输入字段将是一个下拉框,其中包含1、2和3这三个选项。如果我们不做选择,将默认使用2。这里定义了有效的参数类型,在这里我们可以使用Number或String。

我们的目标是能够使用这个模板在任意地区创建资源栈。在撰写本文时,大多数地区现在至少有三个可用区域,但有些地域则没有(蒙特利尔、孟买、北京、首尔只有两个)。在这些地区选择使用三个AZ将导致错误。限制模板的灵活性以便避免这种少数情况下才会发生的尴尬错误,这样值不值得取决于你。

CLI用法:通过AWS命令行界面(CLI)创建资源栈时,输入参数仍然有用。如果有必要,我们可以为参数提供值,或者使用默认值。如果提供的值超出了允许的范围将会出现错误。

在指定所需的AZ数量之后,我们需要修改模板的其余部分,以便让CloudFormation构建我们想要的子网。

Coditions部分

为了让CloudFormation可以构建一个、两个或三个子网,我们将定义一些可以在Resources部分使用的“条件”。在Parameters部分下方和Resources部分上方添加以下代码:

Conditions:  BuildPublicB:         !Not [ !Equals [ !Ref NumberOfAZs, 1 ]]   BuildPublicC:         !Equals [ !Ref NumberOfAZs, 3 ] 

条件是布尔值(true/false)表达式,我们将在模板中使用它们。这里我们创建两个条件,一个用于指示我们是否要构建“B”子网,一个用于指示我们是否要构建“C”子网。由于“1”是NumberOfAZs允许的最小数量,因此我们始终都会构建“A”子网。

BuildPublicB:这个表达式检查所选的NumberOfAZs是否为1以外的值。因为CloudFormation中没有大于和小于内联函数,所以我们将使用!Equals函数来引用输入参数并检查值是否与“ 1”相等。!Not用于获得相反的结果(即false变true,true变false)。布尔值结果将被保存在BuildPublicB中,然后在模板中的其他位置引用它。

BuildPublicC:这个表达式更简单,NumberOfAZs要么是“3”(我们允许的最大值)要么不是。如果它是true,我们将只构建PublicSubnetC。

现在,我们已经明确定义了要创建哪些子网的条件,接下来可以用它们来创建实际的资源。

条件属性

在第一篇文章的原始模板中,我们使用以下代码创建了PublicSubnetB:

PublicSubnetB:    Type: AWS::EC2::Subnet    Properties:      VpcId: !Ref VPC      CidrBlock: 10.1.20.0/24      AvailabilityZone: !Select [ 1, !GetAZs ]    # Get the second AZ in the list       Tags:      - Key: Name        Value: !Sub ${AWS::StackName}-Public-B

注意下面的替换代码,特别是新的“Condition”属性:

PublicSubnetB:    Type: AWS::EC2::Subnet    Condition: BuildPublicB    Properties:      VpcId: !Ref VPC      CidrBlock: 10.1.20.0/24      AvailabilityZone: !Select [ 1, !GetAZs ]    # Get the second AZ in the list       Tags:      - Key: Name        Value: !Sub ${AWS::StackName}-Public-B

条件属性就是任意CloudFormation资源的可用选项。也就是说,“只有在BuildPublicB条件为true时才创建这个资源”。当它为false时,就会忽略资源的创建——将不会有PublicSubnetB。

现在让我们添加第三个公共子网,但仅当BuildPublicC条件为true时:

PublicSubnetC:   Type: AWS::EC2::Subnet   Condition: BuildPublicC   Properties:     VpcId: !Ref VPC     CidrBlock: 10.1.30.0/24     AvailabilityZone: !Select [ 2, !GetAZs ]    # Get the third AZ in the list      Tags:     - Key: Name       Value: !Sub ${AWS::StackName}-Public-C

你可能想知道是否有一种方法可以直接在资源上内联表达式条件,而不是使用单独的“Coditions”部分。在撰写本文时,没有。但是在编写了很多模板之后,我发现逻辑表达式的计算与用法的简单解耦其实是有好处的。毕竟,如果使用内联表达式,这些模板可能会变得非常复杂,例如你在此处看到的AvailabilityZone或Tag/Value。

最后一步,为不同数量的公共子网调整子网的路由表关联。请注意以下的条件属性:

PublicSubnetBRouteTableAssociation:    Type: AWS::EC2::SubnetRouteTableAssociation    Condition: BuildPublicB    Properties:      SubnetId: !Ref PublicSubnetB      RouteTableId: !Ref PublicRouteTable  PublicSubnetCRouteTableAssociation:    Type: AWS::EC2::SubnetRouteTableAssociation    Condition: BuildPublicC    Properties:      SubnetId: !Ref PublicSubnetC      RouteTableId: !Ref PublicRouteTable

这里没有显示PublicSubnetA的关联。因为它始终存在于资源栈中,因此不需要条件属性。同样,PublicRouteTable也是必需存在的。我们的资源栈将根据输入参数创建一个、两个或三个公共子网。接下来让我们来看看私有子网……

私有子网

我们假设要将这个模板生成的VPC用于一些面向公众的快速演示。在这样的VPC中拥有私有子网或NAT有点超出了实际,而且需要更长的时间来创建。让我们添加一个参数,可以用它来指定纯公共子网。在“Parameters”部分添加:

PrivateSubnets:    Type: String    AllowedValues:    - True    - False    Default: True    Description: Do you want to create private subnets in addition to public subnets?

我们定义了一个输入参数来控制是否创建了任何私有子网。我希望CloudFormation为这样的情况提供“布尔”输入类型,但现在我们不得不是有只接受“True”或“False”的String类型。

让我们在Coditions部分中添加以下这些条件,用于计算输入值。这个会有点复杂:

BuildPrivateSubnets: !Equals [ !Ref PrivateSubnets, True ]  BuildPrivateA:       !Equals [ !Ref PrivateSubnets, True ]  BuildPrivateB:       !And[!Not[!Equals[!Ref NumberOfAZs,1]],!Equals[!Ref PrivateSubnets,True]]  BuildPrivateC:       !And[!Equals[!Ref NumberOfAZs,3],!Equals[!Ref PrivateSubnets, True]]

BuildPrivateSubnets:这是一个直接用于表达输入参数的简单条件。有时候,我们会根据是否存在私有子网(即NAT)来构建一些东西。

BuildPrivateA:“BuildPrivateSubnets”的同义词,不是绝对必需的,但它的代码看起来非常干净。但有点遗憾的是,我们无法在一个条件中引用另一个条件。

BuildPrivateB:这里的逻辑是“如果我们想要使用多个AZ并且想要构建私有子网,那么就构建PrivateSubnetB”。

BuildPrivateC:这里的逻辑是“如果我们想要使用三个AZ,并且想要构建私有子网,那么就构建PrivateSubnetC”。

现在,我们可以将私有子网定义从原始模板转换为使用条件属性,如下所示:

PrivateSubnetA:    Type: AWS::EC2::Subnet    Condition: BuildPrivateA    Properties:      VpcId: !Ref VPC      CidrBlock: 10.1.50.0/24      AvailabilityZone: !Select [ 0, !GetAZs ]    # Get the first AZ in the list       Tags:      - Key: Name        Value: !Sub ${AWS::StackName}-Private-A  PrivateSubnetB:    Type: AWS::EC2::Subnet    Condition: BuildPrivateB    Properties:      VpcId: !Ref VPC      CidrBlock: 10.1.60.0/24      AvailabilityZone: !Select [ 1, !GetAZs ]    # Get the second AZ in the list       Tags:      - Key: Name        Value: !Sub ${AWS::StackName}-Private-B  PrivateSubnetC:    Type: AWS::EC2::Subnet    Condition: BuildPrivateC    Properties:      VpcId: !Ref VPC      CidrBlock: 10.1.70.0/24      AvailabilityZone: !Select [ 2, !GetAZs ]    # Get the third AZ in the list       Tags:      - Key: Name        Value: !Sub ${AWS::StackName}-Private-C

我们所做的修改就是在原始模板中添加了条件属性。另外,我们还添加了PrivateSubnetC,它可以很容易地从PrivateSubnetA和PrivateSubnetB的定义中克隆出来。

子网路由表关联也需要修改。如果没有子网,则不需要子网关联:

PrivateSubnetARouteTableAssociation:    Type: AWS::EC2::SubnetRouteTableAssociation    Condition: BuildPrivateA    Properties:      SubnetId: !Ref PrivateSubnetA      RouteTableId: !Ref PrivateRouteTable  PrivateSubnetBRouteTableAssociation:    Type: AWS::EC2::SubnetRouteTableAssociation    Condition: BuildPrivateB    Properties:      SubnetId: !Ref PrivateSubnetB      RouteTableId: !Ref PrivateRouteTable  PrivateSubnetCRouteTableAssociation:    Type: AWS::EC2::SubnetRouteTableAssociation    Condition: BuildPrivateC    Properties:      SubnetId: !Ref PrivateSubnetC      RouteTableId: !Ref PrivateRouteTable

NAT网关

我们的模板现在可以有条件地创建私有子网,因此我们需要相应地调整NAT网关和路由表条目。首先是NAT网关,如果我们不构建私有子网,就没有理由创建它或与其关联的弹性IP地址:

# A NAT Gateway will be built and used if the user selected Private subnets and a Gateway instead of an EC2 instance.  NATGateway:   Type: AWS::EC2::NatGateway   Condition: BuildPrivateSubnets   Properties:     AllocationId: !GetAtt ElasticIPAddress.AllocationId     SubnetId: !Ref PublicSubnetA     Tags:     - Key: Name       Value: !Sub NAT-${AWS::StackName} ElasticIPAddress:   Type: AWS::EC2::EIP   Condition: BuildPrivateSubnets   Properties:     Domain: VPC

原始模板的唯一变化是条件属性,我们希望只在选择了要构建私有子网时才构建这些东西。

接下来,条件表明我们可能不需要私有路由表或路由表的条目:

# Here is a private route table: PrivateRouteTable:   Type: AWS::EC2::RouteTable   Condition: BuildPrivateSubnets   Properties:     VpcId: !Ref VPC     Tags:     - Key: Name       Value: Private PrivateRoute1:            # Private route table can access web via NAT (created below)   Type: AWS::EC2::Route   Condition: BuildPrivateSubnets   Properties:     RouteTableId: !Ref PrivateRouteTable     DestinationCidrBlock: 0.0.0.0/0     # Route traffic through the NAT Gateway:     NatGatewayId: !Ref NATGateway

这个时候,当“BuildPrivateSubnets”为false时,我们将忽略任何私有子网、路由表或NAT的创建。我们的模板能够根据参数输入创建一到六个子网。相当灵活,而且要实现这一点不需要太多的工作量。

可选的NAT类型

为了进一步提高灵活性,我们让模板支持NAT网关。内建的托管服务非常适合用于生产环境,但用于POC可能就有点贵了。在出现NAT网关之前,我们通过常规的EC2实例来提供NAT支持,不管哪种方式都有其优缺点。所以,出于实验的目的,我们添加一个参数,以便提供这种选择:

NATType:    Type: String    AllowedValues:    - \u0026quot;EC2 NAT Instance\u0026quot;    - \u0026quot;NAT Gateway\u0026quot;    Default:  \u0026quot;NAT Gateway\u0026quot;    Description:  What type of NAT to use for private instances to communicate with the internet.  A single EC2 instance can be used as a NAT, or you can use the AWS NAT Gateway (managed, scalable, more expensive).  This setting will be IGNORED if you do not build private subnets.

并在Conditions部分添加以下内容:

BuildNATGateway:  !And[!Equals[!Ref PrivateSubnets,True],!Equals[!Ref NATType, \u0026quot;NAT Gateway\u0026quot;]]  BuildNATInstance: !And[!Equals[!Ref PrivateSubnets,True],!Equals[!Ref NATType, \u0026quot;EC2 NAT Instance\u0026quot;]]

第一行的意思是“如果我们要构建私有子网和NAT网关,那么就构建NAT网关”。第二行的意思是“如果我们要构建私有子网,并选择使用EC2实例,那么就构建一个EC2实例作为NAT”。

我们之前描述的NAT网关/弹性IP地址条件也需要做出调整,我们现在想要根据BuildNATGateway条件控制它们的创建:

# A NAT Gateway will be built and used if the user selected Private subnets and a Gateway instead of an EC2 instance.  NATGateway:   Type: AWS::EC2::NatGateway   Condition: BuildNATGateway   Properties:     AllocationId: !GetAtt ElasticIPAddress.AllocationId     SubnetId: !Ref PublicSubnetA     Tags:     - Key: Name       Value: !Sub NAT-${AWS::StackName} ElasticIPAddress:   Type: AWS::EC2::EIP   Condition: BuildNATGateway   Properties:     Domain: VPC

基于EC2的NAT实例需要一些新的构造。首先,EC2实例需要AMI,但不同地区的AMI ID值是不一样的。为了可以在任意地区使用这个模板,我们在Condition部分之前将以下的Mappings部分添加到模板中(从技术上说,这些部分的放置顺序是随意的,有些人喜欢把它放在底部附近):

Mappings: #  This is the Amazon Linux 2 AMI.  Adjust these values as needed, they can change a few times per year: AmazonLinuxAMI:   us-east-1:     AMI: ami-04681a1dbd79675a5    # N Virginia   us-east-2:     AMI: ami-0cf31d971a3ca20d6    # Ohio        us-west-1:     AMI: ami-0782017a917e973e7    # N California   us-west-2:     AMI: ami-6cd6f714             # Oregon   eu-west-1:     AMI: ami-0bdb1d6c15a40392c    # Ireland   eu-central-1:     AMI: ami-0f5dbc86dd9cbf7a8    # Frankfurt   sa-east-1:     AMI: ami-0ad7b0031d41ed4b9    # Sao Paulo   ap-southeast-1:     AMI: ami-01da99628f381e50a    # Singapore   ap-southeast-2:     AMI: ami-00e17d1165b9dd3ec    # Sydney   ap-northeast-1:     AMI: ami-08847abae18baa040    # Tokyo

这个Mapping部分定义了Amazon Linux 2 OS的AMI ID值。ID值根据资源栈所在地区的不同而不同。稍后我们将看到在定义EC2实例资源时如何使用这个映射表。但在继续往下介绍之前,有一些要点需要提及:1)注释是你的好朋友;2)我没有为每个地区都提供值;3)这些值只是截止撰写本文时的值,EC2团队将会时不时发布改进过的新AMI版本,你或许可以使用它们。

找到这些值并不难,我通过使用AWS管理控制台的EC2实例创建向导就可以找到它们。在AMI选择页面,我利用区域选择来获得所有集合的值。当然,还有更多高级技术可以取代映射表(例如由Lambda函数支持的参数存储查找或CloudFormation自定义资源),但我不想在这篇文章中过多地介绍它们。

接下来,我们的EC2实例需要一个安全组:

# A security group for our NAT.  Ingress from the VPC IPs only.  Egress is TCP \u0026amp; UDP only:  NATSecurityGroup:    Type: AWS::EC2::SecurityGroup    Condition: BuildNATInstance    DependsOn: AttachGateway    Properties:      GroupName: !Sub NATSecurityGroup-${AWS::StackName}      GroupDescription: Enable internal access to the NAT device      VpcId: !Ref VPC      SecurityGroupIngress:      - IpProtocol: tcp        FromPort: '0'        ToPort: '1024'        CidrIp: !GetAtt VPC.CidrBlock      SecurityGroupEgress:      - IpProtocol: tcp        FromPort: '0'        ToPort: '65535'        CidrIp: 0.0.0.0/0      - IpProtocol: udp        FromPort: '0'        ToPort: '65535'        CidrIp: 0.0.0.0/0

为了保持这篇文章的简短,我不打算解释所有复杂的东西,所以这里给出了摘要:

  • 仅在BuildNATInstance的条件为true时创建它。
  • 在Internet Gateway连接到VPC之前不会尝试创建它(请参阅上一篇文章)。
  • 安全组的名称以CloudFormation资源栈的名称为基础(请参阅上一篇文章)。
  • 安全组仅允许来自VPC内部地址范围内的入站流量。只有VPC内的私有IP才能向NAT发送流量。
  • 出站流量可以基于TCP或UDP,并且基本上可以流向任何地方。
  • 有关安全组的完整信息,请参阅AWS::EC2::SecurityGroup

接下来是EC2实例:

# A NAT Instance will be used if the user selected Private subnets and EC2-based NAT.       NATInstance:    Type: AWS::EC2::Instance    Condition: BuildNATInstance    DependsOn: PublicRoute1                           # Must have route to IGW established.    Properties:      ImageId: !FindInMap [ AmazonLinuxAMI, !Ref \u0026quot;AWS::Region\u0026quot;, AMI]  # lookup from AMI map      InstanceType: t2.small                          # Any instance type is fine      NetworkInterfaces:      - DeviceIndex: '0'        SubnetId: !Ref PublicSubnetA                  # Any public subnet is fine        AssociatePublicIpAddress: true                # We will need a public IP address        GroupSet: [!Ref NATSecurityGroup]             # Plug in the security group      SourceDestCheck: false  # NATs don't work if EC2 matches source with destinations.      Tags:      - Key: Name        Value: !Sub NAT-${AWS::StackName}      UserData:      #  This code is NAT code.  Last line signals completion:        Fn::Base64: !Sub |          #!/bin/bash          yum -y update          yum install -y aws-cfn-bootstrap          echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward          echo 0 \u0026gt; /proc/sys/net/ipv4/conf/eth0/send_redirects          /sbin/iptables -t nat -A POSTROUTING -o eth0 -s 0.0.0.0/0 -j MASQUERADE          /sbin/iptables-save \u0026gt; /etc/sysconfig/iptables          mkdir -p /etc/sysctl.d/          cat \u0026lt;\u0026lt; NatConfFileMarker \u0026gt; /etc/sysctl.d/nat.conf          net.ipv4.ip_forward = 1          net.ipv4.conf.eth0.send_redirects = 0          NatConfFileMarker          /opt/aws/bin/cfn-signal -e 0 --resource NATInstance --stack ${AWS::StackName} --region ${AWS::Region}    # This NATInstance is only complete when you get 1 signal back within 5 minutes'.    CreationPolicy:      ResourceSignal:        Count: 1        Timeout: PT5M

简述:

  • Condition:只在BuildNATInstance为true时才会创建这个实例。
  • DependsOn:在PublicRoute1构建完成之前,我们不会尝试创建它,也就是说我们必须连接到互联网。这对于在UserData中正常运行“yum”命令来说至关重要。
  • ImageID:要使用的AMI(来自之前创建的映射表)。“AWS::Region”是一个伪参数,它会告诉我们正在创建的资源栈所在的地区。实质上,我们要求CloudFormation在映射表中查找地区并使用生成的AMI。
  • SubnetId:我们将这个EC2 NAT实例放在公共子网中。在单个公共子网中使用单个NAT实例显得有点简陋,但本文的主要目的是演示基本的灵活性,并不是要介绍详尽的最佳实践。
  • AssociatePublicIPAddress:NAT用来与公共网络上的各方发起通信的公共IP地址。
  • GroupSet:NAT与之前定义的安全组相关联。这个参数需要一个安全组列表,而不是单个值,所以添加了方括号“[]”,将单个值强制转换为列表结构。
  • SourceDestCheck:让EC2忽略通常的流量检查,通常情况下,它需要确保EC2实例是它接收到的流量的源或目的地,但这对于NAT来说不适用。简单的说我们必须这么做才能让NAT生效。请参阅源和目的地检查获得更深入的解释。
  • UserData:提供NAT功能的Linux脚本,不过这里不详细介绍这个脚本的相关内容了。!Sub内部函数会查找和替换${}表达式(例如${AWS::StackName})。脚本的最后一行cfn-signal是一个CloudFormation函数,它会在脚本执行完成时向CloudFormation栈发出信号。
  • Fn::Base64:!Sub:UserData脚本必须经过Base64编码,这可以通过Fn::Base64内部函数来完成。通常我会使用!Base64快捷语法,也会使用!Sub函数来替换脚本中的占位符。在!Base64中使用!Sub是有效的Cloud Formation,但却是无效的YAML,所以我们必须使用外部函数的完整函数名。
  • CreationPolicy:通常,CloudFormation会在底层服务表示资源已创建完成之后才会认为资源创建完成。对于EC2实例来说,就是要等到操作系统开始启动。不过,为了让EC2 NAT实例可供资源栈中的任何组件使用,UserData脚本需要先执行完毕。CreationPolicy就像是在说:“在我们收到信号(来自cfn-signal命令)之前,这个资源还不算创建完成”。

好多东西啊!但CloudFormation或基础设施即代码的美妙之处在于,这些事情我只需要做一次。有关所有这些设置的完整细节,请参阅AWS::EC2::Instance

最后,我们需要调整之前构建的PrivateRoute。我们需要将出站流量路由到NAT网关或NAT实例,具体取决于创建的是哪个:

PrivateRoute1:            # Private route table can access web via NAT (created below)   Type: AWS::EC2::Route   Condition: BuildPrivateSubnets   Properties:     RouteTableId: !Ref PrivateRouteTable     DestinationCidrBlock: 0.0.0.0/0     # If we are using a NAT Instance, route traffic through the NAT Instance:     InstanceId:   !If [ BuildNATInstance, !Ref NATInstance, !Ref \u0026quot;AWS::NoValue\u0026quot; ]     # Otherwise if we are using a NAT Gateway, route traffic through the NAT Gateway:     NatGatewayId: !If [ BuildNATGateway, !Ref NATGateway, !Ref \u0026quot;AWS::NoValue\u0026quot; ]

请注意InstanceId和NatGatewayId这两个属性,根据AWS::EC2::Route文档,它们是互斥的。当我们将流量路由到EC2实例是会用到InstanceId。如果我们选择了BuildNATInstance,那么!If内部函数只会将该值设置为NATInstance。AWS::NoValue所做的事情比它看起来的要多,它不只是表示没有设置值,而且CloudFormation知道这意味着根本不需要设置这个属性。如果我们选择了BuildNATGateway,NatGatewayId的镜像逻辑会将值设置为NATGateway。由于条件是互斥的,因此只有一个会设置成功,出站流量将使用NATInstance或NATGateway,最终取决于我们的原始输入决策。

可选的模板元数据

我们可以对修改后的模板进行一些装饰性的调整。我们希望能够控制参数的输入顺序,为此,在“Parameters”部分之前添加“Metadata”部分(尽管有些人喜欢将其放在模板的底部):

Metadata: # Control the UI display when running this template from the AWS Management Console: AWS::CloudFormation::Interface:   ParameterGroups:     - Label:         default: \u0026quot;Network Configuration\u0026quot;       Parameters:         - NumberOfAZs         - PrivateSubnets         - NATType

现在,在AWS管理控制台中使用这个模板创建资源栈时,参数页将提示操作员进行“网络配置”,并按所需顺序显示参数。当在使用CLI时,这部分不会有任何影响。

Outputs

我们创建的模板是一个很好的通用模板,可以作为其他需要使用VPC的CloudFormation资源栈的起点。我们希望能够轻松地将其作为其他资源栈的输入。这对于大多数IT组织来说尤为重要,在这些组织中,团队之间承担着细分的责任,负责管理网络资源的团队和负责构建使用网络的资源的团队是不同的团队。要让资源栈提供输出值,可以通过在模板中创建Outputs部分来实现:

Outputs: VPC:   Description: VPC of the base network   Value: !Ref VPC   Export:     Name: !Sub ${AWS::StackName}-VPC PublicSubnetA:   Description: First Public Subnet   Value: !Ref PublicSubnetA   Export:     Name: !Sub ${AWS::StackName}-PublicSubnetA PublicSubnetB:   Description: Second Public Subnet   Condition: BuildPublicB   Value: !Ref PublicSubnetB   Export:     Name: !Sub ${AWS::StackName}-PublicSubnetB PublicSubnetC:   Description: Third Public Subnet   Condition: BuildPublicC   Value: !Ref PublicSubnetC   Export:     Name: !Sub ${AWS::StackName}-PublicSubnetC PrivateSubnetA:   Condition: BuildPrivateSubnets   Description: First Private Subnet   Value: !Ref PrivateSubnetA   Export:     Name: !Sub ${AWS::StackName}-PrivateSubnetA PrivateSubnetB:   Condition: BuildPrivateB   Description: Second Private Subnet   Value: !Ref PrivateSubnetB   Export:     Name: !Sub ${AWS::StackName}-PrivateSubnetB PrivateSubnetC:   Condition: BuildPrivateC   Description: Third Private Subnet   Value: !Ref PrivateSubnetC   Export:     Name: !Sub ${AWS::StackName}-PrivateSubnetC

基本上,在资源栈创建完成后,这些输出条目会在AWS管理控制台/CLI JSON输出上显示相关的值。请注意,包含条件属性是为了仅触发实际创建的资源的值。

需要注意的部分是Export/Name。它生成了一个区域范围的名称,另一个资源栈可以通过这个名称引用该资源。以PublicSubnetA为例,并假设资源栈名称为“my-network”,导出的值为“my-network-PublicSubnetA”。另一个资源栈可以使用“!ImportValue my-network-PublicSubnetA”来引用该资源,就像在资源栈中使用“!Ref”一样容易。通常,初始(基础)资源栈会被作为输入参数,因此,资源栈名称部分可以是动态的,例如:

Fn::ImportValue: !Sub ${BaseStack}-PublicSubnetA

其中“BaseStack”是另一个资源栈的输入参数。!ImportValue里的!Sub是无效的YAML,所以我们必须使用“长格式”的函数名称Fn::ImportValue

这里的的Export/Name/!ImportValue技术在多团队环境中很常见。每个团队通常需要引用其他团队生成的资源栈中的资源,并生成被其他资源栈引用的资源。导出的名称成为团队间的可靠通信点。此外,CloudFormation会跟踪这些资源栈间的引用,以防止因为删除或更新资源栈导致另一个资源栈中的相关资源无效。

总结

除了添加EC2 NAT实例选项和输出部分外,我们只修改了原始模板中的少量行。参数和条件为我们的模板带来了更强悍的功能。我们现在可以创建具有一到六个子网的VPC,包含各种可能的排列组合。我们创建的资源栈可以被其他资源栈引用。更令人惊奇的是,我们可以使用这个模板修改生成的资源栈,为其添加或删除子网,例如在POC时进行试用性部署。你可以扩展这项技术,让这个模板变得更加复杂,例如创建只包含私有子网的VPC。

关于作者

Ken Krueger的专业使命宣言是“通过应用现代技术来指导组织和个人实现商业成功”。他拥有超过30年软件从业经验,做过软件开发者、项目负责人、项目经理、Scrum Master,以及大型机、客户端-服务器、Web讲师。他在Java、Spring、SQL、Web开发、云计算和相关技术方面有着丰富的经验。他的行业经验包括电信、金融、房地产、零售、电力、航运、酒店和软件开发。他拥有南佛罗里达大学MIS学位、罗林斯学院Crummer商学院MBA学位,以及Scrum Master、PMP、AWS和Java认证。

查看英文原文https://www.infoq.com/articles/aws-vpc-cloudformation-part2

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值