AWS-管理秘籍-全-

AWS 管理秘籍(全)

原文:annas-archive.org/md5/cf1c4e1db999839ba88fc56df4011156

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

AWS 平台的增长速度非常快,正在被各行各业广泛采用。正如俗话所说,朋友不会让朋友建立数据中心。不管从哪个角度看,按需计算、网络和存储的模式将持续存在。尤其是当你看到 AWS 平台在功能和增强方面的更新速度时,很难再去反对站在巨人的肩膀上,尤其是与其他云服务提供商或所谓的私有云相比。

我们与许多技术专家合作,他们在各自领域有着深厚的知识,但对于 AWS 平台却可能是全新的。或者,他们可能熟悉 AWS,但对于自动化和基础设施代码实践则是新手。

我们希望为这些人写一本书。

本书旨在通过提供教程、模式和最佳实践,帮助你开启 AWS 之旅,尤其是在我们在咨询工作中常常需要帮助的领域。书中的所有教程和建议都基于我们个人的经验和我们在帮助客户使用 AWS 平台时的观察。

CloudFormation 是 AWS 原生的自动化方法,用于部署 AWS 资源(可重复和可靠),我们在本书中广泛使用它。接下来的教程将帮助你熟悉 CloudFormation,快速开始定制和构建自己的模板。拥有如此强大的功能,可能会让你陷入迷茫。本书旨在引导你走上正确的道路,帮助你以可持续和可维护的方式采用该平台。

本书内容

第一章,AWS 基础,是对基础设施即代码、CloudFormation 和 AWS CLI 工具的概述。

第二章,管理 AWS 账户,涵盖了你需要了解的所有关于管理账户和开始使用 AWS 组织的内容。

第三章,存储与内容分发,展示了如何备份数据并将文件对象提供给用户。

第四章,使用 AWS 计算,深入讲解了如何在 AWS 上运行虚拟机(EC2 实例),如何进行自动扩展,以及如何创建和管理负载均衡器。

第五章,管理工具,概述了如何审计你的账户并监控你的基础设施。

第六章,数据库服务,展示了如何在 AWS 平台上创建、管理和扩展数据库。

第七章,网络,介绍了私有网络、路由和 DNS。

第八章,安全与身份,提供了关于如何管理身份和基于角色的访问的建议和实用解决方案。

第九章,估算成本,概述了如何估算在 AWS 平台上的花费,以及如何通过购买预留实例容量来降低成本。

本书所需的内容

本书中的食谱展示了如何在 AWS 上部署多种资源,因此您至少需要一个具有完全管理权限的 AWS 账户。您还需要一个文本编辑器来编辑 YAML/JSON CloudFormation 模板,以及支持常见操作系统(macOS/Linux/Windows)的 AWS CLI 工具。

本书适用对象

本书适用于任何具有技术背景、并有兴趣使用 AWS 的人,无论是迁移现有工作负载还是部署全新应用程序。那些希望学习 CloudFormation 的读者也会发现本书非常有用。

章节

在本书中,您会发现几个经常出现的标题(准备就绪如何做……它是如何工作的……更多内容以及另见)。

为了清晰地指示如何完成食谱,我们使用以下这些章节:

准备就绪

本节告诉您在食谱中可以期待的内容,并描述如何设置任何软件或为食谱所需的任何初步设置。

如何做……

本节包含执行食谱所需的步骤。

它是如何工作的……

本节通常包含对前一节内容的详细解释。

还有更多……

本节包含关于食谱的附加信息,旨在让读者对食谱有更深入的了解。

另见

本节提供了有用的链接,指向与食谱相关的其他有用信息。

约定

在本书中,您会找到几种文本样式,用以区分不同类型的信息。以下是这些样式的一些示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名的显示方式如下:“您现在应该在应用账户下有一个活动会话,使用PowerUserRole角色。”

一段代码设置如下:

Parameters:
  EC2KeyName:
    Type: String
    Description: EC2 Key Pair to launch with

当我们希望引起您对代码块中特定部分的注意时,相关行或项会以粗体显示:

Parameters:
  EC2KeyName:
    Type: String
    Description: EC2 Key Pair to launch with

任何命令行输入或输出如下所示:

aws ec2 describe-availability-zones --output json 

新术语重要词汇会以粗体显示。您在屏幕上看到的词语,例如在菜单或对话框中出现的词汇,将以这种方式呈现:“点击‘下一步’按钮可以将您带到下一个屏幕。”

警告或重要备注会以这样的框显示:

提示和技巧以这样的方式呈现:

读者反馈

我们欢迎读者的反馈。告诉我们你对这本书的看法——喜欢或不喜欢的部分。读者反馈对我们很重要,因为它帮助我们开发出你真正能从中受益的书籍。

若要向我们发送一般反馈,请直接发送电子邮件至feedback@packtpub.com,并在邮件主题中注明书籍标题。

如果你在某个领域有专长,并且有兴趣为书籍写作或贡献内容,请参阅我们的作者指南:www.packtpub.com/authors

客户支持

现在你已经是一本 Packt 书籍的骄傲拥有者,我们提供许多帮助你充分利用购买内容的方式。

下载示例代码

你可以从www.packtpub.com的账户中下载这本书的示例代码文件。如果你在其他地方购买了本书,可以访问www.packtpub.com/support,并注册以将文件直接通过电子邮件发送给你。

你可以按照以下步骤下载代码文件:

  1. 使用你的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的 SUPPORT 标签上。

  3. 点击“代码下载与勘误”。

  4. 在搜索框中输入书籍名称。

  5. 选择你想下载代码文件的书籍。

  6. 从下拉菜单中选择你购买此书的地方。

  7. 点击“代码下载”。

你还可以通过点击书籍网页上的“代码文件”按钮下载代码文件。此页面可以通过在搜索框中输入书籍名称访问。请注意,你需要登录 Packt 账户。

文件下载后,请确保使用最新版本的以下工具解压或提取文件夹:

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

书籍的代码包也托管在 GitHub 上,链接为github.com/PacktPublishing/AWS-Administration-Cookbook。我们还有其他来自丰富书籍和视频目录的代码包,可以在github.com/PacktPublishing/查看!快去看看吧!

勘误

尽管我们已经尽一切努力确保内容的准确性,但错误还是可能发生。如果你在我们的书籍中发现了错误——可能是文本或代码中的错误——我们将非常感激你能向我们报告。通过这样做,你可以帮助其他读者避免困惑,并帮助我们改进后续版本的书籍。如果你发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择你的书籍,点击勘误提交表单链接,并填写勘误的详细信息。你的勘误经过验证后,将会被接受,勘误内容将会上传到我们的网站或添加到该书籍勘误列表的勘误部分。

要查看先前提交的勘误,请访问www.packtpub.com/books/content/support并在搜索框中输入书籍名称。所需信息将显示在勘误部分。

盗版

互联网盗版版权材料是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可。如果你在互联网上发现了任何形式的我们作品的非法复制品,请立即提供该盗版材料的地址或网站名称,以便我们采取措施。

请通过copyright@packtpub.com与我们联系,并提供可疑盗版材料的链接。

我们感谢你在保护我们的作者和确保我们能够为你提供有价值内容方面的帮助。

问题

如果你对本书的任何方面有问题,可以通过questions@packtpub.com与我们联系,我们将尽力解决问题。

第一章:AWS 基础知识

在本章中,我们将介绍:

  • 基础设施即代码

  • AWS CloudFormation

  • AWS 命令行工具

介绍

亚马逊网络服务AWS)是一个公共云服务提供商。它提供按需付费的基础设施和平台服务。这意味着您可以按需访问曾经需要一次性购买的资源。您可以访问企业级服务,同时只为您所需的部分付费,通常按小时计费。

AWS 以提供开发者所需的基础组件为荣,使他们能够构建和扩展所需的解决方案。

创建账户

为了跟上教程,您需要一个 AWS 账户。请访问aws.amazon.com/点击注册按钮并输入您的信息,创建账户。

尽管我们会尽可能利用免费套餐,但您仍然需要一张有效的信用卡来完成注册过程。请访问aws.amazon.com/free/了解更多信息。请注意,免费套餐仅适用于账户生命周期的第一年。

区域和可用区

AWS 的一个基本概念是,其服务及基于这些服务构建的解决方案是 为故障而设计的。这意味着,底层资源的故障是一个积极规划的场景,而不是等到无法忽视时才去避免。

因此,所有可用的服务和资源都被划分到地理上分散的 区域 中。使用特定区域意味着您可以为用户提供速度和性能优化的服务。

在一个区域内,总是有多个 可用区(又称 AZ)。每个 AZ 代表一个地理上独立—但仍然接近—的物理数据中心。AZ 拥有自己的设施和电源,因此,可能导致单个 AZ 离线的事件不太可能影响该区域的其他 AZ。

较小的区域至少有两个 AZ,而最大的区域有五个。

在撰写本文时,以下区域是活跃的:

代码 名称 可用区
us-east-1 北弗吉尼亚 5
us-east-2 俄亥俄州 3
us-west-1 北加利福尼亚州 3
us-west-2 俄勒冈州 3
ca-central-1 加拿大 2
eu-west-1 爱尔兰 3
eu-west-2 伦敦 2
eu-central-1 法兰克福 2
ap-northeast-1 东京 3
ap-northeast-2 首尔 2
ap-southeast-1 新加坡 2
ap-southeast-2 悉尼 3
ap-south-1 孟买 2
sa-east-1 圣保罗 3

AWS 网页控制台

创建 AWS 账户后,您首先会看到的是基于网页的控制台,并且在查看和确认您的配置时,您将经常使用它。

AWS 网页控制台

控制台提供了所有可用服务的概览,以及相关的计费和费用信息。每个服务都有自己的部分,显示的信息取决于查看的服务。随着新特性和服务的发布,控制台会不断变化和改进。如果你登录时发现界面有所变化,不必惊讶。

请记住,控制台始终按 区域 显示你的资源。如果你看不到已创建的资源,请确保已选择正确的区域。

选择距离你物理位置最近的区域,以获得最快的响应时间。请注意,并非所有区域都有相同的服务可用。较大的老旧区域通常有最多的服务可用。某些较新的或较小的区域(可能离你较近)可能还没有启用所有服务。虽然服务会持续发布到各个区域,但如果你必须使用新服务,可能需要选择其他区域。

us-east-1(即北弗吉尼亚)区域是特殊的,因为它是第一个区域。所有服务都可以在那里使用,新的服务 总是 首先发布在那里。

随着你对 AWS 使用的熟练度提高,你将在控制台上花费的时间会减少,而更多地通过 AWS CLI 工具和 CloudFormation 以编程方式控制服务,接下来我们将更详细地讨论这些内容。

CloudFormation 模板

在可能的情况下,我们已经将食谱(recipe)基于 CloudFormation 模板。CloudFormation 是 AWS 提供的 基础设施即代码 服务。

在 CloudFormation 不适用的情况下,我们使用了 AWS CLI 来使流程具备可重复性和自动化能力。

由于食谱基于 CloudFormation 模板,你可以轻松地将不同的模板结合起来,实现你想要的结果。通过编辑模板或将它们结合在一起,你可以以最小的努力创建更有用和定制化的配置。

基础设施即代码

基础设施即代码 (IaC) 是通过代码定义来管理基础设施的做法。

基础设施即服务 (IaaS) 平台上,例如 AWS,需要使用 IaC 才能最大化效用和价值。IaC 与传统的 交互式 基础设施管理方法的主要区别在于,它是机器可处理的。这带来了诸多好处:

  • 改进的资源可见性

  • 部署和环境之间更高的一致性

  • 更容易的故障排除

  • 能够以更少的努力实现更大的扩展性

  • 更好的成本控制

在更为抽象的层面,所有这些因素也为你的开发人员带来了其他改进:你现在可以利用经过验证的软件开发实践来管理基础设施,并在团队中实现 DevOps 实践。

可见性

由于你的基础设施以机器可读的文件形式表示,你可以像对待应用程序代码一样对待它。你可以采用软件开发的最佳实践,并将其应用到你的基础设施中。这意味着你可以像管理代码一样将其存储在版本控制中(例如,Git 和 SVN),并享受它所带来的好处:

  • 所有对基础设施的更改都会记录在提交历史中

  • 你可以在接受/合并之前审查变更

  • 你可以轻松比较不同的配置

  • 你可以选择并使用特定时间点的配置

一致性

在各个环境(例如开发、测试和生产环境)中保持一致的配置意味着你可以更加自信地部署基础设施。当你知道正在使用的配置时,由于有一个共同的基准,你可以轻松地在其他环境中测试变更。

IaC 不同于仅仅编写基础设施脚本。大多数工具和服务将利用高级语言和领域特定语言(DSL),使你能够专注于更高层次的需求。它使你能够使用先进的软件开发技术,如静态分析、自动化测试和优化。

故障排除

IaC 使得复制和故障排除变得更加容易:因为你可以复制你的环境,所以可以准确地重现你的生产环境进行测试。

过去,由于硬件成本过高,测试环境很少有完全相同的基础设施。现在,由于可以按需创建和销毁,你能够仅在需要时复制环境。你只需要为它们运行的时间付费,通常按小时计算。测试完成后,只需关闭环境并停止支付费用。

比故障排除更好的是在问题导致错误之前就修复它们。当你在多个环境中完善你的基础设施即代码(IaC)时,你将获得一种没有它很难获得的自信。在将你的基础设施部署到生产环境时,你已经做过多次了。

扩展性

手动配置基础设施可能是一个繁琐且容易出错的过程。通过自动化,你消除了手动实现的潜在可变性:计算机擅长无聊的重复任务,所以把这些任务交给它们做吧!

一旦自动化,提供更多资源的劳动成本几乎为零——你已经完成了这项工作。无论你是需要启动一台服务器还是一千台,它都不需要额外的工作。

从实际角度来看,AWS 中的资源几乎没有限制。如果你愿意为此付费,AWS 会让你使用它。

成本

AWS 有着强烈的(商业)利益,希望你能够尽可能容易地提供基础设施。作为客户,你的好处是你可以根据需求创建和销毁这些资源。

显然,在传统的物理硬件环境中按需销毁基础设施是不可能的。你几乎无法找到一个数据中心,允许你仅仅因为当前没有使用服务器和空间,就停止为它们付费。

另一个按需基础设施能够大幅节省成本的使用场景是你的开发环境。只有在有开发人员使用它时,开发环境才有意义。当开发人员下班回家时,你可以关闭开发环境,这样就不需要为其付费了。在开发人员早上来之前,简单地安排他们的环境进行创建。

DevOps

DevOps 和 IaC 是密切相关的。将你的基础设施(传统上是运营的关注点)作为代码(传统上是开发的关注点)进行存储的做法,促使了责任的共享,从而促进了协作。

图片来源:维基百科

通过自动化软件开发生命周期中的PACKAGERELEASECONFIGURE活动(如图所示),你可以提高发布速度,同时增加信心。

基于云的 IaC 鼓励为故障做架构设计:因为你的资源是虚拟化的,所以必须为物理(主机)硬件故障的可能性做计划,尽管这种情况不太可能发生。

能够在几分钟内重建整个环境是终极的恢复解决方案。

与物理硬件不同,你可以通过删除关键组件来轻松模拟和测试软件架构中的故障——反正它们都是虚拟的!

服务器配置

服务器端的 IaC 示例包括配置管理工具,如 Ansible、Chef 和 Puppet。

虽然重要,这些配置管理工具并非 AWS 特有,因此我们在此不会详细介绍。如果你需要了解更多,市面上有无数书籍和课程专门讲解此话题。

AWS 上的 IaC

CloudFormation 是 AWS 的 IaC 服务。

使用特定格式和语言编写的模板定义了应当配置的 AWS 资源。CloudFormation 是声明式的,不仅可以配置资源,还可以更新它们。

我们将在下一个主题中详细介绍 CloudFormation。

CloudFormation

本书中我们将广泛使用 CloudFormation,因此理解它是什么以及它如何融入 AWS 生态系统非常重要。这里的信息应该足以帮助你入门,但在必要时,我们会参考 AWS 的官方文档。

什么是 CloudFormation?

CloudFormation 服务允许你以自动化和可重复的方式配置和管理一组 AWS 资源。在 AWS 术语中,这些资源集合被称为堆栈。不过请注意,堆栈的大小可以根据需要调整。它可以由一个单一的 S3 桶组成,也可以包含托管你三层 Web 应用所需的一切。

在本章中,我们将向你展示如何定义要包含在 CloudFormation 堆栈中的资源。我们会进一步讨论这些堆栈的组成,以及何时和为何更倾向于将资源分配到多个堆栈中。最后,我们将分享一些在构建无数 CloudFormation 堆栈过程中学到的小技巧。

请注意!

几乎每个人在使用 CloudFormation 的过程中都会遇到至少一两个小问题。不过,这一切都是值得的。

为什么 CloudFormation 很重要?

到现在为止,自动化的好处应该已经开始显现给你看了。但不要陷入误区,认为 CloudFormation 只对大规模资源集合有用。即使是执行最简单的任务,比如创建一个 S3 桶,如果你需要在每个区域都做一遍,也会变得非常重复。

我们与很多客户合作,这些客户在其基础设施方面有非常严格的控制和治理,尤其是在网络层面(例如 VPC、NACL 和安全组)。能够使用 YAML(或 JSON)表达一个人的云足迹,将其存储在源代码仓库中,并通过高可见性的流水线进行处理,能让这些客户确信他们的基础设施更改已经经过同行评审,并且会在生产环境中按预期工作。当然,遵循 IaC SDLC 实践的纪律性和承诺在其中起着重要作用,但 CloudFormation 帮助我们摆脱了依赖 20 页手动变更清单、应对未追踪或未解释的配置漂移,以及避免因操作失误导致的意外停机的时代。

层蛋糕

现在是开始从层次的角度思考你的 AWS 部署的好时机。你的各个层次将会相互叠加,你将会在它们之间定义清晰的关系。

这是一个自下而上的示例,展示了你的层蛋糕可能的样子:

  • VPC 与 CloudTrail

  • 子网、路由和 NACL

  • NAT 网关、VPN 或堡垒主机及相关安全组

  • 应用堆栈 1:安全组,S3 桶

  • 应用堆栈 1:跨可用区的 RDS 和只读副本

  • 应用堆栈 1:应用程序和 Web 服务器自动扩展组以及 ELB

  • 应用堆栈 1:CloudFront 和 WAF 配置

在这个示例中,你的 VPC 中可能会有许多应用堆栈层,前提是你的子网中有足够的 IP 地址!这通常出现在开发环境中的 VPC。因此,你可以立即享受到多租户能力与应用隔离的好处。

这种方法的一个优势是,在你开发 CloudFormation 模板时,如果你弄错了应用服务器的配置,你不需要回滚 CFN 为你完成的所有工作。你可以直接删除该特定层(以及依赖它的层),然后从那里重新开始。如果你将所有内容包含在一个单一的模板中,就不可能做到这一点。

我们通常与客户合作,其中每一层的所有权和管理反映了公司内技术部门的结构。传统的基础设施、网络和网络安全人员通常对为数字团队创建安全的应用部署环境感兴趣,因此他们喜欢对基础层进行严格的管理。康威定律(Conway's Law),由梅尔文·康威提出,在这里开始发挥作用:

“任何设计系统的组织,最终都会产生一个设计,其结构是该组织沟通结构的复制。”

最后,即使你是一个小团队中的单人基础设施开发人员,你也会从这种方法中受益。例如,你会发现它显著减少了你遇到 AWS 限制、超时和循环依赖的风险。

CloudFormation 模板

这时我们开始动手实践。CloudFormation 模板文件是你堆栈的编程表示,通常以 YAML 或 JSON 形式表达。当你希望创建 CloudFormation 堆栈时,你将这个模板文件通过 API、Web 控制台、命令行工具或其他方法(例如 SDK)推送到 CloudFormation。

模板可以被 CloudFormation 一次又一次地重放,创建多个堆栈实例。

YAML 与 JSON

直到最近,JSON 是唯一的选择。我们实际上鼓励你采用 YAML,并且我们将在本书中使用它作为所有示例。以下是其中一些原因:

  • 它看起来更美观。语法更简洁,如果你选择生成 CloudFormation 模板,几乎每种编程语言都有某种形式的 YAML 库。

  • 你的模板大小将会更小。这从开发人员的角度来看更实用,同时也意味着你更不容易遇到 CloudFormation 模板文件大小限制(50 KB)。

  • 字符串替换功能更易于使用和解释。

  • 你的 EC2 UserData(当 EC2 实例启动时运行的脚本)将更容易实现和维护。

进一步了解 CloudFormation 模板

CloudFormation 模板由多个部分组成,但我们将重点关注以下四个部分:

  • 参数

  • 资源

  • 输出

  • 映射

这是一个简短的 YAML 示例:

AWSTemplateFormatVersion: '2010-09-09' 
Parameters: 
  EC2KeyName: 
    Type: String 
    Description: EC2 Key Pair to launch with 
Mappings: 
  RegionMap: 
    us-east-1: 
      AMIID: ami-9be6f38c 
    ap-southeast-2: 
      AMIID: ami-28cff44b 
Resources: 
  ExampleEC2Instance: 
    Type: AWS::EC2::Instance 
    Properties: 
      InstanceType: t2.nano 
      UserData: 
        Fn::Base64: 
          Fn::Sub': |
            #!/bin/bash -ex
            /opt/aws/bin/cfn-signal '${ExampleWaitHandle}' 
      ImageId: 
        Fn::FindInMap: [ RegionMap, Ref: 'AWS::Region', AMIID ]    
      KeyName: 
        Ref: EC2KeyName 
  ExampleWaitHandle: 
    Type: AWS::CloudFormation::WaitConditionHandle 
    Properties: 
  ExampleWaitCondition: 
    Type: AWS::CloudFormation::WaitCondition 
    DependsOn: ExampleEC2Instance 
    Properties: 
      Handle: 
        Ref: ExampleWaitHandle 
      Timeout: 600 
Outputs: 
  ExampleOutput: 
    Value: 
      Fn::GetAtt: ExampleWaitCondition.Data 
    Description: The data signaled with the WaitCondition

参数

CloudFormation 参数是你在创建或更新堆栈时定义的输入值,类似于你向任何命令行工具提供参数的方式。它们允许你在不修改模板的情况下自定义堆栈。参数可能用于的常见示例如下:

  • EC2 AMI ID:你可能希望使用安装了最新安全补丁的新 AMI 重新部署堆栈。

  • 子网 ID:你可能有一个自动伸缩组需要在其中部署服务器的子网列表。这些子网 ID 在开发、测试和生产环境中会有所不同。

  • 端点目标和凭证:这些包括 API 主机名、用户名和密码等内容。

你会发现有很多种参数类型。简而言之,它们包括:

  • 字符串

  • 数字

  • 列表

  • 逗号分隔列表

除了这些,AWS 还提供了一些 AWS 特定的参数类型。这些在通过 CloudFormation 网页控制台执行模板时特别有用。例如,AWS::EC2::AvailabilityZone::Name类型的参数会让网页控制台显示一个有效可选可用区的下拉列表。在ap-southeast-2区域,列表如下所示:

  • ap-southeast-2a

  • ap-southeast-2b

  • ap-southeast-2c

AWS 特定的参数类型列表正在不断增长,已经大到无法在此列举。尽管如此,我们会在本书中多次使用它们,并且它们可以很容易地在 AWS CloudFormation 文档中找到。

在创建或更新堆栈时,你需要为模板中定义的所有参数提供值。在适当的情况下,你可以为参数定义默认值。例如,你可能有一个名为debug的参数,用来指示应用程序以调试模式运行。你通常不希望默认启用此模式,因此可以将此参数的默认值设置为falsedisabled或应用程序理解的其他值。当然,在创建或更新堆栈时,可以覆盖此值。

你可以并且应该为每个参数提供一个简短而有意义的描述。这些描述会在网页控制台的每个参数字段旁边显示。当正确使用时,它们为执行你 CloudFormation 模板的人提供了提示和上下文。

在这一点上,我们需要介绍内置的Ref函数。当你需要引用一个参数值时,可以使用这个函数:

KeyName:
  Ref: EC2KeyName

虽然Ref并不是你需要了解的唯一内置函数,但它几乎肯定是你将使用最多的一个。我们将在本章后面详细讨论内置函数。

资源

资源是你实际的 AWS 基础设施组件。这些包括 EC2 实例、S3 存储桶、ELB 等。几乎你可以通过 AWS 网页控制台点击和选择创建的任何资源类型,都可以通过 CloudFormation 来创建。

在本章中列出所有 AWS 资源类型并不实际,尽管在你完成本书的食谱时,你会熟悉最常见的资源类型。AWS 在这里保留了一个资源类型的完整列表

docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

关于 CloudFormation 资源,有一些重要的事项需要记住:

  • 新的或领先的 AWS 资源通常不会立即得到支持。CloudFormation 支持通常滞后于新 AWS 功能发布几周(有时几个月)。对于基础设施自动化至关重要的人员来说,过去这是非常令人沮丧的。快进到今天,通过使用自定义资源,这种情况在一定程度上得到了缓解。本章后面将进一步讨论这些内容。

  • 资源有一个默认的返回值。您可以使用 Ref 获取这些返回值,以在模板的其他位置使用。例如,AWS::EC2::VPC 资源类型有一个默认返回值,即 VPC 的 ID。它们看起来像这样:vpc-11aa111a

  • 资源通常包含额外的返回值。使用内置的 Fn::GetAtt 函数来获取这些额外的值。继续上一个示例,AWS::EC2::VPC 资源类型还返回以下内容:

    • CidrBlock

    • DefaultNetworkAcl

    • DefaultSecurityGroup

    • Ipv6CidrBlocks

输出

就像 AWS 资源一样,CloudFormation 堆栈也可以有返回值,称为 outputs。这些值完全由用户定义。如果您没有指定任何输出,则在堆栈完成时不返回任何内容。

当您使用 CI/CD 工具创建 CloudFormation 堆栈时,输出可以非常有用。例如,您可能希望输出 ELB 的公共主机名,以便您的 CI/CD 工具可以在作业输出中将其转换为可点击的链接。

当您连接层次蛋糕的各个部分时,也会使用它们。您可能希望引用另一个堆栈中创建的 S3 存储桶或安全组。这在新的跨堆栈引用功能中更加容易,我们稍后会在本章讨论。您可以预期在任何 CloudFormation 模板的输出部分经常看到 RefFn::GetAtt 函数。

映射

映射部分用于定义一组键/值对。如果您需要任何类型的 AWS 区域可移植性,例如用于灾难恢复或可用性目的,或仅仅为了使您的应用程序更接近最终用户,那么几乎肯定需要在模板中指定一些映射。如果您在模板中引用了任何区域特定的内容,则特别需要这样做。

典型的例子是在模板中指定一组 EC2 AMI ID 的映射。这是因为 AMI 是区域特定的资源,因此在一个区域中对有效 Amazon Machine Image (AMI) ID 的引用在另一个区域中是无效的。

映射看起来像这样:

Mappings:
  RegionMap:
    us-east-1:
      AMIID: ami-9be6f38c
    ap-southeast-2:
      AMIID: ami-28cff44b

依赖关系和顺序

在执行模板时,CloudFormation 将自动确定资源之间的依赖关系,并相应地按顺序创建它们。此外,资源的创建尽可能并行化,以使您的堆栈执行尽快完成。然而,有时会出现问题。

假设应用服务器依赖于数据库服务器。在连接数据库之前,应用服务器需要知道数据库的 IP 地址或主机名。实际上,这种情况需要你先创建数据库服务器,然后使用 Ref 获取其 IP 地址,并将其提供给应用服务器。由于 CloudFormation 并不了解这两个资源之间的耦合关系,因此它会按任意顺序(或者如果可能,则并行)创建这些资源。

为了解决这个问题,我们使用 DependsOn 属性告诉 CloudFormation 应用服务器依赖于数据库服务器。事实上,DependsOn 实际上可以接收一个字符串列表,如果某个资源依赖于多个资源才能创建。例如,如果我们的应用服务器还依赖于一个 Memcached 服务器,那么我们可以使用 DependsOn 来声明这两个依赖关系。

如果需要,你可以进一步扩展这个过程。假设数据库服务器启动后,它将自动启动数据库、设置模式并导入大量数据。我们可能需要等待此过程完成,然后再创建一个应用服务器,该服务器试图连接到一个期望已完成模式和数据集的数据库。在这种情况下,我们希望有一种方法向 CloudFormation 发出信号,表示数据库服务器已完成初始化,以便它可以继续创建依赖于它的资源。这就是 WaitConditionWaitConditionHandle 的作用。

首先,你创建一个 AWS::CloudFormation::WaitConditionHandle 类型,之后你可以通过 Ref 来引用它。

接下来,你创建一个 AWS::CloudFormation::WaitCondition 类型。在我们的例子中,我们希望等待时间从数据库服务器创建开始,因此我们指定该 WaitCondition 资源依赖于我们的数据库服务器(DependsOn)。

在数据库服务器完成数据导入并准备接受连接后,它会调用由 WaitConditionHandle 资源提供的回调 URL,向 CloudFormation 发出信号,表示它可以停止等待并开始执行其余的 CloudFormation 堆栈。该 URL 通过 UserData 提供给数据库服务器,仍然使用 Ref。通常,curlwget 或类似工具用于调用该 URL。

WaitCondition 资源也可以设置 Timeout 时间。这是一个以秒为单位的值。在我们的示例中,我们可能会设置为 900,因为我们知道启动数据库并导入数据不应该超过 15 分钟。

下面是一个 DependsOnWaitConditionHandleWaitCondition 组合使用的示例:

ExampleWaitHandle: 
  Type: AWS::CloudFormation::WaitConditionHandle 
  Properties: 
ExampleWaitCondition: 
  Type: AWS::CloudFormation::WaitCondition 
  DependsOn: ExampleEC2Instance 
  Properties: 
    Handle: 
      Ref: ExampleWaitHandle 
    Timeout: 600

函数

CloudFormation 提供了一些内置函数,使得编写模板变得更加简单。我们已经了解了 RefFn::GetAtt。接下来我们看看你可能会遇到的其他一些函数。

Fn::Join

使用 Fn::Join 将一组字符串通过指定的分隔符连接起来,例如:

"Fn::Join": [ ".", [ 1, 2, 3, 4 ] ]

这将产生以下值:

"1.2.3.4"

Fn::Sub

使用 Fn::Sub 执行字符串替换。请考虑以下情况:

DSN: "Fn::Sub"
  - mysql://${db_user}:${db_pass}@${db_host}:3306/wordpress
  - { db_user: lchan, db_pass: ch33s3, db_host: localhost }

这将产生以下值:

mysql://lchan:ch33s3@localhost:3306/wordpress

当你将这些函数与 RefFn::GetAtt 结合使用时,你可以开始做一些非常强大的事情,正如我们将在本书的配方中看到的那样。

其他可用的内置函数包括:

  • Fn::Base64

  • Fn::FindInMap

  • Fn::GetAZs

  • Fn::ImportValue

  • Fn::Select

所有这些功能的文档可以在这里找到 docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

条件语句

根据你的堆栈运行的环境,通常会根据需要配置一个相似但不同的资源集。例如,在开发环境中,你可能不希望创建一整套数据库服务器(HA 主服务器和只读副本),而是选择仅创建一个数据库服务器。你可以通过使用条件语句来实现这一点:

  • Fn::And

  • Fn::Equals

  • Fn::If

  • Fn::Not

  • Fn::Or

权限和服务角色

关于 CloudFormation,有一点很重要:它基本上只是代表你进行 API 调用。这意味着 CloudFormation 将使用你执行模板时所用的相同权限或角色。例如,如果你没有权限在 Route 53 中创建新的托管区域,那么你尝试运行的任何包含新 Route 53 托管区域的模板都将失败。

另一方面,这创造了一个相对棘手的情况,任何开发 CloudFormation 的人通常都具有非常高的权限,并且这些权限在每次执行模板时都会被不必要地授予给 CloudFormation。

如果我的 CloudFormation 模板仅包含一个资源,即一个 Route 53 托管区域,那么让该模板以完全管理员权限执行就没有意义。给 CloudFormation 一个非常有限的权限集来执行模板,更有意义,这样如果执行了一个错误的模板(即错误的复制粘贴操作导致资源被删除),爆炸半径也会受到限制。

幸运的是,最近引入了服务角色,你现在可以定义一个 IAM 角色,并告知 CloudFormation 在执行堆栈时使用该角色,从而为你提供了一个更安全的操作空间。

自定义资源

如本章前面所讨论的那样,在发布新的 AWS 功能和你能够在 CloudFormation 中使用该功能之间,通常会有一个较长的等待期。

在自定义资源出现之前,这将 AWS 开发人员带入了在 CloudFormation 中执行超过 95% 自动化任务的路径,然后再运行一些 CLI 命令来填补空白。通常很难准确地知道哪些资源属于哪个堆栈,并且也很难确定你的堆栈何时完成执行,这成了一个猜谜游戏。

快进到今天,新的趋势是使用自定义资源委托给 AWS Lambda函数。Lambda 可以通过代表你进行 API 调用来填补空白,并且更容易追踪这些资源的来源和完成情况。

如果运气好的话,你暂时不需要使用这个功能。与此同时,AWS 自定义资源的文档非常全面。如果你尝试使用 CloudFormation 创建一个在 AWS 文档中找不到的资源,那么很可能它在 CloudFormation 中还不被支持,这时候使用自定义资源就是解决方案。更多信息请参考docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html

跨堆栈引用

在使用分层结构方法时,常常需要将一个堆栈的输出作为另一个堆栈的输入。例如,你可能在一个堆栈中创建了一个 VPC,并且在创建另一个堆栈中的资源时需要其 VPC ID。

长时间以来,创建堆栈时需要提供一些辅助机制来传递堆栈之间的输出。AWS 最近引入了跨堆栈引用,这提供了一种更原生的方式来实现这一目标。

你现在可以导出一个或多个堆栈的输出。这使得这些输出可以在其他堆栈中使用。请注意,这些值的名称需要是唯一的,因此最好在导出的名称中包括 CloudFormation 堆栈名称,以确保唯一性。

一旦某个值被导出,它就可以通过Fn::ImportValue函数在另一个堆栈中导入,非常方便!

但是,请确保在导出的值被引用期间,你无法删除或修改它。此外,你也不能删除包含导出值的堆栈。一旦某个东西引用了一个导出的值,它就会一直存在,直到没有堆栈再引用它为止。

更新资源

基础设施即代码(IaC)的一个原则是,所有的变更都应该以代码的形式进行表示,以便于审查和测试。这一点在使用 CloudFormation 时尤为重要。

在为你创建堆栈后,CloudFormation 服务实际上是“放手”的。如果你对 CloudFormation 创建的任何资源进行更改(无论是在 Web 控制台、命令行,还是通过其他方法),你实际上是在导致配置漂移。CloudFormation 此时已经无法准确了解堆栈中资源的状态。

正确的方法是在 CloudFormation 模板中进行这些更改,并对堆栈执行更新操作。这确保了 CloudFormation 始终知道堆栈的状态,并使你能够保持信心,认为你的基础设施代码是正在运行环境的完整且准确的表示。

变更集

在执行堆栈更新时,可能无法清楚地了解将对堆栈进行哪些更改。根据你要更改的资源,你可能会发现它需要被删除并重新创建,以便实现更改。当然,如果相关资源包含你希望保留的数据,这种行为是完全不希望发生的。请记住,RDS 数据库可能是一个特别的痛点。

为了缓解这种情况,CloudFormation 允许你在执行更新之前创建并查看变更集。变更集显示了 CloudFormation 打算在你的资源上执行哪些操作。如果变更集看起来没问题,你可以选择继续。如果你不满意看到的内容,可以删除变更集并选择另一个行动方案——也许选择创建并切换到一个全新的堆栈以避免服务中断。

其他需要了解的事项

在你开始构建自己的 CloudFormation 堆栈时,还有一些其他事项需要放在心上。我们来看看。

名称冲突

通常,如果你省略了资源的名称属性,CloudFormation 会为你生成一个名称。这可能导致奇怪的资源名称,但它会增加模板的可重放性。以 AWS::S3::Bucket 为例,如果你指定了 BucketName 参数但没有确保其唯一性,CloudFormation 在第二次执行模板时会失败,因为该桶已存在。省略 BucketName 可以解决此问题。或者,你可以选择每次运行模板时生成一个独特的名称。这里可能没有对错之分,所以做最适合你的方式就好。

回滚

在创建 CloudFormation 堆栈时,你可以选择禁用回滚。在你决定将其设置为 true 之前,请记住,这个设置会在堆栈创建后持续存在。我们曾经遇到过在更新现有堆栈失败(由于某种原因)但回滚已被禁用的危险情况。这对任何人来说都不是一种愉快的处境。

限制

最有可能让你担心的限制如下:

  • CloudFormation 模板的最大大小为 50 KB。这个限制相当宽松,如果你达到了这个限制,那么几乎可以肯定需要考虑将模板拆分成多个较小的模板。如果你确实需要超过 50 KB 的限制,最常见的方法是先将模板上传到 S3,然后提供 S3 URL 给 CloudFormation 执行。

  • 你可以指定的最大参数数量为60。如果你需要更多,那么再次考虑是否需要为你的“蛋糕”添加更多的层次。否则,使用列表或映射可能会帮你脱困。

  • 输出数量也限制为60。如果你达到了这个限制,可能是时候考虑使用多个较小的模板了。

  • 资源数量限制为200。这里的规则与之前相同。

  • 默认情况下,你的 CloudFormation 堆栈数量被限制为200个。你只需联系 AWS 即可增加这个限制。

循环依赖

需要牢记的一点是,你可能会遇到循环依赖的情况,即多个资源在创建时相互依赖。一个常见的例子是两个安全组互相引用,以便允许彼此之间的访问。

针对这种特定场景的解决方法是使用 AWS::EC2::SecurityGroupEgressAWS::EC2::SecurityGroupIngress 类型,而不是 AWS::EC2::SecurityGroup 的入口和出口规则类型。

DSL 和生成器

DSL 和生成器是基础设施编码人员之间热烈辩论的焦点。一些人喜欢它们,另一些人则讨厌它们。人们喜欢它们的原因包括以下几点:

  • 它们允许用更本地化的语言或团队语言编写 CloudFormation。

  • 它们允许使用一些高级编程构造。迭代就是一个被广泛引用的例子。

  • 在 CloudFormation 支持 YAML 之前,使用 DSL 通常能使代码更易读,且冗长度大大降低。

人们不喜欢它们的原因包括以下几点:

  • DSL 在历史上曾经成为废弃软件,或与 CloudFormation 版本差距较大,尽管现在有一些得到良好支持的 DSL。

  • 开发人员可能需要学习一门新语言,并且还要应对另一个新的文档体系,除了要学习 CloudFormation 和浏览 AWS 文档。

  • Google 和 Stack Overflow 的作用会略显不足,因为需要翻译问题和答案。

除了这里写的内容外,本书中不会再涉及这个话题。我们不能给出具体的建议,让你选择哪条路,因为这几乎总是一个高度个人化和情境化的选择。不过,特别是在熟悉 AWS 和 CloudFormation 的过程中,一个明智的做法是坚持使用 YAML(或 JSON),直到你认为 DSL 或生成器可能有用为止。

凭证

无论如何,你都不希望在模板中硬编码凭证或将其提交到源码仓库中。这样做不仅增加了凭证被盗用的风险,还降低了模板的可移植性。如果凭证被硬编码且需要更改,这显然需要你编辑 CloudFormation 模板。

相反,你应该将凭证作为参数添加到模板中。确保在这样做时使用 NoEcho 参数,以便 CloudFormation 在显示参数时屏蔽其值。

堆栈策略

如果您的堆栈中有资源希望保护免受意外删除或修改,可以应用堆栈策略来实现这一点。默认情况下,所有资源都可以被删除或修改。当您应用堆栈策略时,所有资源将被保护,除非您在策略中明确允许它们被删除或修改。请注意,堆栈策略在堆栈创建期间不生效——它们仅在您尝试更新堆栈时生效。

命令行界面工具

AWS 命令行界面 (CLI) 工具是 AWS 管理员工具包中的一个重要组成部分。

CLI 工具通常是与 API 交互的最快和最简单的方法之一。作为一种基于文本的工具,它比使用网页控制台更容易扩展。与控制台不同,它可以通过脚本等方式进行自动化。AWS 应用程序编程接口 (API) 表示作为 AWS 管理员可用的所有功能。它也更容易通过命令行历史记录进行跟踪。像所有优秀的 CLI 工具一样,简单的单个命令可以串联(或管道)在一起,执行复杂的任务。

CLI 工具是开源软件,维护在 GitHub 上 github.com/aws/aws-cli。更多详细文档,请参考 AWS CLI 主页 aws.amazon.com/cli

安装

CLI 工具需要 Python 2.6.5 或更高版本。

安装它的最简单方法是使用 Python 包管理器pip

pip install awscli

这将使得aws命令在您的系统上可用。

升级

AWS 经常发布新服务和新功能。为了使用这些新功能,您需要升级 CLI 工具。

要升级,请定期运行以下pip命令:

pip install --upgrade awscli

配置

CLI 工具与 AWS API 之间的认证是通过两项信息完成的:

  • 访问密钥 ID

  • 秘密访问密钥

顾名思义,您应该保密您的秘密访问密钥!小心存储或发送它的地方。

创建用户后,您可以配置工具以用于认证目的。

虽然您可以直接用访问密钥配置 CLI 工具,但不推荐这样做。相反,您应该使用配置文件来存储凭证。使用配置文件可以让您在一个一致的、易于管理的集中位置来保护您的秘密密钥。

默认配置文件

在没有任何额外配置或选项的情况下,您的 CLI 工具命令将使用默认配置文件。

要设置默认配置文件,您可以使用以下命令:

aws configure

这将提示您输入访问密钥 ID、秘密访问密钥、区域和输出格式。

命名配置文件

除了默认配置文件外,您还可以配置其他命名的配置文件。这对于在具有不同访问权限级别的用户之间切换(例如,只读用户和管理员)或甚至在不同帐户的用户之间切换非常有用。

aws configure --profile <profile-name>

在响应提示后,你可以通过在命令中传递 --profile <profile-name> 选项来引用命名的配置文件。

环境变量

你还可以通过使用环境变量来配置 CLI:

export AWS_PROFILE=<profile-name>

虽然你应该优先使用配置文件,而不是直接设置访问 ID 和密钥,但有时你可能不得不这么做。如果你必须直接设置密钥,请通过环境变量来设置,这样你就不需要传递密钥或将其硬编码:

export AWS_ACCESS_KEY_ID=<access-key-id>
export AWS_SECRET_ACCESS_KEY=<secret-access-key>

实例角色

在 EC2 实例上运行 CLI 工具时,你可以利用实例的 IAM 角色进行调用。这意味着你不需要手动配置凭证或设置环境变量。

在幕后,实例将检索并设置它自己的 AWS 环境变量,以允许 API 调用。你需要确保该实例具有适当的权限。

AWS CLI 工具在 AWS 基于 Linux 的实例上预装。

用法

所有 CLI 工具命令都是基于服务的。通过使用服务命令和子命令,你可以直接向 AWS API 发出调用。

命令

每个命令代表一个 AWS 服务。虽然大多数服务只有一个命令与之关联,但有些服务有多个命令(例如,S3 有 s3s3api)。

运行 aws help 查看所有可用的命令/服务—它们可能会在本书打印时有所变化!

子命令

每个命令都有一组子命令,用于执行特定服务的操作。

运行 aws <command> help 查看所有子命令。

选项

子命令接受以 -- 开头的选项。

使用 aws <command> <subcommand> help 查看所有选项及其用途。

虽然大多数是可选的(因此得名),但那些没有被方括号([])括起来的选项是必需的。如果你没有包含它们,将会收到错误信息(并附有相关细节)。

内置文档是开始寻找答案的最佳地方。通常在所有选项描述之后会有示例。否则,网上有很多可用的示例。

有些选项适用于所有或大多数命令,因此它们特别有用,值得了解。

输出

CLI 工具可以配置为以 JSON、表格或文本格式输出。要控制输出类型,请使用 --output 选项。

要为所有命令设置默认的输出类型,可以为你的配置文件设置 output 参数。

JSON

JavaScript 对象表示法 (JSON) (json.org/),一种标准的、机器和人类可读的信息交换格式。以下是 us-east-1(北弗吉尼亚)区域的可用可用区(AZ)在 JSON 格式中的表示:

aws ec2 describe-availability-zones --output json 
{ 
"AvailabilityZones": [ 
        { 
"State": "available", 
"RegionName": "us-east-1", 
"Messages": [], 
"ZoneName": "us-east-1a" 
        }, 
        { 
"State": "available", 
"RegionName": "us-east-1", 
"Messages": [], 
"ZoneName": "us-east-1c" 
        }, 
        { 
"State": "available", 
"RegionName": "us-east-1", 
"Messages": [], 
"ZoneName": "us-east-1d" 
        }, 
        { 
"State": "available", 
"RegionName": "us-east-1", 
"Messages": [], 
"ZoneName": "us-east-1e" 
        } 
    ] 
}

表格

表格格式会显示一个文本/ASCII 表格的结果。这对于生成可打印报告非常有用:

文本

文本输出格式仅显示结果的键/值响应。没有添加任何额外的格式或显示字符。

查询

CLI 工具支持使用 --query 选项转换 API 的响应。此选项将 JMESPath 查询作为参数,并返回查询结果。

JMESPath 是一种用于查询 JSON 的语言。有关更多信息,请访问 jmespath.org/

由于查询作为命令的一部分在服务器上处理,而不是在客户端处理,因此可以通过将工作卸载到服务器,减少生成的有效负载的大小并提高响应时间。

JMESPath 可用于转换你收到的响应:

$ aws ec2 describe-availability-zones \
 --output json \
 --query 'AvailabilityZones[].ZoneName'
 [
 "us-east-1a",
 "us-east-1c",
 "us-east-1d",
 "us-east-1e"
 ]

它也可以用来过滤接收到的数据:

$ aws ec2 describe-availability-zones 
 --output json 
 --query "AvailabilityZones[?ZoneName == 'us-east-1a'].State"
 [
 "available"
 ]

生成 CLI 骨架

在使用 CLI 工具执行复杂任务时,传递一个包含选项的 JSON 对象可能会更简单。这种交互方式可能意味着你应该使用某个 AWS 软件开发工具包SDK)。

输入

要生成一个示例 JSON 对象(它将被接受),可以使用 --generate-cli-skeleton 选项运行命令:

$ aws ec2 describe-availability-zones --generate-cli-skeleton 
{ 
"DryRun": true, 
"ZoneNames": [ 
"" 
 ], 
"Filters": [ 
 { 
"Name": "", 
"Values": [ 
"" 
 ] 
 } 
 ] 
}

你可以复制、编辑并使用此对象来定义你的命令选项,而无需传递多个单独的选项。这对于包含选项数组或选项数量可变的命令效果最佳。

输出

你还可以通过调用带有 --generate-cli-skeleton output 选项的命令,预览命令的输出。这可以加速合并 CLI 命令的过程,因为你可以在不实际调用 API 的情况下查看响应:

$ aws ec2 describe-availability-zones --generate-cli-skeleton output 
{ 
"AvailabilityZones": [ 
 { 
"ZoneName": "ZoneName", 
"State": "State", 
"RegionName": "RegionName", 
"Messages": [ 
 { 
"Message": "Message" 
 } 
 ] 
 } 
 ] 
}

分页

CLI 工具返回的结果默认限制为 1,000 个资源。

通常这不是问题,但在某些规模下,你可能会遇到分页问题。一个常见的例子是 S3 桶中的文件。

如果你完全确定应该在响应中看到某个特定资源但没有看到,请检查你的分页设置。该资源可能已经包含在匹配的资源中,只是没有出现在返回的响应部分。

以下选项允许你控制从 API 返回的结果的数量和起始位置:

  • --page-size:此选项限制显示给你的资源数量,但不会实际限制返回的资源数量。默认的项目数(即 1,000 个)仍将被处理并返回给你。

  • --max-items:此选项设置响应中实际返回的项目数量的上限。你可能会收到更少的项目,但不会收到超过此数量的项目。

  • --starting-token:此选项更改响应的起始位置。使用此选项可以显示后续的结果,超出第一页的内容。

aws s3 ls --bucket bucket-name --max-items 100 --starting-token None___100

自动完成

你可以通过配置 CLI 工具中包含的自动完成器来启用命令、子命令和选项的制表符自动完成。

在 OS X、Linux 和 Windows 系统中使用 bash shell,你可以使用以下命令加载自动完成器:

complete -C 'which aws_completer'aws

默认情况下,aws_completer 程序安装在 /usr/local/bin。如果你的工具安装在非标准位置,你需要找到它并将 which aws_completer 命令更改为相关路径。

相关工具

以下程序与 AWS CLI 工具配合良好,可能会派上用场。

jq

jq 是一个轻量级的工具,用于处理和转换 JSON。它遵循 Unix 哲学:做一件事,并做到最好。可以在 stedolan.github.io/jq/ 找到它。

虽然 jq 和 JMESPath 很相似,但 jq 更容易上手。它还支持将 JSON 转换为纯文本;而 JMESPath 查询总是返回更多的 JSON 数据。

你可以将来自 CLI 工具的 JSON 结果传递给它,并轻松转换结果以供其他地方使用。这个示例使用 jq 的属性名称选择器将 JSON 输出转换为文本:

$ aws ec2 describe-availability-zones --output json | jq '.AvailabilityZones[].ZoneName'
"us-east-1a"
"us-east-1c"
"us-east-1d"
"us-east-1e"

第二章:管理 AWS 账户

在本章中,我们将介绍以下主题:

  • 设置主账户

  • 创建会员账户

  • 邀请账户

  • 管理你的账户

  • 添加服务控制策略

介绍

我们与许多公司合作,这些公司管理着大量不断增长的 AWS 账户。保持对所有这些账户的控制通常是非常困难的——即使对于经验丰富的 AWS 用户来说也是如此。

随着 AWS Organizations 的发布,你现在可以集中管理 AWS 账户,将它们安排成逻辑分组和层级结构,并对它们进行以前在 AWS 平台上无法实现的控制。

设置主账户

所有使用 AWS Organizations 进行计费和控制的账户都必须拥有一个主账户。该账户控制组织的成员资格,并支付所有成员的账单(总得有人做这个事情)。

如何操作...

要设置主账户,请执行以下步骤:

  1. 转到你希望成为主账户的账户的我的组织部分。你必须使用根凭证登录(即你用来创建账户的凭证):

  1. 在 AWS 控制台的 AWS Organizations 部分,点击创建组织,如下图所示:

  1. 除非有特定需求,否则选择启用所有功能,以便充分利用 AWS Organizations,如下图所示:

  1. 现在你的账户已经转换,你可以返回 AWS Organizations 页面查看所有账户的列表:

工作原理...

虽然这是一个非常简单的流程,但它是你在使用 AWS Organizations 的任何有用功能之前必须做的第一件事。

在这里你可以看到主账户、成员和组织单位OUs)之间关系的高层次图示:

我们故意启用所有组织功能。合并计费选项用于向后兼容——在组织功能之前,合并计费是唯一可以链接账户的选项。

不要将主账户用于日常任务。由于主账户非常重要,因此不建议冒险使用它或为其设置访问密钥。如果主账户被不正当地访问,将会影响所有成员账户。最好避免这样做。

主账户的名称旁边将始终有一个星标。

还有更多内容…

所有组织功能都通过 API 提供。这意味着你可以使用 AWS SDK 或 CLI 工具执行你在 Web 控制台中执行的相同操作。

多因素认证

如在合并账单确认邮件中所提到,建议在控制台上配置多因素认证MFA)。为此,请以根用户身份登录(即你首次创建账户时使用的凭证),进入身份与访问管理IAM)控制台,并按照激活根账户 MFA 的提示操作。

使用 CLI

你可以轻松使用 CLI 工具创建你的主账户。以下命令将使你的账户成为主账户,并启用所有组织功能:

aws organizations create-organization

另见

  • 邀请账户的操作步骤

  • 创建成员账户的操作步骤

创建成员账户

一旦你的组织启动并运行,最常见的用途就是自动化账户创建过程。组织内创建的账户被称为成员账户

所有由成员账户产生的费用将由主账户进行计费。

准备工作

显然,你需要一个组织来执行这个操作步骤。请参阅本章中的其他操作步骤来开始使用。

操作步骤...

  1. 运行 CLI 工具命令以创建一个新账户,并填写适当的值:
 aws organizations create-account \
 --email <member-account-owners@email.com> \
 --account-name <member-account-name> \ 
 --query 'CreateAccountStatus.Id'

  1. 此命令将返回一个create account status请求 ID,你可以用它来检查状态:
 aws organizations describe-create-account-status \ 
 --create-account-request-id <your-create-account-status-id>

工作原理...

在你的组织中创建成员账户的命令非常简单。

使用的电子邮件地址不能与其他 AWS 账户关联。

账户创建过程需要一些时间,因此是异步进行的。这意味着你不会立即收到create-account命令的状态。相反,本操作步骤中的命令将返回一个请求 ID。

该 ID 会被传递到另一个账户中,用于检查创建的状态。当状态为CREATED时,你就可以开始使用新账户了。

还有更多...

虽然这个功能确实很有用,但 AWS 组织服务相对较新。这意味着你需要了解一些功能

访问成员账户

一旦你创建了成员账户,就可以开始使用它了!

新账户中将存在一个 IAM 角色,默认名称为OrganizationAccountAccessRole。这是为了让你能够(从主账户)承担该角色并管理成员账户。虽然这个名称已经足够好,但你也可以在创建账户时传递--role-name参数来配置该名称。

为了承担该角色,你需要知道它的Amazon 资源名称ARN)。计算 ARN 是一个多步骤过程:

  1. 在主账户中运行以下命令以列出你的成员账户:
 aws organizations list-accounts

  1. 找到你创建的账户(根据其名称),并记录下记录中的 ID 值。使用该 ID,按照以下模式生成角色的 ARN:
 arn:aws:iam::<your-member-account-
        id>:role/OrganizationAccountAccessRole

  1. 如果你更改了创建的角色名称,请相应更新 ARN 的最后部分。

请参见第八章中的配方,安全性与身份,了解如何最佳地管理多个账户。

服务控制策略

服务控制策略SCPs)是 AWS Organizations 的另一个主要功能。你可以在多个级别/资源上应用它们,包括账户(成员账户和被邀请账户)。查看本章中的其他配方,获取更多细节。

根凭证

一些活动仍然需要账户的根凭证。一个示例活动是关闭(或删除)账户(更多细节请参见下一节)。

为了做到这一点,你需要进行与账户关联的电子邮件的密码恢复过程,尤其是在发送 create-account 请求时。

删除账户

在撰写本文时,无法通过 API 删除在你的组织中创建的账户。我们只能推测,能够通过编程删除组织中创建的成员账户将是一个高度需求的功能,并将在不久的将来得到解决。你仍然可以进入成员账户并使用根凭证关闭它,但这些凭证默认并不存在。

虽然你可以通过 API 删除你的组织,但如果你在组织中创建了任何成员账户,就无法进行删除(因为你不能删除它们,所以你的组织永远不会为空)。这一点在不久的将来会有所改进,但现在仍然值得注意。

另见

  • 设置主账户 配方

  • 添加服务控制策略 配方

  • 第八章中的跨账户用户角色 配方,安全性与身份

邀请账户

虽然创建新账户进入你的组织是有意义的,但你现在该如何处理所有其他账户呢?

你可以邀请现有账户加入你的组织,这意味着你可以从管理角度将它们视为成员账户。这极大简化了账户的管理工作量,因为账户和账户没有单独的处理流程。

由于这通常仅针对每个现有账户执行一次,我们将使用控制台。

所有 AWS 组织功能都可以通过 SDK 和 AWS CLI 工具访问。如果你需要自动化此过程,完全可以实现。

准备工作

你必须为其中一个账户(主账户)启用了 AWS Organizations,并且有另一个尚未成为组织一部分的账户(你将邀请它)。

如何操作...

  1. 从主账户的 AWS 控制台中,点击你的用户名,并从下拉菜单中选择“我的组织”:

  1. 你将被带到 AWS Organizations 控制台,在那里你会看到当前账户:

  1. 点击控制台右上角的“邀请”标签:

  1. 点击“邀请账户”按钮,指定要邀请的账户 ID(或主电子邮件地址):

  1. 一旦点击“邀请”,你将被带到一个邀请列表页面,在那里可以查看状态:

  1. 在这个阶段,目标/受邀账户将收到一封通知他们邀请的电子邮件:

  1. 登录受邀账户,并在用户菜单下点击“我的组织”链接:

  1. 在 AWS Organizations 控制台中,你可以在左侧看到待处理的邀请:

  1. 点击邀请,你可以查看其详细信息:

  1. 当邀请包含所有功能时,你将被要求确认接受:

  1. 确认后,你现在可以看到你已加入的组织的详细信息:

  1. 在这个阶段,主账户会收到已接受邀请的通知:

  1. 返回主账户,现在你可以看到新账户与主账户一起显示:

它是如何工作的...

虽然有许多步骤,但邀请现有账户的过程是一个相对简单的握手过程。这意味着双方必须主动发起/接受邀请,才能成功——邀请无法被强制进行。

在指定目标账户的账户 ID(或电子邮件地址)后,关联的电子邮件地址将收到通知。

作为握手过程的一部分,受邀账户必须明确接受邀请。

需要注意的是,默认的邀请类型(以及我们在此过程中使用的)是使用 AWS Organizations 的完整功能集。如控制台中所述,这意味着如果相关策略已配置,受邀账户可能会被阻止离开组织

确认后,双方将收到一封详细说明会员资格的电子邮件。从此时起,受邀账户的账单将由主账户支付。

还有更多...

受邀账户与通过组织功能创建的账户有所不同。

移除账户

成员账户(通过 AWS Organizations API 创建的账户)不同,受邀账户可以从组织中移除。

合并账单

作为全功能邀请的替代方案,可以仅为组织指定合并账单模式。在这种模式下,不会有 OU 或策略,仅会在账户之间共享账单关系(即主账户将支付成员账户的账单)。

任何预先配置为使用合并账单的账户将自动迁移到 AWS Organizations 合并账单模式中。

参见

  • 创建成员账户过程

管理你的账户

有多种方法可以对 AWS 帐户进行分组和排列。如何做完全取决于您,但以下是一些考虑的示例:

  • 业务单元(BU)或位置:您可能希望允许每个 BU 在其自己的产品或服务上独立工作,按照自己的时间表进行工作,而不会影响业务的其他部分。

  • 成本中心:根据成本分组可能有助于您跟踪支出与分配预算的情况。

  • 环境类型:将开发、测试和生产环境合理地分组,有助于您跨每个环境管理控制。

  • 工作负载类型或数据分类:您的公司可能希望将工作负载类型相互隔离,或者确保所有包含特定数据类型的帐户应用特定的控制。

在下面的虚构示例中,我们通过将其放入名为突然谷的 OU 中,将Sitwell Enterprises 帐户与组织的其他部分隔离开来。也许他们在不同的地理位置运营,并且对控制和访问有不同的法规要求。

组织层次结构

请注意,虽然我们也可以将主帐户放入 OU 中,但我们避免这样做是为了明确以下事实:

  • 它是主帐户,并且对整个组织拥有控制权。

  • 我们设置的规则,使用 SCP 来控制组织中的成员帐户,不适用于主帐户(因为它们不能)。

在本章节的添加服务控制策略食谱中了解更多关于 SCP 的信息。

准备就绪

在继续之前,您应该已经完成了以下工作:

  • 设置主 AWS 帐户

  • 创建了一个组织

  • 在您的组织中创建成员帐户,或者通过邀请手动添加成员帐户

如何操作...

现在我们将介绍执行管理 OU 所需的常见任务的单行命令。这些命令只能在您的主帐户中执行。

获取组织的根 ID

您可以运行此命令来获取组织的根 ID。当您在主帐户中创建组织时,根会自动为您创建。返回给您的 ID 看起来会像这样:r-bmdw

aws organizations list-roots

创建一个 OU

要创建 OU,请执行以下步骤:

  1. 确定您希望此 OU 存在的位置。如果它直接位于根目录下,那么根 ID 将作为父级。或者,如果此 OU 将作为另一个 OU 的子级,使用该 OU 的 ID。显然,如果这是您创建的第一个 OU,则根将作为父级。

  2. 使用 CLI 像这样创建您的 OU:

aws organizations create-organizational-unit \
 --parent-id <root-id or parent-ou-id> \ --name <desired-ou-name>

获取 OU 的 ID

如果您需要获取 OU 的 ID,可以使用 CLI 进行操作;请注意,您需要知道 OU 的父级。以下是如何在根或 OU 中获取所有 OU 及其 ID 列表的方法:

aws organizations list-organizational-units-for-parent \
  --parent-id <root-id or parent-ou-id>

将帐户添加到 OU 中

要将账户添加到 OU,执行以下步骤:

  1. 当账户首次添加到你的组织时,它将是组织根目录的子账户。要将其添加到你刚刚创建的 OU,你需要使用以下 CLI 命令将其移动:
aws organizations move-account \
 --account-id <twelve-digit-account-id> \
 --source-parent-id <root-id> \
 --destination-parent-id <new-parent-ou-id>

  1. 如果你希望将账户从一个 OU 移动到另一个 OU,只需使用相同的命令,但使用现有父 OU 的 ID,而不是根目录的 ID。

从 OU 中移除账户

要从 OU 中移除账户,执行以下步骤:

  1. 如果你希望将一个账户从 OU 中移除,你有两个选择。你可以将它移动到另一个 OU,或者将它移回根目录。如果你决定删除一个 OU,你需要首先确保它里面没有账户(我们接下来会展示如何操作)。

  2. 运行以下命令将账户移回根目录:

 aws organizations move-account \
 --account-id <twelve-digit-account-id> \
 --source-parent-id <existing-parent-ou-id> \
 --destination-parent-id <root-id>

删除 OU

要删除一个 OU,你首先需要确保它是空的,通过移除它的子账户(如前所述)。然后你可以像下面这样删除 OU:

aws organizations delete-organizational-unit \
  --organizational-unit-id <ou-id>

它是如何工作的...

如果操作得当,使用 OU 将你的账户分组在一起,将帮助你简化管理和操作它们的方式。尽量只使用刚好够用的 OU 来完成任务。我们的目标是使用 OU 让你的工作更轻松,而不是更困难。

还有更多...

  • 组织控制策略OCPs)可以附加到你的根目录、OU 或 AWS 账户上。目前,只支持一种 OCP 类型:SCP。

  • 账户只能属于一个组织单位(OU)或根目录。

  • 同样地,OU 只能属于一个 OU 或根目录。

  • 最好避免在主账户中部署资源,因为该账户无法通过 SCP 进行控制。主账户应仅作为审计、控制和计费的管理账户使用。

另见

  • 添加服务控制策略操作步骤

添加服务控制策略

在开始之前,我们应该先讲一下 SCP 是什么,它是如何应用到你的组织中的。

SCP 由一个策略文档组成,通过过滤定义了哪些服务和操作可以在 OU 或 AWS 账户内使用和执行。如果你之前配置过 IAM 策略,那么你已经有了足够的背景知识来开始使用 SCP。除了少数几个小例外,它们看起来完全一样。

SCP 可以在组织的不同级别应用。以下是这些级别,从下到上:

  • AWS 账户级别:应用于 AWS 账户的 SCP 只会对该账户生效。需要注意的是,SCP 与账户内的 IAM 策略是完全独立的。例如,SCP 可能允许 AWS 账户完全访问 S3,但账户内的 IAM 策略可能会拒绝访问(针对某些角色和/或用户)。

  • OU 级别:在 OU 级别应用的 SCP 将适用于该 OU 中的所有 AWS 账户以及任何子 OU(记住,OU 可以是另一个 OU 的成员)。

  • 根级别:如果在此级别应用 SCP,它将应用于组织内所有 AWS 账户。

当你在多个层次上应用 SCP 时,事情可能会变得非常有趣。在根、OU、账户和 IAM 层次上的策略交集将被评估,并决定是否允许 API 调用。例如,即使某人属于具有账户完全管理员访问权限的 IAM 角色,如果任何上层 SCP(账户、OU、根)拒绝 EC2 访问,他们仍然无法调用任何 EC2 API。

在下面的示例中,我们有一个顶级组织单元(OU),Austero Bluth,附加了一个 SCP,该 SCP 允许对其下所有 OU 和账户的所有 AWS 资源访问:

组织层级和策略

Austero Bluth 有两个子 OU;让我们专注于 Sudden Valley。它有一个 SCP,只允许 EC2 和 S3。通过使用白名单方法,除了这两个服务,其他任何服务都会被拒绝访问。记住,SCP 像一个过滤器,任何位于 Sudden Valley OU 下的 OU 或 AWS 账户,最多只能访问 EC2 和 S3。

Sitwell Enterprises 账户也附加了一个 SCP。这个特定的 SCP 允许 S3 和 SQS。请注意,由于 Sitwell 账户位于一个不允许 SQS 的 OU 中,SQS 声明在这里不会生效。还要注意,尽管Sudden Valley OU允许 EC2,但该账户无法访问 EC2,这是因为附加在账户上的 SCP 中没有明确允许 EC2。

在 IAM 级别,我们在 Sitwell AWS 账户中有一个角色,允许完全的管理员权限。但由于管理此账户的 SCP 交集只允许 S3,因此任何使用该角色的人,如果尝试使用 EC2 或 SQS 等服务,将被拒绝访问。

让我们再看一下Bluth Company 账户。附加在该账户上的 SCP 允许完全的 AWS 访问;然而,它位于一个仅允许 EC2、RDS 和 S3 的 OU(Balboa Bay)中。该账户内有一个 IAM 角色,也允许完全的管理员访问,但同样,该账户中的管理员将被限制在 EC2、RDS 和 S3 之内。

准备工作

我们将一步步创建一个 SCP 并将其添加到 OU 中。

你需要该 OU 的 ID;你可以从组织的网页控制台获取,或者使用 CLI。它看起来像这样:ou-bmdw-omzypry7

我们还将准备一个策略文档。在这个例子中,我们将向 Sudden Valley OU 添加一个 SCP,允许访问 EC2 和 S3。以下是我们的 SCP 的样子:

{ 
    "Version":"2012-10-17", 
    "Statement":[ 
        { 
            "Effect":"Allow", 
            "Action":["EC2:*","S3:*"], 
            "Resource":"*" 
        } 
    ] 
}

如何操作...

  1. 在文本编辑器中打开一个新文件,添加你的 JSON 策略文档,并保存。

  2. 像这样运行create-policy CLI 命令。我们在这里使用tr命令稍微有些技巧:我们用它来移除策略文档中的回车符,因此请特别注意示例中的语法。不幸的是,组织 CLI 不允许我们直接提供策略文档的路径:

 aws organizations create-policy \
 --content "$(tr -d '\n' < my-policy-file.json)" \ 
 --description "A policy description goes here" \
 --name "My policy" \
 --type SERVICE_CONTROL_POLICY

  1. 如果前面的 CLI 命令成功执行,将返回一些 JSON,包含我们刚刚添加的策略 ID。它看起来像这样:p-o9to04s7

  2. 现在你可以继续将此策略附加到 OU。使用以下 CLI 命令来执行此操作:

 aws organizations attach-policy \
 --target-id <ou-or-aws-account-id> \
 --policy-id <policy-id>

  1. 不幸的是,前面的命令如果成功执行,则不会输出任何内容。你可以通过 AWS Web 控制台重新检查你的操作,或者使用以下 CLI 命令来验证它是否成功:
 aws organizations list-targets-for-policy \
 --policy-id <policy-id>

它是如何工作的……

再次强调,你添加的策略将在组织结构的每一层充当过滤器。考虑到这一点,可能是时候指出,在将策略应用于整个组织之前,先在单一账户上测试策略将为你节省许多麻烦。对组织顶层的 SCP 进行大范围更改可能会在链条底部的 AWS 账户层面产生意想不到的情况。AWS 账户中的本地管理员无法覆盖 SCP。

还有更多……

  • 在发布时,你只能在一个组织中拥有一个根(当你创建组织时,它会自动为你创建)。

  • 出于显而易见的原因,主账户不会受到附加到它的任何 SCP 的影响。你还可能注意到,技术上是可以将主账户放置到 OU 中的;同样,它也不会受到附加到该 OU 的任何 SCP 的影响。

  • 由于主账户不受 SCP 的影响,最好尽可能保持它为空,并且不要在其中创建任何资源。使用子 AWS 账户,这样你可以对其应用细粒度控制。

  • 每个组织单元(OU)和账户都需要使用 SCP,但不应将其视为 AWS 账户的唯一访问控制方式。根据需要应用 IAM。

  • 创建策略时,我们必须指定--type参数。在发布时,AWS 仅支持一种 OCP 变体:SERVICE_CONTROL_POLICY

  • 尽可能遵循最小权限原则。你应该只给予 AWS 账户访问它们所需的服务。这有助于减少误操作、编程错误或账户被攻破所带来的损害。

  • 从长远来看,你可能会发现不在根级别分配控制更有利。相反,可能更好将所有账户添加到一个 OU,并将控制应用于该 OU。

  • 你的策略可以采用白名单或黑名单方法。在这个示例中,我们使用了白名单方法,但你也可以选择允许你的组织单元(OU)和账户使用所有服务,除了那些你明确禁止的服务。你应该选择其中一种方法并坚持使用,因为混合这两种方法会在未来造成很多困惑。

  • 与 IAM 策略不同,你不能在 SCP 文档中指定条件,并且Resource必须*

另请参见

  • 第八章中的与 AWS 账户联邦示例,安全与身份章节,涉及一些关于 IAM 角色的讨论。

第三章:存储与内容交付

在本章中,我们将涵盖以下方案:

  • 托管静态网站

  • 缓存网站

  • 使用网络存储

  • 用于合规的备份数据

介绍

每个这些方案都由 CloudFormation 模板支持,使其可以快速、轻松地复现和修改。

存储

存储是任何组织云计算使用的关键部分。正确使用时,服务器是短生命周期且可替换的。这意味着拥有一个耐用、可用的存储服务对于持久化和共享状态至关重要。

下面是 AWS 提供的存储服务的高级概述:

AWS 的存储服务

弹性块存储

弹性块存储EBS)为 EC2 实例提供块设备存储。它的行为类似于存储区域网络SAN),并且提供了各种存储服务中最低延迟的访问。EBS 卷一次只能被一个实例访问。卷的大小必须在创建时指定,并且之后不能更改。

卷托管在特定可用区的冗余硬件上,但它们不提供跨可用区的冗余。

EBS 的一些推荐使用场景:

  • 实例启动卷

  • 强大的数据处理

  • 事务性写入

我们将在第四章中更详细地讨论 EBS,使用 AWS 计算,因为它的主要用途是作为 EC2 实例的底层存储。

弹性文件系统

弹性文件系统EFS)提供一种文件存储服务,多个实例可以同时访问,类似于网络附加存储NAS)。虽然它的速度不如 EBS 快,但仍提供低延迟访问。由于可以被多个客户端同时访问,因此它的吞吐量可以远高于 EBS。EFS 文件系统的大小会动态扩展,因此不需要预分配或在使用过程中进行修改。文件系统会在可用区(AZ)之间冗余存储。

EFS 的一些推荐使用场景:

  • 主页目录

  • 提供共享网站内容

  • 内容管理

EFS 的性能会根据文件系统的大小进行扩展。由于文件系统的大小不是预分配的,唯一增加性能的方法是向其中添加更多数据。

简单存储服务

简单存储服务S3)提供一个基于 Web 的文件托管服务。文件被称为对象,并分组在存储桶中。对象实际上是一个键值对,类似于文档数据库。键像文件路径一样使用,/用作分隔符和分组字符。存储桶可以像网站一样通过自动生成的域名轻松访问。

由于与域名相关联,存储桶名称必须是全局唯一的。

S3 的一些推荐使用场景:

  • 静态网站资产

  • 共享大文件

  • 短期(也称为温暖)备份

Glacier

Glacier 是 S3 的配套服务,但它是 存储选项。冷存储是一种你无法直接访问数据的服务;你必须提出数据恢复请求(恢复到 S3),并在数据准备好时收到通知。冷存储的物理例子可能是存放在安全位置的备份磁带。与 S3 类似,文件被称为 对象。文件被分组存储在 归档 中。归档可以创建和删除,但不能修改。归档被分组存储到 金库 中,金库让你能够控制访问。

最短的恢复时间是 1-5 分钟(有一些限制)。标准恢复时间需要 3-5 小时,还有其他选项可用。

Glacier 的一些推荐用例包括:

  • 长期(即 )备份

  • 合规性备份

内容交付

内容交付的目标是快速、高效地将内容分发给用户。最佳实践是利用 内容分发网络CDN)。亚马逊的 CDN 服务是 Amazon CloudFront

虽然 AWS 目前有 14 个区域,但它还有 68 个额外的边缘位置,可以作为 CloudFront 的一部分使用。这为你提供了一个庞大的全球网络资源,你可以利用这些资源提升用户对你应用的体验。

CloudFront 与 S3 紧密合作,提供静态资源。除此之外,它还可以配置缓存动态内容。这为你提供了一种简单的方式来提升不直接与 CloudFront 配合使用的应用的性能。

CloudFront 网站被称为 分发,这突出了它们作为 CDN 的角色。

分发也可以用于为多个不同的内容来源提供共同的前端。

托管静态网站

在 AWS 上托管静态网站非常简单。事实证明,它不仅成本低廉、快速、可靠,而且具备大规模扩展能力。

你可以通过将内容存储在 S3 桶中,并配置该桶使其像网站一样运行来实现这一点。

需要注意的是,我们这里只讨论静态内容。此方法不适用于需要服务器端处理或其他后端功能的网站。例如,WordPress 需要 PHP,这意味着你需要一个完全功能的 web 服务器来运行它。S3 无法为你解析 PHP 页面,它只会直接将文件提供给浏览器。

那么,为什么你会想要在 S3 中托管静态网站呢?我们常见的场景有:

  • 简单来说,你的网站完全是静态的,而且你不经常更改它。

  • 你的公司正在推出一款新产品或服务。你预计在短时间内会有大量客户访问一个迷你网站;流量可能远超你现有的 web 托管环境所能承受的。

  • 你需要为故障转移或 维护中 页面提供一个托管位置,这个页面应该与现有的 web 托管环境分开。

当 S3 用于提供静态内容时,不支持 HTTPS。

如何操作……

这个配方为你提供了创建所需 CloudFormation 配置:

  • 一个用于托管内容的 S3 存储桶

  • 一个 Route 53 托管区和必要的 DNS 记录

  • wwwroot/apex的重定向,适用于你的域名

在运行完 CloudFormation 后,你当然需要将内容上传到 CloudFormation 为你创建的存储桶中。

创建 S3 存储桶并托管内容

在这个例子中,我们将为我们的网站www.example.org/创建两个 S3 存储桶。它们分别对应以下主机名:

  • www.example.org

  • example.org

也许现在是时候提醒你,S3 存储桶名称是全局唯一的。你还需要用你拥有的域名替换example.org

  1. 我们将把所有内容放在example.org存储桶中,并告诉 S3 请求www.example.org时应重定向到另一个存储桶。以下是 CloudFormation 的相关部分,展示如何创建这些存储桶(请注意,随着我们继续本配方,我们将扩展这个例子):
      Resources: 
        ApexBucket: 
          Type: AWS::S3::Bucket 
          Properties: 
            BucketName: !Ref DomainName 
        WWWBucket: 
          Type: AWS::S3::Bucket 
          Properties: 
            BucketName: !Sub 
              - www.${Domain} 
              - Domain: !Ref DomainName

  1. 我们不会将域名硬编码到存储桶名称中。相反,我们将作为参数将域名传递给 CloudFormation 模板,以最大化其可重用性,然后通过!Ref DomainName引用它。为了简化这个配方,我们将设置一个单页网站。在现实中,你的网站当然会由多个文件组成,但你需要遵循的过程完全相同。

  2. 配置索引文档:

    • 索引文档是 S3 在用户在浏览器的地址栏中输入域名时默认提供的文件。这避免了用户需要输入文件的完整路径,即example.org/index.html

    • 通常,你的索引文档会被命名为index.html。我们将在本章最后提供该文件的代码片段。

  3. 配置错误文档:

    • 错误文档是 S3 在出现问题时(如文件丢失、访问禁止、请求错误等)提供的文件。为了保持一致性,我们将其命名为error.html。同样,我们将在本章稍后提供该文件的代码片段。
  4. 在存储桶上启用网站托管:

    • 如前所述,我们需要告诉 S3 它应该从我们的example.org存储桶提供静态网站内容。通常用户会通过 S3 Web 控制台进行此配置,但我们将使用 CloudFormation 来完成此操作。CLI 也提供了一个简便的命令行来完成这个操作。你无需运行这个命令,我们在这里仅作为参考添加它:
 aws s3 website s3://example.org/ 
                  --index-document index.html --error-document error.html

  1. 设置从www主机名的重定向:

    • 在手动执行此任务时,你几乎没有选择,只能打开 Web 控制台并将 www.example.org 存储桶配置为重定向到 example.org 存储桶。没有简便的一行 CLI 命令可以做到这一点。幸运的是,在 CloudFormation 中,这很容易,正如你将在接下来的 CloudFormation 代码片段中看到的那样。
  2. 配置权限:

    • 最后一个存储桶设置任务是配置权限。默认情况下,S3 存储桶是私有的,只有存储桶的所有者可以查看其内容。在这种情况下,这对我们没有太大用处,因为我们需要每个人都能够看到我们的存储桶内容。毕竟,这是一个公共网站。
  3. 如果我们手动配置存储桶,我们会应用类似下面这样的存储桶策略:

      { 
        "Version":"2012-10-17", 
        "Statement": [{ 
          "Sid": "Allow Public Access to everything in our bucket", 
          "Effect": "Allow", 
          "Principal": "*", 
          "Action": "s3:GetObject", 
          "Resource": "arn:aws:s3:::example.org/*" 
        } 
       ] 
      }

  1. 幸运的是,在 CloudFormation 中,这个任务要简单得多。基于之前的示例,我们的 CloudFormation 模板的 Resources 部分现在看起来是这样的:
        ApexBucket: 
          Type: AWS::S3::Bucket 
          Properties: 
            BucketName: 
              Ref: DomainName 
            AccessControl: PublicRead 
            WebsiteConfiguration: 
              IndexDocument: index.html 
              ErrorDocument: error.html 
        WWWBucket: 
          Type: AWS::S3::Bucket 
          Properties: 
            BucketName: 
              Fn::Join: [ ., [ www, Ref: DomainName ] ]
            AccessControl: BucketOwnerFullControl 
            WebsiteConfiguration: 
              RedirectAllRequestsTo: 
                HostName: 
                  Ref: ApexBucket

创建托管区域

为了开始添加 DNS 记录,我们首先需要向 Route 53 添加一个托管区域。如以下代码所示,这相对简单。我们将提供的 Name 将作为参数传递给我们的 CloudFormation 模板:

DNSHostedZone: 
  Type: "AWS::Route53::HostedZone" 
  Properties: 
    Name: 
      Ref: DomainName

创建 DNS 记录

  1. 现在我们有了托管区域,可以继续为它创建 DNS 记录了。为此,我们使用 AWS 资源类型 AWS::Route53::RecordSetGroup

  2. 我们将为我们域名的 root/apex 项创建一个 A 记录,并且我们会将它设置为别名。这个别名将配置为指向我们选择的区域内 S3 托管网站的 AWS 终端节点。

  3. 为了实现模板中的区域可移植性,我们将使用映射来提供所有终端节点。这个映射中的值由 AWS 在其 API 终端节点文档中发布。不过,你不需要查找这些值,因为我们的代码示例提供了最新的终端节点(截至写作时)。终端节点通常不会更改,但随着 AWS 增加更多区域,列表显然会增长。

  4. 映射看起来是这样的:

        us-east-1: 
          S3HostedZoneID: Z3AQBSTGFYJSTF 
          S3AliasTarget: s3-website-us-east-1.amazonaws.com 
        us-east-2: 
          S3HostedZoneID: Z2O1EMRO9K5GLX 
          S3AliasTarget: s3-website.us-east-2.amazonaws.com

我们还需要为 www 创建一个 CNAME,它将指向我们的 WWWBucket,以便进行重定向。我们的 DNS 记录的最终资源将如下所示:

        DNSRecords: 
          Type: "AWS::Route53::RecordSetGroup" 
          Properties: 
            HostedZoneId: 
              Ref: DNSHostedZone 
            RecordSets: 
              - Name: 
                  Ref: DomainName 
                Type: A 
                AliasTarget: 
                  HostedZoneId: 
                    Fn::FindInMap: [ RegionMap, Ref: "AWS::Region",
                      S3HostedZoneID ]
                  DNSName: 
                    Fn::FindInMap: [ RegionMap, Ref: "AWS::Region",
                      S3AliasTarget ]
              - Name: 
                  Fn::Join: [ ., [ www, Ref: DomainName ] ] 
                Type: CNAME 
                TTL: 300 
                ResourceRecords: 
                  - Fn::GetAtt: WWWBucket.DomainName

  1. 我们准备好发布了。是时候创建我们的 CloudFormation 堆栈了。你可以使用以下 CLI 命令来完成:
 aws cloudformation create-stack \ 
 --stack-name static-website-1 \ 
 --template-body file://03-hosting-a-static-website.yaml \ 
 --parameters \
 ParameterKey=DomainName,ParameterValue=<your-domain-name>

上传网站内容

现在是时候将一些内容上传到我们的 S3 存储桶了。这里是我们之前承诺给你的代码片段。内容并不复杂。一旦你成功运行了这些示例,你可以尝试将它们替换为你真实的网站内容:

  • index.html
      <html> 
        <head> 
          <title>Welcome to exmaple.org</title> 
        </head> 
        <body> 
          <h1>example.org</h1> 
          <p>Hello World!</p> 
        </body> 
      </html>

  • error.html
      <html> 
        <head> 
          <title>Error</title> 
        </head> 
        <body> 
          <h1>example.org</h1> 
          <p>Something went wrong!</p> 
        </body> 
      </html>

它是如何工作的……

就这样!一旦 S3 有了一个 index.html 文件来提供服务,你就可以在 S3 上托管一个单页网站了。去试试看吧。提供的 CloudFormation 示例将输出一个 URL,你可以用它来查看你新建的网站。在验证其正常工作后,你可以继续上传你真实的静态网站,享受快速、廉价且无需服务器的托管服务。

还有更多……

让我们来看看一些需要考虑的额外事项。

将你的域名委托给 AWS

虽然我们已经在 Route 53 中创建了托管区域和一些 DNS 记录,但实际上还没有人能看到它们。为了将你的网站访客引导到新的 S3 静态网站,你需要将域名委托给 Route 53。这部分留给你作为练习;不过,有一些重要事项需要记住:

  • 你需要将域名委托给的 DNS 服务器可以在你的托管区域的 NS 记录中找到。

  • 如果你的域名已经上线并接近生产环境,你需要确保你的区域 DNS 记录都已经在 Route 53 中重新创建,包括如 MX 记录这类对邮件服务持续性至关重要的内容。

  • 在委托给 AWS 之前,你可以考虑减少 DNS 记录的 TTL 值。如果你需要重新委托或更改记录,这将非常有用。一旦你的 DNS 设置稳定,可以增加 TTL 值。

跨源资源共享

在这里值得讨论一下跨源资源共享CORS),因为你在 S3 上托管更多静态网页内容时,了解 CORS 的可能性会更高,尤其是在涉及网页字体时。

一些浏览器实施了同源策略限制。这会阻止浏览器从与当前显示给用户的页面不同的主机名加载某些类型的资源。网页字体就是一个常见的例子,因为如果它们无法正确加载,通常会导致你的网站看起来与预期大相径庭。解决方案是为你的存储桶添加 CORS 配置,允许特定来源或主机名加载其内容。

我们将会把 CORS 配置从完整示例中省略,但如果你需要为存储桶添加一个,下面是如何操作的。更新你的AllowedOrigins属性,格式可以参考下面的 CloudFormation,你应该就能设置好:

  ApexBucket: 
    Type: AWS::S3::Bucket 
    Properties: 
      BucketName: !Ref DomainName 
      AccessControl: PublicRead 
      WebsiteConfiguration: 
        IndexDocument: index.html 
        ErrorDocument: error.html 
      CorsConfiguration: 
        CorsRules: 
        - AllowedOrigins: 
            - example.net 
            - www.example.net 
            - example.com 
            - www.example.com 
          AllowedMethods: 
            - GET 
          MaxAge: 3000 
          AllowedHeaders: 
            - Content-* 
            - Host

缓存网站

在这个操作中,我们将向你展示如何使用 AWS CloudFront 来缓存你的网站。

你考虑执行此操作的主要原因如下:

  • 你内容的副本将地理上靠近你的终端用户,从而改善他们的体验,并更快地为他们提供内容。

  • 内容的提供负担将从你的服务器群上移除。如果你能够关闭一些服务器或减少带宽账单,可能会带来巨大的成本节约。

  • 你可能需要保护自己免受大规模且意外的流量激增。

  • 虽然这章的重点不是这个,CloudFront 让你能够实现Web 应用防火墙WAF),为你提供额外的保护层,防止恶意攻击者。

不同于大多数 AWS 服务是区域特定的,CloudFront 是一个全球服务。

准备就绪

首先,你需要一个公开可访问的网站。这可以是托管在 S3 上的静态网站,也可以是托管在 EC2 上的动态生成网站。事实上,你的网站甚至不需要托管在 AWS 上也能使用 CloudFront。只要你的网站是公开可访问的,就可以使用 CloudFront。

你还需要能够修改网站的 DNS 记录。我们最终将把它们指向 CloudFront,而不是指向你的 Web 服务器(或 S3 存储桶)。

关于动态内容

如果你的网站主要由动态内容组成,你仍然可以通过实施 CloudFront 受益。

首先,CloudFront 将保持与源服务器的持久连接池。这减少了文件传输到用户所需的时间,因为需要执行的三次握手次数减少了。

其次,CloudFront 在 TCP 连接方面实施了一些额外的优化,以提高性能。由于 CloudFront 使用更大的初始 TCP 窗口,因此能够更快地传输更多的数据。

最后,实施像 CloudFront 这样的 CDN 确实给你提供了审查缓存策略和使用缓存控制头的机会。如果你的首页是动态生成的,通过 CloudFront 服务它你会立刻得到一些好处,但如果你让 CloudFront 缓存几分钟,效果会更好。再次强调,成本、最终用户体验和后台性能都是你需要考虑的因素。

配置 CloudFront 分发

分发可以配置多种选项。我们的配置将相对简单,以便你能够尽快上手。但我们会讨论一些更常见的配置选项:

  • 源:一个分发需要至少有一个源。正如其名称所示,源是你的网站内容的来源,来自你公开面向用户的网站。你最关心的属性通常是:

    • 源域名:这是你公开面向用户的网站的主机名。我们提供的 CloudFormation 模板将这个主机名作为参数接受。

    • 源路径:可以配置分发从源站的目录或子文件夹获取内容,例如,如果你只使用 CloudFront 缓存图片,可以设置 /content/images。在我们的案例中,我们缓存的是整个网站,因此我们根本不指定源路径。

    • 源 ID:当你使用非默认的缓存行为设置并配置多个源时,这一点尤其重要。你需要为源分配唯一的 ID,以便缓存行为知道要针对哪个源进行操作。稍后会对缓存行为进行更多讨论。

    • HTTP 端口,HTTPS 端口:如果你的源在非标准端口上监听 HTTP 或 HTTPS,你可以使用这些参数来定义这些端口。

    • 源协议策略:你可以配置分发与源服务器进行通信的方式:

      • 仅支持 HTTP

      • 仅支持 HTTPS

      • 匹配查看者

匹配查看者选项根据用户在浏览器中请求的协议将请求转发到源服务器。由于我们在这个方案中保持简单,所以我们选择了仅支持 HTTP。

  • 日志记录:请记住,由于流量减少,访问源服务器的请求也会减少,因此捕获的访问日志也会较少。将这些日志存储在 S3 桶中由 CloudFront 来管理是有意义的。在这个方案中,CloudFormation 模板已经包括了这项内容:

    • 缓存行为:在这个方案中,我们将配置一个单一的(默认)缓存行为,它将把所有请求转发到我们的源服务器。

    • CloudFront:它允许你对配置的行为进行非常精细的控制。例如,你可能想要对源服务器上的所有.js.css文件应用一个规则。也许你希望将查询字符串转发到源服务器针对这些文件类型。类似地,你可能希望忽略源服务器尝试为图像文件设置的 TTL,而是指示 CloudFront 缓存至少 24 小时。

  • 别名:这些是你希望分发为其提供流量的附加主机名。例如,如果你的源域名配置为loadbalancer.example.org,你可能希望添加类似以下的别名:

    • example.org

    • www.example.org

本方案提供的 CloudFormation 模板期望至少包含一个或多个

别名应以逗号分隔的字符串列表的形式提供。

  • 允许的 HTTP 方法:默认情况下,CloudFront 只会将 GET 和 HEAD 请求转发到源服务器。这个方案没有更改这些默认设置,因此我们没有在提供的模板中声明这个参数。如果你的源服务器提供动态生成的内容,你可能希望声明此参数,并将其值设置为 GET、HEAD、OPTIONS、PUT、POST、PATCH 和 DELETE。

  • TTL(最小/最大/默认):你可以选择定义对象在 CloudFront 缓存中保留多长时间,直到过期并从源服务器重新获取。由于我们选择保持 CloudFront 的默认值以简化方案,因此在模板中没有包含这个参数。默认值如下:

    • 最小 TTL:0 秒

    • 默认 TTL:1 天

    • 最大 TTL:1 年

  • 价格类别:默认情况下,CloudFront 会从所有的边缘位置提供内容,从而提供最佳的性能。我们将使用最低的价格类别 Price Class 100来部署我们的分发。这对应于美国、加拿大和欧洲的边缘位置。来自澳大利亚的用户可能不会从这个价格类别中受益太多,但你也支付的费用较少。价格类别200增加了亚洲地区,价格类别All则包括南美洲和澳大利亚。

创建 CloudFront 分发时可以指定的值的详细列表和解释,请参见此链接:docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html

如何操作...

我们需要做的第一件事(也是唯一的事)是按照下图所示配置 CloudFront 分发:

  1. 创建一个新的 CloudFormation 模板,并添加以下代码:
      AWSTemplateFormatVersion: '2010-09-09'
      Parameters:
        OriginDomainName:
          Description: The hostname of your origin
           (i.e. www.example.org.s3-website-ap-southeast-2.amazonaws.com)
          Type: String
        Aliases:
          Description: Comma delimited list of aliases
           (i.e. example.org,www.example.org)
          Type: CommaDelimitedList
      Resources:
        DistributionALogBucket:
          Type: AWS::S3::Bucket
        DistributionA:
          Type: AWS::CloudFront::Distribution    
          Properties:
            DistributionConfig:
              Origins:
              - DomainName:
                  Ref: OriginDomainName
                Id: OriginA
                CustomOriginConfig:
                  OriginProtocolPolicy: http-only
              Enabled: true
              Logging:
                IncludeCookies: false
                Bucket:
                  Fn::GetAtt: DistributionALogBucket.DomainName
                Prefix: cf-distribution-a
              Aliases:
                Ref: Aliases
              DefaultCacheBehavior:
                TargetOriginId: OriginA
                ForwardedValues:
                  QueryString: false
                ViewerProtocolPolicy: allow-all
              PriceClass: PriceClass_100
      Outputs:
        DistributionDomainName:
          Description: The domain name of the CloudFront Distribution
          Value:
            Fn::GetAtt: DistributionA.DomainName
        LogBucket:
          Description: Bucket where CloudFront logs will be stored
          Value:
            Ref: DistributionALogBucket

  1. 使用我们上面创建的模板,继续创建 CloudFront 分发。预计需要等待约 20-25 分钟,直到这个堆栈完成创建。由于需要将你的分发配置推送到所有 AWS CloudFront 位置,所以这会花费一些时间:
 aws cloudformation create-stack \ 
 --stack-name cloudfont-cache-1 \ 
 --template-body file://03-caching-a-website.yaml \ 
 --parameters \ 
 ParameterKey=OriginDomainName,ParameterValue=<your-domain-name> \ 
 ParameterKey=Aliases,ParameterValue='<alias-1>\,<alias-2>'

使用网络存储

在这个教程中,我们将使用 Amazon EFS 为实例提供基于网络的存储。

使用 EFS 与其他 AWS 服务相比的一些好处如下:

  • 保证分布式客户端之间的写入顺序

  • 自动调整大小——无需预分配,也无需缩小

  • 你只需为你使用的空间付费(按 GB 计),无需支付传输或额外费用

准备工作

这个示例使用的是默认 VPC 和子网,这些在所有 AWS 账户创建时都会自动创建。即使你已经更改了网络配置,本教程所需的仅是一个包含两个或更多不同可用区(AZ)的工作 VPC。

如何操作...

  1. 打开你喜欢的文本编辑器,通过定义AWSTemplateFormatVersionDescription来开始一个新的 CloudFormation 模板:
        AWSTemplateFormatVersion: "2010-09-09" 
        Description: Create an EFS file system and endpoints.

  1. 创建一个顶级的Parameters部分,并在其中定义必需的参数VpcIdSubnetIds
        VpcId: 
          Description: VPC ID that contains the subnets that will 
            access the file system 
          Type: AWS::EC2::VPC::Id 
        SubnetIds: 
          Description: Subnet IDs allowed to access the EFS file system 
          Type: List<AWS::EC2::Subnet::Id>

  1. 创建一个顶级Resources属性,用来包含所有定义的资源。

  2. Resources属性下,添加EFS文件系统资源:

        FileSystem: 
          Type: AWS::EFS::FileSystem 
          Properties: 
            FileSystemTags: 
              - Key: Name 
                Value: 
                  Fn::Sub: "${AWS::StackName} EFS File System" 
            PerformanceMode: generalPurpose

  1. 添加挂载目标资源,用于连接到你刚刚创建的文件系统:
      MountTargetA: 
        Type: AWS::EFS::MountTarget 
        Properties: 
          FileSystemId: 
            Ref: FileSystem 
          SecurityGroups: 
            - Fn::GetAtt: MountTargetSecurityGroup.GroupId 
          SubnetId: 
            Fn::Select: [ 0, Ref: SubnetIds  ] 
      MountTargetB: 
        Type: AWS::EFS::MountTarget 
        Properties: 
          FileSystemId: 
            Ref: FileSystem 
          SecurityGroups: 
            - Fn::GetAtt: MountTargetSecurityGroup.GroupId 
          SubnetId: 
            Fn::Select: [ 1, Ref: SubnetIds  ]           

  1. 创建一个安全组来控制对挂载目标的访问:
      MountTargetSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: EFS endpoint security group 
          Tags: 
            - Key: Name 
              Value: MountTargetSecurityGroup 
          VpcId: 
            Ref: VpcId

  1. 创建一个安全组以访问你在上一步创建的挂载目标安全组:
      MountTargetAccessSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: EFS endpoint access security group 
        Tags: 
          - Key: Name 
            Value: MountTargetAccessSecurityGroup 
        VpcId: 
          Ref: VpcId

  1. 定义挂载目标安全组的入站和出站规则:
      MountTargetIngress: 
        Type: AWS::EC2::SecurityGroupIngress 
        Properties: 
          FromPort: 2049 
          GroupId: 
            Fn::GetAtt: MountTargetSecurityGroup.GroupId 
          IpProtocol: tcp 
          SourceSecurityGroupId: 
            Fn::GetAtt: MountTargetAccessSecurityGroup.GroupId 
          ToPort: 2049 
      MountTargetEgress: 
        Type: AWS::EC2::SecurityGroupEgress 
        Properties: 
          DestinationSecurityGroupId: 
            Fn::GetAtt: MountTargetAccessSecurityGroup.GroupId 
          FromPort: 2049 
          GroupId: 
            Fn::GetAtt: MountTargetSecurityGroup.GroupId 
          IpProtocol: tcp 
          ToPort: 2049

  1. 定义挂载目标访问安全组的入站和出站规则:
      MountTargetAccessIngress: 
        Type: AWS::EC2::SecurityGroupIngress 
        Properties: 
          FromPort: 22 
          GroupId: 
            Fn::GetAtt: MountTargetAccessSecurityGroup.GroupId 
          IpProtocol: tcp 
          CidrIp: 0.0.0.0/0 
          ToPort: 22 
      MountTargetAccessEgress: 
        Type: AWS::EC2::SecurityGroupEgress 
        Properties: 
          DestinationSecurityGroupId: 
            Fn::GetAtt: MountTargetSecurityGroup.GroupId 
          FromPort: 2049 
          GroupId: 
            Fn::GetAtt: MountTargetAccessSecurityGroup.GroupId 
          IpProtocol: tcp 
          ToPort: 2049

  1. 将你的模板保存为03-working-with-network-storage.yaml

  2. 使用以下 AWS CLI 命令启动 CloudFormation 堆栈,替换成你自己的 VPC ID 和子网 ID:

 aws cloudformation create-stack \
 --stack-name wwns1 \
 --template-body file://03-working-with-network-storage.yaml \
 --parameters \
 ParameterKey=VpcId,ParameterValue=<your-vpc-id> \ 
 ParameterKey=SubnetIds,ParameterValue="<subnet-id-1>\, \
          <subnet-id-2>"

它是如何工作的...

这是在教程结束时创建的资源的样子:

使用网络存储

我们从第 1 步开始创建标准的 CloudFormation 模板属性。

在第 2 步中,你定义了模板的参数,这些参数将在配置资源时使用。

第 3 步和第 4 步是指定 EFS 资源的地方。它们由一个 EFS 文件系统和在每个可用区(AZ)中的挂载目标组成,这些挂载目标将访问该文件系统。

然后,我们在步骤 5 和 6 中创建安全组:一个用于挂载目标,另一个用于允许连接到挂载目标的实例。

由于这两个安全组之间有双向(或循环)引用,我们必须在步骤 7 和 8 中将它们之间的规则定义为单独的资源。

在步骤 9 中,你将以特定的文件名保存模板,以便在步骤 10 中的命令中引用它来启动堆栈。

还有更多...

为了确认你的 EFS 文件系统、挂载目标和安全组是否正常工作,你还可以配置一些客户端实例来连接它们。将以下资源和参数添加到你已经创建的模板中:

  1. 将以下参数添加到你的顶层 Parameters 部分以配置你的实例:
      MountPoint: 
        Description: The path on disk to mount the EFS file system 
        Type: String 
        Default: /mnt/efs 
      KeyName: 
        Description: The SSH key pair allowed to connect to the client 
          instance 
        Type: AWS::EC2::KeyPair::KeyName

  1. Resources 部分添加一个 AutoScalingGroup;无论你的服务器被配置到哪个可用区,它们都可以通过本地挂载点访问 EFS 文件系统:
      AutoScalingGroup: 
        Type: AWS::AutoScaling::AutoScalingGroup 
        DependsOn: MountTargetA 
        Properties: 
          MinSize: 2 
          MaxSize: 2 
          LaunchConfigurationName: 
            Ref: LaunchConfiguration 
          Tags: 
            - Key: Name 
              Value: 
                Fn::Sub: "${AWS::StackName} EFS Client" 
              PropagateAtLaunch: true 
          VPCZoneIdentifier: 
            Ref: SubnetIds

  1. 仍然在 Resources 部分,添加一个启动配置:
      LaunchConfiguration: 
        Type: AWS::AutoScaling::LaunchConfiguration 
        DependsOn: FileSystem 
        Properties: 
          ImageId: ami-1e299d7e 
        SecurityGroups: 
          - Ref: MountTargetAccessSecurityGroup 
        InstanceType: t2.micro 
        KeyName: 
          Ref: KeyName 
        UserData: 
          Fn::Base64: 
          Fn::Sub: |
            #!/bin/bash -xe
            mkdir -p ${MountPoint}
            echo 'Waiting for mount target DNS to propagate'
            sleep 90
            echo '${FileSystem}.efs.${AWS::Region}.amazonaws.com:/
            ${MountPoint} nfs4
            nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,
            retrans=2 0 0' >> 
            /etc/fstab
            mount -a\nchown ec2-user: ${MountPoint}\n"

  1. 使用以下 AWS CLI 命令启动 CloudFormation 堆栈,替换为你自己的参数值:
 aws cloudformation create-stack \ 
 --stack-name wwns1 \
 --template-body file://03-working-with-network-storage.yaml \
 --parameters \
 ParameterKey=VpcId,ParameterValue=<vpc-id> \
 ParameterKey=SubnetIds,ParameterValue='<subnet-id-1>\, \
          <subnet-id-1>' \ 
 ParameterKey=MountPoint,ParameterValue=<local-path-to-mount-efs> \
 ParameterKey=KeyName,ParameterValue=<existing-key-pair-name>

一旦新堆栈准备就绪,你将能够 SSH 连接到你的实例并进行验证。

确保它们已经挂载了 EFS 文件系统。

数据备份以符合合规要求

我们与许多公司合作(尤其是在金融行业),这些公司对数据需要保存的最短时间有严格的规定。例如,如果你需要保存客户记录至少 7 年,这可能会变得非常繁琐和昂贵。

使用 S3、Glacier 和生命周期规则,我们可以创建一个灵活的长期备份解决方案,同时自动化备份的归档和清除,降低成本。

我们还将利用 版本控制 来减轻由于文件在备份桶中被意外删除或覆盖而造成的损坏。

如何操作...

  1. 首先,我们需要定义一些参数:

    • ExpirationInDays:这是我们希望将文件保存在备份中的最大时间。我们已将此值的默认设置为 2,555 天(7 年)。

    • TransitionToInfrequentAccessInDays:在备份复制到 S3 后,我们希望将其转移到 不常访问 存储类,以减少成本。这不会影响备份的持久性,但会对其可用性产生轻微影响。我们将其设置为 30 天。

    • TransitionToGlacierInDays:在备份保留在不常访问的存储类一段时间后,我们希望将其转移到 Glacier。这样做有助于我们降低成本,但会牺牲检索时间。如果我们需要从 Glacier 恢复备份,等待时间大约为 3-5 小时。我们将默认设置为 60 天。

    • PreviousVersionsExpirationInDays:鉴于我们将在桶中启用版本控制,我们希望确保旧版本的文件不会被永久保留——我们使用此功能仅是为了防止意外。我们将此值设置为 60 天,这样我们就有足够的时间识别并从意外删除或覆盖中恢复。

    • PreviousVersionsToInfrequentAccessInDays:就像我们的其他备份文件一样,我们希望在一段时间后将旧版本移动到不常访问的存储类,以最小化成本。我们将此值设置为 30 天:

               AWSTemplateFormatVersion: '2010-09-09'
               Parameters:
                 ExpirationInDays:
                   Description: The maximum amount of time to keep files
                     for
                   Type: Number
                   Default: 2555
                 TransitionToInfrequentAccessInDays:
                   Description: How many days until files are moved to
                     the Infrequent Access class
                   Type: Number
                   Default: 30
                 TransitionToGlacierInDays:
                   Description: How many days until files are moved
                     to Glacier
                   Type: Number
                   Default: 60
                 PreviousVersionsExpirationInDays:
                   Description: The maximum amount of time to keep previous
                     versions of files for
                   Type: Number
                   Default: 60
                 PreviousVersionsToInfrequentAccessInDays:
                   Description: How many days until previous versions
                     of files are moved to the Infrequent Access class
                   Type: Number
                   Default: 30

  1. 接下来,我们需要创建 S3 桶来存储备份。请注意,我们省略了该桶的 name 属性,以避免桶名冲突并最大化区域可移植性。我们还启用了版本控制,并添加了之前 Parameters 中的生命周期规则:
      Resources: 
        BackupBucket: 
          Type: AWS::S3::Bucket 
          Properties: 
            VersioningConfiguration: 
              Status: Enabled 
            LifecycleConfiguration: 
              Rules: 
                - Status: Enabled 
                  ExpirationInDays: 
                    Ref: ExpirationInDays 
                  Transitions: 
                    - StorageClass: STANDARD_IA 
                      TransitionInDays: 
                        Ref: TransitionToInfrequentAccessInDays 
                    - StorageClass: GLACIER 
                      TransitionInDays: 
                        Ref: TransitionToGlacierInDays 
                    NoncurrentVersionExpirationInDays: 
                      Ref: PreviousVersionsExpirationInDays 
                    NoncurrentVersionTransitions: 
                    - StorageClass: STANDARD_IA 
                      TransitionInDays: 
                        Ref: PreviousVersionsToInfrequentAccessInDays

  1. 最后,让我们添加一个输出,以便我们知道将备份存储到哪个桶中:
      Outputs: 
        BackupBucket: 
          Description: Bucket where backups are stored 
          Value: 
            Ref: BackupBucket

工作原理...

继续并启动此 CloudFormation 堆栈。如果您对参数的默认值感到满意,则无需在 CLI 命令中提供它们:

aws cloudformation create-stack \
 --stack-name backup-s3-glacier-1 \
 --template-body file://03-backing-up-data-for-compliance.yaml

一旦堆栈创建完成,您就可以开始将备份复制到 S3 桶中,并且不再需要过多担心备份的生命周期和管理。如果在创建桶之后,您决定需要更改过期或转换时间,可以通过简单地更新堆栈的参数来实现。

第四章:使用 AWS 计算

本章内容包括:

  • 创建密钥对

  • 启动实例

  • 附加存储

  • 安全访问私有实例

  • 自动扩展应用服务器

  • 创建机器映像

  • 创建安全组

  • 创建负载均衡器

介绍

弹性云计算EC2)是 AWS 目录中使用最多且最复杂的服务。EC2 不仅仅是虚拟机,它提供了一套子服务框架,帮助你以弹性的方式管理和保护实例。

创建密钥对

密钥对用于通过 SSH 访问实例。这是访问实例最快且最简单的方式。

准备工作

为了执行本食谱,你必须正确配置 AWS CLI 工具。

如何操作...

  1. 创建密钥对,并将其保存到磁盘:
 aws ec2 create-key-pair \
        --key-name MyEC2KeyPair \
        --query 'KeyMaterial' \
        --output text > ec2keypair.pem

  1. 更改创建文件的权限:
        chmod 600 ec2keypair.pem

它是如何工作的...

该调用请求 EC2 提供一个新的私钥。响应将使用 JMESPath 查询进行解析,私钥(位于KeyMaterial属性中)将保存到一个新的 .pem 扩展名的密钥文件中。

最后,我们更改密钥文件的权限,确保其他用户无法读取它——这是在 SSH 允许使用该密钥前的必要步骤。

启动实例

在某些场景下——通常是在测试和开发基础设施代码时——你可能需要快速访问一个实例。通过 AWS CLI 创建实例是创建临时实例最快且最一致的方式。

本书中有其他需要运行实例的食谱。本食谱将帮助你入门。

准备工作

对于本食谱,你必须拥有一个现有的密钥对。

在本食谱中,我们使用位于us-east-1区域的 AMI ID 启动一个 AWS Linux 实例。如果你在不同区域工作,需要更新image-id参数。

你必须已使用有效的凭证配置 AWS CLI 工具。

如何操作...

运行以下 AWS CLI 命令,使用你自己的密钥对名称:

 aws ec2 run-instances \
        --image-id ami-9be6f38c \
        --instance-type t2.micro \
        --key-name <your-key-pair-name>

它是如何工作的...

虽然你可以通过 AWS Web 控制台创建实例,但它涉及许多分散注意力的选项。在开发和测试时,CLI 工具是配置实例的最佳方式。

虽然key-name参数是可选的,但除非你事先配置了其他登录方式,否则无法连接到你的实例。

本食谱中使用的t2.micro实例类型包含在 AWS 免费套餐中。你可以在使用的前 12 个月内,每月免费运行一个微型实例。更多信息请参见aws.amazon.com/free

由于没有指定 VPC 或安全组,实例将启动在你的账户的默认 VPC 和安全组中。默认安全组允许来自任何地方的所有端口的访问,因此不适合长期运行的实例。你可以在实例启动后修改其安全组,而无需停止实例。

还有更多...

如果你已经创建了自己的 AMI,那么你可以更改image-id参数来快速启动你的特定 AMI。

你可能还想注意 API 响应中返回的InstanceId值,因为你可能需要在未来的命令中使用它。

另请参见

  • 创建密钥对 配方

  • 创建机器映像 配方

附加存储

理想情况下,你应该在前期就通过像 CloudFormation 这样的服务定义所有存储需求作为代码。然而,有时由于应用程序限制或需求变化,这种方式无法实现。

你可以通过附加一个新卷来轻松地为实例添加额外的存储空间,且实例正在运行时也可以进行此操作。

准备工作

对于这个配方,你将需要以下内容:

  • 正在运行的实例的 ID。它将以i-开头,后跟字母数字字符。

  • 实例运行所在的可用区(AZ)。这通常看起来像是区域名称后跟一个字母,例如,us-east-1a

在这个配方中,我们使用的是 AWS Linux 实例。如果你使用的是其他操作系统,挂载卷的步骤会有所不同。我们将会在us-east-1a这个可用区中运行一个实例。

你必须已经用有效的凭证配置了 AWS CLI 工具。

如何操作...

  1. 创建一个卷:
 aws ec2 create-volume --availability-zone us-east-1a 

记下响应中返回的VolumeId,它将以vol-开头,后跟字母数字字符。

  1. 将卷附加到实例,使用上一步中记录的卷 ID 和你最初启动的实例 ID:
 aws ec2 attach-volume \
        --volume-id <your-volume-id> \
        --instance-id <your-instance-id> \
        --device /dev/sdf

  1. 在实例本身上,挂载卷设备:
 mount /dev/xvdf /mnt/volume

它是如何工作的...

在这个配方中,我们从创建卷开始。卷是从快照中创建的。如果你没有指定快照 ID,它将使用一个空白快照,结果得到一个空白卷。

虽然卷是冗余托管的,但它们仅托管在单一的可用区内,因此必须在实例运行所在的相同可用区中进行配置。

create-volume 命令返回一个响应,其中包含新创建卷的VolumeId。然后我们将在下一步中使用这个 ID。

有时候,卷需要几秒钟才能变为可用状态。如果你正在编写这些命令,可以使用aws ec2 wait命令来等待卷变为可用状态。

在第 3 步中,我们将一个卷附加到实例。当将卷附加到实例时,必须指定它将在操作系统中呈现为的设备名称。不幸的是,这并不能保证设备的实际显示名称。在 AWS Linux 的情况下,/dev/sdf会变成/dev/xvdf

设备命名是与内核相关的,因此如果你使用的是 AWS Linux 以外的系统,设备名称可能会有所不同。详细信息请参见 docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html

另请参见

  • 启动实例 配方

  • 第三章中的 使用网络存储 配方,存储与内容交付

安全访问私有实例

任何位于 VPC 私有子网中的实例或资源都将无法从互联网访问。从安全角度来看,这样做是有道理的,因为它为你的实例提供了更高的保护级别。

当然,如果它们无法从互联网访问,那么它们就不容易进行管理了。

一个常见的模式是使用 VPN 服务器作为进入你私有网络的单一、严格控制的入口点。这就是我们在本示例中将要展示的内容,如下图所示:

安全访问私有实例

准备就绪

我们将在这个示例中使用 OpenVPN。它们在 AWS 市场提供一个免费的 AMI(最多支持两个用户),该 AMI 已预装并配置了 OpenVPN。你需要接受使用该 AMI 的条款和条件。你可以通过访问 AMI 的市场页面aws.amazon.com/marketplace/pp/B00MI40CAE/来接受这些条款。

你需要决定一个密码,这将是你的临时管理员密码。我们会将这个密码输入到 CloudFormation 模板中,然后在创建堆栈后再更改它。

你可以使用默认的 VPC 来完成这个示例。

如何实现……

  1. 创建一个新的 CloudFormation 模板并添加以下Mappings。这是每个区域中所有最新OpenVPN AMI 的列表。我们添加这些映射是为了最大化模板的区域可移植性——你可以省略那些你不打算使用的区域:
      Mappings: 
        AWSRegion2AMI: # Latest OpenVPN AMI at time of publishing: 2.1.4 
          us-east-1: 
            AMI: ami-bc3566ab 
          us-east-2: 
            AMI: ami-10306a75 
          us-west-2: 
            AMI: ami-d3e743b3 
          us-west-1: 
            AMI: ami-4a02492a 
          eu-west-1: 
            AMI: ami-f53d7386 
          eu-central-1: 
            AMI: ami-ad1fe6c2 
          ap-southeast-1: 
            AMI: ami-a859ffcb 
          ap-northeast-1: 
            AMI: ami-e9da7c88 
          ap-southeast-2: 
            AMI: ami-89477aea 
          sa-east-1: 
            AMI: ami-0c069b60

  1. 我们现在需要定义一些Parameters。首先,我们需要知道将 VPN 实例部署到哪个 VPC 和子网。请注意,这里需要指定一个公共子网,否则你将无法访问 OpenVPN 服务器:
      VpcId: 
        Type: AWS::EC2::VPC::Id 
        Description: VPC where load balancer and instance will launch 
      SubnetId: 
        Type: List<AWS::EC2::Subnet::Id> 
        Description: Subnet where OpenVPN server will launch 
         (pick at least 1)

  1. 我们还需要定义InstanceTypeKeyName。这分别是用于启动 OpenVPN 服务器的 EC2 实例类型和 SSH 密钥对:
      InstanceType: 
        Type: String 
        Description: OpenVPN server instance type 
        Default: m3.medium 
      KeyName: 
        Type: AWS::EC2::KeyPair::KeyName 
        Description: EC2 KeyPair for SSH access

  1. 我们需要一个AdminPassword的参数。这是启动时分配给openvpn用户(管理员)的临时密码:
      AdminPassword: 
        Type: String 
        Description: Password for 'openvpn' user 
        Default: openvpn 
        NoEcho: true

  1. 最后一个参数是 CIDR 块,我们希望允许其连接到我们的 VPN 服务器。例如,你可以将其限制为公司网络的公共 IP 范围:
      AllowAccessFromCIDR: 
        Type: String 
        Description: IP range/address to allow VPN connections from 
        Default: "0.0.0.0/0"

  1. 我们需要定义的第一个Resource是 OpenVPN 服务器所在的安全组。你还将使用这个安全组来允许访问网络中的其他资源。按如下方式将其添加到模板中:
      VPNSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: Inbound access to OpenVPN server 
          VpcId: !Ref VpcId 
          SecurityGroupIngress: 
          - CidrIp: !Ref AllowAccessFromCIDR 
            FromPort: 443 
            IpProtocol: tcp 
            ToPort: 443 
          - CidrIp: !Ref AllowAccessFromCIDR 
            FromPort: 22 
            IpProtocol: tcp 
            ToPort: 22 
          - CidrIp: !Ref AllowAccessFromCIDR 
            FromPort: 1194 
            IpProtocol: udp 
            ToPort: 1194

  1. 现在我们可以定义实际的 OpenVPN 实例了。你会注意到我们正在显式配置网络接口。这是必要的,因为我们希望声明这个实例必须获得一个公共 IP 地址(否则你将无法访问它)。在UserData中,我们声明了一些变量,当 OpenVPN 软件启动时,它会读取这些变量以进行自我配置:
      OpenVPNInstance: 
        Type: AWS::EC2::Instance 
        Properties: 
          ImageId: !FindInMap [ AWSRegion2AMI, !Ref "AWS::Region", AMI ] 
          InstanceType: !Ref InstanceType 
          KeyName: !Ref KeyName 
          NetworkInterfaces: 
            - AssociatePublicIpAddress: true 
              DeviceIndex: "0" 
              GroupSet: 
                - !Ref VPNSecurityGroup 
              SubnetId: !Select [ 0, Ref: SubnetId ] 
          Tags: 
            - Key: Name 
              Value: example-openvpn-server 
          UserData: 
            Fn::Base64: !Sub 
              - | 
                public_hostname=openvpn 
                admin_user=openvpn 
                admin_pw=${admin_pw} 
                reroute_gw=1 
                reroute_dns=1 
              - admin_pw: !Ref AdminPassword

  1. 最后,我们添加一些有用的Outputs
      Outputs: 
        OpenVPNAdministration: 
          Value: 
            Fn::Join: 
              - "" 
              - - https:// 
                - !GetAtt OpenVPNInstance.PublicIp 
                - /admin/ 
          Description: Admin URL for OpenVPN server 
        OpenVPNClientLogin: 
          Value: 
            Fn::Join: 
              - "" 
              - - https:// 
                - !GetAtt OpenVPNInstance.PublicIp 
                - / 
          Description: Client login URL for OpenVPN server 
        OpenVPNServerIPAddress: 
          Value: !GetAtt OpenVPNInstance.PublicIp 
          Description: IP address for OpenVPN server

  1. 现在,可以通过 CloudFormation 网页控制台或 CLI 使用以下命令启动这个堆栈:
 aws cloudformation create-stack \ 
        --template-body file://04-securely-access-private-instances.yaml \ 
        --stack-name example-vpn \ 
        --parameters \ 
        ParameterKey=KeyName,ParameterValue=<key-pair-name> \ 
        ParameterKey=VpcId,ParameterValue=<your-vpc-id> \ 
        ParameterKey=SubnetId,ParameterValue=<your-public-subnet-id> 

配置

  1. 一旦堆栈创建完成,您需要更改openvpn用户(管理员)的密码。现在请访问管理员控制面板并执行此操作:https://<vpn 服务器的 ip 或主机名>/admin。如果 VPN 服务器运行正常,登录后您将看到一个状态页面,如下图所示:

在那里时,您应该创建一个非管理员用户帐户。这个帐户将用于连接 VPN。在用户权限页面上添加该帐户,如下图所示:

  1. 在服务器网络设置中,在主机名或 IP 地址字段中,输入服务器的主机名或 IP 地址。此步骤很重要,下载 OpenVPN 配置文件时(下一步),如果它包含正确的主机名或 IP 地址,会让您的操作变得更加简单。以下截图显示了服务器网络设置页面上您可以预期看到的内容:

工作原理...

现在您应该能够连接到您的 VPN 服务器了。请转到用户登录页面,并使用您之前为非管理员用户设置的凭证进行登录:

https://<vpn 服务器的 ip 或主机名>/

登录后,您将有选项下载特定于您帐户的 OpenVPN 客户端及其配置文件。或者,如果您已经安装了 VPN 客户端,您也可以单独下载配置文件。

还有更多...

现在,既然您的 OpenVPN 服务器已经启动并运行,您需要记住几个重要的事项:

  • 如果需要通过 SSH 连接到实例,您必须使用用户名openvpnas进行连接。

  • 要访问其他实例,您需要允许来自本食谱中创建的 VPN 安全组的连接。

自动扩展应用服务器

自动扩展是云计算中的一个基本组成部分。它不仅能根据应用负载动态扩展和缩减容量,还能通过确保始终有可用的容量提供冗余。即使在极不可能发生的可用区故障情况下,自动扩展组也会确保实例能够运行您的应用程序。

自动扩展还允许您仅为所需的 EC2 容量付费,因为使用不足的服务器可以被自动撤销配置。

准备工作

您必须提供两个或更多子网 ID,以便此食谱能够正常工作。

以下示例使用的是us-east-1区域的 AWS Linux AMI。如果您在其他区域工作,请根据需要更新参数。

如何操作...

  1. 首先定义模板版本和描述:
      AWSTemplateFormatVersion: "2010-09-09"
      Description: Create an Auto Scaling Group

  1. 添加一个Parameters部分,包含稍后将在模板中使用的必需参数:
      Parameters:
        SubnetIds:
          Description: Subnet IDs where instances can be launched
          Type: List<AWS::EC2::Subnet::Id>

  1. Parameters部分下,添加可选的实例配置参数:
    AmiId: 
        Description: The application server's AMI ID 
        Type: AWS::EC2::Image::Id 
        Default: ami-9be6f38c # AWS Linux in us-east-1 
      InstanceType: 
        Description: The type of instance to launch 
        Type: String 
        Default: t2.micro

  1. Parameters部分下,添加可选的自动扩展组配置参数:
      MinSize: 
        Description: Minimum number of instances in the group 
        Type: Number 
        Default: 1
      MaxSize: 
        Description: Maximum number of instances in the group 
        Type: Number 
        Default: 4 

      ThresholdCPUHigh: 
        Description: Launch new instances when CPU utilization 
          is over this threshold 
        Type: Number 
        Default: 60 

      ThresholdCPULow: 
        Description: Remove instances when CPU utilization
          is under this threshold 
        Type: Number 
        Default: 40 

      ThresholdMinutes: 
        Description: Launch new instances when over the CPU 
          threshold for this many minutes 
        Type: Number 
        Default: 5

  1. 添加一个Resources部分,定义自动扩展组资源:
      Resources: 
        AutoScalingGroup: 
          Type: AWS::AutoScaling::AutoScalingGroup 
          Properties: 
            MinSize: !Ref MinSize 
            MaxSize: !Ref MaxSize 
            LaunchConfigurationName: !Ref LaunchConfiguration 
            Tags: 
              - Key: Name 
                Value: !Sub "${AWS::StackName} server" 
                PropagateAtLaunch: true 
            VPCZoneIdentifier: !Ref SubnetIds

  1. 仍然在Resources部分下,定义自动扩展组使用的启动配置:
      LaunchConfiguration: 
        Type: AWS::AutoScaling::LaunchConfiguration 
        Properties: 
          ImageId: !Ref AmiId 
          InstanceType: !Ref InstanceType 
          UserData: 
            Fn::Base64: !Sub | 
              #!/bin/bash -xe 
              # This will be run on startup, launch your application here

  1. 接下来,定义两个扩展策略资源——一个用于扩展实例数量,另一个用于缩减实例数量:
        ScaleUpPolicy: 
          Type: AWS::AutoScaling::ScalingPolicy 
          Properties: 
            AdjustmentType: ChangeInCapacity 
            AutoScalingGroupName: !Ref AutoScalingGroup 
            Cooldown: 60 
            ScalingAdjustment: 1 

        ScaleDownPolicy: 
          Type: AWS::AutoScaling::ScalingPolicy 
          Properties: 
            AdjustmentType: ChangeInCapacity 
            AutoScalingGroupName: !Ref AutoScalingGroup 
            Cooldown: 60 
            ScalingAdjustment: -1

  1. 定义一个报警,当 CPU 超过ThresholdCPUHigh参数时发出警报:
      CPUHighAlarm: 
        Type: AWS::CloudWatch::Alarm 
        Properties: 
          ActionsEnabled: true 
          AlarmActions: 
            - !Ref ScaleUpPolicy 
          AlarmDescription: Scale up on CPU load 
          ComparisonOperator: GreaterThanThreshold 
          Dimensions: 
            - Name: AutoScalingGroupName 
              Value: !Ref AutoScalingGroup 
          EvaluationPeriods: !Ref ThresholdMinutes 
          MetricName: CPUUtilization 
          Namespace: AWS/EC2 
          Period: 60 
          Statistic: Average 
          Threshold: !Ref ThresholdCPUHigh

  1. 最后,定义一个报警,当 CPU 低于ThresholdCPULow参数时发出警报:
      CPULowAlarm: 
        Type: AWS::CloudWatch::Alarm 
        Properties: 
          ActionsEnabled: true 
          AlarmActions: 
            - !Ref ScaleDownPolicy 
          AlarmDescription: Scale down on CPU load 
          ComparisonOperator: LessThanThreshold 
          Dimensions: 
            - Name: AutoScalingGroupName 
              Value: !Ref AutoScalingGroup 
          EvaluationPeriods: !Ref ThresholdMinutes 
          MetricName: CPUUtilization 
          Namespace: AWS/EC2 
          Period: 60 
          Statistic: Average 
          Threshold: !Ref ThresholdCPULow

  1. 将模板保存为文件名04-auto-scaling-an-application-server.yaml

  2. 使用以下 AWS CLI 命令启动模板,并提供你的子网 ID:

     aws cloudformation create-stack \ 
        --stack-name asg \ 
        --template-body file://04-auto-scaling-an-application-server.yaml \ 
        --parameters \ 
        ParameterKey=SubnetIds,ParameterValue='<subnet-id-1>\, \
          <subnet-id-2>' 

工作原理...

本示例定义了一个自动扩展组及其依赖资源,具体包括以下内容:

  • 启动配置,用于启动新实例

  • 两个扩展策略,一个用于扩展实例数量,另一个逆向策略用于缩减实例数量

  • 当 CPU 跨越某个阈值并持续一定时间时,触发报警

本示例中的自动扩展组和启动配置资源对象大多数使用的是默认值。如果你希望能够连接到实例(例如,通过 SSH),你需要在LaunchConfiguration资源配置中指定自己的SecurityGroupsKeyName参数。

AWS 会自动确保将你的实例均匀分布到你配置的子网中,因此请确保它们位于不同的可用区(AZ)中!当缩减时,最旧的实例会先被删除,再删除较新的实例。

扩展策略

扩展策略详细说明了在触发时要创建或删除多少个实例。它还定义了一个Cooldown值,帮助防止反复波动的服务器——即服务器在启动完成并变得有用之前就被创建和删除。

虽然这个示例中的扩展策略使用了相同的值,但你可能想要调整它们,以便让你的应用程序能够快速扩展,并且为了最佳用户体验,缩减速度较慢。

报警

CPUHighAlarm参数将在平均 CPU 利用率超过ThresholdCPUHigh参数中设置的值时发出警报。此警报将被发送到ScaleUpPolicy资源,启动更多实例,从而降低整个自动扩展组的平均 CPU 利用率。顾名思义,当平均 CPU 利用率低于ThresholdCPULow参数时,CPULowAlarm参数会执行相反操作。

这意味着新的实例将会被启动,直到自动扩展组中的 CPU 利用率稳定在 40-60%之间(基于默认的参数值),或者实例的MaxSize达到上限。

在高低报警阈值之间留有间隙非常重要。如果它们设置得过于接近,报警将无法稳定,你会看到实例几乎不断地被创建和销毁。

实例的最短计费时间是一小时,因此在一小时内多次创建和销毁实例可能会导致比预期更高的费用。

创建机器镜像

创建或构建你自己的Amazon Machine ImagesAMIs)是 AWS 系统管理中的关键部分。拥有一个预构建的镜像可以帮助你比手动配置更快、更容易且更一致地配置服务器。

Packer 是一个事实标准工具,帮助你制作自己的 AMI。通过自动化实例的启动、配置和清理,它确保每次都能得到一个可重复的镜像。

在这个配方中,我们将创建一个预装并配置好的 Apache web 服务器镜像。虽然这是一个简单的例子,但它也是一个非常常见的使用场景。

通过将你的 web 服务器预先配置到镜像中,你可以根据网站的需求动态扩展 web 服务层。预装和配置好的软件意味着你可以获得最快和最可靠的启动体验。

准备工作

对于这个配方,你必须在系统中安装 Packer 工具。从项目的官网www.packer.io/downloads.html下载并安装 Packer。

如何操作...

  1. 创建一个新的 Packer 模板文件,并首先在builders部分定义一个amazon-ebs构建器:
      "builders": [ 
        { 
          "type": "amazon-ebs", 
          "instance_type": "t2.micro", 
          "region": "us-east-1", 
          "source_ami": "ami-9be6f38c", 
          "ssh_username": "ec2-user", 
          "ami_name": "aws-linux-apache {{timestamp}}" 
        } 
      ],

整个模板文件必须是一个有效的 JSON 对象。记得将各个部分用大括号{ ... }括起来。

  1. 创建一个provisioners部分,并包含以下代码片段来安装并激活 Apache:
      "provisioners": [ 
        { 
          "type": "shell", 
          "inline": [ 
            "sudo yum install -y httpd", 
            "sudo chkconfig httpd on" 
          ] 
        } 
      ]

  1. 使用特定的文件名保存文件,例如04-creating-machine-images.json

  2. 使用以下packer validate命令验证你创建的配置文件:

 packer validate 04-creating-machine-images.json

  1. 当配置有效时,使用以下命令构建 AMI:
 packer build 04-creating-machine-images.json

  1. 等待直到过程完成。在此期间,你将看到类似以下的输出:

  1. 记录 Packer 返回的 AMI ID,以便将来启动实例时使用:

它是如何工作的...

虽然这是一个非常简单的配方,但背后有许多操作。这也是我们推荐使用 Packer 来创建机器镜像的原因。

模板

在模板的builders部分,我们定义了构建的详细信息。

我们使用最常见的 AMI 构建器类型:amazon-ebs。还有其他类型的 AWS 构建器,例如存储支持的实例类型。

接下来,我们定义在创建镜像时使用的实例类型。

确保你能够通过使用更大的实例类型来缩短构建实例的时间。记住,实例的最短计费时间为一小时。

本配方中的source_ami属性是我们指定的region中的 AWS Linux AMI ID。ssh_username允许你设置用于连接并运行provisioners的用户名。这个用户名取决于你的操作系统,在我们的例子中是ec2-user

最后,ami_name 字段包含内置的 Packer 变量 {{timestamp}}。这确保你创建的 AMI 始终具有唯一的名称。

验证模板

packer validate 命令是确保模板没有语法错误的一种快速方法,适合在启动任何实例之前使用。

构建 AMI

一旦你创建并验证了模板,packer build 命令会为你执行以下操作:

  • 创建一次性密钥对以便通过 SSH 访问实例

  • 创建专用的安全组来控制对实例的访问

  • 启动实例

  • 等待直到 SSH 准备好接收连接

  • 在实例上执行配置步骤

  • 停止实例

  • 从停止的实例生成 AMI

  • 终止实例

查看 Packer 文档以获取更多的提供者和功能,网址为 www.packer.io/docs/

还有更多...

虽然 Packer 使得在 AWS 上管理镜像变得更加轻松,但仍然有一些需要注意的地方。

调试

显然,既然许多步骤已经为你自动化,还是有很多事情可能出错。Packer 提供了几种不同的方式来调试构建过程中的问题。

使用 Packer 时,最有用的参数之一是 -debug 标志。这个标志会强制你在每一步操作执行之前手动确认。这样做可以让你很容易地找出命令中哪一步出错,从而通常能够明确需要更改的地方。

另一个有用的做法是在 Packer 命令期间提高日志输出的级别。你可以通过将 PACKER_LOG 变量设置为 true 来实现。最简单的方式是在 Packer 命令行前面加上 PACKER_LOG=1。这将意味着在命令执行期间,你会获得更多的信息打印到控制台(例如,SSH 日志、AWS API 调用等)。你可能还希望在构建过程中正常使用此级别的日志记录,作为审计用途。

孤立资源

Packer 在管理和清理它所使用的资源方面做得很好,但它只能在运行期间完成这些工作。

如果你的 Packer 任务因任何原因中止(最可能是网络问题),可能会有一些资源变成孤立未管理的状态。最好检查是否有任何 Packer 实例(它们的名称中会包含 Packer),如果没有正在运行的 Packer 任务,最好将它们停止。

你可能还需要清理任何剩余的密钥对和安全组,但这不是大问题,因为这些没有费用(不像实例)。

注销 AMI

随着创建 AMI 的过程变得越来越简单,你可能会发现自己最终创建了超过需要的数量!

AMI 由 EC2 快照组成,这些快照存储在 S3 中。存储快照是有成本的,因此你需要定期清理它们。鉴于大多数 AMI 的大小(通常为几 GB),它不太可能成为你主要的费用来源。

一个更大的成本是管理过多 AMI 的行政开销。随着镜像的改进和修复(尤其是安全修复)的应用,你可能希望阻止他人使用这些镜像。

要删除 AMI,必须首先 注销 它,然后删除其底层快照。

确保不要注销正在使用中的 AMI。例如,引用已注销 AMI 的自动扩展组将无法启动新实例!

你可以通过网页控制台或使用 AWS CLI 工具轻松注销快照。

一旦 AMI 不再注册,你就可以删除关联的快照。Packer 会自动将 AMI ID 添加到快照的描述中。通过搜索快照中的已注销 AMI ID,你可以找到需要删除的快照。

如果 AMI 没有被注销,或者注销过程仍在进行中(可能需要几分钟),你将无法删除快照。

其他平台

还值得注意的是,Packer 不仅可以为 AWS 构建镜像。你还可以为 VMWare、Docker 等其他平台构建镜像。

这意味着你可以在本地几乎完全相同地构建机器镜像(例如,使用 Docker),就像在 AWS 中一样。这使得在设置本地开发环境时更加方便。

请查阅 Packer 文档中的 builders 部分以获取详细信息。

创建安全组

AWS 将安全组描述为 虚拟防火墙。虽然这个类比有助于新手理解 EC2 平台的目的和功能,但更准确的描述是将它们看作一种 防火墙式 的流量授权方法。它们并不像传统防火墙那样提供所有功能,但这种简化也使得它们非常强大,尤其是与基础设施即代码和现代 SDLC 实践相结合时。

我们将通过一个基本场景来演示,涉及到 Web 服务器和负载均衡器。我们希望负载均衡器能够响应来自任何地方的 HTTP 请求,并且希望将 Web 服务器与除了负载均衡器之外的所有内容隔离。

准备工作

在我们开始之前,有一个小清单是你需要准备的:

  • AmiId 这是你所在区域的 AMI ID。对于本教程,我们建议使用 AWS Linux AMI,因为我们的实例将尝试在启动时运行一些 yum 命令。

  • VPCID:这是你希望启动 EC2 服务器的 VPC ID。

  • SubnetIDs:这些是我们的 EC2 实例可以启动的子网。

如何操作...

  1. 打开你的文本编辑器并创建一个新的 CloudFormation 模板。我们将从添加以下几个 Parameters 开始:
      AWSTemplateFormatVersion: '2010-09-09' 
      Parameters: 
        AmiId: 
          Type: AWS::EC2::AMI::Id 
          Description: AMI ID to launch instances from 
        VPCID: 
          Type: AWS::EC2::VPC::Id 
          Description: VPC where load balancer and instance will launch 
        SubnetIDs: 
          Type: List<AWS::EC2::Subnet::Id> 
          Description: Subnets where load balancer and instance will launch 
            (pick at least 2)

  1. 让我们来看看我们将应用于公共负载均衡器的安全组:
      ExampleELBSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: Security Group for example ELB 
          SecurityGroupIngress: 
            - IpProtocol: tcp 
              CidrIp: 0.0.0.0/0 
              FromPort: 80 
              ToPort: 80

任何位于此安全组中的内容都会允许从任何地方(0.0.0.0/0)通过端口 80 进行 TCP 连接。请注意,一个安全组可以包含多个规则;我们几乎肯定还需要允许 HTTPS(443),但为了简化示例,我们省略了这一部分。

  1. 现在,让我们来看一下位于负载均衡器后面的 Web 服务器的安全组:
      ExampleEC2InstanceSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: Security Group for example Instance 
          SecurityGroupIngress: 
            - IpProtocol: tcp 
              SourceSecurityGroupName: 
                Ref: ExampleELBSecurityGroup 
              FromPort: 80 
              ToPort: 80

这里你可以看到我们没有指定源 IP 范围。相反,我们指定了一个源安全组,我们将接受来自该安全组的连接。在这种情况下,我们表示希望允许来自我们的 ELB 安全组的任何内容连接到我们的 EC2 实例安全组中的任何内容,端口为 80

由于这是我们指定的唯一规则,我们的 Web 服务器将不会接受来自任何地方的连接,除非是来自我们的负载均衡器,且仅限于端口 80。我们的 Web 服务器并没有完全暴露在互联网上,它甚至与 VPC 中的其他实例隔离。

记住,多个实例可以位于同一个安全组中。如果你有多个 Web 服务器连接到这个负载均衡器,给每个 Web 服务器创建一个新的安全组将是不必要的、低效的,甚至有些反模式。由于所有连接到该负载均衡器的 Web 服务器都将承担相同的角色或功能,因此将相同的安全组应用于它们是有意义的。

这就是安全组真正强大的地方。如果一个 EC2 实例担任多个角色——假设你在 VPC 中有一个出站的 HTTP 代理服务器,并且你也希望它充当 SMTP 中继——那么你可以简单地为其应用多个安全组。

  1. 接下来,我们需要添加负载均衡器。这可能是你遇到的最基础的负载均衡器配置。以下代码将为你提供一个负载均衡器、一个监听器以及一个包含我们 EC2 实例的目标组。
      ExampleLoadBalancer: 
        Type: AWS::ElasticLoadBalancingV2::LoadBalancer 
        Properties: 
          Subnets:  
            - Fn::Select: [ 0, Ref: SubnetIDs ] 
            - Fn::Select: [ 1, Ref: SubnetIDs ] 
          SecurityGroups: 
            - Fn::GetAtt: ExampleELBSecurityGroup.GroupId 
      ExampleListener: 
        Type: AWS::ElasticLoadBalancingV2::Listener 
        Properties: 
          LoadBalancerArn: 
            Ref: ExampleLoadBalancer 
          DefaultActions: 
            - Type: forward 
              TargetGroupArn: 
                Ref: ExampleTargetGroup 
          Port: 80 
          Protocol: HTTP 
      ExampleTargetGroup: 
        Type: AWS::ElasticLoadBalancingV2::TargetGroup 
        Properties:  
          Port: 80 
          Protocol: HTTP 
          VpcId: 
             Ref: VPCID 
          Targets: 
            - Id: 
                Ref: ExampleEC2Instance

  1. 我们将添加到模板中的最后一个资源是一个 EC2 服务器。当该服务器启动时,它将安装并启动 nginx
      ExampleEC2Instance: 
        Type: AWS::EC2::Instance 
        Properties: 
          InstanceType: t2.nano 
          UserData: 
            Fn::Base64: 
              Fn::Sub: | 
                #!/bin/bash -ex 
                yum install -y nginx 
                service nginx start 
                exit 0 
        ImageId: 
          Ref: AmiId 
        SecurityGroupIds: 
          - Fn::GetAtt: ExampleEC2InstanceSecurityGroup.GroupId 
        SubnetId:  
          Fn::Select: [ 0, Ref: SubnetIDs ]

  1. 最后,我们将向模板中添加一些 Outputs,使得在堆栈创建后使用我们的 ELB 和 EC2 实例更为方便。
      Outputs: 
        ExampleEC2InstanceHostname: 
          Value: 
            Fn::GetAtt: [ ExampleEC2Instance, PublicDnsName ] 
        ExampleELBURL: 
          Value: 
            Fn::Join: 
              - '' 
              - [ 'http://', { 'Fn::GetAtt': [ ExampleLoadBalancer,
                  DNSName ] }, '/' ]

  1. 继续使用 CloudFormation Web 控制台或 AWS CLI 启动这个模板。

还有更多…

当使用 CloudFormation 配置安全组时,你最终会遇到循环依赖问题。假设你希望我们在 ExampleEC2InstanceSecurityGroup 中的所有服务器都能通过端口 22(SSH)互相访问。为了实现这一点,你需要将此规则作为单独的资源类型 AWS::EC2::SecurityGroupIngress 添加。因为在 CloudFormation 中,一个安全组在尚未创建时不能引用自身。这就是额外资源类型的样子:

      ExampleEC2InstanceIngress: 
        Type: AWS::EC2::SecurityGroupIngress 
        Properties: 
          IpProtocol: tcp 
          SourceSecurityGroupName: 
            Ref: ExampleEC2InstanceSecurityGroup 
          GroupName: 
            Ref: ExampleEC2InstanceSecurityGroup 
          FromPort: 22 
          ToPort: 22

与传统防火墙的区别

  • 安全组不能用于显式阻止流量。只能添加允许类型的规则;不支持拒绝风格的规则。本质上,所有入站流量都会被拒绝,除非你明确允许它。

  • 你的规则也可能不会涉及源端口;只支持目标端口。

  • 当创建安全组时,它们将包含一个允许所有出站连接的规则。如果你删除此规则,新的出站连接将被丢弃。一个常见的做法是保留此规则,并仅通过入站规则来过滤所有流量。

  • 如果你替换了默认的出站规则,需要注意的是,只有新的出站连接会被过滤。任何响应入站连接的出站流量仍然会被允许。这是因为安全组是有状态的

  • 与安全组不同,网络 ACL 是无状态的,并且支持DENY规则。你可以将它们作为 VPC 内部的安全补充层,特别是当你需要控制子网之间的流量时。

创建负载均衡器

AWS 提供两种类型的负载均衡器:

  • 经典负载均衡器

  • 应用负载均衡器

我们将重点讨论应用负载均衡器。它实际上是 ELB 服务的升级版、第二代,并且比经典负载均衡器提供更多功能。例如,它原生支持 HTTP/2 和 WebSocket。每小时的费用也相对便宜。

应用负载均衡器不支持第四层负载均衡。对于这种功能,你需要使用经典负载均衡器。

如何做...

  1. 打开文本编辑器并创建一个新的 CloudFormation 模板。我们需要一个 VPC ID 和一些子网 ID 作为Parameters。像这样将它们添加到模板中:
      AWSTemplateFormatVersion: '2010-09-09' 
      Parameters: 
        VPCID: 
          Type: AWS::EC2::VPC::Id 
          Description: VPC where load balancer and instance will launch 
        SubnetIDs: 
          Type: List<AWS::EC2::Subnet::Id> 
          Description: Subnets where load balancer and instance will launch
           (pick at least 2)

  1. 接下来,我们需要添加一些 ELB 账户 ID 的Mappings。这些映射将帮助我们为负载均衡器提供将日志写入 S3 桶的权限。你的映射应该像这样:

你可以在这里找到完整的 ELB 账户 ID 列表:docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html#attach-bucket-policy

      Mappings: 
        ELBAccountMap: 
          us-east-1: 
        ELBAccountID: 127311923021 
          ap-southeast-2: 
        ELBAccountID: 783225319266

  1. 我们现在可以开始向模板中添加Resources。首先,我们将创建一个 S3 桶和桶策略,用于存储我们的负载均衡器日志。为了使这个模板具有可移植性,我们将省略桶名称,但为了方便起见,我们将在输出中包含桶名称,以便 CloudFormation 将名称回显给我们。
      Resources: 
        ExampleLogBucket: 
          Type: AWS::S3::Bucket 
        ExampleBucketPolicy: 
          Type: AWS::S3::BucketPolicy 
          Properties: 
            Bucket: 
              Ref: ExampleLogBucket 
            PolicyDocument: 
              Statement: 
                - 
                  Action: 
                    - "s3:PutObject" 
                  Effect: "Allow" 
                  Resource: 
                    Fn::Join: 
                      - "" 
                      - 
                        - "arn:aws:s3:::" 
                        - Ref: ExampleLogBucket 
                        - "/*" 
                  Principal: 
                    AWS: 
                      Fn::FindInMap: [ ELBAccountMap, Ref: "AWS::Region",
                        ELBAccountID ]                       

  1. 接下来,我们需要为负载均衡器创建一个安全组。这个安全组将允许对端口80(HTTP)的入站连接。为了简化这个过程,我们将省略端口443(HTTPS),但我们将在本节稍后简要介绍如何添加此功能。由于我们正在添加一个公共负载均衡器,我们希望允许来自任何地方(0.0.0.0/0)的连接。这是我们安全组的配置:
      ExampleELBSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: Security Group for example ELB 
          SecurityGroupIngress: 
            - 
              IpProtocol: tcp 
              CidrIp: 0.0.0.0/0 
              FromPort: 80 
              ToPort: 80

  1. 现在我们需要定义一个目标组。完成这个配方后,你可以将实例注册到这个目标组,以便 HTTP 请求能够被转发到该组。或者,你也可以将目标组附加到自动扩展组,AWS 将为你处理实例的注册和注销。

  2. 目标组是我们指定负载均衡器应执行健康检查的地方。这个健康检查对于确定一个注册的实例是否应接收流量非常重要。此示例中提供的健康检查参数已设置为默认值。你可以根据需要进行调整,或者如果默认设置适合你的情况,也可以选择删除它们。

      ExampleTargetGroup: 
        Type: AWS::ElasticLoadBalancingV2::TargetGroup 
        Properties: 
          Port: 80 
          Protocol: HTTP 
          HealthCheckIntervalSeconds: 30 
          HealthCheckProtocol: HTTP 
          HealthCheckPort: 80 
          HealthCheckPath: / 
          HealthCheckTimeoutSeconds: 5 
          HealthyThresholdCount: 5 
          UnhealthyThresholdCount: 2 
          Matcher: 
            HttpCode: '200' 
          VpcId: 
            Ref: VPCID

  1. 我们需要定义至少一个监听器,添加到我们的负载均衡器中。监听器将监听我们为其配置的端口和协议上到达负载均衡器的请求。与端口和协议匹配的请求将被转发到我们的目标组。

我们的监听器配置将相对简单。我们将监听端口80上的 HTTP 请求。我们还为此监听器设置了一个默认动作,它将我们的请求转发到我们之前定义的目标组。每个负载均衡器最多可以配置 10 个监听器。

目前,AWS 只支持一个动作:转发。

      ExampleListener:
        Type: AWS::ElasticLoadBalancingV2::Listener 
        Properties: 
          LoadBalancerArn: 
            Ref: ExampleLoadBalancer 
          DefaultActions: 
            - Type: forward 
              TargetGroupArn: 
        Ref: ExampleTargetGroup 
          Port: 80 
          Protocol: HTTP

  1. 最后,既然我们已经准备好了所有需要的Resources,我们可以继续设置负载均衡器了。我们需要定义至少两个子网来部署负载均衡器,这些在我们的示例模板中作为Parameters提供:
      ExampleLoadBalancer: 
        Type: AWS::ElasticLoadBalancingV2::LoadBalancer 
        Properties: 
          LoadBalancerAttributes: 
            - Key: access_logs.s3.enabled 
             Value: true 
            - Key: access_logs.s3.bucket 
             Value: 
               Ref: ExampleLogBucket 
            - Key: idle_timeout.timeout_seconds 
              Value: 60 
          Scheme: internet-facing 
          Subnets: 
            - Fn::Select: [ 0, Ref: SubnetIDs ]               
            - Fn::Select: [ 1, Ref: SubnetIDs ]               
          SecurityGroups: 
            - Fn::GetAtt: ExampleELBSecurityGroup.GroupId

  1. 最后,我们将向模板中添加一些Outputs,以便更方便查看。我们特别关注我们创建的 S3 桶的名称和负载均衡器的 URL。
      Outputs: 
        ExampleELBURL: 
          Value: 
            Fn::Join: 
              - '' 
              - [ 'http://', { 'Fn::GetAtt': [ ExampleLoadBalancer,
                  DNSName ] }, '/' ] 
        ExampleLogBucket: 
          Value: 
            Ref: ExampleLogBucket

它是如何工作的……

如你所见,我们正在应用一个日志配置,指向我们创建的 S3 桶。我们正在将这个负载均衡器配置为面向互联网,并设置了 60 秒的空闲超时(默认值)。

默认情况下,所有负载均衡器都是面向互联网的,因此在我们的示例中,定义Scheme并非绝对必要;然而,即使如此,包含它仍然是很有帮助的。如果你的 CloudFormation 模板包含了公共和私有负载均衡器的混合配置,尤其如此。

如果你指定了日志配置,但负载均衡器无法访问 S3 桶,那么你的 CloudFormation 堆栈将无法完成。

私有 ELB 是非面向互联网的,仅对位于你 VPC 内部的资源可用。

就这样!现在你已经配置好了一个可以正常工作的应用程序负载均衡器,并能够将日志传输到 S3 桶。

还有更多……

AWS 上的负载均衡器具有高度的可配置性,并且提供许多选项供你选择。以下是一些你常遇到的 ELB 选项:

HTTPS/SSL

如果你希望接受 HTTPS 请求,你需要配置一个额外的监听器。它看起来大致如下:

      ExampleHTTPSListener: 
        Type: AWS::ElasticLoadBalancingV2::Listener 
        Properties: 
          Certificates: 
            - CertificateArn:
               arn:aws:acm:ap-southeast-2:123456789012:
               certificate/12345678-1234-1234-1234-123456789012 
          LoadBalancerArn: 
            Ref: ExampleLoadBalancer 
          DefaultActions: 
            - Type: forward 
              TargetGroupArn: 
                Ref: ExampleTargetGroup 
          Port: 443 
          Protocol: HTTPS

监听器需要引用你希望使用的证书的有效Amazon 资源名称ARN)。让 AWS 证书管理器为你创建证书非常简单,但它确实需要验证你为其生成证书的域名。当然,你也可以带上自己的证书。如果你愿意,需先将证书导入 AWS 证书管理器,然后才能将其与 ELB(或 CloudFront 分发)一起使用。

除非你对加密套件有特定要求,否则一个不错的起点是不要定义 SSL 策略,而是让 AWS 选择当前的最佳实践

基于路径的路由

一旦你对 ELB 配置感到熟悉,就可以开始尝试基于路径的路由。简而言之,它提供了一种检查请求并根据请求的路径将其代理到不同目标的方法。

你可能遇到的一个常见场景是,需要将/blog的请求路由到一组运行 WordPress 的服务器,而不是路由到运行 Ruby on Rails 应用程序的主服务器池。

第五章:管理工具

在本章中,我们将介绍:

  • 审计您的 AWS 账户

  • 使用受信顾问的建议

  • 创建电子邮件告警

  • 在 CloudWatch 中发布自定义指标

  • 创建监控仪表盘

  • 创建预算

  • 将日志文件输入到 CloudWatch 日志

介绍

和所有管理操作一样,监控和告警是使用基于 AWS 的基础设施时至关重要的一部分。实际上,由于云资源的短暂性,跟踪和衡量使用情况比使用本地系统时更加重要。

审计您的 AWS 账户

我们现在将向您展示如何在 AWS 账户中设置 CloudTrail。一旦启用了 CloudTrail,它将开始记录您账户中对 AWS 服务进行的所有 API 调用,并将其作为日志文件传输到 S3 存储桶中。

当我们谈论 API 调用时,我们指的是类似以下内容:

  • 在 AWS 控制台中执行的操作。

  • 使用 CLI 或 SDK 调用 AWS API。

  • AWS 服务代表您所做的调用。比如 CloudFormation 或自动扩展服务。

日志中的每个条目将包含有用的信息,例如:

  • 被调用的服务

  • 所请求的操作

  • 请求中发送的参数

  • AWS 返回的响应

  • 调用者的身份(包括 IP 地址)

  • 请求的日期和时间

如何操作……

  1. 创建一个新的 CloudFormation 模板文件;我们将定义以下 Resources

    • 存储我们 CloudTrail 日志文件的 S3 存储桶

    • 一个允许 CloudTrail 服务写入我们的 S3 存储桶的策略

    • 一个 CloudTrail 轨迹

  2. 如此定义一个 S3 存储桶。我们不需要给它命名;稍后我们会将存储桶名称添加到 Outputs 列表中:

      ExampleTrailBucket: 
        Type: AWS::S3::Bucket

  1. 接下来,我们需要为我们的存储桶定义一个策略。本节内容稍微有些冗长,您可以选择从代码示例中获取此内容。该策略基本上允许 CloudTrail 对我们的存储桶执行两项操作:s3:GetBucketAcls3:PutObject
      ExampleBucketPolicy: 
        Type: AWS::S3::BucketPolicy 
        Properties: 
          Bucket: !Ref ExampleTrailBucket 
          PolicyDocument: 
            Statement: 
            - Sid: AWSCloudTrailAclCheck20150319 
              Effect: Allow 
              Principal: 
                Service: cloudtrail.amazonaws.com 
                Action: s3:GetBucketAcl 
                Resource: !Join 
                  - "" 
                  - 
                    - "arn:aws:s3:::" 
                    - !Ref ExampleTrailBucket 
            - Sid: AWSCloudTrailWrite20150319 
              Effect: Allow 
              Principal: 
                Service: cloudtrail.amazonaws.com 
              Action: s3:PutObject 
              Resource: !Join 
                - "" 
                - 
                  - "arn:aws:s3:::" 
                  - !Ref ExampleTrailBucket 
                  - "/AWSLogs/" 
                  - !Ref AWS::AccountId 
                  - "/*" 
              Condition: 
                StringEquals: 
                  s3:x-amz-acl: bucket-owner-full-control

  1. 现在我们可以设置我们的轨迹。

需要注意的是,我们使用 DependsOn 来确保 CloudFormation 在创建 S3 存储桶和策略之后再创建这个轨迹。如果不这样做,您在创建堆栈时可能会遇到错误,因为 CloudTrail 无法访问存储桶。

  1. 像这样将 Trail 添加到您的模板中:
      ExampleTrail: 
        Type: AWS::CloudTrail::Trail 
        Properties: 
          EnableLogFileValidation: true 
          IncludeGlobalServiceEvents: true 
          IsLogging: true 
          IsMultiRegionTrail: true 
          S3BucketName: !Ref ExampleTrailBucket 
        DependsOn: 
          - ExampleTrailBucket 
          - ExampleBucketPolicy

  1. 最后,我们将输出存储我们 CloudTrail 日志的 S3 存储桶名称:
      Outputs: 
        ExampleBucketName: 
          Value: !Ref ExampleTrailBucket 
          Description: Bucket where CloudTrail logs will be stored

  1. 您可以使用以下命令运行您的 CloudFormation 堆栈:
 aws cloudformation create-stack \
        --template-body file://05-auditing-your-aws-account.yaml \
        --stack-name example-cloudtrail

它是如何工作的……

此模板将使用以下配置设置 CloudTrail:

  • CloudTrail 会在你账户的所有地区启用。这是一个合理的起点,因为它让你能够查看你的 AWS 资源是在哪些地区创建的。即使你是唯一使用 AWS 账户的人,知道自己是否错误地调用了其他地区的 API 也很有帮助(这很容易发生)。当你创建一个多地区的 trail 时,当新地区上线时,它们将自动被包含在内,且无需你额外操作。

  • 全球服务事件也将被记录。同样,这也是一个合理的默认设置,因为它包括了那些不是特定于某个地区的服务。CloudFront 和 IAM 是 AWS 中两个不受地区限制的服务示例。

  • 日志文件验证已启用。启用此功能后,CloudTrail 将每小时提供一个摘要文件,你可以使用该文件来确定你的 CloudTrail 日志是否被篡改。CloudTrail 使用 SHA-256 进行哈希处理并进行签名(RSA)。AWS CLI 可用于执行 CloudTrail 日志的临时验证。

若想快速查看你的 CloudTrail 日志,并使用一些基本的搜索和过滤功能,你可以前往 AWS 网络控制台:

CloudTrail 网络控制台

还有更多内容...

  • 日志文件使用服务器端加密在 S3 中进行加密。此加密对你是透明的,但如果你愿意,你可以选择使用你自己的客户主密钥CMK)来加密这些文件。

  • API 调用会在 15 分钟内被 CloudTrail 记录。

  • 日志每五分钟会被发送到你的 S3 存储桶。

  • 可以将多个账户的 CloudTrail 事件聚合到一个存储桶中。这是一个常见的模式,通常用于将 AWS 活动日志记录到 SecOps 或类似账户中进行审计。

  • 除了日志记录外,CloudTrail 会保留你的 API 活动七天。

  • 你可以创建多个 trail。你可能会考虑为你的开发人员创建一个与安全团队使用的 trail 分开的 trail。

  • 如果 CloudFormation 堆栈创建了一个 S3 存储桶,并且该 S3 存储桶中有对象,当你选择删除堆栈时,删除操作将失败。你可以在 S3 网络控制台中手动删除 S3 存储桶,如果你希望绕过这个问题的话。

使用 Trusted Advisor 的建议

Trusted Advisor 涵盖了四个主要领域,旨在为你的云部署提供一些最佳实践的指导。涵盖的领域有:

  • 成本优化

  • 性能

  • 安全性

  • 容错

它对所有人开放并且免费使用——但有一个相当大的限制。除非你为 AWS 的商业或企业级支持付费,否则你只能访问四个检查项。在本文发布时,总共有 55 个可能的检查项。

它是如何操作的...

好消息是,你无需做任何事情来启用 Trusted Advisor。当你的 AWS 账户创建时,它会自动启用,并将在你的账户的生命周期内持续更新。

继续访问 AWS 网络控制台中的 Trusted Advisor 部分。

它是如何工作的...

该服务提供的四个免费检查项是:

  • 不受限制的端口:这是对安全组中最高风险端口的检查。如果这些端口对所有人开放(0.0.0.0/0),它们将被标记。

  • IAM 使用情况:这是一个相对基础的检查。如果你的账户中没有至少一个 IAM 用户,这个检查将不通过。通常的好做法是避免使用根账户凭证登录 AWS,而是创建具有最小权限的 IAM 用户。

  • 根账户启用 MFA:这也是一个相对基础的检查。你需要为根账户启用 MFA 才能通过此检查。显然,启用 IAM 用户的 MFA 也是一个好主意。

  • 服务限制:这个检查很有用:如果你接近服务限制的 80%,这个检查将不通过。例如,知道你是否快要达到 CloudFormation 堆栈或 EC2 实例的上限,在尝试创建它们之前是很有用的。

即使这里只有四个检查项,这些也是比较有用的,我们鼓励你关注它们。

控制台使用颜色方案来表示每个检查的状态:

  • 红色:建议你采取措施修复此检查项。

  • 黄色:此检查需要调查并可能进行整改。

  • 绿色:此检查通过,无需关注。

如果你希望每周收到电子邮件报告,可以访问 Trusted Advisor 网站控制台中的偏好设置页面。

Trusted Advisor 控制台

还有更多内容……

除了开放整个 Trusted Advisor 检查套件外,企业级或商业级支持还可以访问以下内容:

  • 通知:你可以通过多种方式更频繁地接收通知。由于 Trusted Advisor 是 CloudWatch Events 中的可用源,你将能够创建通知,这些通知可以通过 SNS(电子邮件、推送、短信)处理,甚至可以触发 Lambda 函数。

  • API 访问:你将能够使用多个 Trusted Advisor API 方法,如 DescribeTrustedAdvisorCheckResultDescribeTrustedAdvisorCheckSummaries。你可以使用这些方法将检查结果集成到自己的仪表盘或监控系统中。你还可以使用这些 API 来刷新 Trusted Advisor 检查(例如,在你采取纠正措施后)。

  • 排除:你可以选择性地静音失败的检查。有时你可能会希望为开发环境中的 RDS 实例(例如没有启用多 AZ 模式或没有启用备份的实例)这样做。

最后,一些我们为企业级支持客户提供的更有用的检查项包括:

  • 预留实例:如果你有相对静态的工作负载,这是一种很好的成本优化方式。

  • 未关联的弹性 IP:如果 IP 地址没有与网络接口(例如 EC2 实例)关联,您仍然需要为其付费。此外,如果存在未关联的 IP,这通常意味着它们是手动分配的,而不是通过 CloudFormation 配置的。记住,目标是实现更多的自动化,而不是更少。

  • 空闲负载均衡器:同样,这些也需要付费,且在低自动化环境中很容易成为孤立资源。

  • S3 存储桶权限:如果 S3 存储桶的权限配置错误,通常并不显而易见。此检查帮助您避免不小心泄露数据。

创建电子邮件警报

虽然电子邮件警报可能不是所有警报中最具可扩展性的(由于大多数人收到大量电子邮件),但它们是最容易集成的——几乎每个人都有电子邮件地址!

本示例使用了两个 AWS 服务:

  • CloudWatchCW

  • 简单通知服务SNS

由于您通常会在通过 CloudWatch 仪表板查看指标后创建警报,因此本例将使用控制台创建警报。

如何操作...

  1. 在 CloudWatch 控制台中,转到警报部分:

  1. 点击创建警报以启动向导:

  1. 选择您希望触发警报的指标。在本例中,我们将在 Lambda 指标下选择按函数名称:

  1. 选择特定的指标。您可以通过表中的任何值进行过滤。在本例中,我们将选择错误并点击下一步:

  1. 定义警报,至少指定一个名称和阈值。在本例中,我们将在出现任何错误(例如 > 0)时触发警报:

  1. 在操作部分,通过输入您希望收到违规通知的电子邮件地址和主题名称(本例中使用 EmailMe),然后点击创建警报来创建一个新列表:

  1. 您将被要求确认电子邮件地址,且在确认之前不会收到任何通知。

  1. 确认电子邮件将如下所示:

  1. 一旦您点击电子邮件中的确认订阅链接,您将看到如下确认信息:

  1. 回到控制台后,状态会更新,显示您已成功确认订阅:

  1. 然后您将在控制台中看到新创建的警报,并可以查看其状态和历史记录:

  1. 在 SNS 控制台中,您可以看到为您创建的主题,如下所示:

如何工作...

虽然我们通常更倾向于使用 CLI(或 CloudFormation)来创建 AWS 资源,但创建警报的向导为您做了很多工作,所以它是一个很好的起点。一旦您知道自己感兴趣的警报类型,就可以将其自动化。

CloudWatch 控制台是监视您资源性能的好地方。经常在查看指标时,您可能会发现一些需要通知的场景,并迅速创建相应的警报。

虽然电子邮件可能是启动警报的最简单方式,但它的可扩展性并不太好(您真的想要更多电子邮件吗?)。对于非常重要的指标,您可能希望使用 CloudWatch 仪表板,或者选择不同的通知协议/目标。

我们首先选择我们感兴趣的指标;在本例中,这是来自示例 Lambda 函数的错误,但无论您选择哪个指标,此过程都将相同。

您必须为警报定义名称,可以选择创建描述。警报的最重要部分之一是如何定义触发它的阈值。您不仅可以选择使用的值和比较运算符(例如,大于(>),小于(<),大于或等于(>=),等等),还可以选择在触发警报前必须发生的失败数据点数量。这可以防止因指标值的临时波动而不必要地触发警报。在这种情况下,我们想知道是否存在任何错误,因此我们将值设置为1

在右侧,您可以定义检查周期和使用的统计数据(例如,平均值、最大值、最小值等)。您还可以在右上角查看所选指标的最近历史记录。图表上的红线表示当前定义的阈值,因此您可以快速查看是否会触发警报。

在警报的“操作”部分,您定义触发时将采取的操作。虽然您可以选择现有的 SNS 主题,但我们将通过点击“新建列表”来定义一个新的主题。然后,系统会提示您输入新主题的详细信息;您必须提供名称和要订阅主题的电子邮件地址。

当您点击“创建警报”时,您将看到订阅的状态。收到电子邮件并点击确认链接后,状态将自动更新。在确认订阅之前,您可以从窗口中导航离开,这没有关系。只需记住,如果您不确认订阅,目标电子邮件地址将不会收到任何通知。

查看新创建的警报显示其当前状态和最近的历史记录。警报有三种可能的状态:

  • ALARM:指标高于定义的阈值

  • INSUFFICIENT_DATA:没有足够的数据点来确定指标是在阈值之上还是之下

  • OK:指标低于定义的阈值

您可以通过侧边菜单中的链接按其状态筛选警报,并显示每个状态中有多少警报的更新视图。

在后台,向导已经为你创建了一个 SNS 主题。该主题负责将警报消息转换为电子邮件并发送出去。如果没有 SNS 主题,警报仍然会触发(即改变状态),但没有办法知道,除非查看 CloudWatch 仪表板中的指标。

还有更多……

这个食谱展示了 SNS 主题和 CW 警报的最简单有效配置,但这个模式还有更多深度可以探索。

现有主题

在向导中,除了选择“新列表”外,你还可以使用“选择列表”功能。然后,你可以提供一个现有 SNS 主题的名称来使用,而不是创建一个新的主题。

这意味着你可以设置一个单独的主题来推送多个警报。除了更简单之外,这还意味着你只需要确认订阅一次,而不必为每个警报都进行确认。

其他订阅

一个通知电子邮件的 SNS 主题是最常见的订阅方式,但并不是唯一的选择。SNS 主题还可以将通知发送到:

  • HTTP(S)端点

  • Amazon SQS

  • AWS Lambda

  • 短信

另请参见

  • 创建监控仪表板食谱

在 CloudWatch 中发布自定义指标

一旦你习惯了使用 CloudWatch,很可能你会希望看到更多的 AWS 内置指标以外的内容。

用户在开始运行 EC2 服务器后最常问的一个常见指标是内存使用情况;EC2 实例的内置指标包括 CPU 利用率、网络流量、磁盘读写和状态——默认情况下并不包括内存!

这个食谱将展示如何将 Linux 实例上使用的内存量传送到 CloudWatch,这样你就可以将它们与其他实例指标一起查看。

知道你的实例是否被充分利用(或根本没有利用)是选择适合工作负载的实例类型的关键因素。选择错误可能会让你付出大量的金钱!

准备工作

你需要一台运行 Linux 的 EC2 实例,并安装 AWS CLI 工具来执行这个食谱。如果你使用基于 AWS Linux 的实例,AWS CLI 工具将为你预安装。

你用于运行以下命令的实例角色或凭证必须有权限向 CloudWatch 提交指标。这是CloudWatch:PutMetricData权限。

如何执行……

  1. 在实例上,运行以下 AWS CLI 命令:
 aws cloudwatch put-metric-data \ 
 --metric-name MemoryUsagePercent \
 --namespace CustomMetrics \
 --dimensions InstanceId=`curl -s \
 http://169.254.169.254/latest/meta-data/instance-id` \
 --unit Percent \
 --value `free | grep Mem | awk '{print $3/$2 * 100.0}'`

  1. 进入 CloudWatch 控制台,导航到“指标”仪表板。你的指标将出现在命名空间 CustomMetrics 下,显示实例 ID 和实例的唯一 ID,并且指标名称为 MemoryUsagePercent。

自定义指标出现在 CloudWatch 仪表板上可能需要最多15 分钟(尽管通常需要的时间较短)。即使是内置的指标,也可能需要一到两分钟才能在控制台中看到该指标数据。

工作原理……

在这个食谱中,我们使用内置的put-metric-data AWS CLI 命令将我们的指标发送到 CloudWatch。

我们首先定义度量名称和命名空间,值将显示在该命名空间下。这一点很重要,因为它定义了我们在控制台和仪表板中如何看到该度量。名称应该能够标识并描述度量。名称不需要唯一,因为我们添加的维度(dimension)会处理这个问题(稍后会讨论)。命名空间用于将类似的度量分组,就像是一个类别。内置度量会出现在命名空间AWS/下。例如,EC2 度量会出现在AWS/EC2命名空间下。

然后我们为度量指定一个维度。维度是唯一标识相似度量的方式。在这种情况下,我们使用实例的 ID 来标识度量,因为该度量是该实例特有的,但我们可能会有许多MemoryUsagePercent度量(在许多 EC2 实例中)。我们通过查询(使用curl命令)实例元数据服务来获取实例 ID,该服务通过 HTTP 访问,位于特殊的 IP 地址169.254.169.254

实例元数据中有很多其他有用的信息。有关实例元数据的更多细节,请参阅 AWS 文档:docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html

接下来我们指定一个百分比,因为我们知道自己处理的是哪种数据。如果你不知道(或不在意),可以省略这个参数,因为 CloudWatch 对此没有特殊含义(尽管其他一些应用程序可能会使用它,例如用于显示)。

最后,我们指定要发送的值。我们通过动态计算free命令的输出值,并使用awk将其转换为内存使用百分比。

一旦度量被发送到 CloudWatch,我们就可以在控制台中查看它。最简单的方法是选择你的特定度量,并在 CloudWatch 控制台的度量部分查看它。

还有更多...

这是一个很好的实际案例,适合开始使用你自己的自定义度量,但你可以用它做的事情远不止这些。

Cron

单次的度量值通常不会单独有太大用处。真正的价值在于你可以将其绘制并查看其随时间的变化;它们如何变化,变化的速度,变化的范围等等。

在 Linux 上,你可以使用cron命令轻松安排任务。通过将 AWS CLI 命令放入脚本中,并通过cron定期调度它运行,你可以持续地将度量数据提供给 CloudWatch,而无需在实例上运行专门的代理程序。

自动扩展

像内存使用这样的基于实例的度量在从自动扩展组的所有实例收集时变得尤为有用。

通过收集实例甚至应用程序特定的度量(例如:使用的线程数、内部请求持续时间等),你可以在最合适的时间根据工作负载和性能配置,自动调整扩展组的大小。

为了实现这一点,将自动伸缩组名称设置为度量之一的维度(你可以定义多个维度),并将其与度量值一起发送。

回填

你可以通过运行相同的命令并提供额外的--timestamp参数来回填度量数据。时间戳参数接受 UTC 时间的 ISO 8601 日期和时间戳,例如:2017-01-01T12:00:00.000Z

请记住,CloudWatch 只会在一定时间内保留你的度量数据,这段时间由度量数据的粒度决定。保留期为:

  • 周期为 60 秒(1 分钟)的数据点可保存 15 天

  • 周期为 300 秒(5 分钟)的数据点可保存 63 天

  • 周期为 3600 秒(1 小时)的数据点可保存 455 天(15 个月)

虽然你可以发送毫秒精度的度量数据,但 CloudWatch 会以 1 分钟为最小存储单位存储数据。低于 1 分钟的数据,CloudWatch 会将其汇总。当汇总时,你可以看到一些关于你的度量的附加信息;例如样本大小、最小和最大值,以及值的平均值。

另见

  • 创建监控仪表板配方

  • 第四章中启动实例的配方,使用 AWS 计算

创建监控仪表板

收集度量数据的真正价值在于能够发现不同系统之间的趋势和关系(通常是未知或出乎意料的)。有了这种可见性,你可以在问题成为事件之前识别并排除故障。

除了提供一种汇总和查看系统度量数据的方式外,CloudWatch 服务还使得创建监控仪表板变得容易,这样你就可以快速、清晰地查看最重要的度量数据。

本配方使用 AWS 控制台,因为目前你不能通过 CloudFormation 或 AWS CLI 工具创建仪表板。

准备工作

你需要在 CloudWatch 中已有一些度量数据,才能创建仪表板。

如果你已经在使用 AWS 服务(例如:EC2、RDS、DDB 等),那么你应该有很多——几乎所有 AWS 服务默认都会在 CloudWatch 中填充度量数据。

如何执行…

  1. 进入 AWS 控制台的 CloudWatch 部分:

  1. 通过左侧菜单中的链接进入控制台的仪表板部分:

  1. 点击“创建仪表板”按钮:

  1. 选择你想用来显示度量的部件类型。在这个例子中,我们将选择最通用的“折线图”:

  1. 浏览“所有度量”标签,找到你想要包含的度量,点击度量详细信息左侧的勾选框进行选择。你将看到该度量的预览以及它的显示方式:

  1. 一旦选择了你可以通过“图表指标”选项卡上的设置来修改指标的显示方式。在这种情况下,我们给小部件起了一个名字,并将指标的周期设置为 1 分钟,以反映可用的更高精度(你可以看到由于这个设置,指标线变得更平滑了)。

  1. 一旦你点击“创建小部件”,你将看到小部件出现在仪表板上。一旦你点击“保存仪表板”,它会出现在左侧菜单的“仪表板”标题下:

  1. 在仪表板级别,你可以打开自动刷新并设置刷新频率间隔:

  1. 你可以通过拖动小部件来调整大小和重新排列它们。只需记住点击“保存仪表板”以保留任何更改:

还有更多……

CloudWatch 仪表板的价值在于,它让你能够轻松简单地公开最重要的指标。

与任何仪表板一样,确保你选择展示的指标是相关且可操作的。如果更改时没有需要采取的行动,那么展示这个指标就没有意义。

小部件类型

线形图并不是仪表板上唯一可以显示的小部件类型。还有:

  • 堆叠区域

  • 数字

  • 文本

根据你收集或感兴趣的指标类型,你应该尝试不同类型的小部件来展示它们。并非所有指标都适合用线形图表示。

另见

  • 在 CloudWatch 中发布自定义指标 配方。

创建预算

使用 AWS 的主要吸引力之一就是它的按需付费模式。你只需为使用的部分付费,既不多也不少。

不幸的是,这有时会导致所谓的账单震惊,即在月底时发生的情况。这通常是当你做了一些你可能不知道是收费服务的事情,或者你不知道它的收费标准,而直到太晚才发现时发生的。特别是在刚开始使用时,用户可能没有充分意识到他们所进行活动的费用。

你也可以通过一些方法来优化 AWS 上的成本,比如降低传输速度、取消外部访问等。这一切意味着你需要关注你的费用义务,并实时管理它们。为此,你可以创建预算,帮助你了解你的使用情况和支出。

虽然你可以通过 AWS CLI 工具创建预算,但了解计费仪表板的工作原理对于管理目的很有用,因此我们将使用 AWS 控制台进行本教程。

准备就绪

默认情况下,IAM 用户无法访问 AWS 控制台的计费部分。你必须使用账户的根登录信息执行这些步骤,或者为其他用户启用 IAM 访问权限,这是一次性操作。

虽然在管理 AWS 账户时你通常不应使用根凭证,但创建预算(这种情况应该很少发生)是个例外。

在任何情况下,您不应该为根帐户创建访问密钥,这也是为什么我们在此示例中使用控制台(而不是 CLI)的另一个原因。

如何做...

  1. 使用根凭据登录 AWS 控制台,并通过点击右上角的用户名访问的用户菜单导航到我的计费仪表板:

  1. 计费仪表板显示本月的最新使用情况。在左侧菜单中点击“预算”:

  1. 当您首次进入预算控制台时,将没有预算可显示。点击“创建预算”按钮开始:

  1. 首先填写预算详情,例如成本作为测量类型,每月作为周期,以及预算金额。选择开始日期(默认为当月的第一天),以及可选的结束日期。将结束日期字段留空以创建每月重置的滚动预算:

  1. 接下来输入通知详情。这包括通知阈值,我们将设置为预测使用量的 80%(我们的预算)。对于电子邮件通知,只需输入您想要接收通知的电子邮件地址。完成后点击“创建”:

  1. 您将返回到计费仪表板的预算部分,并可以看到您新创建的预算:

  1. 您创建的每个预算,都可以选择查看完整详情:

工作原理...

计费仪表板与帐户本身紧密相关,这就是为什么它不是控制台中常规服务的一部分。通过用户菜单访问它暗示了它需要特殊访问权限。通常,您会在首次打开新的 AWS 帐户时配置预算,以便在月底不会有任何意外费用。

如果在计费仪表板中收到访问被拒绝的消息,很可能是因为您正在使用 IAM 用户,而 IAM 访问尚未启用。您必须使用根帐户凭据(例如您用来创建帐户的凭据),或者启用 IAM 访问。只有根用户才能启用 IAM 访问。

当您首次进入计费部分时,您将看到您的使用情况和费用的高级摘要。由于我在一个新帐户中执行了此示例,目前没有太多可见的内容。右侧的按服务分类的当月支出图表尤其有用,可以帮助您找出您最常使用的服务是什么。这是在尝试减少或优化 AWS 支出时的绝佳起点。

然后我们进入预算部分并创建一个新预算。大多数细节应该是显而易见的,目的就是为了预算管理。你主要的选择是决定是否要针对使用量或费用发出警报。费用预算是基于你将被收取的美元(或适当的计费货币)金额。使用量预算是基于所选的使用单位,例如 EC2 的实例小时数或数据传输量。一个使用量预算只能跟踪一种使用单位,因此你需要创建多个预算来跟踪可能会被收费的各种单位。这也是我们更倾向于使用费用预算的原因,因为它考虑了多种使用形式。

指定电子邮件地址进行警报通知是发送预算警报的最简单方法。对于更高级的用例,你可以指定一个 SNS 主题来接收通知。例如,如果你想通过短信在手机上接收预算警报,或者将警报自动发送到其他系统(通过 HTTP/JSON),都可以。

完成后,你可以在仪表盘中查看所有预算。你可以重复这个过程来创建多个预算。这意味着你可以为预测使用量和实际使用量创建预算,也可以为不同的时间段创建预算。

将日志文件发送到 CloudWatch 日志

CloudWatch 日志是 AWS 中一个托管的、高度持久化的日志存储系统。它能够从多个源摄取日志。我们将重点介绍最常见的用例之一,即将日志从 EC2 实例发送到 CloudWatch 日志。

这个功能在高度动态的自动扩展环境中尤为重要。由于 EC2 实例的生命周期可能非常短暂,任何仅写入本地磁盘的日志都会在实例终止时丢失。你不可避免地会发现自己在实例消失后希望能够访问服务器日志。

我们即将展示的以下模式允许你聚合、搜索和筛选来自多个源的日志条目。你可以根据日志活动创建自定义指标并触发报警。非常方便!

在这个方案中,我们将:

  • 启动一个 EC2 实例

  • 配置它将日志发送到 CloudWatch 日志

  • 基于 SSH 登录实例创建一个筛选器

  • 在筛选匹配时向自己发送电子邮件警报

这可能是你考虑在堡垒主机上执行的操作,因为它们通常是你环境中 SSH 访问的唯一入口点,并且如果有人登录到生产服务器,制造一些噪音是个好主意。

准备工作

我们将在us-east-1区域使用 AWS Linux AMI 来完成所有操作。如果你希望在其他区域执行此操作,只需向我们将要创建的模板提供不同的 AMI ID 即可。

让我们开始吧;你将需要以下内容:

  • us-east-1区域中默认 VPC 的 VPC ID。你不必使用默认 VPC,只需确保选择一个具有公共子网(已配置为分配公共 IP 地址)的 VPC 即可。

  • 公共子网的子网 ID

  • us-east-1中配置的 SSH 密钥对

  • 一个我们可以发送警报到的电子邮件地址

如何操作...

  1. 创建一个新的 CloudFormation 模板。将以下Parameters添加到模板中:
      AmiId: 
        Type: AWS::EC2::Image::Id 
        Description: AMI ID to launch instances from 
        Default: ami-0b33d91d 
      VpcId: 
        Type: AWS::EC2::VPC::Id 
        Description: VPC where load balancer and instance will launch 
      SubnetIDs: 
        Type: List<AWS::EC2::Subnet::Id> 
        Description: Public subnet where the instance will launch
          (pick at least 1) 
      KeyPair: 
        Type: AWS::EC2::KeyPair::KeyName 
        Description: Key to launch EC2 instance with 
      AlertEmail: 
        Type: String 
        Description: Email Address which alert emails will be sent to

  1. 现在,对于Resources,我们需要为我们的 EC2 实例定义一个RoleInstanceProfile。这将赋予我们的服务器适当的权限,允许它将日志发送到 CloudWatch。
      ExampleRole: 
        Type: AWS::IAM::Role 
        Properties: 
          AssumeRolePolicyDocument: 
            Version: "2012-10-17" 
            Statement: 
              - 
                Effect: Allow 
                Principal: 
                  Service: 
                    - ec2.amazonaws.com 
                Action: 
                  - sts:AssumeRole 
          Path: / 
          Policies: 
            - 
             PolicyName: WriteToCloudWatchLogs 
             PolicyDocument: 
               Version: "2012-10-17" 
               Statement: 
                 - 
                  Effect: Allow 
                    Action: 
                      - logs:CreateLogGroup 
                      - logs:CreateLogStream 
                      - logs:PutLogEvents 
                      - logs:DescribeLogStreams 
                    Resource: "*" 
      ExampleInstanceProfile: 
        Type: AWS::IAM::InstanceProfile 
        Properties: 
          Roles: 
            - !Ref ExampleRole 
          Path: /

  1. 我们的实例需要位于一个允许 SSH 访问的安全组中,因此我们现在就添加这个安全组:
      ExampleEC2InstanceSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: Security Group for example Instance 
          SecurityGroupIngress: 
            - IpProtocol: tcp 
              CidrIp: "0.0.0.0/0" 
              FromPort: 22 
              ToPort: 22 
          VpcId: !Ref VpcId

  1. 接下来,我们可以定义我们的实例。我们确保使用我们刚创建的配置文件和安全组,并添加一些用户数据,具体操作如下:

    1. 安装awslogs包。

    2. 写入一个配置文件,将/var/log/secure发送到 CloudWatch 日志。

    3. 启动awslogs服务。

    4. 使awslogs服务在启动时自动启动(以应对重启情况)。

              ExampleEC2Instance: 
                Type: AWS::EC2::Instance 
                Properties: 
                  IamInstanceProfile: !Ref ExampleInstanceProfile 
                  InstanceType: t2.nano 
                  KeyName: !Ref KeyPair 
                  UserData: 
                    Fn::Base64: 
                      Fn::Sub: | 
                        #!/bin/bash -ex 
                        yum update -y 
                        yum install -y awslogs 
                        cat << EOF >
                        /etc/awslogs/config/var-log-secure.conf 
                        [/var/log/secure] 
                        datetime_format = %b %d %H:%M:%S 
                        file = /var/log/secure 
                        buffer_duration = 5000 
                        log_stream_name = {instance_id} 
                        initial_position = start_of_file 
                        log_group_name = /var/log/secure 
                        EOF 
                        service awslogs start 
                        chkconfig awslogs on 
                  ImageId: !Ref AmiId 
                  SecurityGroupIds: 
                    - Fn::GetAtt: ExampleEC2InstanceSecurityGroup.GroupId 
                  SubnetId: !Select [ 0, Ref: SubnetIDs ]

  1. 我们现在将添加一个 SNS 主题。这个主题将接收警报并将其转发到我们用于接收警报的电子邮件地址:
      ExampleSNSTopic:  
        Type: AWS::SNS::Topic 
        Properties:  
            Subscription:  
              -  
                Endpoint: !Ref AlertEmail 
                Protocol: email

  1. 接下来,我们需要过滤/var/log/secure中的登录日志。MetricFilter资源允许我们这样做。如果我们引用了一个尚不存在的日志组,CloudFormation 会抛出错误,因此我们也在这里添加该日志组(通过DependsOn引用):
      ExampleLogGroup: 
        Type: AWS::Logs::LogGroup 
        Properties: 
          LogGroupName: /var/log/secure 
          RetentionInDays: 7 
      ExampleLogsMetricFilter: 
        Type: AWS::Logs::MetricFilter 
        Properties:  
          FilterPattern: '"Accepted publickey for ec2-user from"' 
          LogGroupName: /var/log/secure 
          MetricTransformations: 
            -  
              MetricValue: "1" 
              MetricNamespace: SSH/Logins 
              MetricName: LoginCount 
        DependsOn: ExampleLogGroup

  1. 我们需要的最后一个资源是实际的Alarm。像这样添加:
      ExampleLoginAlarm: 
        Type: AWS::CloudWatch::Alarm 
        Properties: 
          AlarmDescription: SSH Login Alarm 
          AlarmActions: 
          - Ref: ExampleSNSTopic 
          MetricName: LoginCount 
          Namespace: SSH/Logins 
          Statistic: Sum 
          Period: 60 
          EvaluationPeriods: 1 
          Threshold: 0 
          ComparisonOperator: GreaterThanThreshold

  1. 最后,我们将实例的公共 IP 地址添加到Outputs中,这样我们就不需要进入 EC2 web 控制台去查找它:
      Outputs: 
        ExampleEC2InstancePublicIp: 
          Value: !GetAtt [ ExampleEC2Instance, PublicIp ]

  1. 继续并启动这个 CloudFormation 堆栈。你可以通过 AWS CLI 像这样启动:
 aws cloudformation create-stack \ 
 --template-body \
        file://05-feed-log-files-in-to-cloudwatch-logs.yaml \ 
 --stack-name example-cloudwatchlogs \ 
 --capabilities CAPABILITY_IAM \ 
 --parameters \ 
 ParameterKey=VpcId,ParameterValue=<your-vpc-id> \ 
 ParameterKey=SubnetIDs,ParameterValue='<your-subnet-id>' \ 
 ParameterKey=KeyPair,ParameterValue=<your-ssh-key-name> \ 
 ParameterKey=AlertEmail,ParameterValue=<your-email-address>

  1. 在继续之前,你需要检查你的电子邮件并确认你对 SNS 主题的订阅。如果你不这样做,就不会收到来自 CloudWatch 的任何警报:

在以下截图中,展示了确认订阅的示例:

  1. 继续并通过 SSH 连接到你的实例。如果登录成功,你将在 CloudWatch web 控制台看到触发的警报:

一封电子邮件将发送到你的收件箱,如以下截图所示:

它是如何工作的...

理解日志流和日志组之间的区别非常重要。

日志流是来自单一源的日志序列。这可以是一个 EC2 实例、一个应用程序进程,或者 AWS 中的其他源。在我们的案例中,日志流的名称是我们 EC2 实例的 ID。实际上,CloudWatch 日志代理默认会将log_stream_name设置为实例 ID。

日志组是具有相同属性的日志流的集合。在我们之前的例子中,日志组将对应于/var/log/secure。因此,我们最终得到的配置如下:

      log_group_name = /var/log/secure 
      log_stream_name = {instance_id}

当你安装 CloudWatch 日志代理时,它实际上会以我们刚才描述的相同方式设置/var/log/messages

      log_group_name = /var/log/messages 
      log_stream_name = {instance_id}

一旦代理启动,它将大约每 5 秒钟将新的日志条目发送到 CloudWatch 日志。

还有更多...

  • CloudWatch 日志支持传统文本格式日志条目以及 JSON 格式日志的摄取。

  • 日志可以从其他来源摄取,包括 CloudTrail、IAM、Kinesis Streams 和 Lambda。

  • 默认情况下,日志会被无限期存储。不过,你可以根据需要自定义这个时间周期。

  • 度量过滤器(比如我们之前创建的那个)可以用于在 CloudWatch 控制台中绘制图表和图形。将它们添加到你的仪表板以及警报系统中。

  • CloudWatch 网页控制台允许你在添加度量过滤器之前进行测试。使用此功能可以节省大量在 CloudFormation 中的试错时间。但请不要完全依赖网页控制台:一旦调试正确,你应该将这些度量过滤器迁移到 CloudFormation 中。

  • 日志流和日志源之间是 1 对 1 的关系。例如,你不能让多个实例将/var/log/secure发送到同一个日志流。

  • 我们创建的报警的非警报状态将是 INSUFFICIENT_DATA。这是因为我们的度量过滤器仅在检测到登录时才会输出值。

第六章:数据库服务

在本章中,我们将涵盖以下内容:

  • 创建带自动故障切换的数据库

  • 创建 NAT 网关

  • 创建数据库只读副本

  • 将只读副本提升为主数据库

  • 创建一次性数据库备份

  • 从快照恢复数据库

  • 迁移数据库

  • 计算 DynamoDB 性能

简介

持久化存储服务是有效使用 AWS 云服务管理系统的关键组成部分。通过确保拥有一个高度可用、容错的存储位置来保存应用状态,你可以不再依赖单个服务器来存储数据。

创建带自动故障切换的数据库

在这个食谱中,我们将创建一个配置为多可用区(multi-AZ)模式的 MySQL RDS 数据库实例,以便于自动故障切换。

带自动故障切换的数据库

准备工作

默认 VPC 对于本示例而言足够了。一旦你熟悉了创建数据库的操作,你可能会考虑使用包含私有子网的 VPC,将数据库与互联网及其他资源隔离开来(类似三层架构的风格)。无论如何,你需要记下以下信息:

  • VPC 的 ID

  • VPC 的 CIDR 范围

  • 至少两个子网的 ID,这些子网需要位于不同的可用区,例如 us-east-1aus-east-1b

如何操作…

创建一个新的 CloudFormation 模板。我们将向其中添加总共 12 个参数:

  1. 前三个参数将包含我们在 准备工作 部分提到的值:
      VPCId: 
        Type: AWS::EC2::VPC::Id 
        Description: VPC where DB will launch 
      SubnetIds: 
        Type: List<AWS::EC2::Subnet::Id> 
        Description: Subnets where the DB will launch (pick at least 2) 
      SecurityGroupAllowCidr: 
        Type: String 
        Description: Allow this CIDR block to access the DB 
        Default: "172.30.0.0/16"

  1. 我们还将添加数据库凭证作为参数。这是一个好习惯,因为这样意味着我们不会在基础设施源代码中存储任何凭证。请注意,密码包含 NoEcho 参数,并将其设置为 true。这可以防止 CloudFormation 在显示 CloudFormation 堆栈详情时输出密码:
      DBUsername: 
        Type: String 
        Description: Username to access the database 
        MinLength: 1 
        AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*" 
        ConstraintDescription: must start with a letter, must
          be alphanumeric 
      DBPassword: 
        Type: String 
        Description: Password to access the database 
        MinLength: 1 
        AllowedPattern: "[a-zA-Z0-9]*" 
        NoEcho: true 
        ConstraintDescription: must be alphanumeric

  1. 接下来的参数块涉及到成本和性能,它们应该大多数都是自解释的。如果你希望更改本示例的实例类型,请参考 AWS 文档中的数据库实例类型。我们为存储大小提供默认值 10 GB,并选择一个磁性(standard)卷作为存储类型。gp2 提供更好的性能,但价格稍高:
      DBInstanceClass: 
        Type: String 
        Description: The instance type to use for this database 
        Default: db.t2.micro 
      DBStorageAmount: 
        Type: Number 
        Description: Amount of storage to allocate (in GB) 
Default: 10 
      DBStorageType: 
        Type: String 
        Description: Type of storage volume to use
          (standard [magnetic] or gp2) 
        Default: standard 
        AllowedValues: 
          - standard 
          - gp2

  1. 我们需要为数据库设置一些附加参数。这些包括 MySQL 引擎版本和端口。请参考 AWS 文档,查看所有可用的版本。我们为此参数设置了一个默认值,使用写作时的最新 MySQL 版本:
      DBEngineVersion: 
        Type: String 
        Description: DB engine version 
        Default: "5.7.11" 
      DBPort: 
        Type: Number 
        Description: Port number to allocate 
        Default: 3306 
        MinValue: 1150 
        MaxValue: 65535

  1. 最后,我们将定义一些与备份和可用性相关的参数。我们希望我们的数据库在 multi-AZ 模式下运行,默认将此值设置为 true。我们还默认将备份保留期设置为 1 天;你可能希望选择一个更长的保留期。如果你将该值设置为 0,则备份将被禁用(不推荐!):
      DBMultiAZ: 
        Type: String 
        Description: Should this DB be deployed in Multi-AZ configuration? 
        Default: true 
        AllowedValues: 
          - true 
          - false 
      DBBackupRetentionPeriod: 
        Type: Number 
        Description: How many days to keep backups (0 disables backups) 
        Default: 1 
        MinValue: 0 
        MaxValue: 35

  1. 我们已经完成了此模板的参数定义,现在可以开始定义我们的 Resources 了。首先,我们需要一个安全组来容纳我们的数据库。这个安全组允许从我们定义的 CIDR 范围内访问数据库端口:
      ExampleDBSecurityGroup: 
        Type: AWS::EC2::SecurityGroup 
        Properties: 
          GroupDescription: Example security group for inbound access to DB 
          SecurityGroupIngress: 
            - IpProtocol: tcp 
              CidrIp: !Ref SecurityGroupAllowCidr 
              FromPort: !Ref DBPort 
              ToPort: !Ref DBPort 
          VpcId: !Ref VPCId

  1. 接下来,我们需要定义一个 DBSubnetGroup 资源。该资源用于声明我们的数据库将位于哪些子网。我们为此资源定义了两个子网,这样主数据库和备用数据库就会分别位于不同的可用区:
      ExampleDBSubnetGroup: 
        Type: AWS::RDS::DBSubnetGroup 
        Properties: 
          DBSubnetGroupDescription: Example subnet group for example DB 
          SubnetIds: 
            - Fn::Select: [ 0, Ref: SubnetIds ] 
            - Fn::Select: [ 1, Ref: SubnetIds ]

  1. 最后,我们定义我们的 RDS 实例资源。我们指定它为 MySQL 数据库,其他属性则由我们之前定义的参数和资源组成。在这里需要大量使用 !Ref
      ExampleDBInstance: 
        Type: AWS::RDS::DBInstance 
        Properties: 
          AllocatedStorage: !Ref DBStorageAmount 
          BackupRetentionPeriod: !Ref DBBackupRetentionPeriod 
          DBInstanceClass: !Ref DBInstanceClass 
          DBSubnetGroupName: !Ref ExampleDBSubnetGroup 
          Engine: mysql 
          EngineVersion: !Ref DBEngineVersion 
          MasterUsername: !Ref DBUsername 
          MasterUserPassword: !Ref DBPassword 
          MultiAZ: !Ref DBMultiAZ 
          StorageType: !Ref DBStorageType 
          VPCSecurityGroups: 
            - !GetAtt ExampleDBSecurityGroup.GroupId

  1. 为了更精确,我们可以在这个模板中添加一个输出项,用于返回此 RDS 数据库的主机名:
      Outputs: 
        ExampleDbHostname: 
          Value: !GetAtt ExampleDBInstance.Endpoint.Address

  1. 你可以通过 CloudFormation Web 控制台配置数据库,或者像这样使用 CLI 命令:
      aws cloudformation create-stack \ 
        --stack-name rds1 \ 
        --template-body \
        file://06-create-database-with-automatic-failover.yaml \ 
        --parameters \ 
        ParameterKey=DBUsername,ParameterValue=<username> \ 
        ParameterKey=DBPassword,ParameterValue=<password>  \
        ParameterKey=SubnetIds,"ParameterValue='<subnet-id-a>, \
        <subnet-id-b>'" \ 
        ParameterKey=VPCId,ParameterValue=<vpc-id>

它是如何工作的…

在多可用区配置中,AWS 将在一个独立的可用区配置一个备用 MySQL 实例。对你的数据库的更改将以同步方式复制到备用数据库实例。如果主数据库出现问题,AWS 会自动切换到备用实例,将其提升为主数据库,并配置新的备用实例。

你无法直接查询备用数据库。所以,例如,你不能用它来处理所有的读取查询。如果你希望使用额外的数据库实例来增加读取能力,你需要配置一个只读副本。我们将在另一个实例中讨论这些内容。

备份将始终从备用实例中获取,这意味着不会中断数据库的可用性。如果你没有选择在多可用区模式下部署数据库,则情况不一样。

部署此示例时,堆栈报告完成大约需要 20 分钟或更长时间。这是因为 RDS 服务需要完成以下过程以便配置一个完全正常工作的多可用区数据库:

  • 配置主数据库

  • 备份主数据库

  • 使用主数据库的备份配置备用数据库

  • 配置两个数据库进行同步复制

警告

在开始向 RDS 配置写入数据后,请小心修改 RDS 配置,特别是当使用 CloudFormation 更新时。一些 RDS 配置变更要求重新配置数据库,这可能会导致数据丢失。我们建议使用 CloudFormation 变更集,这样你可以看到哪些更改可能会导致破坏性行为。CloudFormation RDS 文档也提供了相关信息。

还有更多内容...

  • 你可以为 RDS 实例定义维护窗口。这个时间段是 AWS 执行维护任务(如安全补丁或小版本升级)的时间。如果你没有指定维护窗口(我们在这个示例中没有指定),则会为你选择一个。

创建一个 NAT 网关

除非必要,否则你的实例不应公开暴露到互联网。当你的实例处于互联网时,你必须假设它们在某个阶段会遭受攻击。

这意味着大多数工作负载应该运行在私有子网中的实例上。私有子网是指那些没有直接连接到互联网的子网。

为了让你的私有实例可以访问互联网,你需要使用网络地址转换NAT)。NAT 网关允许你的实例发起到互联网的连接,而不允许来自互联网的连接。

准备工作

对于此示例,你必须拥有以下资源:

  • 一个带有互联网网关IGW)的 VPC

  • 公共子网

  • 一个私有子网路由表

你需要公共子网和私有子网路由表的 ID。两者应位于同一个可用区。

如何操作...

  1. 从通常的 CloudFormation 模板版本和描述开始:
      AWSTemplateFormatVersion: "2010-09-09" 
      Description: Create NAT Gateway and associated route.

  1. 模板必须包含以下必需的参数:
      Parameters: 
        PublicSubnetId: 
          Description: Public Subnet ID to add the NAT Gateway to 
          Type: AWS::EC2::Subnet::Id 
        RouteTableId: 
          Description: The private subnet route table to add the NAT
            Gateway route to 
          Type: String

  1. Resources 部分,定义将分配给 NAT 网关的弹性 IP:
      Resources: 
        EIP: 
          Type: AWS::EC2::EIP 
          Properties: 
            Domain: vpc

  1. 创建 NAT 网关资源,并将你在公共子网中定义的 EIP 分配给它:
        NatGateway: 
          Type: AWS::EC2::NatGateway 
          Properties: 
            AllocationId: !GetAtt EIP.AllocationId 
            SubnetId: !Ref PublicSubnetId

  1. 最后,定义到 NAT 网关的路由,并将其与私有子网的路由表关联:
      Route: 
        Type: AWS::EC2::Route 
        Properties: 
          RouteTableId: !Ref RouteTableId 
          DestinationCidrBlock: 0.0.0.0/0 
          NatGatewayId: !Ref NatGateway

它是如何工作的...

该示例所需的参数如下:

  • 公共子网 ID

  • 一个私有子网路由表 ID

公共子网 ID 用于托管 NAT 网关,因为它必须具有互联网访问权限。私有子网路由表将更新为指向 NAT 网关的路由。

使用 AWS NAT 网关服务意味着 AWS 会为你托管和保护该服务。该服务将冗余地托管在单个可用区中。

你可以多次使用该模板,在每个私有子网中部署 NAT 网关。只需确保公共子网和私有子网位于同一个可用区(AZ)。

为了应对可用区(AZ)停机的极不可能事件(虽然不太可能,但可能发生),你应在每个子网中部署一个 NAT 网关。这意味着,如果一个 NAT 网关下线,另一个可用区中的实例可以继续正常访问互联网。你确实在多个可用区中部署了你的应用,对吧?

此示例仅在你已创建自己的私有子网时有效,因为在新 AWS 账户中,默认的子网都是公共的。公共子网中的实例可以直接访问互联网(通过 IGW),因此不需要 NAT 网关。

另见

  • 第七章中的构建安全网络示例,网络

创建数据库只读副本

本示例将展示如何创建一个 RDS 只读副本。你可以使用只读副本,通过将数据库读取任务分配给一个单独的数据库实例来提高应用的性能。每个源数据库最多可以配置五个只读副本。

只读数据库从库

准备工作

你需要一个启用了备份保留的 RDS 数据库。我们将基于前面的 创建具有自动故障转移的数据库 配方中的数据库进行构建。

你需要以下值:

  • 源 RDS 实例的标识符,例如,eexocwv5k5kv5z

  • 我们将要创建的读取副本的唯一标识符,例如,read-replica-1

如何操作...

在 AWS CLI 中,输入此命令:

aws rds create-db-instance-read-replica \
 --source-db-instance-identifier <source-db-identifier> \
 --db-instance-identifier <unique-identifier-for-replica>

它是如何工作的...

RDS 现在将为你创建一个新的读取副本。

有些参数是从源实例继承的,创建时无法定义:

  • 存储引擎

  • 存储大小

  • 安全组

CLI 命令接受一些我们本可以定义的参数,但为了简化操作,我们没有定义这些参数。它们将从源数据库继承。主要的两个参数如下:

  • --db-instance-class:使用与源实例相同的实例类型

  • --db-subnet-group-name:将使用源实例的子网组,并随机选择一个子网(因此,随机选择一个可用区)

还有更多...

  • 读取副本部署在单一的可用区内;没有备用读取副本。

  • 在创建时无法在读取副本上启用备份功能。必须在创建之后进行配置。

  • 默认存储类型为 standard(磁盘)。通过选择 gp2 或使用预置 IOPS,可以提高性能。

  • 可以直接向读取副本添加 MySQL 索引,以进一步提高读取性能。这些索引在主数据库上不必存在。

  • 将读取副本用于可用性目的更像是一种补充的灾难恢复策略,不能替代多可用区 RDS。多可用区配置为你提供故障检测和自动故障转移的好处。

  • 可以将读取副本部署在完全不同的区域。

  • 与主数据库和备用数据库之间的同步复制不同,读取副本的复制是异步的。这意味着读取副本可能会落后于主数据库。在向读取副本发送时间敏感的读取查询时,务必记住这一点。

将读取副本提升为主实例

我们将向你展示如何将 RDS 读取副本提升为主实例。你可能有几个理由这么做:

  • 处理通常会导致大量停机时间的表迁移,尤其是在处理列或索引时

  • 因为你需要实现分片

  • 如果你选择不在多可用区模式下部署现有的主数据库(不推荐),则可以从故障中恢复

准备工作

你将需要唯一标识符,这已经分配给了 RDS 读取副本。如果你按照之前的 创建具有自动故障转移的数据库创建数据库读取副本 配方操作,那么你应该已经准备好。

在提升之前,最好已经在该只读副本上启用了备份。这样可以缩短提升过程,因为您无需等待备份完成。您需要将备份保留期设置为 18 之间的值。

启用只读副本的备份将导致其重启!

为了启用备份,您可以使用以下 CLI 命令:

aws rds modify-db-instance \
 --db-instance-identifier <identifier-for-read-replica> \
 --backup-retention-period <days-to-keep-backups-for> \
 --apply-immediately

如果您希望等到重启发生在配置的维护窗口期间,您可以省略 --apply-immediately 参数。但您仍然需要等到重启完成后再继续进行提升操作。

为了确保在提升前拥有最新的数据,您需要停止当前源主数据库的所有写入流量。最好还要确保您的只读副本的复制延迟为 0(您可以在 CloudWatch 中检查此项)。

如何操作...

  1. 运行以下命令将您的只读副本提升为主数据库实例。此命令将导致您的只读副本重启:
 aws rds promote-read-replica \
 --db-instance-identifier <identifier-for-read-replica>

  1. 如果您希望继续配置新的主 RDS 实例以运行多可用区(multi-AZ)配置,那么您需要运行此额外的命令。预计该操作将需要一段时间才能完成:
 aws rds modify-db-instance \
 --db-instance-identifier <identifier-for-new-primary> \
 --multi-az \
 --apply-immediately

创建一次性数据库备份

现在我们将向您展示如何创建数据库的单次快照。如果您有特定的需求,需要保留数据库的某个时间点的备份,您可以选择执行此操作。您也可能希望创建一个快照,以便为您的数据集创建一个新的工作副本。

准备工作

为了继续操作,您将需要以下内容:

  • 您希望备份的 RDS 实例的标识符

  • 您希望分配给此快照的唯一标识符

快照标识符有一些限制:

  • 它必须以字母开头

  • 它不能超过 255 个字符

如果您的主数据库没有运行在多可用区配置中,请注意,创建快照将会导致停机。在多可用区配置中,快照是在备用实例上进行的,因此不会发生停机。

如何操作...

输入以下 AWS CLI 命令以启动快照的创建。您需要等待几分钟,直到快照完成,才能使用它:

aws rds create-db-snapshot \
 --db-instance-identifier <primary-rds-id> \
 --db-snapshot-identifier <unique-id-for-snapshot>

从快照恢复数据库

我们现在将讲解如何从快照恢复数据库。此过程会创建一个新数据库,该数据库将保留大部分源数据库的配置。

准备工作

您将需要以下信息:

  • 您希望从中恢复的快照的 ID

  • 您希望为我们即将创建的数据库指定的名称或标识符

AWS 不允许您账户中的 RDS 服务共享相同的标识符。如果源数据库仍在线,您需要确保选择一个不同的标识符(或者重命名源数据库)。

如何操作...

  1. 输入以下命令:
 aws rds restore-db-instance-from-db-snapshot \
 --db-snapshot-identifier <name-of-snapshot-to-restore > \
 --db-instance-identifier <name-for-new-db> \
 --db-subnet-group-name <your-db-subnet-group> \
 --multi-az

  1. 你可能已经注意到,这个命令会在默认安全组中创建一个新数据库。这是因为restore-db-instance-from-db-snapshot命令不接受安全组 ID 作为参数。你需要运行第二个命令来为新数据库分配非默认的安全组:
 aws rds modify-db-instance \
 --db-instance-identifier <name-of-newly-restored-db> \
 --vpc-security-group-ids <id-of-security-group>

除非目标数据库的状态为available,否则modify-db-instance命令会返回错误。

此外,安全组名称在此命令中无效;你需要使用安全组 ID,例如sg-7603d50a

还有更多内容...

上一个命令包含了启用新数据库的多可用区(multi-AZ)选项。如果你希望新数据库仅运行在单可用区模式下,可以简单地去掉这个标志。

迁移数据库

在这个教程中,我们将使用数据库迁移服务DMS)将外部数据库迁移到关系型数据库服务RDS)。

与许多其他教程不同,本教程将通过 Web 控制台手动执行。

大多数数据库迁移都是一次性的,并且涉及许多步骤。我们建议你首先通过控制台手动执行该过程,之后如果需要,可以使用 AWS CLI 工具或 SDK 进行自动化操作。

准备中

对于这个教程,你需要以下内容:

  • 一个外部数据库

  • 一个 RDS 数据库实例

本示例中的源数据库名为employees,所以请根据需要替换为自己的数据库名称。

两个数据库必须能够从将要创建的复制实例访问。最简单的方法是允许数据库从互联网访问,但显然这会带来安全隐患。

如何操作...

  1. 进入 DMS 控制台:

  1. 点击“创建迁移”以启动迁移向导:

  1. 指定复制实例的详细信息。除非你有特定的 VPC 配置,否则默认设置就足够了:

  1. 在等待复制实例准备好时,填写源端点和目标端点的信息,包括服务器主机名、端口以及连接时使用的用户名和密码:

  1. 一旦实例准备好,界面会更新,你可以继续操作:

  1. 为了确认并创建源端点和目标端点,点击每个数据库的“运行测试”按钮:

  1. 在成功测试并创建端点后,定义你的任务。在本教程中,我们将简单地迁移数据(不进行持续复制):

  1. 为简化操作,在目标数据库中删除表(目标数据库应该为空)以确保两数据库之间的一致性:

  1. 最后,定义源数据库和目标数据库之间的映射关系。在本教程中,我们将迁移源数据库中所有表(通过使用通配符%):

  1. 一旦你点击“添加选择规则”,你将看到规则出现在选择规则列表中:

  1. 一旦任务定义完成,你就完成了向导。然后你将看到任务正在创建:

  1. 一旦任务的状态为“准备就绪”,你可以选择它并点击“开始/恢复”按钮:

  1. 完成后,你将在控制台中看到任务的详细信息已更新:

它是如何工作的……

从高层次来看,DMS 架构大致如下所示:

数据库和目标数据库都位于DMS之外。它们在内部由端点资源表示,端点资源是数据库的引用。如果需要,端点可以在不同任务之间复用。

本教程从定义复制实例的详细信息开始。请记住,当两个数据库之间的迁移/转换保持内存中时,DMS 迁移过程效果最佳。这意味着对于较大的任务,你应该分配更强大的实例。如果过程需要暂时将数据写入磁盘(如交换分区),则性能和吞吐量将会大大降低。这可能会产生连锁反应,特别是对于包含持续复制的任务。

接下来,定义这两个端点。通过使用内置的测试功能验证端点配置非常重要,以确保任务在后续过程中不会失败。通常,如果连接性测试失败,可能是以下两个主要问题之一:

  • 复制实例与数据库之间的网络连接问题。这对本地部署的数据库尤其重要,因为这些数据库通常会特别限制外部访问。

  • 用户权限问题:例如,在 MySQL 的情况下,不能使用 root 用户进行外部数据库连接,因此无法使用该默认用户。

定义任务时需要定义迁移类型。本教程使用了最简单的类型:迁移表。这意味着数据将被复制到两个数据库之间,当数据传播完毕时任务就完成了。我们还可以定义目标数据库上的行为。为了简便起见,我们将任务配置为删除目标数据库中的表,以确保两个数据库尽可能相似,即使表名被更改或表映射发生变化。对于任务表映射,我们使用通配符符号 % 来匹配源数据库中的所有表。显然,如果只想匹配部分数据,你也可以更加精确地选择。

一旦定义了复制实例、端点和任务,向导就结束了,你将返回到 DMS 控制台。任务创建完成后,可以启动任务。

由于这是一个迁移现有数据类型的任务,当所有数据传播到目标数据库时,它将完成。

还有更多……

这显然是 DMS 能做的一个简单示例。还有其他功能和性能方面,您应在更复杂的场景中考虑。

数据库引擎

虽然这个例子使用了两个 MySQL 数据库,但也可以从一个数据库引擎迁移到另一个完整的数据库引擎,例如,从 Oracle 到 MySQL。不幸的是,这可能是一个复杂的过程,尽管这个功能非常有用,但超出了本食谱的范围。由于各种引擎之间的差异,您在迁移和转换过程中会遇到一些限制。

请参阅 AWS Schema Conversion Tool 文档,了解不同数据库引擎之间可以迁移的内容。

持续复制

还有一些关于数据持续传播的限制——只能迁移表数据,索引、用户和权限等无法持续复制。

多可用区(Multi-AZ)

对于持续复制任务,您可能希望创建一个多可用区的复制实例,以便将服务中断的影响降到最低。显然,您需要拥有一个配置相似的(如多可用区)RDS 实例作为目标,以充分获得其好处!

为了获得最佳性能,在设置复制实例时,您应确保其与目标 RDS 实例位于 相同的 可用区。

计算 DynamoDB 性能

DynamoDBDDB)是 AWS 提供的托管 NoSQL 数据库服务。

由于 DDB 定价基于已配置的读写容量单位数量,因此能够计算出您用例的需求是很重要的。

本食谱使用书面公式来估算应分配给 DDB 表的 读取容量单位RCU)和 写入容量单位WCU)。

同样重要的是要记住,虽然新的分区会自动添加到 DDB 表中,但不能自动删除。这意味着过度的分区会对性能产生长期影响,因此您需要对此保持警惕。

准备工作

所有这些计算都假设您已经为您的数据选择了一个合适的分区键。一个好的分区键确保以下几点:

  • 数据均匀分布在所有可用的分区中

  • 读写活动在时间上均匀分布

不幸的是,选择一个好的分区键非常依赖于数据,且超出了本食谱的范围。

假设所有读取操作都是强一致性的。

如何操作...

  1. 从项目的大小开始,单位为 千字节KB):

ItemSize = 项目(行)大小,以 KB 为单位

  1. 通过将数字除以 4 并向上取整,计算所需的 RCU 数量:

每项 RCPU = 项目大小 / 4(向上取整)

  1. 定义每秒预期的读取操作次数。这是您用来配置表格的其中一个数字:

所需 RCPU = 预期读取次数 * 每项 RCPU

  1. 将数字除以 3,000 来计算所需的 DDB 分区数量以达到容量:

读取分区 = 所需 RCPU / 3,000

  1. 接下来,通过将项大小除以1并向上取整,计算所需的写入容量:

每项 WCPU = 项大小 / 1(向上取整)

  1. 定义每秒预期的写入操作数。这是您用于配置表时需要使用的数字之一:

所需 WCPU = 预期写入数量 * 每项 WCPU

  1. 将数字除以1,000以计算所需的 DDB 分区数量以达到容量:

写入分区 = 所需 WCPU / 1,000

  1. 将这两个值相加,得到所需的容量分区(向上取整到整数):

容量分区 = 读取分区 + 写入分区(向上取整)

  1. 根据您计划存储的数据量计算所需的最小分区数:

大小分区 = 总大小(GB)/ 10(向上取整)

  1. 一旦您确定了针对您的用例的分区需求,请取出您之前计算中的最大值:

所需分区 = 容量分区与大小分区中的最大值

  1. 由于您的分配容量在各个分区之间平均分配,请将 RCU 和 WCU 值进行除法运算,以获得每个分区的表性能:

分区读取吞吐量 = 所需 RCPU / 所需分区

分区写入吞吐量 = 所需 WCPU / 所需分区

工作原理...

在后台,DDB 吞吐量由分配给您的表的分区数量控制。考虑到您的数据将如何分布在这些分区中非常重要,以确保您获得预期的性能并支付了相应的费用

我们通过计算数据库中项的大小开始此步骤,以便用于吞吐量计算。DDB 有一个最小考虑大小,即使操作使用的大小小于此值,也会按照分配的吞吐量向上取整。最小大小取决于操作类型:

  • 读取操作按 4 KB 的块计算

  • 写入操作按 1 KB 的块计算

然后,我们根据预期的操作数量计算所需的 RCPU 和 WCPU。这些值可以用于配置 DDB 表,因为它们代表了在最佳条件下所需的最小吞吐量。

一旦您获得了这些值,您可以使用它们来配置您的表。

接下来,我们计算每个分区键的吞吐量。这些计算依赖于了解每个分区的预期性能。数字 3,000(用于 RCU)和 1,000(用于 WCU)代表单个 DDB 分区的容量。通过以分区性能(读取和写入)来表示容量,并将它们加在一起,我们可以得到从容量角度来看所需的最小分区数。

然后,我们对总数据大小进行相同的计算。每个 DDB 分区最多可以处理 10 GB 的数据。超过这个大小的数据需要在多个分区之间拆分。

分区容量(包括读取、写入和大小)的具体数值已经稳定了一段时间,但未来可能会发生变化。请仔细检查当前值是否与此处使用的值相同,以确保完全准确。

一旦我们确定了容量和大小的最小分区数,我们将选择其中的最大值并以此为基础。这可以确保我们满足容量和大小的要求。

最后,我们将配置的容量除以分区数。这将为我们提供每个分区键的吞吐量性能,我们可以用它来与我们的用例进行确认。

还有更多...

使用 DDB 时有许多细节需要注意。以下是一些更重要/有影响力的事项。

突发容量

对于超出分配容量的表格,提供了一定的突发容量。未使用的读写容量可以保留最多五分钟(例如,300 秒,计算时使用)。依赖这种容量并不是一种好的做法,未来某个阶段无疑会导致问题。

指标

DDB 表格会自动将数据发送到 CloudWatch 指标。这是确认计算和配置容量是否满足需求的最快和最简单的方法。它还帮助你监控使用情况,跟踪随着时间推移的吞吐量需求。所有指标都显示在AWS/DynamoDB命名空间下。以下是一些用于吞吐量计算的有趣指标:

  • ConsumedReadCapacityUnits

  • ConsumedWriteCapacityUnits

  • ReadThrottleEvents

  • WriteThrottleEvents

还有其他可用的指标;有关更多详细信息,请参见Amazon DynamoDB Metrics and Dimensions文档。

最终一致性读取

使用最终一致性读取(与强一致性读取相对)将计算所需的 RCU 要求减少一半。在这个例子中,我们使用了强一致性读取,因为它适用于所有工作负载,但你应该确认你的用例是否真的需要它。如果不需要,使用最终一致性读取。

通过减少所需的读取配置容量,你可以有效降低使用 DDB 的成本

第七章:网络

在本章中,我们将涵盖以下内容:

  • 构建一个安全的网络

  • 创建 NAT 网关

  • 通过 DNS 进行金丝雀部署

  • 托管一个域

  • 基于位置的路由与故障转移

  • 网络日志记录与故障排除

介绍

网络是使用其他 AWS 服务(如 EC2、RDS 等)的基础组件。使用 VPC 和 NAT 网关等构造可以使你有能力并自信地在网络层面保护你的资源。在 DNS 层面,Route 53 为你的用户提供响应迅速且容错性强的连接,确保在多种场景下提供最佳性能。

构建一个安全的网络

在这个实例中,我们将在 AWS 中构建一个安全的网络(VPC)。该网络将包含两个公共子网和两个私有子网,分布在两个可用区中。它还将允许外部连接到公共子网,具体包括:

  • SSH(端口22

  • HTTP(端口80

  • HTTPS(端口443

构建一个安全的网络

准备工作

在继续之前,你需要了解我们要部署的区域至少有两个可用区的名称。本书中的实例通常会部署到us-east-区域,所以为了方便,你可以直接使用以下内容:

  • us-east-1a

  • us-east-1b

当你创建一个 AWS 账户时,你的可用区是随机分配的。这意味着,你账户中的us-east-1a不一定与我账户中的us-east-1a是同一个数据中心。

如何实现...

继续创建一个新的 CloudFormation 模板用于我们的 VPC。提醒一下:这是我们在本书中创建的较大的模板之一:

  1. 前两个Parameters对应我们之前讨论的可用区。我们没有为这些参数提供默认值,以保持区域的可移植性:
      Parameters:
        AvailabilityZone1: 
          Description: Availability zone 1 name (e.g. us-east-1a) 
          Type: AWS::EC2::AvailabilityZone::Name 
        AvailabilityZone2: 
          Description: Availability zone 2 name (e.g. us-east-1b) 
          Type: AWS::EC2::AvailabilityZone::Name

  1. 我们的 VPC 框架已经创建完成。此时,它还没有连接到互联网,因此对我们来说并不完全有用。我们需要添加一个互联网网关,并将其附加到我们的 VPC 上。接下来,按以下步骤操作:
      Resources: 
        # VPC & subnets 
        ExampleVPC: 
          Type: AWS::EC2::VPC 
          Properties: 
            CidrBlock: !Ref VPCCIDR 
            EnableDnsSupport: true 
            EnableDnsHostnames: true 
          Tags: 
            - { Key: Name, Value: Example VPC } 
        PublicSubnetA: 
          Type: AWS::EC2::Subnet 
          Properties: 
            AvailabilityZone: !Ref AvailabilityZone1 
            CidrBlock: !Ref PublicSubnetACIDR 
            MapPublicIpOnLaunch: true 
            VpcId: !Ref ExampleVPC 
          Tags: 
            - { Key: Name, Value: Public Subnet A } 
        PublicSubnetB: 
          Type: AWS::EC2::Subnet 
          Properties: 
            AvailabilityZone: !Ref AvailabilityZone2 
            CidrBlock: !Ref PublicSubnetBCIDR 
            MapPublicIpOnLaunch: true 
            VpcId: !Ref ExampleVPC 
          Tags: 
            - { Key: Name, Value: Public Subnet B } 
        PrivateSubnetA: 
          Type: AWS::EC2::Subnet 
          Properties: 
            AvailabilityZone: !Ref AvailabilityZone1 
            CidrBlock: !Ref PrivateSubnetACIDR 
            VpcId: !Ref ExampleVPC 
          Tags: 
            - { Key: Name, Value: Private Subnet A } 
        PrivateSubnetB: 
          Type: AWS::EC2::Subnet 
          Properties: 
            AvailabilityZone: !Ref AvailabilityZone2 
            CidrBlock: !Ref PrivateSubnetBCIDR 
            VpcId: !Ref ExampleVPC 
          Tags: 
            - { Key: Name, Value: Private Subnet B }

  1. 剩余的Parameters定义了以下内容的 IP 地址范围:

    • 整个 VPC

    • 公共子网(A 和 B)

    • 私有子网(A 和 B)

  2. 我们为子网提供的默认值将为每个子网分配 512 个 IP 地址:

AWS 会在你的 IP 空间中保留少量 IP 地址,用于 AWS 专用服务。VPC 的 DNS 服务器就是一个例子。它通常位于分配给你 VPC 的 IP 块中的第二个(*.2)IP 地址。

      VPCCIDR: 
        Description: CIDR block for VPC 
        Type: String 
        Default: "172.31.0.0/21" # 2048 IP addresses 
      PublicSubnetACIDR: 
        Description: CIDR block for public subnet A 
        Type: String 
        Default: "172.31.0.0/23" # 512 IP address 
      PublicSubnetBCIDR: 
        Description: CIDR block for public subnet B 
        Type: String 
        Default: "172.31.2.0/23" # 512 IP address 
      PrivateSubnetACIDR: 
        Description: CIDR block for private subnet A 
        Type: String 
        Default: "172.31.4.0/23" # 512 IP address 
      PrivateSubnetBCIDR: 
        Description: CIDR block for private subnet B 
        Type: String 
        Default: "172.31.6.0/23" # 512 IP address

  1. 现在我们可以开始定义Resources。我们将首先定义 VPC 本身,以及其中的两个公共子网和两个私有子网:
        # Internet Gateway 
        ExampleIGW: 
          Type: AWS::EC2::InternetGateway 
          Properties: 
            Tags: 
              - { Key: Name, Value: Example Internet Gateway } 
        IGWAttachment: 
          Type: AWS::EC2::VPCGatewayAttachment 
          DependsOn: ExampleIGW 
          Properties: 
            VpcId: !Ref ExampleVPC 
            InternetGatewayId: !Ref ExampleIGW

  1. 我们需要创建几个路由表。我们将首先关注的是公共路由表。我们会将这个路由表分配给我们创建的两个公共子网。该路由表将仅包含一条路由,用于将所有互联网流量导向我们在上一步中创建的互联网网关:
        # Public Route Table 
        # Add a route for Internet bound traffic pointing to our IGW 
        # A route for VPC bound traffic will automatically be added 
        PublicRouteTable: 
          Type: AWS::EC2::RouteTable 
          Properties: 
            VpcId: !Ref ExampleVPC 
            Tags: 
              - { Key: Name, Value: Public Route Table } 
        PublicInternetRoute: 
          Type: AWS::EC2::Route 
          DependsOn: IGWAttachment 
          Properties: 
            RouteTableId: !Ref PublicRouteTable 
            GatewayId: !Ref ExampleIGW 
            DestinationCidrBlock: "0.0.0.0/0" 
        RouteAssociationPublicA: 
          Type: AWS::EC2::SubnetRouteTableAssociation 
          Properties: 
            RouteTableId: !Ref PublicRouteTable 
            SubnetId: !Ref PublicSubnetA 
        RouteAssociationPublicB: 
          Type: AWS::EC2::SubnetRouteTableAssociation 
          Properties: 
            RouteTableId: !Ref PublicRouteTable 
            SubnetId: !Ref PublicSubnetB

  1. 我们将以类似的方式创建私有路由表。由于私有子网与互联网隔离,我们不会添加通往互联网网关的路由。请注意,如果你按照本书中的 NAT 网关配方操作,它将需要一个路由表作为输入参数——这是你想要为 NAT 路由添加的路由表:
        # Private Route Table 
        # We don't add any entries to this route table because there is
          no NAT gateway 
        # However a route for VPC bound traffic will automatically be added 
        PrivateRouteTable: 
          Type: AWS::EC2::RouteTable 
          Properties: 
            VpcId: !Ref ExampleVPC 
            Tags: 
              - { Key: Name, Value: Private Route Table } 
        PrivateSubnetAssociationA: 
          Type: AWS::EC2::SubnetRouteTableAssociation 
          Properties: 
            RouteTableId: !Ref PrivateRouteTable 
            SubnetId: !Ref PrivateSubnetA 
        PrivateSubnetAssociationB: 
          Type: AWS::EC2::SubnetRouteTableAssociation 
          Properties: 
            RouteTableId: !Ref PrivateRouteTable 
            SubnetId: !Ref PrivateSubnetB

  1. 现在我们可以关注网络的安全方面了。让我们集中注意公有子网。这些子网将用于添加负载均衡器;你还将添加堡垒主机和 NAT 网关等内容。因此,我们需要添加一个 网络 ACLNACL),并包含多个条目:

    • 允许向所有端口发送出站流量。我们公有子网中的主机可以无限制地进行出站访问。

    • 允许对临时端口(大于 1024)的入站流量。这确保了从我们的出站连接返回的包不会被丢弃。

    • 允许向低端口号提供入站访问,支持 SSH、HTTP 和 HTTPS(2280443):

              # Public NACL 
              PublicNACL: 
                Type: AWS::EC2::NetworkAcl 
                Properties: 
                  VpcId: !Ref ExampleVPC 
                  Tags: 
                    - { Key: Name, Value: Example Public NACL } 
              # Allow outbound to everywhere 
              NACLRulePublicEgressAllowAll: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: "0.0.0.0/0" 
                  Egress: true 
                  Protocol: 6 
                  PortRange: { From: 1, To: 65535 } 
                  RuleAction: allow 
                  RuleNumber: 100 
                  NetworkAclId: !Ref PublicNACL 
              # Allow outbound to VPC on all protocols 
              NACLRulePublicEgressAllowAllToVPC: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: !Ref VPCCIDR 
                  Egress: true 
                  Protocol: -1 
                  RuleAction: allow 
                  RuleNumber: 200 
                  NetworkAclId: !Ref PublicNACL 
              # Allow inbound from everywhere to ephemeral ports
                (above 1024) 
              NACLRulePublicIngressAllowEphemeral: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: "0.0.0.0/0" 
                  Protocol: 6 
                  PortRange: { From: 1024, To: 65535 } 
                  RuleAction: allow 
                  RuleNumber: 100 
                  NetworkAclId: !Ref PublicNACL 
              # Allow inbound from everywhere on port 22 for SSH 
              NACLRulePublicIngressAllowSSH: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: "0.0.0.0/0" 
                  Protocol: 6 
                  PortRange: { From: 22, To: 22 } 
                  RuleAction: allow 
                  RuleNumber: 200 
                  NetworkAclId: !Ref PublicNACL 
              # Allow inbound from everywhere on port 443 for HTTPS 
              NACLRulePublicIngressAllowHTTPS: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: "0.0.0.0/0" 
                  Protocol: 6 
                  PortRange: { From: 443, To: 443 } 
                  RuleAction: allow 
                  RuleNumber: 300 
                  NetworkAclId: !Ref PublicNACL 
              # Allow inbound from everywhere on port 80 for HTTP 
              NACLRulePublicIngressAllowHTTP: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: "0.0.0.0/0" 
                  Protocol: 6 
                  PortRange: { From: 80, To: 80 } 
                  RuleAction: allow 
                  RuleNumber: 400 
                  NetworkAclId: !Ref PublicNACL 
              # Allow inbound from VPC on all protocols 
              NACLRulePublicIngressAllowFromVPC: 
                Type: AWS::EC2::NetworkAclEntry 
                Properties: 
                  CidrBlock: !Ref VPCCIDR 
                  Protocol: -1 
                  RuleAction: allow 
                  RuleNumber: 500 
                  NetworkAclId: !Ref PublicNACL 
              NACLAssociationPublicSubnetA: 
                Type: AWS::EC2::SubnetNetworkAclAssociation 
                Properties: 
                  NetworkAclId: !Ref PublicNACL 
                  SubnetId: !Ref PublicSubnetA 
              NACLAssociationPublicSubnetB: 
                Type: AWS::EC2::SubnetNetworkAclAssociation 
                Properties: 
                  NetworkAclId: !Ref PublicNACL 
                  SubnetId: !Ref PublicSubnetB

  1. 我们也需要对私有子网做同样的事情。这些子网相对容易处理。它们应该 允许与我们 VPC 内的主机通信,因此我们只需要添加一些 NACL,允许向我们的 VPC IP 范围发送入站和出站流量:
      # Private NACL 
      PrivateNACL: 
        Type: AWS::EC2::NetworkAcl 
        Properties: 
          VpcId: !Ref ExampleVPC 
          Tags: 
            - { Key: Name, Value: Example Private NACL } 
      # Allow all protocols from VPC range 
      NACLRulePrivateIngressAllowVPC: 
        Type: AWS::EC2::NetworkAclEntry 
        Properties: 
          CidrBlock: !Ref VPCCIDR 
          Protocol: -1 
          RuleAction: allow 
          RuleNumber: 100 
          NetworkAclId: !Ref PrivateNACL 
      # Allow TCP responses from everywhere 
      NACLRulePrivateIngressAllowEphemeral: 
        Type: AWS::EC2::NetworkAclEntry 
        Properties: 
          CidrBlock: "0.0.0.0/0" 
          Protocol: 6 
          PortRange: { From: 1024, To: 65535 } 
          RuleAction: allow 
          RuleNumber: 200 
          NetworkAclId: !Ref PrivateNACL 
      # Allow outbound traffic to everywhere, all protocols 
      NACLRulePrivateEgressAllowVPC: 
        Type: AWS::EC2::NetworkAclEntry 
        Properties: 
          CidrBlock: "0.0.0.0/0" 
          Egress: true 
          Protocol: -1 
          RuleAction: allow 
          RuleNumber: 100 
          NetworkAclId: !Ref PrivateNACL 
      NACLAssociationPrivateSubnetA: 
        Type: AWS::EC2::SubnetNetworkAclAssociation 
        Properties: 
          NetworkAclId: !Ref PrivateNACL 
          SubnetId: !Ref PrivateSubnetA 
      NACLAssociationPrivateSubnetB: 
        Type: AWS::EC2::SubnetNetworkAclAssociation 
        Properties: 
          NetworkAclId: !Ref PrivateNACL 
          SubnetId: !Ref PrivateSubnetB

  1. 最后,我们将向模板中添加一些 Outputs。这些输出通常是作为其他模板或自动化组件的输入:
      Outputs: 
        ExampleVPC: 
          Value: !Ref ExampleVPC 
        PublicSubnetA: 
          Value: !Ref PublicSubnetA 
        PublicSubnetB: 
          Value: !Ref PublicSubnetB 
        PrivateRouteTable: 
          Value: !Ref PrivateRouteTable 
        PublicRouteTable: 
          Value: !Ref PublicRouteTable 
        PrivateSubnetA: 
          Value: !Ref PrivateSubnetA 
        PrivateSubnetB: 
          Value: !Ref PrivateSubnetB

  1. 你可以通过 Web 控制台或使用以下命令通过 CLI 创建 VPC:
 aws cloudformation create-stack \
 --stack-name secure-vpc \
 --template-body file://07-building-a-secure-network.yaml \
 --parameters \
 ParameterKey=AvailabilityZone1,ParameterValue=<az-1> \
 ParameterKey=AvailabilityZone2,ParameterValue=<az-2>

它是如何工作的……

当你运行这个模板时,AWS 将为你创建一个隔离的、安全的网络。尽管它包含许多网络管理员熟悉的资源和概念,但它本质上是一个空壳,你现在可以开始填充它。

例如,每个 VPC 都包含一个虚拟路由器。你看不见它,也无法登录到它进行任何特殊配置,但你可以通过修改此模板中的路由表来定制它的行为。

我们部署的 NACL 是无状态的,应视为安全组的替代品。NACL 是对安全组的 补充,安全组是有状态的,实际上比 NACL 更容易更改和管理。虽然我们配方中的 NACL 允许所有地方(0.0.0.0/0)连接到端口 22,但你可能希望使用安全组将其限制为特定的 IP 范围(例如,你的公司数据中心)。

还有更多……

事实上,实际上还有 很多 内容。尽管本配方中有大量的代码,但我们真正只涉及了 VPC 和 AWS 网络中可能的基础内容。以下是你在使用 VPC 时将遇到的一些主要 VPC 主题:

  • Direct Connect:这是一种通过专用的、独立的通道将你的 DC 连接到 VPC 的方法。这样做通常可以提供更好的网络性能,并且可能比通过互联网的 VPN 连接更便宜。

  • 虚拟专用网关VPN):你可以配置你的 VPC 通过 VPN 连接到企业数据中心。这需要你在数据中心运行支持的 VPN 硬件。

  • IPv6 支持最近已添加。我们为简化起见没有包含它。

  • VPC 端点:此功能在你的 VPC 内暴露 AWS 端点,这样你就无需通过公共互联网路由流量来访问它们。写作时只支持 S3。

  • VPC 对等连接:你可以将一个 VPC 与一个或多个 VPC 进行对等连接,以便(未加密的)流量可以在它们之间流动。IP 范围不能冲突,虽然对等连接是免费的,但你仍然需要为 VPC 之间的流量付费。不支持传递对等连接,因此如果需要流量穿越多个 VPC,你将需要 VPN/路由设备。支持跨账户的 VPC 对等连接(我们经常使用此功能),但跨区域对等连接尚不可用。

  • VPC 尺寸

    • IPv4:你可以部署介于 /28 和 /16 之间的网络。

    • IPv6:你的 VPC 的大小将固定为 /56。

    • 一旦你的 VPC 部署完成,你无法更改其大小。如果 IP 空间用尽,你唯一的选择是部署一个更大的 VPC 并迁移所有内容(哎!),或者你可以通过 VPC 对等连接来缓解这个问题。

  • VPC 流日志:你需要启用 VPC 流日志,以便监控流量并进行任何形式的网络调试。

  • 不支持多播流量。

  • 子网必须位于单一的可用区内;它们不能跨越可用区。

  • 弹性负载均衡器ELBs)如果你通过它们发送大量流量,可以扩展使用大量私有 IP 地址。设计子网时请记住这一点。

  • 每个账户每个区域可部署的 VPC 数量限制为五个。如果有必要,你可以申请增加此限制。互联网网关也有相同的限制,增加一个限制会增加另一个限制。

  • 默认 VPC:

    • 首先,默认 VPC 会在你创建账户时自动创建。它具有一些与您自己创建的 VPC 不同的属性和行为。

    • 如果你在启动 EC2 实例时没有指定子网 ID,AWS 会尝试将其启动在默认 VPC 中。

    • 它只包含公共子网。这些子网默认配置为为所有实例提供公共 IP 地址。

    • 可以删除区域中的默认 VPC。如果你不小心删除了它,或者决定撤销此操作,你需要向 AWS 提交支持票,要求他们为你创建一个新的 VPC。

另见...

  • 创建 NAT 网关 配方

创建 NAT 网关

除非有特殊需求,否则你的实例不应该直接暴露给互联网。当你的实例连接到互联网时,你必须假设它们在某个阶段会受到攻击。

这意味着大多数工作负载应该在私有子网中的实例上运行。私有子网是那些没有直接连接到互联网的子网。

为了让你的私有实例访问互联网,你需要使用网络地址转换NAT)。NAT 网关允许你的实例发起到互联网的连接,但不允许互联网发起连接。

准备工作

对于这个方案,你必须拥有以下现有资源:

  • 一个带互联网网关IGW)的 VPC

  • 一个公共子网

  • 一个私有子网路由表

你将需要公共子网和私有子网路由表的 ID。这两个资源应该位于同一可用区(AZ)。

如何操作……

  1. 从常规的 CloudFormation 模板版本和描述开始:
      AWSTemplateFormatVersion: "2010-09-09" 
      Description: Create NAT Gateway and associated route.

  1. 模板必须包含以下必需的参数:
      Parameters: 
        PublicSubnetId: 
          Description: Public Subnet ID to add the NAT Gateway to 
          Type: AWS::EC2::Subnet::Id 
        RouteTableId: 
          Description: The private subnet route table to add the NAT
          Gateway route to 
          Type: String

  1. Resources部分,定义一个将分配给 NAT 网关的弹性 IPEIP):
      Resources: 
        EIP: 
          Type: AWS::EC2::EIP 
          Properties: 
            Domain: vpc

  1. 创建 NAT 网关资源,并将你刚才在公共子网中定义的 EIP 分配给它:
      NatGateway: 
        Type: AWS::EC2::NatGateway 
        Properties: 
          AllocationId: !GetAtt EIP.AllocationId 
          SubnetId: !Ref PublicSubnetId

  1. 最后,定义到 NAT 网关的路由,并将其与私有子网的路由表关联:
      Route: 
        Type: AWS::EC2::Route 
        Properties: 
          RouteTableId: !Ref RouteTableId 
          DestinationCidrBlock: 0.0.0.0/0 
          NatGatewayId: !Ref NatGateway

  1. 将模板保存为已知文件名,例如07-nat-gateway.yaml

  2. 使用以下 CLI 命令启动模板:

 aws cloudformation create-stack \
 --stack-name nat-gateway \
 --template-body file://07-nat-gateway.yaml \
 --parameters \
 ParameterKey=RouteTableId,ParameterValue=<route-table-id> \
 ParameterKey=PublicSubnetId,ParameterValue=<public-subnet-id>

它是如何工作的……

该方案所需的参数如下:

  • 一个公共子网 ID

  • 一个私有子网路由表 ID

需要公共子网 ID 来托管 NAT 网关,NAT 网关必须具有互联网访问权限。私有子网的路由表将会更新,添加到 NAT 网关的路由。

使用 AWS NAT 网关服务意味着 AWS 为你托管并保护该服务。该服务将冗余地托管在单个可用区(AZ)中。

你可以多次使用这个方案,在每个私有子网中部署 NAT 网关。只需确保公共子网和私有子网位于同一可用区(AZ)。

在可用区(AZ)出现故障的情况下(虽然不常见,但有可能发生),你应该在每个子网中部署一个 NAT 网关。这意味着如果一个 NAT 网关下线,另一个可用区的实例可以继续正常访问互联网。你是在多个子网中部署应用程序,对吧?

这个方案只有在你创建了自己的私有子网时才有效,因为新 AWS 账户中的默认子网都是公共的。公共子网中的实例可以直接访问互联网(通过 IGW),因此不需要 NAT 网关。

另见

  • 构建安全网络方案

通过 DNS 进行金丝雀部署

金丝雀部署是一种在云中流行的部署方法。它允许你将资源的新版本与旧版本并行部署,逐步并有选择性地将部分流量引导到新的资源。

通过将一小部分流量引导到新的资源,你可以获取有价值的真实世界数据和指标。这意味着你不需要进行大爆炸部署——即一次性切换所有流量。

它还为你提供了更多的故障排除和监控的灵活性;如果你看到新资源出现错误,你可以将流量重定向回旧资源,直到问题调查完毕。

在这个操作步骤中,我们将创建进行基于 DNS 的金丝雀部署所需的资源,并将流量从一个资源切换到另一个资源(即,从旧资源到新资源)。

准备工作

这个操作步骤需要一些准备工作:

  • 为你的域名后缀设置一个 Route 53 托管区

  • 现有的 DNS 记录针对你的资源/终端节点

如何操作...

  1. 在一个新文件中,定义模板版本和描述:
      AWSTemplateFormatVersion: "2010-09-09"  
      Description: Create a weighted DNS setup for canary deployments.

  1. 开始Parameters部分和所需的参数:
      Parameters: 
        HostedZoneName: 
          Type: String 
          Description: The hosted zone to create records in 

        DomainName: 
          Type: String 
          Description: The domain name to create in the hosted zone 

        OldResource: 
          Type: String 
          Description: The older resource domain name 

        NewResource: 
          Type: String 
          Description: The newer resource domain name

  1. Parameters部分包括可选参数(例如具有默认值的参数):
      OldWeight: 
        Type: Number 
        Default: 1 
        Description: The ratio of requests to send to the older endpoint 

      NewWeight: 
        Type: Number 
        Default: 0 
        Description: The ratio of requests to send to the newer endpoint

  1. 开始模板的Resources部分,定义你的记录集组:
      Resources: 
        RecordSetGroup: 
          Type: AWS::Route53::RecordSetGroup 
          Properties: 
            HostedZoneName: !Ref HostedZoneName 
            Comment: Canary deployment record set group 
            RecordSets: 
              - Name: !Join [ ".", [ Ref: DomainName, Ref: 
                  HostedZoneName ] ] 
                Type: CNAME 
                TTL: "300" 
                SetIdentifier: Old 
                Weight: !Ref OldWeight 
                ResourceRecords: 
                  - !Ref OldResource 
              - Name: !Join [ ".", [ Ref: DomainName, Ref: 
                  HostedZoneName ] ] 
                Type: CNAME 
                TTL: "300" 
                SetIdentifier: New 
                Weight: !Ref NewWeight 
                ResourceRecords: 
                  - !Ref NewResource

  1. 保存模板并给它一个已知的文件名;例如,07-canary-deployments.yaml

  2. 使用以下 CLI 命令启动模板:

 aws cloudformation create-stack \
 --stack-name canary \
 --template-body file://07-canary-deployments.yaml \
 --parameters \
 ParameterKey=DomainName,ParameterValue=<your-domain-name> \
 ParameterKey=OldResource,ParameterValue=<old-resource-dns> \
 ParameterKey=NewResource,ParameterValue=<new-resource-dns> \
 ParameterKey=HostedZoneName,ParameterValue=<your-hosted-zone> 

  1. 准备好后,使用以下 CLI 命令更新堆栈,仅更改域名的权重:
 aws cloudformation update-stack \
 --stack-name canary \
 --parameters \
 ParameterKey=HostedZoneName,UsePreviousValue=true \
 ParameterKey=DomainName,UsePreviousValue=true \
 ParameterKey=OldResource,UsePreviousValue=true \
 ParameterKey=NewResource,UsePreviousValue=true \
 ParameterKey=OldWeight,ParameterValue=0 \
 ParameterKey=NewWeight,ParameterValue=1 \
 --use-previous-template

它是如何工作的...

本模板专注于利用 Route 53 记录集组的特性,并且最有用的属性已被参数化。

你的DomainName参数的值将作为多个CNAME记录在你的托管区中创建(如在HostedZoneName中设置),每个资源都有一个,包括旧资源和新资源。

OldResourceNewResource参数表示将会在这些目标域名之间分配传入请求。

一旦堆栈部署完成,你可以访问你的域名并看到你的资源。默认情况下,此模板将所有流量发送到旧资源终端节点。

一旦你验证了设置正确工作,你可以开始部署,通过更新堆栈将部分请求发送到新资源。

通过 CLI 更改资源记录集的权重相当复杂,因为这需要将一个复杂的 JSON 对象作为参数传递。更简单也更安全的方法是直接更新你已经部署的 CloudFormation 堆栈,只更改已存在的权重参数。

使用update-stack命令,新的权重将传播到你的记录集组成员(不中断),并且新的流量分配将开始生效。

对于没有默认值的参数,你必须明确告诉 CloudFormation 使用之前的值,以及先前提供的模板主体。

请记住,流量分配将由目标的权重与所有目标的总权重之比来决定。这意味着你可以轻松地通过将目标的权重设置为0关闭一个目标,而不管其他目标的权重值。在这个操作步骤中,我们使用了01作为简单的值来说明影响,但你可以(并且应该)使用更细粒度的参数。

域名托管

在本食谱中,我们将向你展示如何在 Route 53 中托管域名,并添加一些记录:

托管一个域名

准备工作

从技术上讲,你不需要先注册域名就可以继续进行此食谱,但如果你有一个实际的域名可用,那会更有帮助。

如何操作...

  1. 创建一个新的 CloudFormation 模板,并向其中添加以下Parameter
      Parameters: 
        DomainName: 
          Description: Your domain name (example.org) 
          Type: String

  1. 接下来,我们需要在模板中添加一个HostedZone资源,如下所示:
      Resources: 
        DNSHostedZone:  
          Type: AWS::Route53::HostedZone 
          Properties: 
            Name: !Ref DomainName

  1. 现在你已经准备好在 Route 53 中创建你的托管区域。你可以通过 CloudFormation Web 控制台进行创建,或者使用以下 CLI 命令:
 aws cloudformation create-stack \
 --stack-name example-hosted-zone \
 --template-body file://07-hosting-a-domain.yaml \
 --parameters \
 ParameterKey=DomainName,ParameterValue=<your-domain-name>

它是如何工作的...

这将创建一个 Route 53 中的托管区域。一旦堆栈创建完成,去 Web 控制台查找它。你会看到有多个名称服务器与之关联。如果你希望通过域名注册商的控制面板将域名委派给 AWS 的 Route 53 服务器,这些就是要使用的名称服务器。

还有更多...

一个没有 DNS 记录的托管区域对你来说用途有限。以下是一些你可能想要添加到模板中的记录示例:

  DNSRecords: 
    Type: AWS::Route53::RecordSetGroup 
    Properties: 
      HostedZoneId: 
        Ref: DNSHostedZone 
      RecordSets: 
      - Name: !Ref DomainName 
        Type: A 
        TTL: 60 
        ResourceRecords: 
          - "127.0.0.1" 
      - Name: !Ref DomainName 
        Type: MX 
        TTL: 60 
        ResourceRecords: 
          - "10 smtp.example.org" 
          - "20 smtp.example.org" 
      - Name: !Ref DomainName 
        Type: TXT 
        TTL: 60 
        ResourceRecords: 
          - '"v=spf1 include:spf.example.org ?all"'

注意事项:

对于MX记录中的优先级,添加记录开头的数字,后跟一个空格。

对于像TXT记录中的spf条目,通常需要加引号,你可以用单引号将双引号括起来。

它们在 Route 53 Web 控制台中的样子如下:

托管一个域名

另见...

  • 第三章中的托管静态网站食谱,存储和内容分发

基于位置的路由与故障切换

在本食谱中,我们将展示两种 Route 53 路由策略:

  • 地理位置路由

  • 故障切换路由

实际上,我们将这两种策略结合起来。浏览 AWS 文档可能会让你认为这不是一种特别常见的做法,但请理解,通过结合路由策略,你可以为性能和可用性做出很大的改进。

准备工作

鉴于我们正在演示故障切换任务,你需要在继续之前设置两个 ELB。我们假设你正在不同的区域中进行操作,但这并非严格必要。这些 ELB 需要接受 HTTP 连接(当然是端口80),并且至少需要有一个实例附加在上面(该实例通过健康检查并正在提供内容)。

第四章中的创建安全组食谱,使用 AWS 计算在两个不同的区域部署,应该正合适。

你还需要一个你想要创建为 Route 53 中新的托管区域的域名。技术上讲,你不需要将这个域名从注册商委派到 Route 53,所以你可以使用任何你选择的域名来完成这个食谱。只要记住,使用一个真实的域名,你可以将其委派给 Route 53,这样就不需要在本地主机文件或 DNS 设置中进行繁琐的操作来测试这个食谱。

总结一下,你需要以下内容:

  • 两个 ELB 的 DNS 名称

  • 两个 ELB 的托管区域 ID

  • 你选择的域名

如何操作…

  1. 继续创建一个新的 CloudFormation 模板。我们将为之前提到的项目添加一些Parameters
      Parameters: 
        DomainName: 
          Description: Your domain name (example.org) 
          Type: String 
        LoadBalancerDNSNameRegionA: 
          Description: The DNS name of your ELB in region A 
          Type: String 
        LoadBalancerHostedZoneRegionA: 
          Description: The Hosted Zone ID of your ELB in region A 
          Type: String 
        LoadBalancerDNSNameRegionB: 
          Description: The DNS name of your ELB in region B 
          Type: String 
        LoadBalancerHostedZoneRegionB: 
          Description: The Hosted Zone ID of your ELB in region B 
          Type: String

  1. 我们想要的第一个Resource是我们域名的HostedZone资源。将其添加到你的模板中,如下所示:
      Resources: 
        DNSHostedZone: 
          Type: AWS::Route53::HostedZone 
          Properties: 
            Name: !Ref DomainName

  1. 为了让故障转移自动发生,我们需要设置一些健康检查。我们希望在两个区域的 ELB 上进行健康检查:
        RegionAHealthCheck: 
          Type: AWS::Route53::HealthCheck 
          Properties: 
            HealthCheckConfig: 
              FailureThreshold: 3 
              FullyQualifiedDomainName: !Ref LoadBalancerDNSNameRegionA 
              Port: 80 
              RequestInterval: 30 
              ResourcePath: "/" 
              Type: HTTP 
            HealthCheckTags: 
              - { Key: Name, Value: Region A Health Check } 
        RegionBHealthCheck: 
          Type: AWS::Route53::HealthCheck 
          Properties: 
            HealthCheckConfig: 
              FailureThreshold: 3 
              FullyQualifiedDomainName: !Ref LoadBalancerDNSNameRegionB 
              Port: 80 
              RequestInterval: 30 
              ResourcePath: "/" 
              Type: HTTP 
            HealthCheckTags: 
              - { Key: Name, Value: Region B Health Check }

  1. 我们现在将为你的域名创建四个记录集:

    • a.<your-domain>-PRIMARY

    • b.<your-domain>-PRIMARY

    • a.<your-domain>-SECONDARY(故障转移到b

    • b.<your-domain>-SECONDARY(故障转移到a

  2. 这些记录对应于 ELB A 和 ELB B(或者如果你更喜欢用站点 A 和 B),它们将允许每个区域在健康检查失败时故障转移到另一个区域。

  3. 让我们从两个 ELB 的主要记录开始:

        RegionAPrimary: 
          Type: AWS::Route53::RecordSet 
          Properties: 
            Name: !Join [ ., [ a, Ref: DomainName ] ] 
            Type: A 
            HostedZoneId: !Ref DNSHostedZone 
            AliasTarget: 
              HostedZoneId: !Ref LoadBalancerHostedZoneRegionA 
              DNSName: !Ref LoadBalancerDNSNameRegionA 
              Failover: PRIMARY 
              SetIdentifier: primary-region-a 
              HealthCheckId: !Ref RegionAHealthCheck 
        RegionBPrimary: 
          Type: AWS::Route53::RecordSet 
          Properties: 
            Name: !Join [ ., [ b, Ref: DomainName ] ] 
            Type: A 
            HostedZoneId: !Ref DNSHostedZone 
            AliasTarget: 
              HostedZoneId: !Ref LoadBalancerHostedZoneRegionB 
              DNSName: !Ref LoadBalancerDNSNameRegionB 
              Failover: PRIMARY 
              SetIdentifier: primary-region-b 
              HealthCheckId: !Ref RegionBHealthCheck

  1. 现在添加次要(故障转移)记录:
        RegionAFailover: 
          Type: AWS::Route53::RecordSet 
          Properties: 
            Name: !Join [ ., [ a, Ref: DomainName ] ] 
            Type: A 
            HostedZoneId: !Ref DNSHostedZone 
            AliasTarget: 
              HostedZoneId: !Ref LoadBalancerHostedZoneRegionB 
              DNSName: !Ref LoadBalancerDNSNameRegionB 
              Failover: SECONDARY 
              SetIdentifier: secondary-region-a 
        RegionBFailover: 
          Type: AWS::Route53::RecordSet 
          Properties: 
            Name: !Join [ ., [ b, Ref: DomainName ] ] 
            Type: A 
            HostedZoneId: !Ref DNSHostedZone 
            AliasTarget: 
              HostedZoneId: !Ref LoadBalancerHostedZoneRegionA 
              DNSName: !Ref LoadBalancerDNSNameRegionA 
              Failover: SECONDARY 
              SetIdentifier: secondary-region-b

  1. 现在我们将添加我们域名的根/apex 记录。为了这个食谱的目的,我们将把来自北美的请求发送到区域/ELB A,把来自全球其他地区的请求发送到区域/ELB B:
        NorthAmericaGeolocation: 
          Type: AWS::Route53::RecordSet 
          Properties: 
            Name: !Ref DomainName 
            Type: A 
            HostedZoneId: !Ref DNSHostedZone 
            AliasTarget: 
              HostedZoneId: !Ref DNSHostedZone 
              DNSName: !Join [ ., [ a, Ref: DomainName ] ] 
            GeoLocation: 
              ContinentCode: NA # North America 
            SetIdentifier: geolocation-region-a 
        RestOfWorldGeolocation: 
          Type: AWS::Route53::RecordSet 
          Properties: 
            Name: !Ref DomainName 
            Type: A 
            HostedZoneId: !Ref DNSHostedZone 
            AliasTarget: 
              HostedZoneId: !Ref DNSHostedZone 
              DNSName: !Join [ ., [ b, Ref: DomainName ] ] 
            GeoLocation: 
              CountryCode: "*" # Rest of world 
            SetIdentifier: geolocation-region-b

  1. 就是这样!现在你可以在 AWS 网页控制台或通过 CLI 运行这个 CloudFormation 模板,如下所示:
 aws cloudformation create-stack \
 --stack-name geolocation-failover \
 --template-body file://07-routing-based-on-location.yaml \
 --parameters \
 ParameterKey=DomainName,ParameterValue=gitrepository.com \
 ParameterKey=LoadBalancerDNSNameRegionA,ParameterValue=<elb-a> \
 ParameterKey=LoadBalancerHostedZoneRegionA, \
        ParameterValue=<elb-zoneid-a> \
 ParameterKey=LoadBalancerDNSNameRegionB,ParameterValue=<elb-b> \
 ParameterKey=LoadBalancerHostedZoneRegionB, \
        ParameterValue=<elb-zoneid-b>

它是如何工作的…

我们有效地构建了一个小型决策树,如下所示:

Route 53 流量

为了自己测试,你需要能够从其他区域执行 DNS 响应。在接下来的截图中,我们通过 AWS 工作空间在北美(左侧)配置了一台机器,而我们实际的位置在澳大利亚(右侧)。

正常操作(地理位置路由)

在正常操作下,我们的北美用户(左侧)将连接到区域 A,为了实际原因,我们已将其部署在us-east-1,尽管它可以在任何区域。我们的澳大利亚用户(右侧)将连接到区域 B,这是我们指定为全球其他地区的区域。同样,出于实际考虑,我们将该站点部署到了ap-southeast-2区域:

左侧:区域 A 为北美用户提供服务。右侧:区域 B 为澳大利亚用户提供服务。

区域 A 故障

为了模拟区域 A 的故障,我们将简单地停止附加到 ELB 的 Web 服务器,如下所示:

    [root@ip-172-30-0-153 ec2-user]# service nginx stop
    Stopping nginx:                                            [  OK  ]

短暂的时间后,Web 控制台会显示区域 A 的健康检查失败:

区域 A 健康检查失败

我们的北美用户(左)现在看到的是区域 B 的内容:

左:由于故障转移,区域 B 为北美用户提供服务。右:区域 B 正常为澳大利亚用户提供服务。

区域 B 故障

我们现在会翻转脚本,模拟在区域 B 中发生相同的场景。这次,区域 B 中的 Web 服务器已停止,但区域 A 中的服务器是健康的:

区域 B 健康检查失败

区域 A 的内容现在将同时展示给北美用户和那些被指定为世界其他地区(包括澳大利亚)的用户:

左:区域 A 正常为北美用户提供服务。右:区域 A 由于故障转移为澳大利亚用户提供服务。

还有更多…

Route 53 提供了其他几个有用的路由策略,因此你应该思考哪种最适合你:

  • 基于延迟的路由:该策略使 Route 53 DNS 服务器以提供最低延迟的 IP 地址响应你。这不一定是地理位置上最接近你的端点。

  • 加权路由:这允许你根据加权系统将流量分配到不同的端点。你可以在两个区域之间进行 50/50 的分配,或者选择 90/10 的比例。

另见…

  • 第三章中的托管静态网站方案,存储与内容交付

  • 第四章中的创建安全组方案,使用 AWS 计算

网络日志记录与故障排除

使用虚拟化基础设施的一个好处是,你可以获得一种在物理硬件中难以或昂贵实现的内部视图。能够快速启用网络设备级别的日志记录是一个非常有用的功能,尤其是在熟悉 VPC、子网、NACL、路由和安全组之间的交互时。

在这个方案中,我们将启用网络资源的日志记录。你可以一直这样做,为自己提供一个额外的监控和审计层,或者在故障排除时选择性地启用它,节省额外的存储费用。

正在准备中

对于这个方案,你必须有一个 VPC 用于记录活动。

如何操作…

  1. 从定义模板版本和描述开始:
      AWSTemplateFormatVersion: "2010-09-09" 
      Description: Flow logs for networking resources

  1. 定义模板的 Parameters。在本例中,它只是启用日志记录的 VpcId
      Parameters: 
        VpcId: 
          Type: String 
          Description: The VPC to create flow logs for

  1. 创建模板的 Resources 部分,并定义用来发送流日志的日志组:
      Resources: 
        LogGroup: 
          Type: AWS::Logs::LogGroup 
          DeletionPolicy: Delete 
          Properties: 
            LogGroupName: LogGroup

  1. 接下来,我们定义将授予流日志服务写入日志权限的 IAM 角色:
        IamRole: 
          Type: AWS::IAM::Role 
          Properties: 
            AssumeRolePolicyDocument: 
              Version: "2012-10-17" 
              Statement: 
                - 
                  Effect: Allow 
                  Principal: 
                    Service: vpc-flow-logs.amazonaws.com 
                  Action: sts:AssumeRole 
            Policies: 
              - 
                PolicyName: CloudWatchLogsAccess 
                PolicyDocument: 
                  Version: "2012-10-17" 
                  Statement: 
                    - 
                      Action: 
                        - logs:CreateLogGroup 
                        - logs:CreateLogStream 
                        - logs:PutLogEvents 
                        - logs:DescribeLogGroups 
                        - logs:DescribeLogStreams 
                      Effect: Allow 
                      Resource: "*"

  1. 最后,我们定义流日志本身:
        FlowLog: 
          Type: AWS::EC2::FlowLog 
          DependsOn: LogGroup 
          Properties: 
            DeliverLogsPermissionArn: !GetAtt IamRole.Arn 
            LogGroupName: LogGroup 
            ResourceId: !Ref VpcId 
            ResourceType: VPC 
            TrafficType: ALL

  1. 保存模板,并为其命名一个已知的文件名,如 07-flow-logs.yaml

  2. 通过以下命令创建模板,创建流日志及相关资源:

 aws cloudformation create-stack \
 --stack-name VpcFlowLogs \
 --template-body file://07-flow-logs.yml \
 --capabilities CAPABILITY_IAM \
 --parameters ParameterKey=VpcId,ParameterValue=<your-vpc-id>

  1. 启动后(假设你有网络活动),你将能够在 CloudWatch 日志控制台中看到你的流日志。

工作原理…

此模板所需的唯一参数是目标 VPC ID。我们专门选择一个 VPC 来开启流日志,因为这能给我们带来最大 效益。虽然您可以为子网和 弹性网络接口ENIs)单独启用流日志,但如果在 VPC 上启用流日志,您将获得该 VPC 中所有网络资源的流日志——这包括子网和 ENIs。

在资源部分,我们首先明确地定义了用于 存储 流日志的日志组。如果您没有自己创建日志组(并在流日志资源配置中指定它),则会为您自动创建一个日志组。这意味着您仍然可以使用流日志,但日志组不会由 CloudFormation 管理,需要手动维护(例如删除)。我们还为我们的日志组设置了 删除策略delete。这意味着如果 CloudFormation 堆栈被删除,日志组也会被删除,这对于这种演示来说是可以的。如果是在 真实 环境中(如生产环境),请移除 DeletionPolicy 属性及其值。

默认情况下,当创建日志组的堆栈被删除时,CloudWatch 日志组 不会 被删除。这样可以让您保留任何重要的日志,但可能会产生持续的费用。

接下来,我们定义了要使用的 IAM 角色。通过 AssumeRolePropertyDocument 值,我们授予 AWS 流日志服务假设此角色的权限。如果没有这个访问权限,流日志服务将无法访问该账户。在 Policies 属性中,我们授予该角色创建和更新日志组与日志流的权限。

最后,既然我们已经创建了相关的依赖资源,我们就定义了流日志资源本身。您不需要按依赖顺序定义资源,但如果这样做通常会更容易阅读。在该资源中,我们还定义了与之前定义的日志组的 DependsOn 关系,以便在创建流日志时,日志组已准备好接收流日志。

最后一步是启动您创建的模板,并将 VPC ID 作为参数传递。由于该模板创建了一个 IAM 角色,允许 VPC 服务将日志发送到 CloudWatch 日志,因此创建堆栈的命令必须给出 CAPABILITY_IAM 标志,以表明您已意识到启动该模板可能带来的影响。

还有更多...

启用日志记录仅仅是故障排除过程的开始。在使用流日志时,还有一些其他事项您需要注意。

日志格式

一旦启用日志记录,您可以在 CloudWatch 日志控制台中查看日志。以下是您将在流日志中看到的信息类型的摘要(按顺序):

  • VPC 流日志版本

  • AWS 账户 ID

  • 网络接口的 ID

  • 流量的源 IPv4 或 IPv6 地址

  • 流量的目标 IPv4 或 IPv6 地址

  • 流量的源端口

  • 流量的目标端口

  • 流量的 IANA 协议编号

  • 传输的数据包数量

  • 传输的字节数

  • 捕获窗口的开始时间(以 Unix 秒为单位)

  • 捕获窗口的结束时间(以 Unix 秒为单位)

  • 与流量相关的操作;例如,ACCEPTREJECT

  • 流日志的日志状态;例如,OKNODATASKIPDATA

要识别协议,请将协议号字段与 IANA 协议号列表 进行对照。

更新

你不能更新现有流日志的配置;如果你想更改任何相关设置,必须删除并重新创建流日志。这也是为什么明确创建和管理关联日志组的另一个原因。

漏掉的部分

某些流量不会被流日志服务捕获,如下所示:

  • 到亚马逊 DNS 服务器的流量(即在你分配的范围内 x.x.x.2

  • 亚马逊 Windows 许可证激活的流量(显然仅适用于 Windows 实例)

  • 到实例元数据服务的流量(即 IP 地址 169.254.169.254

  • DHCP 流量

  • 到默认 VPC 路由器的保留 VPC IP 地址的流量(即在你分配的范围内 x.x.x.1

另见

  • 构建安全网络 配方

第八章:安全性和身份

在本章中,我们将涵盖:

  • 与 AWS 账户联合

  • 创建 SSL 证书

  • 作为服务的活动目录

  • 创建用户

  • 创建实例角色

  • 跨账户用户角色

  • 存储密钥

介绍

安全性是使用云服务时最关键的领域之一。确保安全性做得好非常重要,因为良好的安全实践会相互强化,导致能力和控制的良性循环。

有许多工具和 AWS 服务可以确保您的云基础设施比您自己的资源更安全,甚至达到同样的安全水平。

AWS IAM 是 AWS 安全性的核心。它提供了极其精细的权限控制,允许(和拒绝)特定用户访问您的资源。

与 AWS 账户联合

本教程将展示如何从您的 Active Directory 联合身份,并使用 AD 组和 IAM 角色为多个 AWS 账户提供不同的访问级别。

从高层次来看,我们将拥有一个被指定为身份验证账户(Auth Account)的 AWS 账户。用户将登录到该账户并被分配一个角色。这个角色几乎没有权限,因为我们不希望他们在身份验证账户中做任何事情。然而,他们将能够通过角色切换访问另一个 AWS 账户;我们称这个账户为应用账户

这是一种相对常见的模式,用户可以访问多个 AWS 账户,并使用角色切换在它们之间切换——所有操作都使用经过 AD 后端验证的凭据,以及从 AD 组派生的访问权限。

联邦

准备工作

在继续之前,您需要以下内容:

  • 一个 Simple AD 实例。请参阅作为服务的活动目录教程。

  • 一个访问 URL 的名称,您的用户将使用该 URL 进行登录(即 bluthcorp.awsapps.com)。

  • 两个 AWS 账户。其中一个是您的身份验证 账户,另一个是您的应用 账户

  • 您 VPC 中的一台 Windows 服务器,已加入您的 Simple AD 域,并安装了远程服务器管理工具,以便我们能够管理组和用户。

如果您使用启动向导启动 Windows 服务器,它将为您提供在启动时加入域的选项。请注意,服务器需要使用具有以下两个 AWS 托管策略的实例角色运行:AmazonEC2RoleForSSM 和 AmazonSSMReadOnlyAccess。

如何操作...

这个教程分为五个部分:

  • 活动目录配置

  • 身份验证账户策略配置

  • 身份验证账户角色配置

  • 简单 AD 目录配置

  • 应用账户角色配置

活动目录配置

我们的第一个任务是创建 Active Directory 中所需的组:

  1. 如下图所示,创建一个名为AWSPowerUser的组:

  1. AWSReadOnly组执行相同操作:

  1. 我们现在将创建几个用户,第一个是Lucille,如下图所示:

  1. 下一个用户将是Buster。现在让我们添加他:

  1. Lucille将成为我们的超级用户,因此我们将她添加到AWSPowerUser组:

  1. 我们对Buster完全不信任。正如他的名字所示,他容易弄坏东西。让我们将他添加到AWSReadOnly组:

Auth 账户策略配置

现在我们需要在我们的 Auth 账户中创建一个策略。记住,这是LucilleBuster访问 AWS 控制台时最初登录的账户。我们实际上想要给他们非常有限的访问权限。事实上,我们将允许他们做的唯一事情就是尝试切换到应用账户中的某个角色。

  1. 访问 Auth 账户中的 IAM 控制台并创建一个新的策略:

AWS 将这种类型的策略称为客户管理策略

  1. 将此策略命名为AllowAssumeRole。为它提供一个描述,以帮助你记住它的用途。然后应用以下策略文档。确保将应用账户的账户编号添加到策略中:
      { 
        "Version": "2012-10-17", 
        "Statement": [ 
            { 
              "Sid": "Stmt1487396837000", 
              "Effect": "Allow", 
              "Action": [ 
                  "sts:AssumeRole" 
              ], 
              "Resource": [ 
                  "arn:aws:iam::<app-acct-number>:role/*" 
              ] 
            } 
        ] 
      }

Auth 账户策略配置

Auth 账户角色配置

现在我们将创建两个角色。这些角色将对应于我们在 Active Directory 中定义的组:

  • AWSPowerUserCanAssumePowerUser

  • AWSReadOnlyCanAssumeReadOnly

  1. 首先创建CanAssumePowerUser角色:

  1. 我们希望这个角色是 AWS 目录服务角色,所以在继续之前一定要选择它:

  1. 将我们已经创建的AllowAssumeRole策略附加到此角色:

提示:你可以使用搜索框过滤角色,使其更容易找到。

  1. 点击“创建角色”以确认:

  1. 现在,继续为CanAssumeReadOnly角色做相同的操作。再次附加我们之前创建的AllowAssumeRole策略:

Simple AD 配置

现在,我们需要完成在目录中启用用户账户以登录 AWS 管理控制台的过程。

  1. 打开浏览器,访问 AWS 目录服务控制台,并编辑你的 Simple AD 目录配置。输入你选择的访问 URL:

  1. 现在,我们希望为此服务启用 AWS 管理控制台。点击它以继续到下一步:

  1. 我们已经创建了角色并为其分配了策略。所以选择“使用现有角色”,如下图所示:

  1. CanAssumePowerUser角色开始。我们需要将其映射到我们在 AD 中创建的AWSPowerUser组(Lucille所在的那个):

  1. 搜索AWSPowerUser,然后继续下一步:

  1. 现在你需要为CanAssumeReadOnly角色重复这些步骤。将其映射到我们在 AD 中创建的AWSReadOnly角色:

应用账户角色配置

现在是时候配置我们的应用账户了。在这个账户中,我们需要创建一些新角色,并在这些新角色和我们在 Auth 账户中创建的角色之间建立信任关系:

  1. 首先,进入 Auth 账户的 IAM 控制台并创建一个新角色。这个角色将是PowerUserRole

  1. 这个角色将是跨账户访问类型的角色。确保选择这种类型:

  1. 系统会提示你输入一个 AWS 账户 ID。这是 Auth 账户的账户 ID:

  1. 对于这个角色,我们将使用 AWS 托管的PowerUserAccess策略,所以现在就附加这个策略:

  1. 在确认页面上点击“创建角色”,然后我们就可以进入下一步:

  1. AWS 将自动为我们创建信任关系。不幸的是,这并不完全正确,所以我们需要编辑它:

  1. 我们希望在我们的 Auth 账户中拥有CanAssumePowerUser角色的任何人都能切换到应用账户中的PowerUserRole。因此,我们需要对信任关系做一个小的更改,如下所示(记得将账户 ID 替换为你自己的):
      { 
        "Version": "2012-10-17", 
        "Statement": [ 
            { 
              "Effect": "Allow", 
              "Principal": { 
                 "AWS": "arn:aws:iam::<auth-account-number>:
                    role/CanAssumePowerUser" 
              }, 
              "Action": "sts:AssumeRole" 
            } 
        ] 
      }

  1. 重复这些步骤,创建一个名为ReadOnlyRole的角色,并附加 AWS 托管的ReadOnlyAccess策略:

  1. 再次,我们需要更新信任策略。在这里,我们将允许CanAssumePowerUserCanAssumeReadOnly都切换到ReadOnlyRole。这样做对于那些希望避免在控制台上误操作的管理员非常有用:
      { 
        "Version": "2012-10-17", 
        "Statement": [ 
            { 
              "Effect": "Allow", 
              "Principal": { 
                  "AWS": "arn:aws:iam::<auth-account-number>:
                     role/CanAssumeReadOnly" 
              }, 
              "Action": "sts:AssumeRole" 
            }, 
            { 
              "Effect": "Allow", 
              "Principal": { 
                  "AWS": "arn:aws:iam::<auth-account-number>:
                    role/CanAssumePowerUser" 
              }, 
              "Action": "sts:AssumeRole" 
            } 
        ] 
      }

应用账户角色配置

这就是我们的最后一步。现在是时候进行测试了。

它是如何工作的...

  1. 访问你为 Simple AD 目录分配的访问 URL(例如,bluthcorp.awsapps.com/console)。用Lucille用户的凭证登录,以便我们可以测试我们的PowerUserRole

  1. 如果你在 AWS 控制台中点击,你会发现你几乎无法做任何事情。这是因为你当前被一个只允许你切换角色(在应用账户中的)的策略所限制。那么,让我们试着这么做。点击右上角的账户名,然后选择“切换角色”:

  1. 在下一个页面,你需要输入应用账户的账户 ID 和你希望切换的角色:PowerUserRole。点击“切换角色”将使你以PowerUserRole身份登录到应用账户:

  1. 现在,你应该在应用账户下拥有一个活跃的 PowerUserRole 会话。你应该记得我们为该角色分配了一个 PowerUserAccess 策略。因此,你应该可以自由地在该账户中执行几乎所有操作,使用用户 Lucille 的配置文件(IAM 和组织管理是显著的例外)。如果你再次点击你的名字,你将看到登录时为你分配的角色以及当前活跃的角色信息:

  1. 尝试切换到 ReadOnlyRole。验证你无法创建任何资源,可能通过尝试创建一个新的 EC2 密钥对或创建一个空的安全组来验证:

  1. 登出并返回 Simple AD 的访问 URL。使用用户 Buster 的凭据登录。再次,你会看到你在 Auth 账户中无法做太多操作:

  1. 你现在应该能够切换到应用账户中的 ReadOnlyRole。试试看,确保它能正常工作。你可以使用角色历史记录快捷方式,以避免再次输入账户号码和角色名称:

  1. 最后,试着将 Buster 切换到应用账户中的 PowerUserRoleBuster 绝对不应该有权访问此角色,你应该会看到类似下面的错误页面:

还有更多…

  • 完全相同的设置也可以通过你现有的 Active Directory 安装来实现,即使它位于 AWS 之外的你的数据中心。你需要将 Simple AD 替换为 AD Connector:

与 AD Connector 的联合身份验证

  • 你还可以使用 ADFS 和 SAML 2.0 来启用从现有 AD 安装到 AWS 的联合身份验证。这将消除用户使用 *.awsapps.com 域名登录控制台的需求,同时也不再需要身份验证账户。

另见

  • 作为服务的 Active Directory 配方

创建 SSL 证书

基于 SSL 的通信现在已成为事实上的标准——不安全的方式已经不再是足够好

AWS 提供 AWS 证书管理器 (ACM) 服务,用于配置 AWS 支持的 SSL 证书,你可以将其用于 AWS 资源,如 弹性负载均衡器 (ELB) 和 CloudFront。

ACM 是免费的!证书本身没有费用。你只需按常规为所使用的基础资源付费。

如何操作...

  1. 运行 CLI 命令,包含你要为其创建证书的域名(你可以使用 * 作为通配符):
 aws acm request-certificate --domain-name <your-domain>

  1. 你现在可以在 ACM 控制台中看到请求,但请注意请求仍在等待处理中:

  1. 检查你的域名管理电子邮件。你将收到一条请求确认,类似以下信息:

  1. 一旦你批准请求,你将收到一条确认消息:

  1. 你现在可以在 AWS ACM 控制台中看到证书已经准备好使用:

  1. 使用标识符值将证书应用到你的资源:

它是如何工作的……

使用 CLI 工具是创建证书请求的最快、最简单方法。在你计划使用证书的区域创建证书;即 ELB(s) 所在的区域。

如果你计划将证书与 CloudFront 一起使用,你必须在 us-east-1 区域 中创建它。

在请求创建后,AWS 会通过发送批准电子邮件到多个标准电子邮件地址来确认请求的有效性,这些地址是根据你的域名的惯例和 WHOIS 信息生成的。批准电子邮件将发送到以下地址:

  • 域名注册人

  • 技术联系人

  • 管理联系

  • 以及以下地址:

    • administrator@<your-domain>

    • hostmaster@<your-domain>

    • postmaster@<your-domain>

    • webmaster@<your-domain>

    • admin@<your-domain>

在使用证书之前,你必须至少接受其中一个批准。

一旦批准,你可以在其他资源的配置中使用标识符值,例如 EC2 ELB(s) 和 CloudFront 分发。

还有更多……

虽然 ACM 使得为你的应用程序获取 SSL 证书变得非常简单,但仍有一些限制需要注意。

EC2 实例

你会在文档中注意到,只有 ELB 和 CloudFront 支持 ACM 证书。你不能直接将 ACM 证书应用到 EC2 实例上。

虽然这是一个限制,但在实际操作中并不是一个大问题。通常,你不希望将实例直接暴露到互联网——它们应该位于 ELB/ALB 后面,以保证安全性、性能和管理。如果你提供静态资源,CloudFront 将更加安全,且在较低成本下性能更好。

导入证书

你可以将自己的证书导入 ACM,以便它们可以与 ELB(s) 和 CloudFront 一起使用。这样做的原因可能是你已经从第三方提供商购买了证书,或需要特定的签名授权。

CloudFormation

你也可以作为 CloudFormation 堆栈的一部分请求证书。这非常适合确保每个资源都有一个特定的证书,且每次部署时都独一无二。

这是一个 CloudFormation YAML 示例代码片段,用于创建证书,类似于本食谱前面的示例:

Resources: 
  MyCertificate: 
    Type: "AWS::CertificateManager::Certificate" 
    Properties: 
      DomainName: <your-domain>

作为服务的活动目录

本食谱将向你展示如何部署一个 AWS 简单活动目录Simple AD)服务。

Simple AD 由 Samba 4 提供支持,是一个与 Microsoft Active Directory 兼容的托管服务。它可以与许多需要 Active Directory 支持的应用程序兼容,并提供了许多常用的 Active Directory 功能,包括以下内容:

  • 用户账户

  • 单一登录(Kerberos)

  • 组成员资格

  • 域名加入

它还与 AWS 提供的其他服务集成,如下所示:

  • AWS 管理控制台

  • WorkMail

  • WorkDocs

  • WorkSpaces 和 WorkSpaces 应用程序管理器

AWS 将为你管理目录的备份和恢复,采用每日快照以及能够执行时间点恢复的方式。

不支持的功能包括以下内容:

  • 与其他 AD 域的信任关系

  • DNS 动态更新

  • 架构扩展

  • MFA

  • LDAPS

  • PowerShell AD 命令

  • FSMO 角色转移

Simple AD 的理想使用场景是你不需要高级的 AD 功能,并且支持的用户数少于 5,000 人。如果其中任何一项不符合你的需求,你可能需要考虑使用 AWS 完全成熟的 Microsoft Active Directory 服务。不过,选择这条路径时,你需要做好面对更高复杂度和成本的准备。

准备工作

在继续之前,我们需要以下信息:

  • 你的目录的 FQDN(完全合格域名)(例如,megacorp.com/)。

  • 管理目录的密码。此密码对应将为你创建的Administrator用户。注意,密码长度应在 8 到 64 个字符之间,并且需要包含以下四个组中的三个组的字符:

    • 小写字母

    • 大写字母

    • 数字

    • 非字母数字字符

  • 我们将要部署到的 VPC 的 ID。

  • 该 VPC 中两个子网的 ID。这些子网需要位于不同的可用区(Availability Zones)。

  • 你希望部署的目录的大小。你可以选择Small(小型)或Large(大型)。

域控制器将在你选择的两个子网中各部署一个。它们将通过大量端口进行相互通信。理想情况下,这些子网应该位于同一层级(tier)中,并且没有任何 NACL 阻止控制器之间的通信。

如果由于某些原因,你在 VPC 层中使用 NACL(网络访问控制列表)限制流量,你需要查阅 AWS 文档,获取需要允许的端口列表。

更多细节,请访问docs.aws.amazon.com/directoryservice/latest/admin-guide/prereq_simple.html

如何操作...

  1. 创建一个新的 CloudFormation 模板文件。我们将首先填充与我们之前提到的所有要求相对应的Parameters(参数):
      AWSTemplateFormatVersion: '2010-09-09' 
      Parameters: 
        FullyQualifiedName: 
          Description: The fully qualified name for the directory
            (e.g. megacorp.com) 
          Type: String 
          AllowedPattern: '^([a-zA-Z0-9]+[\\.-])+([a-zA-Z0-9])+$' 
        Password: 
          Description: The password for the directory Administrator 
          Type: String 
          NoEcho: true 
        VpcId: 
          Description: The ID of the VPC to deploy to 
          Type: AWS::EC2::VPC::Id 
        SubnetIds: 
          Description: Subnets where the directory will be deployed to
            (pick at least 2) 
          Type: List<AWS::EC2::Subnet::Id> 
        DirectorySize: 
          Description: The size of the directory to deploy 
          Type: String 
          AllowedValues:  
            - Small 
            - Large

  1. 接下来,我们定义我们的Resources(资源)。尽管将部署两个 Simple AD 域控制器,但在这里我们只需创建一个资源:
      Resources: 
        ExampleDirectory: 
          Type: AWS::DirectoryService::SimpleAD 
          Properties:  
            Name: !Ref FullyQualifiedName 
            Password: !Ref Password 
            Size: !Ref DirectorySize 
            VpcSettings:  
              SubnetIds:  
                - !Select [ 0, Ref: SubnetIds ] 
                - !Select [ 1, Ref: SubnetIds ] 
              VpcId: !Ref VpcId

  1. 现在,你可以在 CloudFormation 网页控制台中运行此模板,或者通过 CLI 按如下方式运行:
 aws cloudformation create-stack \ 
 --stack-name example-directory \ 
 --template-body file://08-active-directory-as-a-service.yaml \ 
 --parameters \ 
 ParameterKey=FullyQualifiedName,ParameterValue=<fqdn> \ 
 ParameterKey=Password,ParameterValue=<password> \ 
 ParameterKey=VpcId,ParameterValue=<vpd-id> \ 
 "ParameterKey=SubnetIds,ParameterValue='<subnet-1>,<subnet-2>'" \ 
 ParameterKey=DirectorySize,ParameterValue=<Small/Large>

它是如何工作的...

创建目录将需要几分钟时间。目录状态变为 Active 后,你可以继续进行后续的设置和集成任务。你的目录列表页面最终会显示类似于以下的目录列表:

点击目录 ID 将显示更多关于目录的详细信息,类似于这样:

还有更多...

  • Administrator 账户的密码无法恢复或重置。请确保将此密码保存在安全的地方。

  • 你可能会在 EC2 控制台中看到一个额外的安全组。这个组是目录控制器所必需的(尽管你不会在控制台中看到它们作为 EC2 实例出现)。

  • 该目录将包含一个以 AWSAdminD- 为前缀的账户。这个账户对于 AWS 执行维护任务(如备份和 FSMO 角色转移)是必要的。删除该账户或更改其密码几乎肯定是一个不好的主意。

另见

  • 第七章中的构建安全网络方案,网络

创建用户

在介绍这个方案之前,我们需要简要地讨论一下身份与访问管理IAM)。它是免费的,并且在每个账户上都已启用。它允许你创建组和用户,并允许你通过策略分配精确控制他们可以做什么以及不能做什么。

默认情况下,组和用户在你分配给他们AWS 管理的策略客户管理的策略(由你管理的策略)之前,没有任何权限。你应尽可能使用 AWS 管理的策略,以避免创建和维护自己的策略。

还有第三种类型的策略,称为内联策略。请谨慎使用。实际上,我们通常只在 CloudFormation 模板中看到它。

你几乎永远不想将策略直接分配给用户。如果你走这条路,将来你会给自己带来很多麻烦。相反,你应该将策略应用于组,然后将用户分配到这些组。幸运的是,这个过程非常简单,我们将带你一步步完成。

IAM 仪表板提供了一个 URL,IAM 用户可以使用该 URL 登录到 Web 控制台(前提是你已为他们分配了密码并允许他们进行此操作)。如果需要,你也可以自定义这个IAM 登录链接。别忘了将这个 URL 给你创建的任何 IAM 用户,让他们知道去哪里登录。

在你自定义之前,它看起来像这样:

https://<account-id>.signin.aws.amazon.com/console

现在,直接跳入吧。没有理由不使用 IAM。从今天开始吧!

准备工作

继续操作所需的只是已安装 CLI 工具并配置了可以调用 AWS IAM API 的配置文件。如果没有配置文件,你也可以跟随方案步骤,使用 AWS Web 控制台来完成,过程是一样的。

如何操作...

  1. 运行以下 CLI 命令来创建一个新组:
 aws iam create-group --group-name <group-name>

  1. 输出看起来像这样:
      { 
          "Group": { 
            "Path": "/", 
            "GroupId": "AGPAIHM2XJ2ELQTNYBFQQ", 
            "Arn": "arn:aws:iam::067180688831:group/PowerUsers", 
            "GroupName": "PowerUsers" 
          } 
      }

  1. 目前该组没有权限执行任何操作,因此你需要将策略附加到该组。你可以使用此命令来实现(但遗憾的是,如果命令执行成功,它不会提供任何反馈):
 aws iam attach-group-policy \
 --group-name <group-name> \
 --policy-arn <policy-arn>

  1. 你可以在 AWS IAM Web 控制台中找到你想要附加的亚马逊资源名称ARN)。你也可以运行以下 CLI 命令来获取策略列表:
 aws iam list-policies

  1. 在这个例子中,我们正在处理PowerUsers,所以我们要附加以下 ARN,它对应 AWS 为权限用户提供的托管策略:
 arn:aws:iam::aws:policy/PowerUserAccess

  1. 现在我们可以通过运行这个 CLI 命令来创建一个新用户:
 aws iam create-user --user-name <new-username>

  1. 你将得到如下的响应:
      { 
        "User": { 
            "UserName": "lucille.bluth", 
            "Path": "/", 
            "CreateDate": "2017-02-19T06:16:50.558Z", 
            "UserId": "AIDAIU5P6ESCGYTVGACFE", 
            "Arn": "arn:aws:iam::07180688831:user/lucille.bluth" 
        } 
      }

  1. 如果你希望授予此用户访问 Web 控制台的权限,你需要为其创建一个登录配置文件。你可以这样做:
 aws iam create-login-profile --user-name <username> \
 --password <password> \
 --password-reset-required

  1. 强制密码重置是一个好的实践。API 应该像这样回应:
      { 
         "LoginProfile": { 
              "UserName": "lucille.bluth", 
              "CreateDate": "2017-02-19T06:29:06.244Z", 
              "PasswordResetRequired": true 
         } 
      }

  1. 要授予 API 用户访问权限,他们需要一组 API 密钥。使用以下命令生成密钥:
 aws iam create-access-key --user-name <username>

  1. 输出结果看起来像这样:
      { 
          "AccessKey": { 
            "UserName": "lucille.bluth", 
            "Status": "Active", 
            "CreateDate": "2017-02-19T06:59:45.273Z", 
            "SecretAccessKey": "abcdefghijklmnopqrstuvwxyz", 
            "AccessKeyId": "AAAAAAAAAAAAAAAAAAAA" 
          } 
      }

  1. 访问密钥只能被检索一次。生成并显示给你之后,无法再次获取。如果你丢失了访问密钥,必须重新生成一组新的密钥。

  2. 这个用户仍然没有任何操作权限;这是因为他们还没有加入任何组。让我们将其加入第 1 步中创建的组:

 aws iam add-user-to-group \
 --group-name <group-name> \
 --user-name <username>

请注意,不幸的是,这个命令也不会返回任何输出。你可以通过运行此命令来验证是否成功:

 aws iam list-groups-for-user --user-name <username>

  1. 你应该看到类似这样的内容:
      { 
          "Groups": [ 
              { 
                 "Path": "/", 
                 "CreateDate": "2017-02-19T07:24:46Z", 
                 "GroupId": "AGPAIHM2XJ2ELQTNYBFQQ", 
                 "Arn": "arn:aws:iam::067180688831:group/PowerUsers", 
                 "GroupName": "PowerUsers" 
              } 
          ] 
      }

还有更多…

这基本涵盖了如何创建 IAM 组和用户并为其分配策略的基本知识。以下是我们多年来遇到的一些 IAM 提示和陷阱:

  • 用户可以存在于多个组中。利用这一点。

  • 然而,组不能存在于其他组中。

  • 用户可以拥有多个 API 密钥。当他们需要执行密钥轮换时,这是必要的。

  • 你可以(并且应该)为你的 IAM 用户定义一个强密码策略。

  • PowerUserAccess策略很好,但不允许 IAM 访问。一开始这似乎不是问题;然而,如果你受此策略限制,当你运行创建 EC2 实例 IAM 角色的 CloudFormation 堆栈时,会遇到问题。

  • IAM 是一个全球性服务,这意味着用户和组是全球性的,而不是特定于某个区域。默认情况下,用户可以在任何区域使用 AWS 服务。

  • EC2 密钥对是特定于区域的,而不是特定于 IAM 用户。换句话说,IAM 用户没有与之关联的 SSH 密钥。

  • 你的 IAM 用户名和密码(以及访问密钥)不会提供 SSH 或 RDP 访问正在运行的实例的权限。这些服务的凭证是单独管理的。

  • 你可以为一个组或用户分配最多 10 个策略。

  • 你还应该考虑为 IAM 用户账户启用 MFA 以增加安全性。主要用于访问 Web 控制台,但你也可以配置策略,使得 API 调用时必须使用 MFA。你可以选择硬件和软件令牌。一个好的经验法则是,IAM 用户使用软件令牌,root 登录使用硬件令牌。通过 SMS 的 MFA 功能很快会推出,目前正在公测中。

另见

  • 与 AWS 账户联合方法

  • 跨账户用户角色方法

创建实例角色

本配方相对简短,但它包含了一个对任何 AWS 平台新手来说非常重要的概念。理解并利用 EC2 的 IAM 角色将显著降低你的凭证丢失风险,可能也会让你晚上睡得更好。简而言之,实例角色帮助你将 AWS 凭证从服务器和代码库中移除。

角色包含一个或多个策略。我们将创建一个包含一些 AWS 托管策略以及一个内联策略的角色。顾名思义,AWS 托管策略是由 AWS 创建并完全控制的策略。内联策略将由我们创建,并将嵌入到我们的角色定义中。

我们将使用的 AWS 托管策略将允许只读访问 S3 和 EC2 API。我们创建的内联策略将允许写入 CloudWatch 日志。稍后在本配方中我们会讨论为什么你可能选择或不选择托管策略。

如何执行...

  1. 创建一个新的 CloudFormation 模板文件,并添加第一个 Resource。这个资源将是我们包含托管策略引用的角色,并且还包括我们的内联策略:
      AWSTemplateFormatVersion: '2010-09-09' 
      Resources: 
        ExampleRole: 
          Type: AWS::IAM::Role 
          Properties: 
            AssumeRolePolicyDocument: 
              Version: "2012-10-17" 
              Statement: 
                - 
                  Effect: Allow 
                  Principal: 
                    Service: 
                      - ec2.amazonaws.com 
                  Action: 
                    - sts:AssumeRole 
            ManagedPolicyArns: 
              - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess 
              - arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess 
            Path: / 
            Policies: 
              - 
                PolicyName: WriteToCloudWatchLogs 
                PolicyDocument: 
                  Version: "2012-10-17" 
                  Statement: 
                    - 
                      Effect: Allow 
                      Action: 
                        - logs:CreateLogGroup 
                        - logs:CreateLogStream 
                        - logs:PutLogEvents 
                        - logs:DescribeLogStreams 
                      Resource: "*"

  1. 我们现在需要创建一个 InstanceProfile 资源。一个配置文件封装了一个 IAM 角色,简而言之,它的作用就是如此。一个配置文件只能包含一个 IAM 角色,所以不太清楚为什么 AWS 要建立这层额外的抽象;可能他们计划为配置文件提供除了角色之外的其他属性:
      ExampleInstanceProfile: 
        Type: AWS::IAM::InstanceProfile 
        Properties: 
          Roles: 
            - !Ref ExampleRole 
          Path: /

  1. 为了方便起见,我们将添加一些 Outputs,在堆栈创建后提供配置文件名称和 ARN:
      Outputs: 
        ExampleInstanceProfile: 
          Value: !Ref ExampleInstanceProfile 
        ExampleInstanceProfileArn: 
          Value: !GetAtt ExampleInstanceProfile.Arn

  1. 现在你可以通过 CloudFormation 网络控制台或 CLI 创建你的实例角色,方法如下:
 aws cloudformation create-stack \
 --stack-name example-instance-profile \
 --template-body file://08-creating-instance-roles.yaml \
 --capabilities CAPABILITY_IAM

这个角色现在可以分配给你的 EC2 实例。在第五章中的 将日志文件馈送到 CloudWatch 日志 配方中,管理工具 章节展示了如何在启动 EC2 实例时,使用 CloudFormation 定义一个角色并将其分配给实例。

它是如何工作的...

这到底是如何解决硬编码 AWS API 密钥的问题的呢?当你将角色分配给 EC2 实例时,会发生一些非常有趣的事情。该实例的元数据将返回一组短期有效的 API 密钥。你可以通过发送 HTTP 请求到元数据 URL 来检索这些密钥(这是 EC2 实例用来获取自己信息的服务):

http://169.254.169.254/latest/meta-data/iam/security-credentials/<role name>

对这个 URL 发出的 curl 请求的输出大概是这样的:

      { 
        "Code" : "Success", 
        "LastUpdated" : "2017-02-17T11:14:23Z", 
        "Type" : "AWS-HMAC", 
"AccessKeyId" : "AAAAAAAAAAAAAAAAAAAA", 
        "SecretAccessKey" : "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", 
        "Token" : "token", 
        "Expiration" : "2017-02-17T12:14:23Z" 
      }

如果你取出响应中返回的 AccessKeyIdSecretAccessKey,你就可以使用它们查询 AWS API。基于分配给实例的角色所应用的策略,将决定实例在使用这些密钥时能够执行的具体 API 操作。

最有趣的部分是,你根本不需要太担心处理这些密钥(虽然了解这些在幕后是如何工作的确很有用)。例如,AWS CLI 工具会在执行任何 CLI 命令之前自动为你获取这些密钥。AWS SDK 也是如此。

假设有一个场景,你的开发人员正在构建一个需要从 S3 获取文件的应用程序。只要他们使用 AWS SDK 来完成这项工作,且应用程序运行在已经分配了包含允许从 S3 获取文件的策略的 EC2 实例上,那么应用程序根本不需要任何凭证!SDK 会为你处理对元数据服务的查询。

AWS SDK 支持几乎所有广泛使用的编程语言,因此不再有理由在配置文件或源代码中硬编码 AWS 凭证。

你将在 IAM 控制台的 Roles 部分看到你实例的角色列表:

点击角色将显示更多细节,例如已分配给它的策略:

还有更多……

  • IAM 是一个全球服务。这意味着你创建的角色和策略将在所有区域中可用。

  • 你将在 AWS 网络控制台中找到所有可用的 AWS 管理策略。它们有很多,所以不要害怕使用搜索栏。

  • 还有第三种策略叫做客户管理策略。这些策略由你管理,并将在 AWS 控制台中与 AWS 管理策略一起显示。

  • 从 2017 年 2 月起,已可以将 IAM 角色附加到现有/运行中的 EC2 实例。这在之前是做不到的,角色只能在实例启动时分配。

  • AWS 会自动并定期轮换由元数据服务返回的凭证。

  • 并不总是适合使用 AWS 管理策略。例如,如果服务器需要写入 CloudWatch 日志,你可能会想直接为它分配提供完全访问权限的 AWS 管理策略。然而,如果这么做,你也会赋予服务器删除日志组和流的权限。这几乎肯定是不可取的。你需要在应用策略之前检查它们,并在适当的情况下使用内联策略或客户管理策略。这里适用最小权限原则。

另见

  • 第五章中的将日志文件输入到 CloudWatch 日志食谱,管理工具

跨账户用户角色

使用多个账户来配置资源(例如,开发环境和生产环境)可以提供一种爆炸半径保护——即使在最坏的情况下,任何问题或损害也仅限于发生问题的账户,而不会影响整个 AWS 环境。

在账户之间创建和假设角色是管理多个账户访问的最佳方式。特定的角色提供了明确且清晰的权限声明,既可以轻松审查,也可以在需要时撤销。

本方法提供了一种在多个账户之间扩展访问权限的方式,同时不会妥协你的安全性。

准备工作

本方法假设你已经创建并准备好两个 AWS 账户。

在一个账户中(即账户,称为账户 A),你需要一个 IAM 用户。

虽然你需要使用账户的根凭证来设置账户中的第一个角色,但不要在日常使用中使用它们。根账户拥有对账户内任何操作的权限,只有在必要时才应使用根账户。

如何操作...

  1. 从一个版本和描述开始新的模板:
      AWSTemplateFormatVersion: "2010-09-09" 
      Description: This template creates a role that can be assumed 
       from another account.

  1. 模板将接受一个参数——可以假设该角色的源账户:
      Parameters: 
        SourceAccountNumber: 
          Type: String 
          Description: The AWS account number to grant access to assume
            the role. 
          AllowedPattern: "[0-9]+" 
          MaxLength: "12" 
          MinLength: "12"

  1. 该角色本身将包括信任角色和一个示例策略:

该角色具有对目标账户的完全访问权限。

        Resources: 
          CrossAccountRole: 
            Type: "AWS::IAM::Role" 
            Properties: 
              Path: "/" 
              AssumeRolePolicyDocument: 
                Version: "2012-10-17" 
                Statement: 
                  - Sid: "" 
                    Action: "sts:AssumeRole" 
                    Effect: Allow 
                    Principal: 
                      AWS: 
                        !Sub "arn:aws:iam::${SourceAccountNumber}:root" 
              Policies: 
                - PolicyName: DoEverything 
                  PolicyDocument: 
                    Version: "2012-10-17" 
                    Statement: 
                      - Action: 
                          - "*" 
                        Effect: Allow 
                        Resource: "*" 
                        Sid: DoEverything

  1. 最后,我们创建一个输出,使得更容易检索目标角色的 ARN:
      Outputs: 
        RoleARN: 
          Description: The Role ARN that can be assumed by the
            other account. 
          Value: !GetAtt CrossAccountRole.Arn

  1. 保存模板并赋予一个已知名称,例如08-target-account-role.yaml

  2. 通过使用 CLI 工具,将角色部署到目标账户(即账户 B):

 aws cloudformation create-stack \
        --stack-name CrossAccountRole \
 --template-body file://src/08-target-account-role.yaml \
 --parameters \
        ParameterKey=SourceAccountNumber, \
        ParameterValue=<your-source-account-number> \
 --capabilities CAPABILITY_IAM

  1. 从 CloudFormation 堆栈的输出中获取(仅)目标角色的 ARN:
 aws cloudformation describe-stacks \
 --stack-name CrossAccountRole \
 --query 'Stacks[0].Outputs[0].OutputValue' \
 --output text

  1. 在你的源账户(即账户 A)中,确认你可以通过手动调用 CLI 工具来假设目标角色:
 aws sts assume-role \
 --role-arn <your-target-role-arn> \
 --role-session-name CrossAccountRole

它是如何工作的...

虽然跨账户角色在管理多个 AWS 账户时极其有用,但它们的配置并不是最直观的。这里有一个图示,展示了资源及其相互作用:

本方法的前几个步骤是通过使用 CloudFormation 以清晰且可重复的方式创建目标 IAM 角色

你必须明确指定允许假设该角色的 AWS 账户号码。如果你希望允许多个账户假设该角色,只需在角色的AssumeRolePolicyDocument属性中添加更多声明。

在此模板中创建的示例策略授予对目标账户的完全访问权限(因为ActionResource都设置为*)。你应根据需要调整此策略。

定义一个输出值以返回 IAM 角色的 ARN,将使得稍后在本方法中更容易获取生成的 ID。

然后,我们在目标账户中启动模板。由于此模板会创建 IAM 资源,你必须提供--capabilities CAPABILITY_IAM参数。如果你没有可以启动它的现有 IAM 用户,使用 AWS Web 控制台(在使用根凭证登录后)。这意味着你不需要在目标账户中创建 IAM 用户。

部署模板后,您将不再需要手动登录账户——您可以直接从可信(源)账户假设新创建的角色。使用目标账户中的 IAM 角色意味着您的日常访问不再需要多个密码,这样可以减少管理和安全存储的工作量。您只需拥有一个密码——即源 IAM 用户的密码。

当堆栈创建完成后(这不会花费太多时间,因为只创建了一个资源),您可以通过 describe-stacks 调用快速提取目标角色的 ARN,并结合特定的 --query 参数。JMESPath 查询 Stacks[0].Outputs[0].OutputValue 获取第一个堆栈返回的第一个输出的 OutputValue 属性,我们知道它将是目标角色 ARN,因为模板中只有一个输出。

最后,示例 assume-role 命令将返回目标角色的凭证(即 ACCESS_KEY_IDSECRET_ACCESS_KEY)。您可以将其用于 API 调用,无论是通过 CLI 工具还是某个 SDK。请记住,这些令牌是短暂的。

请参阅下一部分,了解通过创建配置文件,使用 CLI 工具更方便地使用凭证的方法。

还有更多...

就像有多种方式使用角色一样,也有多种方式利用跨账户角色。

AWS CLI 配置文件

使用跨账户角色最简单的方法之一是将其配置为 AWS CLI 工具使用的配置文件。这意味着您只需通过更改执行命令时使用的配置文件,就可以快速轻松地切换账户。

要做到这一点,您必须在 CLI 配置文件中定义目标角色。假设您的 default 配置文件位于源账户中(即账户 A)。

将以下代码片段添加到 Linux 和 Mac 电脑上的 ~/.aws/config 文件中,Windows 电脑上的 C:\Users\[USERNAME]\.aws\config 文件中:

[profile accountb] 
role_arn = <your-target-account-role-arn> 
source_profile = default

要使用切换角色,您只需在命令中传递 --profile 参数:

 aws --profile accountb ...

参见

  • 创建用户 配方。

存储密钥

新管理员在开始使用基础设施即代码时常犯的一个错误是将密钥(密码、访问密钥等)提交到他们的代码库中。尽管这样可以让他们的基础设施更具可重复性,但也更容易导致凭证被泄露。一旦某些内容进入版本控制系统,就很难也很麻烦将其删除(这正是版本控制的意义所在!)。即使您删除了它,也几乎不可能知道它是否已经被某个不该看到的人查看或复制过。

在本配方中,我们将介绍并使用开源工具 Unicreds

Unicreds 是 Python 工具 Credstash 的 Golang 移植版:github.com/fugue/credstash

尽管功能非常相似,Unicreds 的优势在于它是跨平台的并且没有依赖!

由于该模式完全由 AWS 服务支持,它消除了管理(和担心)密码库、共享密码以及将敏感信息提交到 SCM 的需要。

你甚至可以使用 Unicreds 存储非机密信息,因为它提供了一种方便的方式来存储和共享设置,而不需要运行或维护任何服务器!

准备工作

你必须在目标系统上安装 Unicreds。

由于是用 Golang 编写的,它可以轻松地作为独立的二进制应用程序分发——无需安装程序或依赖项。

所有平台的发布版本可以在 github.com/Versent/unicreds/releases 上找到。

这些命令假设你的默认配置文件具有创建 KMS 密钥和 DynamoDB 表的权限。你可以通过在所有命令中传递 --profile 参数来覆盖使用的配置文件。你还必须配置好 AWS 区域设置。

如何操作...

  1. 创建一个 KMS 密钥,并记录下返回的密钥 ID:
 aws kms create-key --query 'KeyMetadata.KeyId' --output text

  1. 创建密钥的别名:

Unicreds 使用 alias/credstash 别名,使其与 Credstash 兼容。

 aws kms create-alias --alias-name 'alias/credstash' \
        --target-key-id "<your-key-id>"

  1. 设置 Unicreds 所需的资源:
 unicreds setup

  1. 使用 put 命令存储机密:
 unicreds put foo bar

  1. 使用 get 命令获取机密信息:
 unicreds get foo

它是如何工作的...

这里是一个高层次的图示,展示了这些 Unicreds 命令所涉及的组件:

我们通过创建将用于加密机密信息的 KMS 密钥来开始这个流程。请注意,我们永远看不到这个密钥——它只存在于 KMS 中。你所能做的就是请求 KMS 为你加密或解密数据。

你可以将自己的密钥导入到 KMS(这样你就可以在需要时在 AWS 外部解密机密信息),但这不是 Unicreds 正常工作的必需步骤。create-key 命令返回密钥的 GUID,将在接下来的步骤中使用。

别名使得处理 KMS 密钥变得更加简单。你可以在大多数命令中使用别名代替完整的密钥 ARN。更重要的是,它使你能够清楚地知道你正在处理哪个密钥,从而可以快速、轻松且自信地分配访问权限。

Unicreds 使用的密钥的默认别名是alias/credstash。虽然这开始时可能有些令人困惑,但这意味着 Unicreds 向后兼容 Credstash。你可以选择自己的别名;只需要在执行其他命令(如setupput等)时覆盖它。

setup 命令会在你的 AWS 账户中创建所需的资源。这实际上意味着创建一个 DynamoDB 表来存储机密信息。

一旦一切设置完成,你就可以开始使用 Unicreds 存储机密信息了。在这个示例中,机密信息是用(极具创意的)密钥 foo 和值 bar 存储的。

在这一阶段,你可以进入 AWS 控制台中的 DynamoDB,并查看credential-store表中存储的值。如果需要,你还可以更改在运行credstash setup命令时使用的 DDB 表名称。

一旦有秘密被存储,你可以通过get命令将其取出。需要记住的是,不必从存储秘密的同一台机器上进行操作。只要 AWS 用户/角色拥有足够的权限使用 KMS 服务并访问 DDB 表,他们就能检索到秘密。

还有更多...

Unicreds 利用了 AWS 的内置功能,因此你可以获得企业级解决方案,而无需为运行自己的服务器而增加额外的负担。以下是一些其他有用的做法,可以使你的秘密更加安全。

密钥别名

创建多个 KMS 密钥,并通过唯一别名引用它们,是限制特定应用程序或团队进行秘密存取的好方法。

你可以为团队提供他们自己的别名,而不是使用默认的alias/credstash别名,这样你可以放心,他们无法看到或写入其他团队的秘密。

秘密读取角色

由于 IAM 权限的精细粒度,你可以轻松地为不同的访问角色划分 AWS 资源的访问权限。

使用以下 IAM 策略,你可以确保用户/角色只能读取秘密值(使用特定的密钥和表),但无法设置或更改它们:

{ 
  "Version": "2012-10-17", 
  "Statement": [ 
    { 
      "Action": [ 
        "kms:Decrypt" 
      ], 
      "Effect": "Allow", 
      "Resource": "arn:aws:kms:us-east-1:<your-account-id>:
         key/<your-key-id>" 
    }, 
    { 
      "Action": [ 
        "dynamodb:GetItem", 
        "dynamodb:Query", 
        "dynamodb:Scan" 
      ], 
      "Effect": "Allow", 
      "Resource": "arn:aws:dynamodb:us-east-1:<your-account-id>:
         table/credential-store" 
    } 
  ] 
}

秘密写入角色

秘密读取角色的反面是秘密写入角色。

将此代码片段添加到角色的相关 IAM 策略部分,以赋予其设置秘密值的权限,但不能检索它们:

{ 
  "Version": "2012-10-17", 
  "Statement": [ 
    { 
      "Action": [ 
         "kms:GenerateDataKey" 
      ], 
      "Effect": "Allow", 
      "Resource": "arn:aws:kms:us-east-1:<your-account-id>:
         key/<your-key-id>" 
    }, 
    { 
      "Action": [ 
        "dynamodb:PutItem" 
      ], 
      "Effect": "Allow", 
      "Resource": "arn:aws:dynamodb:us-east-1:<your-account-id>:
         table/credential-store" 
    } 
  ] 
}

put-file 命令

你可以将整个文件存储到 Unicreds 中。只需使用put-file命令:

unicreds put-file foo bar.txt

版本控制

虽然安全地存储你的秘密是一个好的开始,但定期更改/旋转密码、密钥和其他秘密仍然是一个良好的实践。

Unicreds 内置了版本控制支持,这意味着你可以更新你的秘密,同时保留先前版本的记录。

当你多次使用相同的秘密名称时,Unicreds 会自动为该值创建新版本。你可以通过在getput命令中提供版本参数来获取特定版本的秘密:

unicreds get foo 1

另见

  • 创建用户的操作步骤

第九章:估算成本

本章内容包括:

  • 计算成本

  • 估算 CloudFormation 模板成本

  • 购买预留实例

  • 估算总拥有成本

介绍

刚开始使用 AWS 时,最难适应的事情之一是几乎每一样使用的东西都要付费。AWS 的最大优势之一就是你只为使用的部分付费。这使得许多人刚开始使用 AWS 时很难迅速回答这会花多少钱?的问题,因为他们并不清楚自己当前到底用多少!

传统的基础设施或数据中心设置中,许多成本在初始支出或年度合同中支付。由于 AWS 没有前期费用,且少有长期承诺,因此关于成本的常规思维方式被颠倒了。

有许多有用的工具可以帮助你更好地估算 AWS 使用成本。别忘了每个 AWS 服务页面都有定价部分。虽然一些定价模型开始时可能会有些困惑,但很快就能理解。

计算成本

AWS 简单月度计算器是一个网站应用程序,旨在帮助你估算和预测 AWS 成本。通过列出你预计将消耗的资源,你可以计算按需付费的费用,这也是 AWS 的计费方式——没有前期费用。

准备就绪

为了有效使用 AWS 简单月度计算器,你需要提前了解你每月将使用的具体服务和资源。

你还需要了解一些细节,比如每月的数据传输量和你需要存储的数据量。在 AWS 中,你需要为进出 AWS 的数据付费(例如,访问你网站的访客),但不需要为 AWS 服务之间的数据传输付费(例如,从 EC2 实例到 RDS 数据库的传输)。

如何操作...

  1. 访问计算器网站,calculator.s3.amazonaws.com/index.html

  1. 根据你的账户情况选择/取消选择免费使用层选项——如果账户少于 12 个月,你有资格使用免费层。

  1. 在添加资源之前,确保你选择了正确的区域,因为不同区域的价格可能不同:

  1. 通过从左侧菜单选择相关服务并填写详细信息来添加资源:

  1. 持续根据需要添加资源:

  1. 一旦你添加了所有资源,查看该标签上的预计月账单:

  1. 在确认估算的详细信息后,点击“保存并分享”按钮以添加一些关于报告的附加元数据。所有字段都是可选的:

  1. 将为你的报告生成一个特定的一次性 URL,你可以将其与他人分享:

工作原理...

计算器的准确性完全依赖于您预测需求和使用情况的能力——当您刚开始使用 AWS 时,这可不是一件容易的事!

不幸的是,并非所有 AWS 服务都在计算器中(一个显著的例外是 AWS Lambda)。对于这些服务,您需要根据服务的定价页面自行计算费用。

服务和资源的费用可能因区域而异。一般来说,us-east-1区域是最便宜的,并且拥有最多的服务(并非所有服务都在所有区域提供),因此如果您想知道最低成本的选项,可以选择该区域。其他区域的价格因供需、运营成本以及 AWS 未公开的其他原因而有所不同。

一些服务(例如 DynamoDB、Lambda 等)有免费的服务层,即使您的账户不符合标准免费层的条件(即账户超过 12 个月),也适用这些免费层。这些服务在其特定的计算器页面上会有一条说明,详细列出包含的内容:

完成后,您可以为估算报告生成一个特定的 URL,并与他人分享。访问此 URL 不需要身份验证,因此请不要在报告中放入任何敏感信息。唯一的保护措施是该 URL 不太可能被猜中(因为它只是计算器网站加上一个 GUID 参数)。

另见

  • 估算 CloudFormation 模板费用配方

估算 CloudFormation 模板的费用

本书中的大多数配方都是使用 CloudFormation(AWS 的基础设施即代码服务)进行管理和部署的。

准备工作

对于这个配方,您需要一个现有的 CloudFormation 模板。模板不需要作为堆栈部署,只需提供文件即可。

在这个例子中,我们将使用来自第四章的模板,使用 AWS 计算服务,以安全地访问私有实例:06-create-database-with-automatic-failover.yaml

如何操作...

  1. 运行命令以生成报告:
 aws cloudformation estimate-template-cost \
 --template-body \
        file://06-create-database-with-automatic-failover.yaml \
 --parameters ParameterKey=VPCId,ParameterValue=test \
 ParameterKey=SubnetIds,ParameterValue=\"test,test\" \
 ParameterKey=DBUsername,ParameterValue=test \
 ParameterKey=DBPassword,ParameterValue=test \
 --query Url \
 --output text

  1. 点击或复制并粘贴 URL 到浏览器中查看报告:

  1. 点击左侧菜单中的 Amazon RDS 查看单个服务页面的详细信息:

  1. 点击“估算您的月度账单”以查看模板资源的总概述:

它是如何工作的...

estimate-template-cost命令需要模板的所有参数。正如在第一步中所看到的,实际值并不重要,因为模板实际上不会被启动。您只需确保提供的值类型与该参数要求的类型匹配(例如,在此模板中,SubnetIds的值必须是一个值列表)。

您指定的区域非常重要!一些服务(但不是所有服务)可能会根据所在区域的不同而产生不同的费用。通常,us-east-1区域是最便宜的。

在命令的末尾,我们通过 --query 参数将输出限制为仅报告 URL。

您可以与他人共享生成的 URL,但除非您自己跟踪该 URL,否则无法检索之前的报告。

在计算器网站上,模板的资源会预先填充,即使您无法立即看到它们。报告始终默认显示 Amazon EC2 服务页面,因此您需要通过左侧菜单访问相关的服务页面(在本例中为 Amazon RDS)。

最后,您可以在“每月账单估算”标签页中查看您的模板每月成本的完整报告。如果您的模板包含许多不同类型的资源/服务,您将会在此看到它们的汇总。

另见

  • 第六章中的 创建具有自动故障转移的数据库 配方,数据库服务

  • 计算成本 配方

购买预留实例

预留实例可能会引起一些混淆,并且常常被误解。以下是一些指导,帮助您走上正确的道路:

  • 与普通按需实例相比,预留实例没有任何区别的技术特性。

  • 预留实例并不是某种特定类型或类别的实例。

  • 简而言之,购买预留实例即是购买按需实例的折扣小时费率,该实例的属性与预留实例相匹配。

  • 折扣后的小时费率会根据您支付的预付款金额有所不同。通常来说,您预付的金额越多,折扣越大。

当您购买预留实例时,您需要指定以下属性:

  • 平台(Linux/Windows)

  • 范围(区域或可用区)

  • 实例类型(例如,m3.large)

  • 租户(共享或专用)

  • 产品类别(标准或可转换)

  • 期限(1-12 个月或 1-3 年)

  • 支付选项(无预付款、部分预付款、全额预付款)

我们将在本节稍后更详细地探讨这些内容。现在,让我们开始,看看如何进行购买。

准备工作

您需要一个 AWS 账户,并且对您希望预留的实例类型及其预留时间有所了解。请参考之前提到的预留实例属性,以获取您继续操作所需的准确信息。

您选择的支付选项将显著影响您购买预留实例时支付的价格:

  • 无预付款:这意味着您现在不需要支付任何费用,但无论是否有与预留实例匹配的实例,您都将按整个期限收取折扣后的小时费率。还需注意,选择此选项将限制您选择标准预留实例的期限为 1 年,选择可转换预留实例的期限为 3 年(我们将在本节稍后讨论这些内容)。

  • 部分预付款:这种预留方式意味着您支付较少的预付款,然后只按您实际使用的实例小时数收取折扣后的小时费率。

  • 全额预付:顾名思义,您需要为整个期限支付实例的全部费用。在该期限内,您的匹配实例的小时费率将获得有效的 100% 折扣。

一旦您了解了实例预订的所有属性,就可以继续进行购买。

如何操作...

  1. 进入 EC2 网页控制台,选择“预留实例”,然后选择“购买预留实例”:

  1. 现在我们需要搜索您希望购买的实例类型。在此示例中,我们将选择以下内容:
  • 平台:Linux/UNIX

  • 租用类型:默认

  • 提供类:标准

  • 实例类型:t2.micro

  • 期限:1 个月-12 个月

  • 支付选项:全额预付

  1. 显然,选择最适合您的工作负载的选项。您几乎肯定想在这里选择“默认”作为租用类型。专用租用/实例运行在只会被一个客户(您)占用的硬件上,且费用更高:

  1. 控制台将返回实例预订的价格。请注意,由于我们没有选择“仅显示预留容量的选项”,我们看到的是一个单一的结果,即适用于我们当前在控制台中查看的区域的预订。可以将其视为区域级别的预订:

  1. 现在尝试选择“仅显示预留容量的选项”,并注意当前区域显示了所有可用区。您可以将其视为可用区级别的预订。显然,选择这些选项将使您锁定到一个特定的可用区;但是,您也会获得一个容量预订(接下来讨论):

  1. 选择适合您的预订,然后点击“添加到购物车”,再点击“查看购物车”。

  2. 下一页将显示您即将购买的摘要。点击“购买”继续。请注意,这是不可逆的操作。预留实例不能取消。请谨慎选择!

它是如何工作的...

完成购买后,您的预订将标记为“待付款”,然后很快会变为“激活”状态(还有一个可能的状态是“已退役”)。

一旦您的预订状态为“激活”,折扣将自动应用于匹配的实例。AWS 将此小时折扣称为计费优惠

选择可转换预订类立即排除了除三年期之外的所有选项。作为回报,您将比标准预订获得更多的灵活性,因为如果您决定该预订不再满足您的需求,您可以转换为等值或更高价值的预订,当然需要支付差额。

如果你为特定的可用区做了预留,AWS 还会为你提供容量预留,这将确保该区域实例的可用性。例如,如果你的工作负载需要在整个可用区发生故障时保持一定的容量,这可能是你需要考虑的事项。此类事件通常会导致未受影响区域的新实例请求激增;然而,没有容量预留的客户可能会发现他们的新实例请求无法得到满足,因为缺乏容量(这并不罕见),从而错失机会或迫使他们为另一个区域和/或实例类型发出新请求,同时还得祈祷好运。

账单优惠(按小时折扣)不同,账单优惠在购买后立即应用,而容量预留则由你在匹配预留属性的区域启动的第一个实例使用。

还有更多...

  • 代表你启动实例的服务(如自动扩展、Elastic Beanstalk 等)也有资格享受按小时折扣。

  • 标准预留可以选择 1 年或 3 年。前面提到过,可转换预留固定为 3 年。

  • 在合并账单模型下,预留实例的折扣适用于所有子账户。例如,如果你为账户 A 购买了一个预留实例,但没有与其属性匹配的服务器,那么预留将自动应用于账户 B 中的匹配实例。这仅适用于账单优惠,而不适用于容量优惠。

  • 预留实例可以在 AWS 市场上出售。如果预留不再适合你的需求,这很有用。请注意,你需要拥有美国银行账户才能进行此操作。

  • 如果预留实例似乎不符合你的工作负载类型,你可以考虑使用计划实例。

估算总拥有成本

AWS TCO 计算器旨在为你提供一个大致的视图,帮助你了解在 AWS 上运行等效基础设施的成本,和在你自己的共址数据中心或本地数据中心相比如何。

计算器已由独立第三方进行审计,但在做出购买决定之前,当然应该将其输出与自己的计算结果进行对比。

准备工作

在这个示例中,我们将描述一个典型的三层 Rails 图像处理应用程序,运行时硬件量适中。你可以使用我们的示例配置,或者根据你自己的硬件需求进行操作。

如何操作...

  1. 访问 awstcocalculator.com/

  2. 选择你的货币、位置、AWS 区域和工作负载类型。在我们的案例中,我们将选择以下内容:

  • 澳大利亚元

  • 共址

  • 亚太地区(悉尼)

  • 一般

TCO 计算器—工作负载

  1. 现在我们需要描述我们的服务器需求。我们将指定我们的应用运行在物理服务器上,配置如下:
  • 应用名称: nginx

    • 服务器类型: 非数据库

    • 每台服务器处理器数量: 2

    • 处理器/核心数: 2

    • 服务器数量: 2

    • 内存(GB): 16

  • 应用名称: rails

    • 服务器类型: 非数据库

    • 每台服务器处理器数量: 2

    • 处理器/核心数: 4

    • 服务器数量: 4

    • 内存(GB): 32

  • 应用名称: mysql

    • 服务器类型: 数据库

    • 每台服务器处理器数量: 2

    • 处理器/核心数: 8

    • 服务器数量: 2

    • 内存(GB): 64

    • 数据库引擎: MySQL

TCO 计算器—服务器

  1. 最后,我们需要输入我们的存储需求。以我们的示例 rails 应用为例,我们需要以下配置:
  • 存储类型: 对象存储

  • 原始存储容量: 2TB

  • 访问频率低的比例: 90

TCO 计算器—存储

  1. 继续点击计算 TCO。

  2. 三年成本细分图提供了潜在节省的高层次视图。您可以看到,在我们的示例中,AWS 估计我们在未来三年内将节省 68% 的基础设施成本。相当令人印象深刻!

TCO 计算器—总结

  1. 继续浏览报告,查看按资源类型分类的成本细分:

TCO 计算器—图表

它是如何工作的...

计算器将根据您的服务器需求,将它们映射到适当大小的 EC2 实例。由于我们已经明确要求需要一个对象存储,因此它会根据我们所在地区 S3 存储的价格来计算我们的存储成本。

还有更多...

让我们来看看幕后运作,看看我们是如何在 AWS 上节省如此多资金的:

  • 我们的 EC2 实例价格基于三年预留实例的价格,并进行部分预付。这是公平的比较吗?是,也不是。您可能会被锁定在本地或共同托管解决方案的固定硬件合同中,因此将类似的合同条款应用于 AWS 定价模型是有意义的。实际上,您可能希望在迁移到 AWS 并对实例类型进行一些微调后,再考虑购买预留实例。另一方面,如果您的服务器使用全额预付的实例预留,AWS 成本可能会进一步降低。

  • 对象存储系统的比较是否公平,可能取决于您的本地或共同托管解决方案的功能集。例如,S3 具有对存储对象应用低频访问存储类别的功能,这可以减少存储成本,但也(理论上)略微降低其可用性。您可能在本地或共同托管存储中没有这个功能。

  • 在我们本地/共同托管的设施中,存储的三年成本为 AU $69,660,其中高达 97%的成本是每月操作机架的费用。这包括空间租赁、冷却、电力等。

  • 虽然成本计算器采取的是纯粹的基础设施视角,但它也考虑到了支持成本。如果你是 AWS 新手,可能会依赖 AWS 支持来帮助你启动和运行。

  • 你还需要考虑一些培训费用,甚至可能需要雇佣熟悉在 AWS 上部署和迁移系统的员工。你的开发人员也会开始从不同的角度思考如何构建和部署他们的应用程序。一定要把这部分也考虑进去。

  • 如果你对本地或共置的估算不完全满意,你可以继续修改计算中使用的数字。滚动到页面顶部,点击“修改假设”来输入你自己的硬件价格:

TCO 计算器——修改假设

另见

  • 购买预留实例 方案
posted @ 2025-07-08 12:23  绝不原创的飞龙  阅读(9)  评论(0)    收藏  举报