通过设置CloudFront来托管Web应用的方法(附实例)

759 阅读11分钟

在上一篇文章中,我们介绍了如何设置一个网络应用,从CloudFront提供大块和小块的CSS和JavaScript。我们将其集成到Vite中,这样当应用程序在浏览器中运行时,从应用程序的根HTML文件中请求的资产将从CloudFront作为CDN拉取。

虽然CloudFront的边缘缓存确实有好处,但从这些多个地点为你的应用程序提供资源也不是没有代价的。让我们来看看我自己的WebPageTest的跟踪,它以上一篇博客文章的配置运行。

请注意第2-4行的大量连接时间。第1行是我们的HTML入口点。该HTML被解析,浏览器看到了驻扎在CDN上的JavaScript和CSS资产的脚本和链接标签,并请求它们。这将导致一个新的连接被建立,正如你所看到的,这需要时间。

这篇文章将告诉你如何解决这个问题。我们将介绍如何在CloudFront上托管整个Web应用,并让CloudFront转发(或 "代理")非缓存的数据、认证等请求到我们的底层Web服务器。

请注意,这比我们在上一篇文章中看到的工作要多得多,而且根据你的网络应用的确切需求,说明可能会有所不同,所以你的里程可能会有所不同。我们将改变DNS记录,根据你的网络应用,你可能需要添加一些缓存头,以防止某些资产被缓存。我们将讨论所有这些问题。

你可能会想,我们在上一篇文章中所涉及的设置是否能提供任何好处,因为我们在这篇文章中所做的事情。考虑到漫长的连接时间,我们是否应该放弃CDN,而从Web服务器上提供我们所有的资产,以避免长时间的等待?我用我自己的网络应用进行了测量,上面的CDN版本确实快了,但不是很多。最初的LCP页面加载大约快了200-300ms。记住,这只是最初的加载。一旦建立了这种连接,边缘缓存应该为你所有后续的、异步加载的块增加更多价值。

设置我们的DNS

我们的最终目标是通过CloudFront为我们的整个网络应用提供服务。这意味着当我们点击我们的域名时,我们希望结果来自CloudFront,而不是它目前链接的任何Web服务器。这意味着我们必须修改我们的DNS设置。为此我们将使用AWS Route 53。

我以mydemo.technology 为例,这是我拥有的一个域名。我将在这里向你展示所有的步骤。但是当你读到这里的时候,我已经从我的网络应用中删除了这个域名。因此,以后当我开始向你展示实际的CNAME记录,以及类似的记录时,这些将不再存在。

进入Route 53主页,点击托管区。

Showing the hosted zone configuration screen in the CloudFront settings.

单击 "创建托管区"并输入应用程序的域名。

现在,注意下一个屏幕中列出的名称服务器。它们看起来应该是这样的。

我们还没有真正完成任何工作。我们告诉AWS,我们希望它为我们管理这个域名,AWS给了我们名称服务器,它将把我们的流量通过这些服务器。为了使之生效,我们需要到我们的域名注册地去。那里应该有一个地方可以让你输入你自己的自定义名称服务器。

注意,我的域名是在GoDaddy注册的,这反映在本文的截图中。UI、设置和选项可能与你在注册商那里看到的不同。

**警告。**我建议在进行修改之前,写下原始名称服务器以及任何和所有DNS记录。这样一来,如果发生故障,你就有了你需要的一切,可以回滚到你开始之前的情况。即使一切正常,你仍然要将其他记录重新添加到Route 53中,即MX记录等。

设置一个CloudFront分布

让我们做一个CloudFront分布来托管我们的Web应用。我们在上一篇文章中介绍了基础知识,所以我们将直接进入主题。与上次相比,一个很大的变化是我们为原生域输入的内容。不要输入顶级域名,例如 your-app.net。你需要的是你的应用程序被托管的底层域名。如果是Heroku,那么就输入Heroku提供给你的URL。

接下来,如果你打算通过安全的HTTPS连接使用这个网站,请务必改变默认协议。

这一部分很关键。如果你的网络应用程序正在运行认证,托管数据,或其他任何东西,请确保启用除GET之外的其他动词。如果你跳过这一部分,那么任何用于认证、突变数据等的POST请求都会被拒绝并失败。如果你的网络应用除了提供资产外什么都不做,所有这些事情都由外部服务处理,那么就很好!你有一个很好的设置。你有一个很好的设置,你可以跳过这个步骤。

与上次相比,我们必须对缓存密钥和原点请求的设置做相当多的改动。

我们需要创建一个最小TTL为0的缓存策略,这样我们发回的非缓存头信息就会得到适当的尊重。你可能还想启用所有的查询字符串。当多个GraphQL请求以不同的查询字符串一起发出时,我看到了奇怪的行为,这些查询字符串被忽略了,导致所有这些请求从CloudFront的角度看都是一样的。

我的策略最终是这样的。

对于一个原点请求策略,如果需要的话,我们应该确保发送查询字符串和cookies,以使认证和数据查询等工作顺利进行。明确地说,这决定了cookies和查询字符串是否会从CloudFront下发到你的Web服务器(例如Heroku或类似的)。

我的看起来像这样。

最后,对于响应头策略,我们可以从列表中选择 "CORS With Preflight"。最后,你的前两个将有不同的名字,取决于你如何设置它们。但我的看起来是这样的。

