在进行滚动部署时,如何确保 Cloudfront 具有正确的资产版本?

How do I ensure Cloudfront has the correct asset version when doing a rolling deployment?

我们目前正在使用 Capifony 和 ec2-capify 插件将我们的代码滚动部署到 ELB 后面的一组实例。我们还使用 CloudFront 来管理静态资产,我们使用查询字符串(例如?v1 或?v2)对其进行版本控制。

我们偶然发现了一个关于更新资产版本的罕见问题。如果当前版本是 v1,我们一次滚动部署一台服务器的 v2,那么 v2 框上的请求可能会发生以下情况:

  1. CloudFront 被要求提供 v2 并且未命中。
  2. CloudFront 转到 ELB 并请求资产。
  3. ELB 选择一个服务器,然后发生以下两种情况之一:Cloudfront 命中一个新部署的服务器(服务 v2)或者它命中旧服务器(v1)。
  4. 无论哪种方式,Cloudfront 都将内容存储为 v2。在它命中 v1 服务器的情况下,内容提供不正确。

我们目前的解决方案是我们必须使用新的资产版本进行另一次部署。

有没有办法通过 ELB 强制 Cloudfront 只访问我们更新的 (v2) 服务器之一,而忽略 v1 的?

或者我是否缺少可以解决问题的替代解决方案?

我认为在您的情况下,正确的部署策略是首先部署能够同时为 v1 和 v2 资产提供服务(但仍在为 v1 提供服务)的实例,然后进行另一个滚动部署以切换到 v2。

还有 'sticky sessions' 在 ELB (http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/US_StickySessions.html) 上可用,但我不知道如何在此处使用它 - 每个查看器 cookie 将消除 CloudFront 缓存的好处

当 Cloudfront 从您的来源获得 404 时(可能是因为它尚未收到新构建),它会将 404 缓存 5 分钟。您可以通过为您的发行版创建一个新的 "Custom Error Response" 来改变这种行为。自定义响应允许您设置非常低的 TTL,以便 Cloudfront 将传递到您的 ELB,直到它找到新文件。这样做的缺点是 Cloudfront 将不再缓存 404 - 您的 ELB 将需要处理该负载(希望很小!)

http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/HTTPStatusCodes.html#HTTPStatusCodes-no-custom-error-pages

我们选择的方法是广泛放弃我们现有的资产部署管道。在 "old" 方式中,我们选择了 asset.css?v=<version> 模型,其中 CloudFront 指向一个由多个实例提供服务的来源。

我们解决它的方法是转移到哈希名称资产模型和基于 S3 的来源。这意味着我们使用同步到 S3 存储桶的 asset-<hash-of-contents>.css 而不是 asset.css?v=<version>。存储桶会逐渐添加越来越新的版本,但如果我们决定返回或如果电子邮件之类的链接指向它(图像的常见问题),旧版本始终可用。

同步到 S3 的脚本在我们部署到包含引用资产的 HTML 的 Web 服务器之前运行,因此 CloudFront 始终能够提供最新的资产。

这是一个示例脚本:

#!/usr/bin/env bash

set -e # Fail on any error
set -x

if [ "$#" -ne "1" ]
then
    echo "Usage: call with the name of the environment you're deploying to"
    exit 1
fi

CDN_ENVIRONMENT=

S3_BUCKET="s3://static-bucket-name-of-your-choice/${CDN_ENVIRONMENT}/"

echo "Generating assets

... do the asset generation here ...

echo "Copying to S3"

# Now do the actual copying of the web dir. We use size-only because otherwise all files are newer, and all get copied.
aws s3 sync --exclude "some-folder-to-exclude/*" --acl public-read --size-only ./web/ ${S3_BUCKET}

echo "Copy to S3 complete"