亚马逊AWS官方博客

Amazon CloudFront 部署小指南(十)- 平滑迁移备用域名到其他账号

在 CloudFront 中,通过使用备用域名(也称为 CNAME),您可以在文件的 URL 中使用您自己的域名(例如 www.example.com),而不是使用 CloudFront 为您的分配指定的域名(例如 d123.cloudfront.net)。CloudFront 将备用域名关联到每个边缘站点(也称为 POP 点)中的 IP 地址。当浏览器提交针对自定义 URL 的 HTTP/HTTPS 请求时,DNS 将该请求传送到正确的边缘站点的 IP 地址。

作为一个 CDN 服务,CloudFront 承载着大量租户的业务,每一个 CloudFront 分配(distribution)对应着一个租户。当某个 CloudFront 边缘站点的 IP 地址收到浏览器提交的针对自定义 URL 的 HTTP/HTTPS 请求时,它必须将该请求传送到正确的 CloudFront 分配上。

如果该请求是一个 HTTP 请求,CloudFront 检查 Host 标头的值。如果该值是一个已经配置在 CloudFront 上的备用域名,CloudFront 找到该备用域名配置在哪个 CloudFront 分配上,然后将 HTTP 请求传送到对应的 CloudFront 分配 ID。如果该请求是一个 HTTPS 请求,CloudFront 检查 Host(HTTP/1.1)或者:authority(HTTP/2 或 HTTP/3)标头的值是否是一个备用域名,通过备用域名找到对应的 CloudFront 分配 ID,还要检查该备用域名是否与 CloudFront 分配所添加的 SSL/TLS 自定义证书中的服务器名称指示(SNI)相同,然后才能执行与浏览器的 SSL/TLS 协商。

因此,备用域名是 CloudFront 区分租户业务的重要依据,必须保证全局唯一。当您尝试为分配添加备用域名但备用域名已在其他分配中使用时,您会收到 CNAMEAlreadyExists 错误(One or more of the CNAMEs you provided are already associated with a different resource)。

CloudFront 提供 ListConflictingAliases 和 AssociateAlias API,分别用于在相同账号内查找备用域名冲突和平滑迁移备用域名(在已经正确添加 SSL/TLS 证书的前提下无业务中断)。但有些客户出于账单相关的原因,需要在不同账号之间迁移备用域名和 CloudFront 分配,并且不希望在迁移的过程中中断业务,AssociateAlias API 就无法满足需求了。本文将介绍如何使用通配符域名(例如 *.example.com)在不同的账号之间迁移- CloudFront 备用域名和整个 CloudFront 分配的配置,同时保持 CDN 业务的连续性。

前期准备

CloudFront 分配的配置文件可能会引用其他的资源,包括:

  • Amazon Certificate Manager 签发的或者导入到 ACM 的 SSL/TLS 证书
  • 缓存策略(Cache Policy)
  • 源请求策略(Origin Request Policy)
  • 响应标头策略(Response Headers Policy)
  • CloudFront Functions 函数和函数所调用的键值存储(KeyValueStore)
  • Lambda@Edge 函数和函数所调用的 Amazon DynamoDB、Amazon Simple Queue Service 等相关资源,以及必要的 Amazon Identity and Access Management 角色
  • 字段级加密(Field Level Encryption)
  • CloudFront 实时日志的配置
  • Amazon WAF Web 访问控制列表(Web ACL)
  • 存储 CloudFront 日志的 Amazon Simple Storage Service(S3)存储桶
  • S3 源站,以及相应的源访问控制(OAC)和源访问身份(OAI)

以上资源都有各自的唯一标识符(ID 或 ARN),迁移到新账号之后,其标识符将改变。如果除了备用域名,您还要迁移整个 CloudFront 分配的配置到目标账号,则需要提前完成一些准备工作。在迁移 CloudFront 分配的配置之前,先迁移上述资源,并记录它们新的标识符,用于更新 CloudFront 配置文件。如果这些资源的数量很少,您可以选择使用亚马逊云科技的 Web 控制台,手工在目标账号内创建相同的资源。如果这些资源的数量较多,则推荐使用 API/SDK 的方式进行程序化迁移。