让我们把我们的域,不管它是什么,连接到这个CloudFront分布。不幸的是,这比你预期的工作要多。我们需要向AWS证明,我们确实拥有这个域名,因为,据Amazon所知,我们并没有。我们在Route 53中创建了一个托管区。我们利用它给我们的名字服务器,在GoDaddy(或你的域名在谁那里注册)注册。但亚马逊还不知道这些。我们需要向亚马逊证明,我们确实控制了这个域名的DNS。

首先,我们要申请一个SSL证书。

接下来,让我们请求证书链接。

现在,我们将选择请求公共证书的选项。

我们需要提供域名。

而且,在我的情况下,证书正在等待。

所以,我将点击它。

这证明了我们拥有并控制着这个域名。在一个单独的标签中,回到Route 53,并打开我们的托管区。

现在我们需要创建CNAME记录。复制记录名称的第一部分。例如,如果CNAME是_xhyqtrajdkrr.mydemo.technology ,那么就把_xhyqtrajdkrr 部分。对于记录值,复制整个值。

假设你在你的域名主机、GoDaddy或其他什么地方注册了AWS名称服务器,AWS将很快能够ping它刚刚要求你创建的DNS条目,看到它期待的响应,并验证你的证书。

你在开始时设置的名称服务器可能需要时间来传播。理论上,它可能需要72小时,但对我来说,它通常在一小时内更新。

你会看到域名上的成功。

...以及证书。

!几乎完成了。现在让我们把所有这些连接到我们的CloudFront分布。我们可以回到CloudFront设置屏幕。现在,在自定义SSL证书下,我们应该看到我们创建的证书(以及你在过去创建的任何其他证书)。

然后,让我们添加应用程序的顶级域名。

剩下的就是告诉Route 53将我们的域名路由到这个CloudFront分布。因此,让我们回到Route 53并创建另一个DNS记录。

我们需要为 IPv4 输入一条 A 记录,为 IPv6 输入一条 AAAA 记录。对于这两个记录,让记录名称为空,因为我们只注册我们的顶级域名,没有其他内容。

选择A记录类型。接下来,将记录指定为别名,然后将别名映射到CloudFront分布。这应该会打开一个选项来选择你的CloudFront分布,由于我们之前在CloudFront注册了域名,你应该看到那个分布,而且在选择时只有那个分布。

我们对我们需要的IPv6支持的AAAA记录类型重复完全相同的步骤。

运行你的网络应用,并确保它实际上,你知道,工作。它应该如此

需要测试和验证的事项

好了,虽然我们在技术上已经完成了,但仍有一些事情需要做,以满足你的网络应用的确切需求。不同的应用程序有不同的需求,到目前为止,我所展示的是通过CloudFront路由的常见步骤,以提高性能。有可能你的应用程序有一些独特的东西,需要更多的爱。因此,让我介绍一下你在设置过程中可能遇到的一些额外项目。

首先,确保你的任何POST被正确地发送到你的原点。假设CloudFront被正确配置为转发cookie到你的起源,这应该已经工作,但检查一下也无妨。

更值得关注的是所有其他发送到你的Web应用的GET请求。默认情况下,CloudFront收到的任何GET请求,如果是缓存的,就会用缓存的响应提供给你的Web应用。这可能是灾难性的。对任何REST或GraphQL端点的任何数据请求都会被CDN缓存起来。如果你正在发送一个服务工作者,它也会被缓存,而不是正常的行为,即在后台发送当前的版本,如果有变化就会更新。

为了告诉CloudFront不要缓存某些东西,一定要把"Cache-Control" 头部设置为"no-cache" 。如果你使用一个框架,比如Express,你可以用这样的东西为你的数据访问设置中间件。

app.use("/graphql", (req, res, next) => {
  res.set("Cache-Control", "no-cache");
  next();
});
app.use(
  "/graphql",
  expressGraphql({
    schema: executableSchema,
    graphiql: true,
    rootValue: root
  })
); 

对于像服务工作者这样的东西,你可以在你的静态中间件之前为这些文件设置特定的规则。

app.get("/service-worker.js", express.static(__dirname + "/react/dist", { setHeaders: resp => resp.set("Cache-Control", "no-cache") }));
app.get("/sw-index-bundle.js", express.static(__dirname + "/react/dist", { setHeaders: resp => resp.set("Cache-Control", "no-cache") }));
app.use(express.static(__dirname + "/react/dist", { maxAge: 432000 * 1000 * 10 }));

以此类推。彻底测试一切,因为有很多东西都可能出错。在你所做的每一个改变之后,一定要在CloudFront中运行一个完全无效的程序,并在重新运行你的Web应用之前清除缓存,以测试东西是否被正确地从缓存中排除。你可以在CloudFront的Invalidations选项卡中进行这项工作。打开它,把/* ,以清除一切。

一个有效的CloudFront实现

现在我们已经运行了一切,让我们在WebPageTest中重新运行我们的跟踪。

就这样,我们的资产不再像我们之前看到的那样有设置连接。对于我自己的网络应用,我看到LCP有了500ms的大幅改善。这是一个坚实的胜利!


在CDN上托管整个网络应用可以提供所有世界中最好的东西。我们得到了静态资源的边缘缓存,但没有连接成本。不幸的是,这种改进并不是免费的。正确设置所有必要的代理并不是完全直观的,而且还需要设置缓存头,以避免非缓存请求进入CDN的缓存。