本文描述如何使用AWS CloudFormation 创建和管理虚拟私有云(VPC),包括子网、NATting 等。这是关于在构建和管理云资源时将基础设施视为代码的经验。
AWS CloudFormation 允许我们在AWS环境中实现“基础设施即代码”。
借助CloudFormation 很容易就可以构建出复杂的虚拟私有云(VPC),并可以自动升级。
我们可以重用CloudFormation 模板构建用于各种用途的各种资源堆栈。
CloudFormation 提供了伪参数和内部函数。
-我们可以在用完之后把堆栈删除。
本文描述如何使用AWS CloudFormation 创建和管理虚拟私有云(VPC),包括子网、NATting 等。本文的重点是使用CloudFormation 和基础设施即代码构建和管理AWS资源,有关VPC 设计的问题介绍得相对较少。
网络专家:由于本文的重点是CloudFormation,所以你可能对CIDR块、路由表等有不同的看法。没问题,你可以根据需要修改这个模板。
转入正题:本文介绍的CloudFormation模板源代码可以在GitHub上找到。你可以随意下载、修改和使用此模板(不过我不会为误用承担责任)。
你可能想知道,当我们可以通过管理控制台中的VPC向导创建VPC时,为什么要使用CloudFormation来构建VPC,原因如下。
基础设施即代码:CloudFormation使我们只用一个步骤就可以创建一个“资源堆栈”。资源是我们创建的东西(EC2实例、VPC、子网等等),一组这样的资源称为堆栈。我们可以编写一个模板,使用它可以很容易地按照我们的意愿通过一个步骤创建一个网络堆栈。这比通过管理控制台或CLI手动创建网络更快,而且可重复,一致性更好。我们可以将模板签入源代码控制,并在任何时候根据需要把它用于任何目的。
可升级:我们可以通过修改CloudFormation 模板来修改网络堆栈,然后根据修改后的模板修改堆栈。CloudFormation足够智能,可以通过修改堆栈来匹配模板。
可重用:我们可以重用这个模板,在不同时期、不同区域创建多个不用用途的网络。
漂移检测:CloudFormation有一个新特性(截止到2018年11月),可以让我们知道资源是否已经“漂移”出了最初的配置。这可能发生在管理员手动更改资源时,这通常不是成熟的组织所鼓励的做法。
用完即弃:我们很容易在用完之后把堆栈删除。
学习本教程之前,你需要做好以下准备。
一个AWS账户。作为IAM用户,你可能可以继续本教程,但你必须拥有创建VPC、子网、路由表、EC2实例等的权限。通常,我会建议你创建一个你可以完全控制的私人AWS账户。在这个帐户中,创建一个具有全部权限的IAM用户,用于日常工作。与本教程相关的成本非常低,特别是当你在用完后把堆栈删除的话。
一个文本编辑器。几乎任何编辑器都可以:Sublime、Atom、nano,或者Eclipse、IntelliJ、Visual Studio等成熟的IDE。你只要确保不使用文字处理器——它会嵌入一些会导致语法错误的特殊字符。我将使用Visual Studio Code,这是我最喜欢的编辑器(本周)。
创建一个空的YAML文件:首先创建一个空文件。保存时将其命名为“MyNetwork.YML”。你可以使用任何自己喜欢的名称,但稍后我会用到这个文件名。一定要使用YML扩展名。CloudFormation支持JSON或YAML,我们将使用后者。主要原因是:1)句法不那么讲究,2)能够在工作中添加注释,没有注释我就记不起一周前在做什么了。
添加样板内容:创建好空文件之后,复制并粘贴下面这个结构,这是任何CloudFormation模板都需要的样板内容:
AWSTemplateFormatVersion: 2010-09-09# 这个CloudFormation模板会部署一个基本的VPC/网络Resources:
注释:在“#”字符之后的所有内容都是注释。我鼓励你们在自己有新发现时写下自己的注释。我的建议是:仅把注释用于描述任何你必须做的不寻常的事情上,尤其是那些你花了一段时间才发现的事情。想象一下,你的同事正在阅读你的模板,他的问题无法通过已发布的文档得到解答,那就是你的注释。
首先要添加的资源是VPC本身。复制这些行,资源段将变成下面这样:
Resources: # 首先,一个VPC: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.1.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Join ['', [!Ref \u0026quot;AWS::StackName\u0026quot;, \u0026quot;-VPC\u0026quot; ]]
缩进:第一件事,把间距调整好。“Resources:”前面不应该有空格。“VPC:”应该缩进两个空格,“Type”和“Properties”应该再缩进两个空格,等等。不要使用制表符(Tab),除非编辑器会将制表符转换为两个空格。只要知道你正在创建的文件的类型,上面列出的大多数编辑器都会这样做。缩进在YAML中非常重要,它是我们为避免JSON中的括号和逗号而付出的代价。
VPC:该资源指示CloudFormation创建一个VPC资源,以及一些基本属性和名称。第一行只有“VPC”——这是我们为了在堆栈中标识这个资源而指定的任意一个名称。令人困惑的是,许多资源类型都具有各自的“名称”属性,那是不同的东西。前者仅用于引用堆栈中的资源,后者是生成的资源的公共名称。
Type: AWS::EC2::VPC:如果你使用谷歌进行搜索,就会看到一个链接,该链接将直接把你带到有关VPC资源的CloudFormation文档。在这里,你会看到一个与你在这里看到的非常相似的示例。我个人认为,CloudFormation参考页面是不可或缺的。我承认,复制和粘贴示例代码片段是我自己创建资源的一个起点。
双引号:你可以看到,官方文档在不必要的地方使用了双引号。值AWS::EC2::VPC不需要用双引号,下面的“true”值也不需要。我的推测是,AWS文档团队最初从JSON中复制了YAML示例,后者几乎需要把所有内容都放在引号中。
Properties:每个资源都有属性。文档会告诉你哪些是必需的,哪些不是。文档没有告诉你可选设置的默认值,也没有解释大多数设置的含义和允许值。一般来说,你需要完全理解所有可能值的含义、语法和所有可能值的结果,但没有人能做到这一点。大概90%的开发时间都花在了这种辅助研究上。
CidrBlock:我给我的VPC指定的CIDR是10.1.0.0/16。如果你需要有关这个值的含义的详细说明,请参阅此描述。但简单地说,我将为VPC提供超过65000个可能的私有IP地址,所有这些都将以“10.1”开头。这对于大多数用例来说已经足够了。
DNS支持/主机名:这些设置只是为了可以把DNS主机名自动分配给在VPC中创建的EC2实例。关于DNS的详细信息这里就不做过多介绍了,这些设置将提供一个名称,通过它我们可以到达EC2实例,而不仅仅是一个IP地址。不是必需的,但通常有用。
Tags / Key / Value:我们的VPC需要一个名称。奇怪的是,这里没有“name”属性要设置。因此,我们将使用一个标签,其“Key”为“Name”,“Value”为我们想要使用的任意VPC名。该值将在许多(但不是所有)情况下用作显示名称。
这个值很有意思。我们可以简单地硬编码一个名称,但是如果我们在相同的区域使用这个模板创建两个堆栈,我们就会拥有两个同名的VPC(这实际上是可以的,可以试一下!)这可能会造成不必要的混乱,因此,我们将采取额外的步骤来动态分配名称。
!Join:”!Join”是CloudFormation中称为内部函数(intrinsic function)的一种东西。CloudFormation大约有15个这样的函数,我们将在本文中用到几个。简单地说,!Join用于将文本字符串连接在一起。第一个中括号中的’'标识是要放在连接值之间的字符。我们不想要,所以就放了一个空字符串。
**!:**在阅读文档时,请注意,函数有一个长格式(fn::Join)和一个可在YAML中作为替代使用的短格式( !Join)。你会看到,我用的就是这个。但是要注意,在某些情况下,短格式不可用,通常是在将一个函数嵌套到另一个函数中的情况下。
**!Ref:**下一个函数是引用函数。它引用在其他地方定义的东西,通常是在模板中。你将会看到,这个函数用的很多,因为资源之间经常相互引用。
AWS::StackName:这就是CloudFormation中所谓的伪参数。当我们运行模板时,它将解析为堆栈的名称。大约有7个伪参数可用来动态确定当前区域、当前用户等。使用这些伪参数可以极大地提高模板的灵活性。在这种情况下,VPC的名称将与它所属的CloudFormation堆栈相呼应。当你拥有由许多不同堆栈创建的大量资源时,这非常有用——请相信我。
-VPC:这只是VPC名称的后缀。因此,如果我们通过CloudFormation以堆栈名“MyNetwork”运行这个模板,我们的VPC将被标记为“MyNetwork-VPC”。这个名称将在许多(但不是全部)列出或显示VPC的地方使用。
把你所做的工作保存,这还远未完成,但是,你可以使用CloudFormation运行这个模板来检查你的工作。
从浏览器打开AWS管理控制台。登录,选择任何区域。在菜单中找到CloudFormation,如果需要的话,使用搜索功能。登入后,点击“创建堆栈”。选择“上传你自己的模板”选项,然后单击“下一步”。
出现什么错误了吗?如果是这样,就是CloudFormation遇到了语法问题。在继续之前修复这些问题。通常,错误是由于使用制表符代替空格、不正常地使用空格、=代替:或使用的编辑器嵌入了特殊字符造成的。
进入下一页后,给堆栈起一个名称。在本文的剩余部分,我将使用“MyNetwork”,但是你可以使用任何你喜欢的名字。继续向导,不输入其他任何内容,创建堆栈。
在堆栈运行时,查看“事件(Events)”和“资源(Resources)”选项卡。这些事件将显示你的VPC是在什么时候创建的,以及它是在什么时候成功创建的。当堆栈中的最后一个资源被创建时,堆栈的状态将更改为已成功创建。
出现什么错误了吗?堆栈将显示创建失败的状态。如果这里遇到的问题不是语法上的,那么通常就与你试图创建的内容的逻辑有关。使用事件选项卡查找最早出现的错误。我发现,大多数情况下,信息都足够清晰,可以引导我解决问题,并明确标识出出问题的资源。
有一个重要的概念需要注意,如果在创建堆栈时遇到任何错误,整个堆栈(所有资源)将回滚。这种行为可以被重写,但通常这样就行!通常情况下,从头开始执行每一步要容易得多。
如果堆栈失败,它仍然会显示在堆栈列表中,即使堆栈中没有资源。这样做是为了给你时间来调查错误。确定问题后,使用“删除堆栈”操作。
如果你安装了AWS CLI,并且已经配置了访问密钥和秘密密钥(任何区域),而且命令提示符与你的YML文件位于同一个目录,那么你就可以运行以下命令:
aws cloudformation create-stack --stack-name MyNetwork --template-body file://MyNetwork.yml
–stack-name: 可以是你喜欢的任何名称,但是我在本文中将始终使用“MyNetwork”。
–template-body:你一直在编辑的文件。
如果有任何语法错误,你立马就可以收到反馈。在继续之前修复这些问题。通常,错误是由于使用制表符代替空格、不正常地使用空格、=代替:或使用的编辑器嵌入了特殊字符造成的。
然而,并不是所有的错误都会被CloudFormation立即发现。在创建堆栈几分钟后,你可能会遇到非语法错误(例如权限)。发生这种情况时,你就需要通过定期地检查状态来检测问题。另一个方便的选择是等待堆栈完成,或者错误输出,借助下面这个函数:
aws cloudformation wait stack-create-complete --stack-name MyNetwork
当堆栈完成后,可以使用下面的命令进行检查:
aws cloudformation describe-stacks
检查VPC:从管理控制台转到VPC部分。你可以在列表中找到你的VPC,以及你提供的名称。当然,这个VPC中没有任何内容,但我们将在下一篇文章中讨论它。如果你感兴趣,这个VPC或CloudFormation的使用都是不收费的,不过下面会有所更改(请参阅NAT部分)。
在继续我们的模板之前,让我们先尝试一下更新堆栈。CloudFormation其中一个奇妙的特性是能够基于对模板的更改修改堆栈。要演示这一点,请返回到模板并进行以下一项或多项更改:
将VPC“enableDns*”设置改为false;
将VPC的tag/value改为 !Join [’’, [!Ref “AWS::StackName”, “-VPC2” ]]。
保存修改。
从列表中选择现有堆栈并选择“更新堆栈”操作。选择“上传你自己的模板”选项,然后单击“下一步”。继续点击“下一步”,一直到要求你创建更改集的界面。更改集实际上就是CloudFormation打算应用于你的资源的更改。CloudFormation可以轻松地进行一些更改,但有些需要删除和重新创建现有资源。选择最后一个选项来执行更改集,并查看正在进行的更改。
从命令行界面更新堆栈请使用以下命令:
aws cloudformation update-stack --stack-name MyNetwork --template-body file://MyNetwork.yml
和之前一样,你可以使用wait 命令来监控这个过程:
aws cloudformation wait stack-update-complete --stack-name MyNetwork
我们的堆栈很小,所以更新应该只需要几分钟。你可能会遇到错误,如果出现这种情况,结果会更令人困惑,因为CloudFormation会将你的堆栈还原到它之前的形式。由于各种原因,可能会发生错误,例如删除/重新创建资源会影响堆栈外的另一个资源。
关于原生云,我们需要熟悉的一个概念是,资源用完即弃。现在我们有了模板,可以随时创建和删除堆栈。
从控制台选择堆栈并执行“删除堆栈”操作,或者从命令行界面运行以下命令:
aws cloudformation delete-stack -stack-name MyNetwork
回到我们的模板:大多数VPC需要连接到互联网。我们将通过为InternetGatway和GatewayAttachment添加资源来实现。在VPC资源下复制以下这些行:
# 我的VPC需要访问互联网: InternetGateway: Type: AWS::EC2::InternetGateway DependsOn: VPC AttachGateway: Type: AWS::EC2::VPCGatewayAttachment # 注意,除非两个都已经创建,否则你无法将IGW附加到VPC: Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway
AWS::EC2::InternetGateway: 没有它,我们的VPC将无法与公网交互。值得注意的是,你不必将VPC连接到互联网,许多组织构建的VPC完全脱离公共世界,使用VPN连接或AWS Direct Connect专门连接到现有的本地网络。
VPCGatewayAttachment: AttachGateway资源更有趣。这是VPC和InternetGateway之间真正的钩子。注意属性:VpcId引用了上面定义的VPC资源,而InternetGatewayId引用我们的InternetGateway。当CloudFormation运行一个堆栈时,它将尝试同时创建所有资源。然而!Ref函数意味着一个顺序,CloudFormation将同时创建VPC和InternetGateway,但是在使用它们创建“附件(attachment)”之前,它们都必须完成。
!Ref: 如上所述,这是CloudFormation内置的“引用”函数。这是资源相互引用的主要方式。
稍后,在运行此模板时,你会发现,创建前三个资源(尤其是附件)需要几秒钟。稍后,这个延迟会成为问题,在附件完成之前我们无法与公网进行任何交互。稍后请注意使用“DependsOn”来解决这个问题。
现在,使用下面的代码创建一个子网:
PublicSubnetA: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.1.10.0/24 AvailabilityZone: !Select [ 0, !GetAZs ] # 获取列表中的第一个AZ Tags: - Key: Name Value: !Sub ${AWS::StackName}-Public-A
PublicSubnetA: 同样,任意资源名称,仅适用于堆栈内。你喜欢怎么叫就怎么叫。名称中的“A”是为了表明将这个子网关联到可用区域“A”(如下)。
**!Ref VPC:**子网必须存在于VPC中,所以这就是我们将它们关联起来的方式。VPC会首先创建,然后把这个子网附加到这个VPC。
CidrBlock:这个CIDR是VPC(10.1.0 /16)的子区间。本质上,它是指这个子网将包含所有以10.1.10.*开始的地址。这也意味着子网只有256个可用地址(实际上是251个,因为AWS保留5个地址自己用,参见说明)。这有点小,而且我正在造成相当数量的浪费,所以在现实场景中,你可能希望调整这个值。想要深入了解CIDR,请参见VPC部分提供的链接。
AvailabilityZone:每个子网的作用域是特定的可用区域。我们可以简单地硬编码一个值,如“us-east-1a”,但这将我们的模板限制在了维吉尼亚州。最佳实践是构建尽可能与区域无关的模板,因此我们动态地确定AZ。
**!GetAZs:**另一个内置的CloudFormation函数。它返回当前区域中所有可用区域的列表(即不管我们在何处运行堆栈)。例如,如果我们在俄勒冈州运行,返回的列表将是{us-west-2a, us-west-2b, us-west-2c}。关键的是,无论何时调用,它都将以相同的顺序显示这些AZ。
**!Select:**这个内置函数从指定的列表中选择指定的索引。0是列表中的第一项,1是第二项,等等。所以这个表达式表示“给我列表中的第一个AZ”。如果是在俄勒冈州运行,就会得到“us-west-2a”,如果是在维吉尼亚州运行,就会得到“us-east-1a”等等。总之,我们的子网会自动把自己与它在其中运行的区域中的第一个AZ关联起来。
Tags:和前面一样,我们想给子网一个简单的名称,但是没有name属性可用。使用“Name”标记提供一个在许多(但不是全部)需要列出或显示子网的地方使用的名称。
**!Sub:**这是替换函数。它获取后面的字符串,并动态替换在${}标记中找到的任何值。在我们的示例中,由于AWS::StackName会被翻译成“MyNetwork”,所以得到的子网名将是“MyNetwork-Public-A”。
你可能已经注意到,这实现了与前面介绍的!Join函数相同的结果。我这样做是为了向你展示两种可以互相替代的技术。哪个更好?那得由你来决定。如果需要在连接的项之间插入分隔符字符,!Join通常会更好。
更多子网
让我们遵循上面的模式创建更多的子网:
PublicSubnetB: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.1.20.0/24 AvailabilityZone: !Select [ 1, !GetAZs ] # 获取列表中的第二个AZ Tags: - Key: Name Value: !Sub ${AWS::StackName}-Public-B PrivateSubnetA: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.1.50.0/24 AvailabilityZone: !Select [ 0, !GetAZs ] # 获取列表中的第一个AZ Tags: - Key: Name Value: !Sub ${AWS::StackName}-Private-A PrivateSubnetB: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.1.60.0/24 AvailabilityZone: !Select [ 1, !GetAZs ] # 获取列表中的第二个AZ Tags: - Key: Name Value: !Sub ${AWS::StackName}-Private-B
这些子网之间的区别包括:1)逻辑名称、2)物理名称(通过标记值)、3)CIDR范围(没有重叠)和4)指定的可用区域。结果是四个子网,两个是“公共”的,两个是“私有”的,两个在可用区域“A”,两个在可用区域“B”。好了。
如果你愿意,现在可以保存并运行此模板。我建议像这样通过迭代开发来检查错误。
不管名称,子网仅根据其所关联的路由表的定义确定是“公共”或“私有”。路由表定义了可以在子网中将流量路由到哪里。这个主题很复杂,我不会在这里详细介绍,有关子网路由的详细信息,请参阅此信息。
PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: Public PublicRoute1: # 直接路由到IGW的公共路由表: Type: AWS::EC2::Route DependsOn: AttachGateway Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway
PublicRouteTable:又一个仅在堆栈中使用的任意名称,但这里我们尽量使这个名称具有描述性。路由表必须与VPC关联。使用堆栈前缀名进行标记,以便以后更容易识别。
PublicRoute1:这描述了路由表中的一个条目。该条目与公共路由表(!Ref PublicRouteTable)关联,并将任何互联网流量(DestinationCidrBlock: 0.0.0.0/0)路由到互联网网关(!Ref InternetGateway)。每个路由表的第一个条目都是隐含的,我们看不到,这个条目称为“local”路由,所有10.1.0.0/16的流量都会留在VPC中。关于本地路由的详细信息,请点击这里。
**DependsOn: AttachGateway:**这是一个关键点。如果我们试图构建一个指向未附加网关的路由表条目,就会发生错误。为了消除这个错误,我们让CloudFormation知道这个依赖关系。在堆栈创建期间,它就不会尝试构建此路由表条目,直到创建了AttachGateway资源之后。它还将等待VPC和互联网网关,不过,这些会提前完成。
你可能还是不知道,为什么CloudFormation不能自己弄清楚这种依赖关系——这是一个很好的问题。CloudFormation肯定知道你通过!Ref或!GetAtt(稍后将介绍)所做的任何显式引用,但是,它确实无法弄清楚这些资源在你引用它们时实际上是否能够工作。CloudFormation只是代替你进行API调用。如果使用CLI创建引用未附加网关的路由表条目,会遇到同样的错误。这种情况也出现在其他一两个地方,比如当EC2实例需要连接到未完成的RDS实例。
你可以保存你的工作并运行该模板,它应该可以成功。你会注意到,大部分创建时间都花在了AttachGateway资源上。
此时,你的公共子网是可用的。如果你在其中一个子网中使用公共IP地址启动EC2实例,就可以从公网访问它。现在,私有子网…
私有路由表
私有路由表在大多数方面都是相似的,除了不引用InternetGateway:
# 这是一个私有路由表: PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: Private PrivateRoute1: # 私有路由表可以通过NAT访问Web(下面创建) Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 # Route traffic through the NAT Gateway: NatGatewayId: ???
NatGatewayId:如果你猜到“???”是一个无效的值,那么你猜对了。要解释这一点,我们首先需要岔开下话题,快速地解释下NAT的概念(你可以根据需要选择跳过)。
NAT代表网络地址转换。要获得完整的解释,请参阅关于NAT的背景信息。但简单来说:我们不希望私有子网中的实例可以从公网访问。但是,我们确实希望这些实例能够发起出站连接,例如下载。另外,我们希望他们能够在没有公共IP地址的情况下做到这一点。
NAT为此提供了便利。它将拥有一个公共IP地址,并与一个公共子网相关联。私有子网中的私有实例将能够使用它发起出站连接。但是,NAT不允许相反的情况发生,位于公网上的一方不能使用NAT连接到我们的私有实例。
在AWS中有两种基本的NATting(这是一个词吗?),一种是配置为NAT的EC2实例,另一种是相对较新的AWS特性,称为NAT网关。我们将使用后者。
添加以下几行创建一个NAT网关:
# NAT网关: NATGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt ElasticIPAddress.AllocationId SubnetId: !Ref PublicSubnetA Tags: - Key: Name Value: !Sub NAT-${AWS::StackName} ElasticIPAddress: Type: AWS::EC2::EIP Properties: Domain: VPC
NAT网关与其中一个公共子网相关联。我们可以(可能也应该)为每个公共子网创建一个NAT网关,但现在,单个网关使事情变得简单。名称像以前一样是动态设置的。
AllocationId:NAT需要一个固定的公共IP地址。这是由一个弹性IP地址提供的,下面会说明。
**!GetAtt:**又一个隐函数,这里有介绍。这引用了另一个资源的特定属性。这里,使用!Ref不起作用,NAT网关资源需要弹性IP地址的allocationId,而不是地址本身。值得注意的是,文档列出了你可以从每个资源“获取”的属性,通常是资源属性的子集。
ElasticIPAddress:EIP是一个公共IP地址,它的值保持不变,不管它附加到什么。这里有完整的说明,足以说明这对于我们的NAT是必要的。
价格:到目前为止,堆栈中的每个资源都是免费的。NAT网关不是。它们是按照小时以及通过的流量收费,见VPC定价。EIP的不同寻常之处在于,只要使用它们,就不收取任何费用。在没有附加到运行资源的情况下,AWS每小时只收取象征性的费用,这可以防止客户囤积资源。这使得这个堆栈的成本大约是每小时1或2美分,这取决于你所运行的区域。便宜,但一定要在完成后删除堆栈,以防止成本累积。
现在,我们有了一个NAT网关,我们可以在路由表中引用它了。修改后的路由如下所示:
PrivateRoute1: # 私有路由表可以通过NAT(下面创建)访问Web Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 # 通过NAT网关路由流量 NatGatewayId: !Ref NATGateway
这条路由将互联网流量(DestinationCidrBlock: 0.0.0.0/0)发送到NAT网关(!Ref NATGateway)。反过来,由于NAT位于公共子网中,它会将其发送到互联网网关。NAT接收到的响应流量被转发到发出请求的实例。同样,没有显示前面介绍的本地隐式路由。
最后,我们需要将子网关联到它们的相关路由表。命名为“public”的需要关联到公共路由表,而“private”的需要关联到私有路由表:
# 把公共子网附加到公共路由表 # 并把私有子网附加到私有路由表: PublicSubnetARouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetA RouteTableId: !Ref PublicRouteTable PublicSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnetB RouteTableId: !Ref PublicRouteTable PrivateSubnetARouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnetA RouteTableId: !Ref PrivateRouteTable PrivateSubnetBRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnetB RouteTableId: !Ref PrivateRouteTable
AWS::EC2::SubnetRouteTableAssociation:关联资源只是将子网关联到路由表。如果没有这些关联,子网将使用与VPC关联的“main”路由表,而这没有包含在我们的模板中。最好有明确的路由表和关联关系。
恭喜!现在,你已经拥有了一个用于构建典型VPC的功能完整的CloudFormation模板。保存起来以供将来使用,并根据需要扩展它。使用这个模板和前面介绍的指令创建一个堆栈,根据需要更新和删除它。
如果你愿意,可以通过在VPC中运行EC2实例来测试VPC。已经把公共IP地址附加到公共子网的的实例将可以从公网访问。私有子网中的实例将无法从公网访问,但可以进行出站调用。关于这一点,我建议你阅读其他文章。
记住,NAT网关每小时要花一到两便士,所以如果你不使用它,就最好删除它。
通过CloudFormation创建、修改和删除资源堆栈的能力是基础设施即代码概念的一个有效的示例。现在,我们有了一个更快、可重复、可重用的系统,我们不再需要通过界面或命令来手动设置基础设施。
在下一篇文章中,我将向你展示如何使这个模板更加灵活,使用参数和条件创建数量不同的子网,使私有子网可选,并探讨其他NAT选项。我们还将看到,如何把这个堆栈的资源所生成的输出作为其他堆栈的输入供其消费。
Ken Krueger以“通过现代技术的应用,指导组织和个人走向商业成功”作为自己的专业使命。他有超过30年的软件开发、项目领导、项目经理、Scrum Master和导师经验,跨越大型机、客户端-服务器和Web时代。他在Java、Spring、SQL、Web开发、云和相关技术方面有丰富的经验。行业经验包括电信、金融、房地产、零售、发电、航运、酒店和软件开发。他拥有南佛罗里达大学MIS学位,罗林斯学院克鲁默商学院MBA学位,以及Scrum Master、PMP、AWS和Java认证。
查看英文原文:Building a VPC with CloudFormation - Part 1