出于篇幅限制,本文将聚焦于迁移备用域名和 CloudFront 分配的配置文件本身。对于相关资源的迁移,我们未来会通过其他文章进行介绍。

注意事项

您还需要了解使用通配符在不同账号之间迁移 CloudFront 备用域名的一些限制条件和注意事项。

  • 您不能使用通配符迁移 Apex 域名(例如 example.com),因为通配符域*.example.com 是 Apex 域的子域,以 Apex 域名为目标的请求无法被传送到通配符域名。如果您需要迁移的备用域名是一个 Apex 域名,请联系亚马逊云科技 Support 团队以请求他们验证您是否拥有该域,然后为您将该备用域名移动到新的 CloudFront 分配。
  • 您需要准备 SNI 列表中包含通配符域名的 SSL/TLS 证书。
  • 无需为通配符域名配置DNS,否则该域内所有无法精确匹配的 DNS 请求都会被指向目标 CloudFront 分配。
  • 此迁移过程涉及对 CloudFront 分配的多次更新。在继续下一步之前,请耐心等待每个分配完全部署完最新的更改。
  • 您无法提前验证备用域名迁移的正确性,只能在迁移之后进行验证。我们建议先用一个不太重要的,或者是测试环境里的备用域名进行迁移过程验证。待验证全部流程准确无误,再逐步迁移其他备用域名。本文也提供了一个故障回退的方法,如果在 CDN 业务流量被迁移到目标分配之后出现业务异常,可以回退到迁移之前的状态。请详见下文–迁移步骤详解:步骤 4
  • 目标分配和源分配无法共享缓存,迁移之后可能会阶段性产生较多的回源流量。

备用域名迁移步骤详解

除了前期准备工作之外,整个迁移分为 5 个步骤。为了方便做介绍,本文提供命令行(CLI)的操作示例。您可以在相应 API reference 网页的底部找到各种语言的 SDK 链接。当然,您也可以在亚马逊云科技的 Web 控制台完成这些迁移步骤。我们分别为源账号和目标账号设置了两份配置和凭证文件,分别为[profile source][profile target]。有关 profile 的详细说明,请参阅配置和凭证文件设置

为了便于读者理解,我们假设待迁移的备用域名为 a.example.comb.example.com,用于迁移备用域名的通配符域名是*.example.com

步骤 1:在目标账号创建 CloudFront 分配,但不配置备用域名

从源账号获取 CloudFront 分配的配置文件,做必要的修改之后,用修改后的配置文件在目标账号创建目标 CloudFront 分配。建议在这个步骤先不配置备用域名,避免可能的备用域名重复而导致创建 CloudFront 分配失败。

  1. 使用 GetDistributionConfig API 从源账号获取 CloudFront 分配的配置文件。我们假设源 CloudFront 分配的 ID 为 E1111111SOURCE
    aws cloudfront get-distribution-config \
    --id E1111111SOURCE \
    --region us-east-1 \
    --profile source
    

下面是一个 GetDistributionConfig 的输出结果(假设变量名:output)示例,并做了一些裁剪,仅保留和资源 ID 或 ARN 相关的部分。

