1.EC2使用
2.编写基础架构:CLI,SDK,CloudFormation
3.自动化部署:CloudFormation,ElasticBeanstalk,OpsWorks
4.保护系统安全:IAM,安全组,VPC
启动虚拟服务器:即启动一个EC2实例
选择OS
第一步是为虚拟服务器选择OS和预装软件,称为Amazon AMI(Amazon Machine Image,Amazon系统映像)。
虚拟服务器是基于AMI启动的。AMI由AWS,第三方供应商机社区提供。
AWS Marketplace提供预装了第三方软件的AMI。
概念解释:虚拟设备,AMI,AKI,Xen,HVM
注意:如果要启动新的虚拟服务器,一定要保证自己使用的是HVM映像。
选择虚拟服务器的规格
选择虚拟服务器的计算能力,在AWS中,计算能力(cpu,mem)被归类到所需的实例类型中。
一个实例类型主要描述了计算能力:cpu的个数即内存memeory的大小。
概念解释:实例类型,实例家族,代,尺寸
检查输出并为SSH选择一个密钥对
用户需要一个密钥来登录自己的虚拟服务器。
用户使用一个密钥而不是密码来完成身份认证,进而登录到自己的虚拟服务器。
密钥比密码更加安全,而且在AWS上运行Linux服务器强制SSH访问使用密钥方式。
(此处的密钥认证方式类似于Github中的密钥认证方式,将生成的公钥上传至服务器,而在本地利用私钥作为访问服务器的凭据)
创建个人密钥的方式:
1.打开aws管理控制台:点击【服务】----【EC2服务】
2.点击左侧【密钥对】
3.点击【创建密钥对】
4.输入密钥对的名字,点击【创建】,使用浏览器下载创建好的密钥对。
5.打开一个终端,切换到下载目录
6.Linux和Mac:在终端中运行chmod 400 密钥文件名.pem
来修改密钥文件的权限仅自己可见
7.WIndows:WIndows没有自带SSH客户端,所以需要安装PuTTY。
PuTTY带有一个工具叫做PuTTYgen,它可以将【密钥名.pem】文件转换成【密钥名.ppk】。
打开PuTTYgen,在【Type of key to generate】选择【rsa】,点击【load】,选择刚刚浏览器下载好的密钥文件。
因为PuTTYgen只显示【*.pkk】文件,需将文件类型切换至【所有】文件类型才可显示刚刚下载好密钥文件。
最后作为【Save private key】。忽略未使用密码保护保存密钥的警告。
现在.pem文件已经被转换成了PuTTY所需的.pkk格式。
虚拟服务器启动后,点击【查看实例】打开概览界面,等待虚拟服务器变为【running】状态。
要完全控制自己的虚拟服务器,用户需要远程登录自己的虚拟服务器。
使用SSH连接到虚拟服务器:
用户可以远程在虚拟服务器上安装额外的软件以及运行命令。
要登录到虚拟服务器,用户要找到虚拟服务器的共有IP地址。
ssh -i $PathToToken/mykey.pem ubuntu@$PublicIp
,$PathToKoken
,使用在控制台显示的链接对话框中的公有IP替换$PublicIp
。手动安装和运行软件
现已经启动了一台ubuntu OS的虚拟服务器。
安装一个名为linkchecker的工具,它能够让我们找到网站上断裂的连接
sudo apt-get install linkchecker -y
现在就可以检查那些指向已经不存在的网站的连接了。
现选择一个网站,然后运行:
linkchecker https://...
根据网页数量的不同,网页爬虫需要一些时间来检查所有的网页是否有断裂的连接。
最终它会列出所有断裂的连接,给用户机会找到并修复他们。
监控和调试虚拟服务器:日志
和监控指标
AWS提供了工具让用户来监控和调试自己的虚拟服务器。
显示虚拟服务器的日志
AWS允许用户使用管理控制台显示服务器的日志(日志可以显示虚拟服务器在启动时和启动后做了什么)。
使用如下步骤打开虚拟服务器的日志:
1.点击【EC2】----【实例】
2.选择一个正在运行的虚拟服务器
3.点击【操作】----【实例设置】----【获取系统日志】
此时会打开一个窗口,然后显示从虚拟服务器得到的日志,这些日志通常在启动期间显示在一台物理监视器上。
这是一个简单有效的访问用户的服务器的日志系统,并且它不需要SSH连接。
监控虚拟服务器的负载
按如下步骤打开虚拟服务器的指标:
关闭虚拟服务器
为了避免产生费用,用户总是应该关闭不用的虚拟服务器。
用户可以使用以下5个操作来控制一台虚拟服务器的状态:【操作】----【实例状态】
资源清理:
1.【EC2】—【实例】
2.点击正在运行的虚拟服务器
3.【操作】—【实例状态】----【终止】
更改虚拟服务器的容量
用户总是可以更改一台虚拟服务器的容量:云计算的优势之一,它给了用户垂直扩展的能力。
如何更改一台正在运行的虚拟服务器的容量:
1.首先启动一个EC2实例
2.使用SSH连接到EC2实例
Linux或Mac:使用ssh -i
连接
Windows:使用PuTTY工具
连接
3.执行:
cat /proc/cpuinfo
free -m
来获取服务器的CPU和内存信息
4.如果用户需要更多的cpu,memory,网络容量,或是修改虚拟服务器的实例家族于版本。
首先【停止】该服务器
在服务器停止后,可以更改实例:【操作】----【实例设置】
更改完成后,公有及私有IP地址也发生了变化。
需要获取新的公有IP地址,通过SSH重新连接到新的IP地址。
5.资源清理:终止该台虚拟服务器
在另一个数据中心开启虚拟服务器
AWS为全球提供数据中心。
要使互联网获得**
**,为主要用户选择一个最近的数据中心十分重要。
更改数据中心:
用户可以为AWS服务指定区域。
各个区域之间完全独立,数据不在区域间进行传输。
典型情况下,一个区域有两个或更多位于同一地区的数据中心组成。
这些数据中心间有着很好的连接,他们能提供高可用的基础架构。
一些AWS服务:如内容分发网络CDN(Content Delivery Network)服务,域名系统DNS(Domian Name System)服务,是在这些区域之外的数据中心之上全球运行的。
由于各个区域之间相互独立,故在切换区域后需要再次创建新的密钥对用以访问EC2.
用户使用密钥对的形式访问自己创建的虚拟服务器EC2资源,将创建好的密钥对的私钥保存在本地中,当SSH连接到EC2实例时,使用刚刚保存到本地私钥进行认证,进而访问EC2.
分配一个固定的公有IP
前面创建的EC2实例都自动分配了一个公有的IP地址。
但是每次启动或是停止一台虚拟服务器EC2时,公有IP地址就改变了。
如果想用一个固定的IP地址运行一个应用程序,则需使用AWS提供的弹性IP地址服务:Elastic IP address。
使用以下步骤来分配并关联一个公有IP地址到一台虚拟服务器上:
在拥有了固定的IP地址:即弹性IP地址后,可以将该弹性IP关联到一个EC2实例上:
1.选择刚刚创建好的弹性IP,【操作】-----【关联地址】
2.在【实例】中点击右侧三角,选择需要关联的EC2实例
3.点击右下角【关联】完成EC2实例固定IP地址的设定。
如果用户需要确保自己的应用的端点不变化,就需要手动创建一个 弹性IP,并将该弹性IP地址关联到EC2实例。
利用弹性IP地址,可以在用户无知觉的情况下换掉后端关联的EC2实例,且可以保持服务的不中断运行。
用户也可以使用多个网络接口来关联多个公有IP地址到一台虚拟服务器。
适用情况:用户需要在同一个端口运行不同的应用或者不同的网站使用一个唯一的固定的公有IP地址。
注意:IPv4地址是稀缺资源。为了防止弹性IP地址浪费,AWS将对没有关联到任何服务器的弹性IP地址收费。
向虚拟服务器添加额外的网络接口
除了公有IP地址,用户还可以控制自己的虚拟服务器的网络接口。
用户可以向一台虚拟服务器添加多个网络接口,并且控制关联到这些网络接口的私有IP地址和公有IP地址。
用户可以使用额外的网络接口管来关联第二个公有IP地址到自己虚拟服务器。
用以下步骤来为自己的虚拟服务器EC2实例,创建一个额外的网络接口:
1.点击EC2服务左侧的【网络接口】
2.点击【创建网络接口】
3.输入该网络接口的描述信息
4.选择自己EC2实例的子网作为新的网络接口的子网。
5.让【私有IP地址】保持默认的自动分配
6.安全组选择一个即可
7.点击创建
当新的网络接口的状态变为Available,用户可以将他附加到自己的虚拟服务器。
1.选择新创建的网络接口,点击【附加】
2.选择正在运行的虚拟服务器的ID,点击【附加】
现在已经附加了一个额外的网络接口到自己的虚拟服务器,接下来关联一个额外的公有IP地址到这个额外的网络接口。
1.点击左侧的【弹性IP】
点击【分配新地址】来分配一个新的固定的弹性IP地址。
【操作】----【关联地址】,将它连接到刚刚创建的网络接口
现在虚拟服务器就可以通过两个不同的IP地址来访问了。
这样用户可以根据公有IP地址提供两个不同的网站服务,需要配置网络服务器来根据公有IP地址来应答请求。
在次使用SSH连接到虚拟服务器,并且在终端输入ifconfig
后,就能看到自己的新网络接口附加到了虚拟服务器上。
每个网络接口都能连接到一个私有IP地址和一个公有IP地址。
我们需要配置网络服务器来根据IP地址提供不同的网站。
虚拟服务器不知道任何有关于他的公有IP地址的信息,但是我么可以根据私有IP地址来区分请求。
清理资源:
1.终止虚拟服务器
2.转到【网络接口】,选择并删除网络接口
3.转至【弹性IP】—【操作】----【释放IP地址】,释放两个公有弹性IP地址
优化虚拟服务器的开销
省钱的两个选项:
AWS提供通过接口来控制的基础架构,叫做应用编程接口API(application programming interface)。
用户能通过API控制AWS的每一部分,用户可以使用大多数编程语言,CLI,和其它工具的SDK调用这些API。
在AWS上,一切都可以通过API来控制,用户通过HTTPS协议调用REST API来与AWS交互。
例如:要列出S3对象存储里的所有文件,可以向API端点发送一个GET请求:
GET / HTTP/1.1
Host: BucketName.s3.amazonaws.com
Authorization: […]
使用底层的HTTPS请求直接调用API不太方便,另一种简单的方法时:使用CLI或SDK来和AWS
交互。
基础架构即代码
【基础架构即代码】:表达了使用高级编程语言来控制IT系统的思想。
在软件开发中,自动化测试,代码库和构建服务器提高了软件工程的质量。
如果用户的基础架构可以当作代码来对待,用户就能过关对自己的基础架构代码和应用程序代码使用相同的技术。
最终用户可以使用自动化测试,代码库和构建服务来改善基础架构的质量。
注意:区别【基础架构即代码】和【基础架构即服务IaaS】:IaaS指的是按照使用量进行付费的租用服务器,存储和网络的业务模式
自动化和DevOps(Development Operations)是软件开发驱动的一个方法,以便让开发和运维更加紧密的结合在一起:即自己开发的软件自己进行后续的运维工作。
基础架构语言:JIML(JSON Infrastructure Markup Language)JSON基础架构标记语言
JSON转为AWS API调用的过程:
注意:JIML解释器确定了资源的创建顺序,JIML解释器把依赖图变成一个现行的使用伪语言的命令流。这个伪语言代表了用正确的舒徐创建所有资源所需要的步骤。最后JIML运行时环境将伪语言命令翻译成AWS API调用。
即:基础代码即架构的所需的一切:都与依赖项相关。
2.使用AWS CLI
CLI官方文档:https://aws.amazon.com/cn/cli/
使用CLI创建基础架构:CLI是实现基础架构即代码的一种工具。
AWS CLI是一个从命令行使用AWS的便捷方法。它运行在linux,mac,Windows上,用python编写。
AWS CLI为所有的AWS服务提供了一个统一的访问接口。
安装AWS CLI
python -version //检查python版本
pip --version //检查pip版本
curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" //若未安装pip,则执行该命令安装pip
sudo python get-pip.py
pip --version //验证pip的安装
sudo pip install awscli //安装AWS CLI
aws --version
Set-ExecutionPolicy Unrestricted
,按Enter键执行这一命令。这样就能执行示例中的未签名的PowerShell副本。aws --version
以验证AWS CLI是否正常工作。配置AWS CLI
要使用AWS CLI需要验证用户的身份。目前为止,使用的是AWS根账号。根账号能做任何事情。
建议不用使用根账号,所以需要创建一个新用户:
aws configure
,用户会被问及4个信息:
us-east-1
json
aws ec2 describe-regions
来获取所有可用区域的列表。aws [--key value ...]
aws help
:显示所有可用的服务aws help
:显示某一服务所有可用的操作aws help
:显示特定服务操作所有可用的选项--query
选项使用JMESPath(一种JSON的查询语言)从结果中提取数据,这个选项用于提取结果中的特定项。
JSON中JMESPath的操作:
aws ec2 describe-images
:列出了可用的AMI。
要启动一个EC2实例,需要ImageId,使用JMESPath可以提取到这一信息:
最好不要运行上述命令,上述命令的输出时间较长,且输出十分多,可按【Ctrl+C】终止命令的输出
运行aws ec2 describe-regions
:显示所有的可用区
要提取第一个Endpoint,
路径为Regions[0].Endpoint,
查询语句为:aws ec2 describe-regions --query Regions[0].Endpoint
查询结果为:ec2.eu-north-1.amazonaws.com
要提取所有的RegionName,
路径为Regions[*].RegionName,
查询语句为:aws ec2 describr-regions --query Regions[*].RegionName
查询结果为:
使用SDK,Cloud9,CDK编程:https://docs.aws.amazon.com/index.html
AWS为许多编程语言提供软件开发套件(Software Development kits,SDK)
用于在 AWS 上进行构建的工具:用于在 AWS 上开发和管理应用程序的工具
https://aws.amazon.com/cn/tools/?id=docs_gateway
AWS Cloud9: 用于编写、运行和调试代码的云 IDE
概览: https://aws.amazon.com/cn/cloud9/
功能: https://aws.amazon.com/cn/cloud9/details/
定价: https://aws.amazon.com/cn/cloud9/pricing/
使用方法: https://aws.amazon.com/cn/cloud9/getting-started/
使用入口:https://us-east-1.console.aws.amazon.com/cloud9/home/product
FAQ(Frequent Ask Question): https://aws.amazon.com/cn/cloud9/faqs/
AWS Cloud9 是一种基于云的集成开发环境 (IDE),您只需要一个浏览器,即可编写、运行和调试代码。它包括一个代码编辑器、调试程序和终端。Cloud9 预封装了适用于 JavaScript、Python、PHP 等常见编程语言的基本工具,您无需安装文件或配置开发计算机,即可开始新的项目。Cloud9 IDE 基于云,因此您可以从办公室、家中或任何地方使用已连接互联网的计算机完成项目。Cloud9 还可以为开发无服务器应用程序提供无缝体验,使您能够轻松定义资源、进行调试,并在本地和远程执行无服务器应用程序之间来回切换。借助 Cloud9,您可以与团队快速共享开发环境,从而能够将程序配对,并实时跟踪彼此的输入。
AWS CDK:https://docs.aws.amazon.com/zh_cn/cdk/?id=docs_gateway
The AWS Cloud Development Kit (AWS CDK) is a software development framework for defining your cloud infrastructure in code and provisioning it through AWS CloudFormation.
AWS CloudFormation
CloudFormation文档:https://docs.aws.amazon.com/zh_cn/cloudformation/?id=docs_gateway
CloudFormation是一个比JIESML(JSON Infrastructure Expression Markup Langue)更好的,用于描述:基础架构即代码的工具。
CloudFormation基于模板来创建基础架构。
CloudFormation是AWS上管理基础架构最强的工具之一。
注意:使用蓝图(blueprint)这一术语来描述基础架构自动化。管理配置服务AWS CloudFormation使用的蓝图被称为模板。
模板使用JSON描述了用户的基础架构,CloudFormation能对其进行解析。
用户只需要使用声明的表述语言,而不是给出实现他的所有操作。
描述的方法意味着用户告诉CloudFormation需要什么样的基础架构以及组件之间时怎样相互连接的。
用户不需要告诉CloudFormation需要哪些操作来创建那样的基础架构,不需要定义操作被执行的顺序。
CloudFormation的概念:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html
ColudFormation的好处:
AWS CloudFormation 模板格式:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/template-formats.html
yaml:http://www.yaml.org
json : http://www.json.org
AWS CloudFormation模板解析:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/template-anatomy.html
CloudFormation模板的组成部分:
模板包含几个主要部分。Resources 部分是唯一的必需部分。模板中的某些部分可以任何顺序显示。但是,在您构建模板时,使用以下列表中显示的逻辑顺序可能会很有用,因为一个部分中的值可能会引用上一个部分中的值。
1.AWSTemplateFormationVersion(可选):格式版本
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/format-version-structure.html
2.Description(可选):这个模板是关于什么的
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/template-description-structure.html
3.元数据(可选):提供模板的详细信息
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html
4.参数(可选):参数使用值来自定义模板。例如:域名,客户ID,DB密码
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html
参数至少有一个名字和类型。建议用户同时添加一个描述
使用可选的 Parameters 部分来自定义模板。利用参数,您能够在每次创建或更新堆栈时将自定义值输入模板。{ [...] "Parameters": { "NameOfParameter": { #参数名 "Type": "", "Description": "" [...] } }, [...] }
5.映像Mappings(可选):可选的 Mappings 部分将密钥与对应的一组命名值相匹配。
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html
6.条件Conditions(可选):可选的 Conditions 部分包含一些声明,以定义在哪些情况下创建或配置实体
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html
7.转换Transforma(可选):可选的 Transform 部分指定 AWS CloudFormation 用于处理您的模板的一个或多个宏。Transform 部分基于 AWS CloudFormation 的简单的声明性语言构建,并带有一个强大的宏系统。
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/transform-section-structure.html
8.资源Resources(必需):必需的 Resources 部分声明您要包含在堆栈中的 AWS 资源,例如 Amazon EC2 实例或 Amazon S3 存储桶。
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/resources-section-structure.html
9.输出Outputs(可选):可选的 Outputs 部分声明输出值,您可以将这些值导入到其他堆栈中(以便创建跨堆栈引用)、在响应中返回这些值(以便描述堆栈调用)或者在 AWS CloudFormation 控制台中查看。例如,您可以输出堆栈的 S3 存储桶名称以使该存储桶更容易找到。
https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html
如果从模板创建一个 基础架构,则CloudFormation称为堆栈。模板只存在一次,而许多堆栈可以从同一个模板中被创建。
CloudFormation创建步骤:
JSON格式的模板
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS in Action: chapter 4",
"Parameters": {
"KeyName": {
"Description": "Key Pair name",
"Type": "AWS::EC2::KeyPair::KeyName",
"Default": "mykey"
},
"VPC": {
"Description": "Just select the one and only default VPC",
"Type": "AWS::EC2::VPC::Id"
},
"Subnet": {
"Description": "Just select one of the available subnets",
"Type": "AWS::EC2::Subnet::Id"
},
"InstanceType": {
"Description": "Select one of the possible instance types",
"Type": "String",
"Default": "t2.micro",
"AllowedValues": [
"t2.micro",
"t2.small",
"t2.medium"
]
}
},
"Mappings": {
"EC2RegionMap": {
"ap-northeast-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"
},
"ap-southeast-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"
},
"ap-southeast-2": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"
},
"eu-central-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"
},
"eu-west-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"
},
"sa-east-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"
},
"us-east-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"
},
"us-west-1": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"
},
"us-west-2": {
"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"
}
}
},
"Resources": {
"SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "My security group",
"VpcId": {
"Ref": "VPC"
},
"SecurityGroupIngress": [
{
"CidrIp": "0.0.0.0/0",
"FromPort": 22,
"IpProtocol": "tcp",
"ToPort": 22
}
]
}
},
"Server": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {
"Fn::FindInMap": [
"EC2RegionMap",
{
"Ref": "AWS::Region"
},
"AmazonLinuxAMIHVMEBSBacked64bit"
]
},
"InstanceType": {
"Ref": "InstanceType"
},
"KeyName": {
"Ref": "KeyName"
},
"SecurityGroupIds": [
{
"Ref": "SecurityGroup"
}
],
"SubnetId": {
"Ref": "Subnet"
}
}
}
},
"Outputs": {
"PublicName": {
"Value": {
"Fn::GetAtt": [
"Server",
"PublicDnsName"
]
},
"Description": "Public name (connect via SSH as user ec2-user)"
}
}
}
YAML格式的模板
AWSTemplateFormatVersion: 2010-09-09
Description: 'AWS in Action: chapter 4'
Parameters:
KeyName:
Description: Key Pair name
Type: 'AWS::EC2::KeyPair::KeyName'
Default: mykey
VPC:
Description: Just select the one and only default VPC
Type: 'AWS::EC2::VPC::Id'
Subnet:
Description: Just select one of the available subnets
Type: 'AWS::EC2::Subnet::Id'
InstanceType:
Description: Select one of the possible instance types
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
Mappings:
EC2RegionMap:
ap-northeast-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-cbf90ecb
ap-southeast-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-68d8e93a
ap-southeast-2:
AmazonLinuxAMIHVMEBSBacked64bit: ami-fd9cecc7
eu-central-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-a8221fb5
eu-west-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-a10897d6
sa-east-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-b52890a8
us-east-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-1ecae776
us-west-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-d114f295
us-west-2:
AmazonLinuxAMIHVMEBSBacked64bit: ami-e7527ed7
Resources:
SecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: My security group
VpcId: !Ref VPC
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
FromPort: 22
IpProtocol: tcp
ToPort: 22
Server:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !FindInMap
- EC2RegionMap
- !Ref 'AWS::Region'
- AmazonLinuxAMIHVMEBSBacked64bit
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref SecurityGroup
SubnetId: !Ref Subnet
Outputs:
PublicName:
Value: !GetAtt
- Server
- PublicDnsName
Description: Public name (connect via SSH as user ec2-user)
更新CloudFormation:
删除堆栈:
CloudFormation的替代方案
如果用户不想写JSON文本来为自己的基础架构创建模板,有几个替代CloudFormation的方案:
部署: 不论想用自主开发的,开源项目的,还是商业厂商的软件,都需要安装,更新和配置应用程序及其依赖的组件,这一过程称为部署。
AWS上用于部署的工具:
AWS提供不同的工具帮助用户将应用部署到虚拟服务器上。
在虚拟服务器启动时插入bash或powershell脚本,可以让用户有区别的初始化服务器,如:安装不同的软件或配置服务。
在AWS上保护用户系统安全的几个重要方面:
AWS安全负责说明
AWS是一个责任共担的环境:安全责任由AWS和用户共同承担。
AWS承担的责任:
保持软件最新
如果一个安全更新发布了,用户必须快速安装它,因为如何利用这一漏洞可能与更新一同发布了,或者说每个人都能通过查看源代码来重建漏洞。
如果用户SSH登录到一台EC2实例,可能会看到显示相关更新的说明,AWS不会替用户在其EC2实例上应用在这些补丁,更新补丁是用户的责任。
用户可以使用包管理工具更新补丁:
Debian系的OS(如ubuntu,kali,debian):apt get update
RHEL系的OS(如RHEL,fedora,centos):yum --security check-update
来查看哪些程序包需要安全更新
处理安全更新的两种情况:
yum -y update
yum -y --security update
yum update-to
使用明确的版本来更新程序,而不是使用最新的yum -y --security update
或yum update-to [...]
,若用户有许多服务器,则可以使用一个脚本来获取所有的服务器列表,然后在所有的服务器上运行yum来使这个任务自动化。 在所有正在运行的EC2实例上安装安全更新
//获得所欲正在运行的EC2实例的公共DNS名
PUBLICNAMES=$(aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].PublicDnsName" \
--output text)
for PUBLICNAME in $PUBLICNAMES; do
//通过SSH连接
ssh -t -o StrictHostKeyChecking=no ec2-user@$PUBLICNAME \
"sudo yum -y --security update"
done
保护AWS账号安全
每个AWS账号默认有一个root用户,root用户具有最高权限。
可以有多个普通用户,新创建的普通用户默认没有任何权限,需要根据用户的职责进行授权,遵照最小权限原则。
若攻击者想要访问你的AWS账户,攻击者必须能使用你的账户认证,由三种方法:
使用MFA(Multi-Factor Authentication,多重身份认证)来保护root用户,然后停止root用户的使用,创建一个新用户用于日常操作,并授予一个角色最小权限。
保护AWS账户的root用户安全
为root用户开启多重身份验证MFA,在MFA被激活后,你需要一个密码和一个临时令牌来作为root用户登录。这样攻击者不仅需要你的密码还需要你的MFA设备。
启用MFA步骤:
IAM(Indentity and Access Management)服务
IAM文档:https://docs.aws.amazon.com/zh_cn/iam/?id=docs_gateway
IAM服务通过AWS API提供身份认证与授权所需的一切。
你通过AWS API发送的每个请求都会通过IAM来检查这个请求是否允许。
IAM控制在你的AWS账户里,谁(身份认证)能做什么(授权)。
使用IAM身份认证是通过用户和角色完成的,授权是通过策略完成的。
角色认证一个EC2实例,而用户可以用于其它一切。
注意:IAM用户和IAM角色使用策略进行授权,用户和角色不能做任何事情直到你在策略中允许特定的操作。
root用户(人) | IAM用户(其它一切) | IAM角色(EC2实例) | |
---|---|---|---|
可以有一个密码 | 总是 | 是 | 否 |
可以有一个访问密码 | 是(不推荐) | 是 | 否 |
可以属于一个组 | 否 | 是 | 否 |
可以与一个EC2实例相关联 | 否 | 否 | 是 |
用于授权的策略
每个策略在JSON中定义且包含一个或多个声明。一个声明可以允许或拒绝在特定资源上做特定的操作。通配符*可以用来创建通用的声明。
策略和权限:https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/access_policies.html
Amazon 资源名称 (ARN) 和 AWS 服务命名空间:https://docs.aws.amazon.com/zh_cn/general/latest/gr/aws-arns-and-namespaces.html
AWS的策略类型:一共有6种。
最常用的是第一个:基于身份的策略。
托管策略与内联策略:https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/access_policies_managed-vs-inline.html
策略三要素:对什么资源(Resource),采取什么动作(Action),产生什么效果(Effect)
下面的策略声明允许对EC2服务中所有资源进行任意操作:
{
"Version": "2012-10-17" //指定2012-10-17来锁定版本
"Statement": [{
"Sid": "1",
"Effect": "Allow", //允许Allow或拒绝Deny
"Action": ["ec2:*"], //所有EC2操作(通配符*)
"Resource": ["*"]
}]
}
如果在同一个操作上有多个声明,拒绝将覆盖允许。
下面的策略允许除了终止实例以外的所有EC2操作:
{
"Version": "2012-10-17" //指定2012-10-17来锁定版本
"Statement": [{
"Sid": "1",
"Effect": "Allow", //允许Allow或拒绝Deny
"Action": ["ec2:*"], //所有EC2操作(通配符*)
"Resource": ["*"]
},{
"Sid": "2",
"Effect": "Deny", //拒绝
"Action": "["ec2:TerminateInstances"], //终止EC2实例
"Resurce": ["*"]
}]
}
下面的策略拒绝所有的EC2操作。声明ec2:TerminateInstances不是关键性的,因为Deny覆盖Allow。当你拒绝一个操作时,是无法通过在另一个声明中允许它的。
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "1",
"Effect": "Deny",
"Action": "["ec2:*"],
"Resource": ["*"]
},{
"Sid": "2",
"Effect": "Allow",
"Action": "["ec2:terminateInstances"],
"Resource": ["*"]
}]
}
Resource
部分使用通配符["*"]
来表示所有资源,在AWS中,资源通过ARN(Amazon Resource Name)来标识。
Amazon 资源名称 (ARN) 和 AWS 服务命名空间:https://docs.aws.amazon.com/zh_cn/general/latest/gr/aws-arns-and-namespaces.html
要找出账户ID,可以使用CLI:
Linux或Mac:打开一个Terminal
Windows:打开一个PowerShell
运行:aws iam get-user --query "User.Arn" --output text
用于身份认证的用户和用于组织用户认证的组
一个用户可以被密码或访问密钥所认证:
.aws
的隐藏文件,点击查看该文件,发现该文件中保存了你刚刚配置的账户ID即访问密钥,存放在credentials文件中,这样第二次即以后登录就直接使用该文件中保存的信息,而不用手动再次输入账户ID及登录密钥了,此后也可以手动修改该文件。$aws iam create-group --group-name "admin"
$aws iam attach-group-policy --group-name "admin" --policy-arn "arn:aws:iam::asw:policy/AdministratorAccess"
$aws iam create-user --user-name "myuser"
$aws iam add-user-to-group --group-name "admin" --user-name "myuser"
$aws iam create-login-profile --user-nsme "myuser" --password "$Password"
用户myuser已经准备好开始使用了,但是,如果不是使用root用户的话,则必须使用一个不同的URL来访问管理控制台,即:https://$accountId.sigin.aws.amazon.com/console
直接登录aws管理控制台:https://console.aws.amazon.com,使用的是root账户,需要root账户的密码。
为IAM(Identity and Access Control)用户启用MFA(Multi-Factor Authentication)
建议为所有用户启用MFA。
最好不要为自己的root用户和日常用户使用同样的MFA设备。
用户可以从AWS合作伙伴,如Gemalto那里以13美元购买硬件MFA设备。
按以下步骤为自己的IAM用户开启MFA:
1.在管理控制台打开【IAM服务】
2.在左侧选择用户
3.选择一个用户
4.在页面底部的【Sign-In Credientials】部分点击【Manage MFA Device】按钮
现在起停止使用root用户,总是使用新创建的用户myuser即管理控制台的新链接。
https://$accountId.sigin.aws.amazon.com/console
用户永远不应该把一个用户访问密钥复制到一个EC2实例上,要使用IAM角色,不要在自己的源代码中存储安全凭证,并且永远不要把它们提交到自己的Git或SVN资源库中,而是尽可能的使用IAM角色。
用于认证AWS的角色
IAM用户(root,普通)用于认证:人
IAM角色用于认证:AWS资源
如果AWS资源附加了一个或多个角色,IAM会检查这些角色附加的所有策略(权限)来确定请求是否被允许。
默认情况下一个新创建的AWS资源没有任何权限,所以就不被允许调用任何AWS API。
IAM 角色:https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_roles.html?icmpid=docs_iam_console
AWS IAM 常见问题:https://aws.amazon.com/cn/iam/faqs/
IAM JSON 策略元素参考:https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/reference_policies_elements.html
例如:创建一个过一会自己终止EC2实例:这个EC2实例需要停止自己的授权,可以使用内联策略进行授权。
以下示例为:如何在CloudFormation中把角色定义成一个资源:
实例代码资源:https://s3.amazonaws.com/awsinaction/chapter6/server.json
"Role": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"service": ["ec2.amazonaws.com"]
},
"Action": ["sts:AssumeRole"]
}]
},
"Path": "/",
"Policies": [{
"PolicyName": "ec2",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Sid": "Stmt1425388787800",
"Effect": "Allow",
"Action": ["ec2:StopInstances"],
"Resource": ["*"],
"Condition": {
"StringEquals": {"ec2:resourceTag/aws:cloudformation:stack-id":{"Ref": "AWS::StackId"}}
}
}]
}
}]
}
}
要附加一个角色给实例,必须首先创建一个实例配置文件:
"InstanceProfile": {
"Type": "AWS::IAM::InstanceProfile",
"Properties": {
"Path": "/",
"Roles": "[{"Ref": "Role"}]
}
}
现在可以把角色(策略,一组权限的定义)和EC2实例(资源)关联起来(即授权):
"Server": {
"Type": "AWS::EC2::Instance",
"Properties": {
"IamInstanceProfile": {"Ref": "InstanceProfile"},
[...],
"UserData": {"Fn::BAse64": {"Fn::Join": ["",[
"#!/bin/bash -ex\n",
"INSTANCEID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`\n",
"echo \"aws --region us-east-1 ec2 stop-instances --instance-ids $INSTANCEID\" | at now + 5 minutes\n"
]]}}
}
}
控制进出虚拟服务器的网络流量
用户只希望必要的网络流量进出自己的EC2实例。
使用防火墙,用户可以控制进入(ingress|inbound)和出去(egress|outbound)的数据流量。
如果用户运行一台网站服务器,则对外打开的端口只有HTTP流量使用的80端口和HTTPS流量使用的443端口,所有其它的端口都应该被关闭,只打开必要的端口,就关闭了许多可能的安全漏洞。
在网络流量进入或离开用户的EC2实例之前,它将穿过AWS提供的防火墙,这个防火墙审查网络流量并使用规则来确定流量是被允许还是被拒绝。
AWS对防火墙负责,你对防火墙规则负责。
默认情况下:所有入站流量都被拒绝,而所有出站流量都被允许。
你可以添加入站流量规则:将默认的拒绝所有转换为你添加的入站流量规则。
使用安全组控制虚拟服务器的流量
一个安全组可以被关联到AWS资源。
一个安全组遵循一组规则,一个规则可以基于以下内容允许网络流量:
在CloudFormation里一个安全组资源类型是AWS::EC2::SecurityGroup
允许ICMP流量
如果用户要从自己的计算机ping一台EC2实例,就必须允许来自因特网的控制报文协议ICMP(Internet Control Message Protocol)
默认情况下,所有的入站流量都被拒绝,尝试ping $PublicName
来确认ping访问不被允许。
用户需要在安全组中添加一条规则来允许入站流量,其中协议是ICMP。
示例代码:https://s3.amazonaws.com/awsinaction/chapter6/firewall1.json
允许ICMP的安全组
{
[...]
"Resources": {
"SecurityGroup": {
[..]
},
"AllowInboundICMP": { //允许ICMP的描述规则
"Type": "AWS::EC2::SecurityGroupIngress", //入站规则类型
"Properties": {
"GroupId": {"Ref": "SecurityGroup"}, //将规则与安全组联系起来
"IpProtocol": "icmp", //指定协议类型
"FromPort": "-1", //-1代表所有端口
"ToPort": "-1",
"CirdIp": "0.0.0.0/0" //无类别域间路由的0.0.0.0/0意味着所有的源IP地址都被允许
}
},
"Server": {
[...]
}
}
}
使用位于 https://s3.amazonaws.com/awsinaction/chapter6/firewall2.json 的模板更新CloudFormation的堆栈。
允许SSH流量
当能够ping自己的EC2实例后,用户会想要通过SSH登录到自己的EC2实例。
要想通过SSH登录到自己的EC2实例,必须创建一条规则,允许端口22上的入站TCP请求。
允许SSH的安全组
[...]
"AllowInboundSSH": {
"Type": "AWS::EC2::SecurityGroupIngress", //允许SSH规则描述
"Properties": {
"GroupId": {"Ref": "SecurityGroup"}, //将安全组与规则联系起来
"IpProtocol": "tcp", //SSH基于TCP协议
"FromPort": "22", //默认的SSH端口是22
"ToPort": "22", //允许一个范围的端口,或者将出站后到达的端口设为与入站时达到的端口相同
"CidrIp": "0.0.0.0/0" //0.0.0.0/0的无类别域间路由意味着所有的源IP地址都被允许
}
},
[...]
使用位于 https://s3.amazonaws.com/awsinaction/chapter6/firewall3.json 的模板更新CloudFormation的堆栈。
现在我们可以使用SSH登录自己的虚拟服务器了,注意:我们还需正确的私钥。
防火墙知识控制网络层,它不能替代基于密码或密钥的身份认证。
允许来自源IP地址的SSH流量
在模板中硬编码进公有IP地址存在问题:公有IP地址会变化,解决方法:使用参数Parameters。
添加一个参数来保存自己当前的公有IP地址,然后修改AllowInboundSSH规则。
允许SSH的安全组
[...]
"Parameters": "{
[...],
"IpForSSH": {
"Description": "Your Public IP address to allow SSH access",
"Type": "String"
}
},
"Resources":{
"AllowInboundSSH": {
"Type": "AWS::EC2::SecurityGroupIngress", //允许SSH规则描述
"Properties": {
"GroupId": {"Ref": "SecurityGroup"}, //将安全组与规则联系起来
"IpProtocol": "tcp", //SSH基于TCP协议
"FromPort": "22", //默认的SSH端口是22
"ToPort": "22", //允许一个范围的端口,或者将出站后到达的端口设为与入站时达到的端口相同
"CidrIp": {"Fn::Join": ["", [{"Ref": "IpForSSH"}, "/32"]]} //使用$IpForSSH/32作为值
}
},
[...]
公有IP地址和私有IP地址的区别:
在自己的本地网络中使用私有IP地址,而当连接到互联网时,使用的是互联网的网关IP地址,所有的请求都被重定向到互联网网关。而你的本地网络并不知道这个公有IP地址。
要找出自己的公有IP地址,访问 http://api.ipify.org
对于大多数人来说,通常当我们重新连接到互联网时,公有IP地址会时不时的发生变化。
使用位于 https://s3.amazonaws.com/awsinaction/chapter6/firewall4.json 的模板更新CloudFormation堆栈。当要求输入参数时,输入你的公有IP地址$IpForSSH,现在只有你的IP地址能够建立到你的EC2实例的SSH连接。
无类别域间路由CIDR:Classless Inter Domian Route
CIDR用于指定源IP地址的范围。
允许来自源安全组的SSH流量:使用堡垒主机
如果要控制一个AWS资源到另一个AWS资源的的流量,可以使用安全组。
可以根据源与目的地是否属于一个特定的安全组来控制网络的流量。
因为云的弹性性质,用户很可能处理动态数量的服务器,所以基于源IP地址的规则难以维护,如果用户的规则是基于安全组的,则易于维护。
用于SSH访问的堡垒主机(跳转盒)
其中的技巧在于:只有一台服务器----堡垒主机,能通过SSH被互联网访问(应该被限制到一个特定的源IP地址)。所有其它服务器只能从堡垒主机使用SSH访问。
优势:
1.用户的系统只有一个入口,且这一入口除了SSH不做其他事。这个盒子被攻破的机会很小
2.如果用户的某台网站服务器,邮件服务器,FTP服务器被攻破了,攻击者无法从这台服务器跳转到所有其它服务器
要实现堡垒主机的概念,必须做到以下两点:
1.允许从0.0.0.0/0或一个指定的IP地址使用SSH访问堡垒主机
2.只有当流量来源于堡垒主机时才允许使用SSH连接到所有其它服务器
例:一台堡垒主机加两台服务器的体系:这两台服务器仅仅接受来自堡垒主机的入站SSH访问。
AWSTemplateFormatVersion: 2010-09-09
Description: 'AWS in Action: chapter 6 (firewall 5)'
Parameters:
KeyName:
Description: Key Pair name
Type: 'AWS::EC2::KeyPair::KeyName'
Default: mykey
VPC:
Description: Just select the one and only default VPC
Type: 'AWS::EC2::VPC::Id'
Subnet:
Description: Just select one of the available subnets
Type: 'AWS::EC2::Subnet::Id'
IpForSSH:
Description: Your public IP address to allow SSH access
Type: String
Mappings:
EC2RegionMap:
ap-northeast-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-cbf90ecb
ap-southeast-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-68d8e93a
ap-southeast-2:
AmazonLinuxAMIHVMEBSBacked64bit: ami-fd9cecc7
eu-central-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-a8221fb5
eu-west-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-a10897d6
sa-east-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-b52890a8
us-east-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-1ecae776
us-west-1:
AmazonLinuxAMIHVMEBSBacked64bit: ami-d114f295
us-west-2:
AmazonLinuxAMIHVMEBSBacked64bit: ami-e7527ed7
Resources:
SecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: My security group
VpcId: !Ref VPC
AllowInboundICMP:
Type: 'AWS::EC2::SecurityGroupIngress'
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: icmp
FromPort: '-1'
ToPort: '-1'
CidrIp: 0.0.0.0/0
AllowInboundSSH:
Type: 'AWS::EC2::SecurityGroupIngress'
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: !Join
- ''
- - !Ref IpForSSH
- /32
SecurityGroupPrivate:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: My security group
VpcId: !Ref VPC
AllowPrivateInboundSSH:
Type: 'AWS::EC2::SecurityGroupIngress'
Properties:
GroupId: !Ref SecurityGroupPrivate
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
SourceSecurityGroupId: !Ref SecurityGroup
BastionHost:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !FindInMap
- EC2RegionMap
- !Ref 'AWS::Region'
- AmazonLinuxAMIHVMEBSBacked64bit
InstanceType: t2.micro
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref SecurityGroup
SubnetId: !Ref Subnet
Server1:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !FindInMap
- EC2RegionMap
- !Ref 'AWS::Region'
- AmazonLinuxAMIHVMEBSBacked64bit
InstanceType: t2.micro
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref SecurityGroupPrivate
SubnetId: !Ref Subnet
Server2:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !FindInMap
- EC2RegionMap
- !Ref 'AWS::Region'
- AmazonLinuxAMIHVMEBSBacked64bit
InstanceType: t2.micro
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref SecurityGroupPrivate
SubnetId: !Ref Subnet
Outputs:
BastionHostPublicName:
Value: !GetAtt
- BastionHost
- PublicDnsName
Description: Bastion host public name (connect via SSH as user ec2-user)
Server1PublicName:
Value: !GetAtt
- Server1
- PublicDnsName
Description: Server1 public name
Server2PublicName:
Value: !GetAtt
- Server2
- PublicDnsName
Description: Server2 public name
使用:https://s3.amazonaws.com/awsinaction/chapter6/firewall5.json 的模板更新CloudFormation堆栈。
若更新完成,堆栈会显示以下三个输出:
现通过SSH命令:ssh -I $PathToKey/mykey.pem -A ec2-user@$BastionHostPublicName
连接到堡垒主机BastionHostName。
将$PathToKey
替换为你的SSH密钥路径,$BastionHostPublicName
替换为你的堡垒主机的公开名称。
-A选项用于启用AgentForwarding,代理转发让你可以使用登录堡垒主机的同一密钥来进一步的从堡垒主机发起的SSH登录进行身份认证。
执行下面的命令把你的密钥加到SSH代理:
ssh-add $PathToKey/mykey.pem
使用PuTTY进行代理转发:
要使用PuTTY进行代理转发,需要确保已经双击私钥文件将密钥装载到PuTTY Pageant。
同时必须启用【Connection-----SSH-----Auth-----Allow Agent Forwarding】
通过PuTTY允许代理转发
先登陆到堡垒主机,进而通过堡垒主机登录到其它服务器。
堡垒主机可以用来为系统增加一层安全保护,如果其中的一台服务器被攻陷了,攻击者无法跳转到系统中的其它服务器上。这样减少了一个攻击者能够造成的潜在的危害。堡垒主机只做SSH,不做其他事情,这样能减少它成为安全风险的机会。
经常使用堡垒主机模式保护客户安全。
在云中创建一个私有网络:虚拟私有云VPC
通过创建VPC(Virtual Private Cloud),用户将在AWS上得到自己的私有网络。
用户可以创建子网,路由表,访问控制列表ACL,访问互联网的网关或VPN端点(Virtual Private Network)。
子网能让用户分离关注点:为用户的DB,网站服务器,缓存服务器或应用服务器,或任何能分离的两个系统创建新的子网。
另一个经验是:用户应该至少有两个子网,即共有子网和私有子网。公有子网能够路由到互联网,私有子网则不能。用户的网站服务器应该位于公有子网中,用户的DB应位于私有子网中。
利用 https://s3.amazonaws.com/awsinaction/chapter6/vpc.json 的模板创建CloudFormation。
创建完成后,复制VarnishServerPublicName输出并在浏览器中打开,将看见一个Varnish缓存的Apache测试页面。
资源清理:最后通过删除堆栈来清除所有用过的资源以避免收取费用。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS in Action: chapter 6 (VPC)",
"Parameters": {
"KeyName": {
"Description": "Key Pair name",
"Type": "AWS::EC2::KeyPair::KeyName",
"Default": "mykey"
}
},
"Mappings": {
"EC2RegionMap": {
"ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-03cf3903"},
"ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-b49dace6"},
"ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-e7ee9edd"},
"eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-46073a5b"},
"eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-6975eb1e"},
"sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-fbfa41e6"},
"us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-303b1458"},
"us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-7da94839"},
"us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7", "AmazonLinuxNATAMIHVMEBSBacked64bit": "ami-69ae8259"}
}
},
"Resources": {
"SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "My security group",
"VpcId": {"Ref": "VPC"}
}
},
"SecurityGroupIngress": {
"Type": "AWS::EC2::SecurityGroupIngress",
"Properties":{
"IpProtocol": "-1",
"FromPort": "-1",
"ToPort": "-1",
"CidrIp": "0.0.0.0/0",
"GroupId": {"Ref": "SecurityGroup"}
}
},
"SecurityGroupEgress": {
"Type": "AWS::EC2::SecurityGroupEgress",
"Properties":{
"IpProtocol": "-1",
"FromPort": "-1",
"ToPort": "-1",
"CidrIp": "0.0.0.0/0",
"GroupId": {"Ref": "SecurityGroup"}
}
},
"VPC": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.0.0.0/16",
"EnableDnsHostnames": "true"
}
},
"InternetGateway": {
"Type": "AWS::EC2::InternetGateway",
"Properties": {
}
},
"VPCGatewayAttachment": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"VpcId": {"Ref": "VPC"},
"InternetGatewayId": {"Ref": "InternetGateway"}
}
},
"SubnetPublicNAT": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]},
"CidrBlock": "10.0.0.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"RouteTablePublicNAT": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableAssociationPublicNAT": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicNAT"},
"RouteTableId": {"Ref": "RouteTablePublicNAT"}
}
},
"RoutePublicNATToInternet": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {"Ref": "RouteTablePublicNAT"},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {"Ref": "InternetGateway"}
},
"DependsOn": "VPCGatewayAttachment"
},
"NetworkAclPublicNAT": {
"Type": "AWS::EC2::NetworkAcl",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"SubnetNetworkAclAssociationPublicNAT": {
"Type": "AWS::EC2::SubnetNetworkAclAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicNAT"},
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"}
}
},
"NetworkAclEntryInPublicNATHTTP": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "80",
"To": "80"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "10.0.0.0/16"
}
},
"NetworkAclEntryInPublicNATHTTPS": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"},
"RuleNumber": "110",
"Protocol": "6",
"PortRange": {
"From": "443",
"To": "443"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "10.0.0.0/16"
}
},
"NetworkAclEntryInPublicNATEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPublicNATHTTP": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "80",
"To": "80"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPublicNATHTTPS": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"},
"RuleNumber": "110",
"Protocol": "6",
"PortRange": {
"From": "443",
"To": "443"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPublicNATEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicNAT"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"SubnetPublicSSHBastion": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]},
"CidrBlock": "10.0.1.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"RouteTablePublicSSHBastion": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableAssociationPublicSSHBastion": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicSSHBastion"},
"RouteTableId": {"Ref": "RouteTablePublicSSHBastion"}
}
},
"RoutePublicSSHBastionToInternet": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {"Ref": "RouteTablePublicSSHBastion"},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {"Ref": "InternetGateway"}
},
"DependsOn": "VPCGatewayAttachment"
},
"NetworkAclPublicSSHBastion": {
"Type": "AWS::EC2::NetworkAcl",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"SubnetNetworkAclAssociationPublicSSHBastion": {
"Type": "AWS::EC2::SubnetNetworkAclAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicSSHBastion"},
"NetworkAclId": {"Ref": "NetworkAclPublicSSHBastion"}
}
},
"NetworkAclEntryInPublicSSHBastionSSH": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicSSHBastion"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "22",
"To": "22"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryInPublicSSHBastionEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicSSHBastion"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "10.0.0.0/16"
}
},
"NetworkAclEntryOutPublicSSHBastionSSH": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicSSHBastion"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "22",
"To": "22"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "10.0.0.0/16"
}
},
"NetworkAclEntryOutPublicSSHBastionEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicSSHBastion"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"SubnetPublicVarnish": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]},
"CidrBlock": "10.0.2.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"RouteTablePublicVarnish": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableAssociationPublicVarnish": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicVarnish"},
"RouteTableId": {"Ref": "RouteTablePublicVarnish"}
}
},
"RoutePublicVarnishToInternet": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {"Ref": "RouteTablePublicVarnish"},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {"Ref": "InternetGateway"}
},
"DependsOn": "VPCGatewayAttachment"
},
"NetworkAclPublicVarnish": {
"Type": "AWS::EC2::NetworkAcl",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"SubnetNetworkAclAssociationPublicVarnish": {
"Type": "AWS::EC2::SubnetNetworkAclAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicVarnish"},
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"}
}
},
"NetworkAclEntryInPublicVarnishSSH": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "22",
"To": "22"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "10.0.1.0/24"
}
},
"NetworkAclEntryInPublicVarnishHTTP": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"},
"RuleNumber": "110",
"Protocol": "6",
"PortRange": {
"From": "80",
"To": "80"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryInPublicVarnishEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPublicVarnishHTTP": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "80",
"To": "80"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPublicVarnishHTTPS": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"},
"RuleNumber": "110",
"Protocol": "6",
"PortRange": {
"From": "443",
"To": "443"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPublicVarnishEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPublicVarnish"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"SubnetPrivateApache": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]},
"CidrBlock": "10.0.3.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"RouteTablePrivateApache": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableAssociationPrivateApache": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPrivateApache"},
"RouteTableId": {"Ref": "RouteTablePrivateApache"}
}
},
"RoutePrivateApacheToInternet": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {"Ref": "RouteTablePrivateApache"},
"DestinationCidrBlock": "0.0.0.0/0",
"InstanceId": {"Ref": "NatServer"}
}
},
"NetworkAclPrivateApache": {
"Type": "AWS::EC2::NetworkAcl",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"SubnetNetworkAclAssociationPrivateApache": {
"Type": "AWS::EC2::SubnetNetworkAclAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPrivateApache"},
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"}
}
},
"NetworkAclEntryInPrivateApacheSSH": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "22",
"To": "22"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "10.0.1.0/24"
}
},
"NetworkAclEntryInPrivateApacheHTTP": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"},
"RuleNumber": "110",
"Protocol": "6",
"PortRange": {
"From": "80",
"To": "80"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "10.0.2.0/24"
}
},
"NetworkAclEntryInPrivateApacheEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "false",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPrivateApacheHTTP": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"},
"RuleNumber": "100",
"Protocol": "6",
"PortRange": {
"From": "80",
"To": "80"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPrivateApacheHTTPS": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"},
"RuleNumber": "110",
"Protocol": "6",
"PortRange": {
"From": "443",
"To": "443"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "0.0.0.0/0"
}
},
"NetworkAclEntryOutPrivateApacheEphemeralPorts": {
"Type": "AWS::EC2::NetworkAclEntry",
"Properties": {
"NetworkAclId": {"Ref": "NetworkAclPrivateApache"},
"RuleNumber": "200",
"Protocol": "6",
"PortRange": {
"From": "1024",
"To": "65535"
},
"RuleAction": "allow",
"Egress": "true",
"CidrBlock": "10.0.0.0/16"
}
},
"NatServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxNATAMIHVMEBSBacked64bit"]},
"InstanceType": "t2.micro",
"KeyName": {"Ref": "KeyName"},
"NetworkInterfaces": [{
"AssociatePublicIpAddress": "true",
"DeleteOnTermination": "true",
"SubnetId": {"Ref": "SubnetPublicNAT"},
"DeviceIndex": "0",
"GroupSet": [{"Ref": "SecurityGroup"}]
}],
"SourceDestCheck": "false",
"UserData": {"Fn::Base64": {"Fn::Join": ["", [
"#!/bin/bash -ex\n",
"/opt/aws/bin/cfn-signal --stack ", {"Ref": "AWS::StackName"}, " --resource NatServer --region ", {"Ref": "AWS::Region"}, "\n"
]]}}
},
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT5M"
}
},
"DependsOn": "VPCGatewayAttachment"
},
"BastionHost": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]},
"InstanceType": "t2.micro",
"KeyName": {"Ref": "KeyName"},
"NetworkInterfaces": [{
"AssociatePublicIpAddress": "true",
"DeleteOnTermination": "true",
"SubnetId": {"Ref": "SubnetPublicSSHBastion"},
"DeviceIndex": "0",
"GroupSet": [{"Ref": "SecurityGroup"}]
}]
},
"DependsOn": "VPCGatewayAttachment"
},
"VarnishServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]},
"InstanceType": "t2.micro",
"KeyName": {"Ref": "KeyName"},
"NetworkInterfaces": [{
"AssociatePublicIpAddress": "true",
"DeleteOnTermination": "true",
"SubnetId": {"Ref": "SubnetPublicVarnish"},
"DeviceIndex": "0",
"GroupSet": [{"Ref": "SecurityGroup"}]
}],
"UserData": {"Fn::Base64": {"Fn::Join": ["", [
"#!/bin/bash -ex\n",
"yum -y install varnish-3.0.7\n",
"cat > /etc/varnish/default.vcl << EOF\n",
"backend default {\n",
" .host = \"", {"Fn::GetAtt": ["ApacheServer", "PrivateIp"]} ,"\";\n",
" .port = \"80\";\n",
"}\n",
"EOF\n",
"sed -i.bak \"s/^VARNISH_LISTEN_PORT=.*/VARNISH_LISTEN_PORT=80/\" /etc/sysconfig/varnish\n",
"service varnish start\n",
"/opt/aws/bin/cfn-signal --stack ", {"Ref": "AWS::StackName"}, " --resource VarnishServer --region ", {"Ref": "AWS::Region"}, "\n"
]]}}
},
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT5M"
}
},
"DependsOn": "VPCGatewayAttachment"
},
"ApacheServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]},
"InstanceType": "t2.micro",
"KeyName": {"Ref": "KeyName"},
"NetworkInterfaces": [{
"AssociatePublicIpAddress": "false",
"DeleteOnTermination": "true",
"SubnetId": {"Ref": "SubnetPrivateApache"},
"DeviceIndex": "0",
"GroupSet": [{"Ref": "SecurityGroup"}]
}],
"UserData": {"Fn::Base64": {"Fn::Join": ["", [
"#!/bin/bash -ex\n",
"yum -y install httpd\n",
"service httpd start\n",
"/opt/aws/bin/cfn-signal --stack ", {"Ref": "AWS::StackName"}, " --resource ApacheServer --region ", {"Ref": "AWS::Region"}, "\n"
]]}}
},
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT10M"
}
},
"DependsOn": "NatServer"
}
},
"Outputs": {
"BastionHostPublicName": {
"Value": {"Fn::GetAtt": ["BastionHost", "PublicDnsName"]},
"Description": "connect via SSH as user ec2-user"
},
"VarnishServerPublicName": {
"Value": {"Fn::GetAtt": ["VarnishServer", "PublicDnsName"]},
"Description": "handles HTTP requests"
},
"VarnishServerPrivateIp": {
"Value": {"Fn::GetAtt": ["VarnishServer", "PrivateIp"]},
"Description": "connect via SSH from bastion host"
},
"ApacheServerPrivateIp": {
"Value": {"Fn::GetAtt": ["ApacheServer", "PrivateIp"]},
"Description": "connect via SSH from bastion host"
}
}
}
AWS CloudFormation 文档:https://docs.aws.amazon.com/zh_cn/cloudformation/?id=docs_gateway
AWS CloudFormation 概念:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html
模板剖析:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/template-anatomy.html
AWS 资源和属性类型参考:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
使用 AWS CloudFormation Designer 设计您自己的模板:https://console.aws.amazon.com/cloudformation/designer/home?region=us-east-1
AWS CloudFormation Designer 界面概述:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/working-with-templates-cfn-designer-overview.html
集成的 JSON 和 YAML 编辑器:https://docs.aws.amazon.com/zh_cn/AWSCloudFormation/latest/UserGuide/working-with-templates-cfn-designer-json-editor.html?icmpid=docs_cfn_console_designer