{
    "ETag": "E111111111ETAG",
    "DistributionConfig": {
        "CallerReference": "41e2a318-7f95-47d1-xxxx-76e2e438a0be",
        "Aliases": {
            "Quantity": 2,
            "Items": [
                "a.example.com",
                "b.example.com"
            ]
        },
        ...(略)
        "Origins": {
            "Quantity": 4,
            "Items": [         
                {
                    ...(略)
                    "S3OriginConfig": {
                        "OriginAccessIdentity": ""
                    },
                    "OriginAccessControlId": "E1VZL190FTXXXX"
                },
                {
                    ...(略)
                    "S3OriginConfig": {
                        "OriginAccessIdentity": "origin-access-identity/cloudfront/E3N9ZBBZQFXXXX"
                    },
                    "OriginAccessControlId": ""
                }
                ...(略)
            ]
        },
        "DefaultCacheBehavior": {
            ...(略)
            "LambdaFunctionAssociations": {
                "Quantity": 1,
                "Items": [
                    {
                        "LambdaFunctionARN": "arn:aws:lambda:us-east-1:636696000000:function:readRegionFromEnv:8",
                        "EventType": "origin-request",
                        "IncludeBody": false
                    }
                ]
            },
            "FunctionAssociations": {
                "Quantity": 1,
                "Items": [
                    {
                        "FunctionARN": "arn:aws:cloudfront::636696000000:function/print_cff_event",
                        "EventType": "viewer-request"
                    }
                ]
            },
            "FieldLevelEncryptionId": "",
            "CachePolicyId": "f74bd362-9ba0-4efc-xxxx-6280beeb1fce",
            "OriginRequestPolicyId": "b689b0a8-53d0-40ab-xxxx-68738e2966ac",
            "ResponseHeadersPolicyId": "3af0b823-7b43-404e-xxxx-7c92b97bca3b"
        },
        ...(略)
        "Logging": {
            "Bucket": "cloudfront-logs-xxxxxx.s3.amazonaws.com",
            ...(略)
        },
        "ViewerCertificate": {
            "ACMCertificateArn": "arn:aws:acm:us-east-1:636696000000:certificate/16abed16-059d-415d-xxxx-acd88cf48347",
            "Certificate": "arn:aws:acm:us-east-1:636696000000:certificate/16abed16-059d-415d-xxxx-acd88cf48347",
            ...(略)
        },
        "WebACLId": "arn:aws:wafv2:us-east-1:636696000000:global/webacl/shield-sample-webacl/f7e012b0-0915-4efd-xxxx-f04428eafb38",
        ...(略)
    }
}

保存 output["DistributionConfig"]到一个变量中。这个示例中列出了 CloudFront 分配引用的其他资源,您需要修改 output["DistributionConfig"],将那些资源的 ID 或 ARN 替换成目标账号中相应资源的新的 ID 或 ARN;分别修改 output["DistributionConfig"]["Aliases"]output["DistributionConfig"]["ViewerCertificate"]为下面的值,以移除备用域名和自定义 SSL/TLS 证书的配置。

{
    ...(略)
    "Aliases": {
        "Quantity": 0
    },
    ...(略)
    "ViewerCertificate": {
        "CloudFrontDefaultCertificate": true,
        "MinimumProtocolVersion": "TLSv1",
        "CertificateSource": "cloudfront"
    },
    ...(略)
}
  1. 通过 CreateDistribution API,用修改后的 CloudFront 分配的配置文件在目标账号中创建目标 CloudFront 分配。

其中--distribution-config 的值是修改之后的 CloudFront 分配的配置文件,可以是文件路径,变量,或者 JSON 格式的文本。例如:

aws cloudfront create-distribution \
--region us-east-1 \
--profile target \
--distribution-config '{
    "CallerReference": "41e2a318-7f95-47d1-xxxx-76e2e438a0be",
    "Aliases": {
        "Quantity": 0
    },
    ...(略)
    "ViewerCertificate": {
        "CloudFrontDefaultCertificate": true,
        "MinimumProtocolVersion": "TLSv1",
        "CertificateSource": "cloudfront"
    },
    ...(略)
}'

当新建 CloudFront 分配的时候,CallerReference 的值必须在账号内唯一;当更新 CloudFront 分配配置的时候,CallerReference 的值必须与源配置文件中的值保持一致。在本例中,我们从源账号读取到的 CallerReference 的值在目标账号并不存在,所以无需修改。

  1. CreateDistribution 的输出结果如下所示,保存其中的 output["ETag"]output["Distribution"]["Id"]用于后续步骤。每一次 CloudFront 分配的配置文件发生变更,其 ETag 值都会变化,下一次更新配置文件的时候必须匹配最新的 ETag 值。我们假设新创建的目标 CloudFront 分配的 ID 是 E2222222TARGET
{
    "Location": "https://cloudfront.amazonaws.com/2019-03-26/distribution/E2222222TARGET",
    "ETag": "E222222222ETAG",
    "Distribution": {
        "Id": "E2222222TARGET",
        ...(略)
    },
    ...(略)
}

步骤 2:检查目标 CloudFront 分配配置无误,添加备用域名为通配符域名并安装自定义 SSL/TLS 证书

通过 UpdateDistribution API,将通配符域名*.example.com 设置为目标 CloudFront 分配的备用域名,并添加自定义 SSL/TLS 证书。请确保该证书的 SNI 列表中包含*.example.com,已导入 ACM,并获取了该证书的 ARN。

aws cloudfront update-distribution \
--id E2222222TARGET \
--if-match E222222222ETAG \
--region us-east-1 \
--profile target \
--distribution-config '{
    "CallerReference": "41e2a318-7f95-47d1-xxxx-76e2e438a0be",
    "Aliases": {
        "Quantity": 1,
        "Items": [
            "*.example.com"
        ]
    },
    ...(略)
    "ViewerCertificate": {
        "CloudFrontDefaultCertificate": false,
        "ACMCertificateArn": "arn:aws:acm:us-east-1:310123456789:certificate/8ce3aef1-15d4-45ad-xxxx-bd51768cf536",
        "SSLSupportMethod": "sni-only",
        "MinimumProtocolVersion": "TLSv1.2_2021",
        "Certificate": "arn:aws:acm:us-east-1:310123456789:certificate/8ce3aef1-15d4-45ad-xxxx-bd51768cf536",
        "CertificateSource": "acm"
    },
    ...(略)
}'

保存 output["ETag"] ,用于下面的步骤 5 使用。我们假定此时目标 CloudFront 分配的 ETag 变成了 E333333333ETAG

步骤 3:删除源 CloudFront 分配的备用域名,CDN 流量自动迁移到目标分配,如有异常则及时回退

步骤 2 部署完成之后,通过 UpdateDistribution API 修改 Aliases 的值,在源 CloudFront 分配中删除备用域名,就可以自动迁移 CDN 业务流量到目标 CloudFront 分配。到目前为止,源 CloudFront 分配的配置文件还没有发生变更,所以下面示例中的 ETag 值仍然为 E111111111ETAG。原有的自定义 SSL/TLS 证书可以不删除,它是一个可选的操作。

aws cloudfront update-distribution \
--id E1111111SOURCE \
--region us-east-1 \
--profile source \
--if-match E111111111ETAG \
--distribution-config '{
    "CallerReference": "41e2a318-7f95-47d1-xxxx-76e2e438a0be",
    "Aliases": {
        "Quantity": 0
    },
    ...(略)
}'

保存 output["ETag"],用于可能的回退操作。我们假定此时源 CloudFront 分配的 ETag 变成了 E444444444ETAG

在源 CloudFront 分配上删除备用域名之后,虽然 a.example.comb.example.com 的 DNS 还继续指向源 CloudFront 分配,但浏览器提交的针对这两个域名的 HTTP/HTTPS 请求仍然会被传送到某个 CloudFront 边缘站点的 IP 地址。CloudFront 边缘站点的服务器检查 HTTP/HTTPS 请求中的 Host 或者:authority 标头,发现无法通过精准匹配找到一个备用域名,就继续通过模糊匹配找到*.example.com,然后将请求传送到配置了*.example.com 为备用域名的 CloudFront 分配 E2222222TARGET。基于这个机制,CDN 业务流量自动地从源 CloudFront 分配迁移到目标 CloudFront 分配,并且不会发生业务中断。

为了更好地展示整个迁移过程,我们通过自定义响应标头策略为源 CloudFront 分配和目标 CloudFront 分配分别添加 x-migration: source account distributionx-migration: target account distribution 响应标头,并在测试 URL 所对应的缓存行为(Behaviors)路径模式(Path pattern)中进行应用,以观察迁移时发生的变化。您在实际的迁移工作中,并不一定需要进行这个操作。

本文使用下面的 cURL 命令来循环打印时间戳,HTTP 状态码和 x-migration 标头来观察迁移过程中发生的变化。您可以根据实际业务情况选择合适的工具或监控手段来观察您的备用域名迁移过程。

macOS:

while :;do echo -n "timestamp:`date +%s`";curl -svo /dev/null https://a.example.com -w 'http status code: %{http_code} ' 2>&1 | egrep 'x-migration|http status code';sleep 0.1;done

Windows PowerShell:

while ($true) {
    $timestamp = Get-Date -UFormat "%s"
    Write-Host -NoNewline "timestamp:$timestamp"
    curl.exe -svo nul https://a.example.com -w ' http status code: %{http_code} ' 2>&1 | Select-String -Pattern 'x-migration|http status code'
    Start-Sleep -Milliseconds 100
}

在迁移测试中,源 CloudFront 分配删除备用域名之后不到 30 秒,CDN 流量开始由目标 CloudFront 分配提供服务。从下面的截图我们可以观察到 x-migration 标头的值从 source account distribution 变成了 target account distribution,并且所有请求的状态码都是 HTTP 200,业务保持连续。

故障回退

如果 CDN 业务流量迁移到目标 CloudFront 分配之后出现异常,需要及时执行回退操作,通过 UpdateDistribution API 将 Aliases 修改回原来的值。请注意,如果执行完下面的步骤 5,就只能发起一个新的反向的备用域名迁移操作,不再有快速回退的机会。

aws cloudfront update-distribution \
--id E1111111SOURCE \
--region us-east-1 \
--profile source \
--if-match E444444444ETAG \
--distribution-config '{
    "CallerReference": "41e2a318-7f95-47d1-xxxx-76e2e438a0be",
    "Aliases": {
        "Quantity": 2,
        "Items": [
            "a.example.com",
            "b.example.com"
        ]
    },
    ...(略)
}'

在回退测试中,源 CloudFront 分配添加回被删除的备用域名之后不到 30 秒,CDN 流量开始由源 CloudFront 分配提供服务。从下面的截图我们可以观察到 x-migration 标头的值从 target account distribution 变成了 source account distribution,并且所有请求的状态码都是 HTTP 200,业务保持连续。

步骤 4:将业务域名的 DNS 解析指向目标 CloudFront 分配

如果步骤 3 一切顺利,就可以将 a.example.comb.example.com 的 DNS CNAME 记录或者别名(alias)记录指向目标 CloudFront 分配。这个步骤同样不会对 CDN 业务流量产生任何影响,但它是步骤 5 的前提条件,否则 CloudFront 将检测到存在域前置(Domain fronting),您将在步骤 5 看到如下报错:

One or more aliases specified for the distribution includes an incorrectly configured DNS record that points to another CloudFront distribution.

步骤 5:待新的 DNS 记录完全生效之后,将目标 CloudFront 分配的备用域名从通配符域名修改为业务域名

按照步骤 2 介绍的方法,用 UpdateDistribution API 将目标 CloudFront 分配的备用域名从*.example.com 修改为 a.example.comb.example.com。您可以根据实际的业务情况决定是否同时修改 SSL/TLS 证书,因为它的 SNI 列表中包含了*.example.com,同样适用于 a.example.comb.example.com,所以不修改也是可以的。下面的示例只修改备用域名,没有修改 SSL/TLS 证书。

aws cloudfront update-distribution \
--id E2222222TARGET \
--if-match E333333333ETAG \
--region us-east-1 \
--profile target \
--distribution-config '{
    "CallerReference": "41e2a318-7f95-47d1-xxxx-76e2e438a0be",
    "Aliases": {
        "Quantity": 2,
        "Items": [
            "a.example.com",
            "b.example.com"
        ]
    },
    ...(略)
}'

总结

当您考虑进行 CloudFront 备用域名跨账号的迁移时,按照我们在本文中详细介绍的五个步骤,你将能够更加顺利地完成这项任务。在准备阶段,你需要仔细评估现有系统,确保所有依赖和资源都被妥善考虑,并根据注意事项的提示制定迁移方案。在迁移过程中,您需要采用适当的方法监控业务的可用性,并在异常状态发生之后及时地执行回退操作。通过遵循这些建议,你可以最大程度地降低迁移工作量,减少迁移过程中的风险,并确保 CDN 业务的平稳过渡。

往期文章

本篇作者

王骏兴

亚马逊云科技边缘产品架构师,负责亚马逊云科技 Edge 服务领域在中国的技术推广。在 CDN 内容分发以及 WAF 领域拥有多年实战经验,专注于边缘服务设计以及体验优化。

陈程

亚马逊云科技高级边缘产品架构师,专注于 Amazon CloudFront、AWS WAF、AWS Shield、AWS Global Accelerator、Amazon Route 53 等产品和服务。