AWS CLI CloudFront update-distribution 命令实战:两种更新方式与 ETag 校验原理
【免费下载链接】aws-cliUniversal Command Line Interface for Amazon Web Services项目地址: https://gitcode.com/GitHub_Trending/aw/aws-cli
aws cloudfront update-distribution是修改 CloudFront 分发(Distribution)配置的核心命令,无论是调整默认根对象、禁用分发,还是批量改动缓存策略、源站与行为配置,都必须经由它提交。本指南以 awscli/examples/cloudfront/update-distribution.rst 为骨架,结合 aws-cli 仓库中该命令的定制实现与自动化测试,完整讲解两种更新方式(--default-root-object快捷参数与--distribution-configJSON 文件)、ETag/If-Match 乐观并发校验机制,以及配置整体替换(非合并)这一最容易踩坑的语义。
命令总览:update-distribution 在 CloudFront 生命周期中的位置
CloudFront 分发的完整生命周期大致为:create-distribution(创建)→get-distribution-config(读取当前配置与 ETag)→update-distribution(修改)→delete-distribution(删除,需先禁用)。其中update-distribution承担了“修改”这一环,对应 CloudFront API 中的PUT /2020-05-31/distribution/{Id}/config请求(见 service-2.json 中UpdateDistribution操作的http定义)。
从服务端模型看,该操作要求两个必填参数:Id(分发 ID,位于 URI 路径)与DistributionConfig(完整的分发配置结构),另有一个可选但强烈建议携带的IfMatch请求头(对应If-Match,见 UpdateDistributionRequest 定义)。每次成功更新都会返回新的ETag与完整的Distribution对象。
方式一:用 --default-root-object 快捷更新默认根对象
原文档的第一个示例展示了最典型的用法——只改一个字段:
aws cloudfront update-distribution \ --id EDFDVBD6EXAMPLE \ --default-root-object index.html--default-root-object的值是当访问者请求根 URL 时 CloudFront 返回的对象(例如index.html)。这个参数之所以能“只改一个字段”,是因为 aws-cli 在底层替你完成了三步工作(实现位于 awscli/customizations/cloudfront.py 的UpdateDefaultRootObject类):
- 自动调用
get_distribution_config(Id=...)拉取该分发的当前完整配置; - 从响应中取出
ETag并填入请求的IfMatch; - 将当前
DistributionConfig原样作为请求体,只把DefaultRootObject覆盖为目标值。
这保证了“只改一个字段、其余配置全部保留”的效果。该类的帮助文本明确写道:“CLI will automatically make a get-distribution-config call to load and preserve your other settings.”(CLI 会自动发起 get-distribution-config 调用来加载并保留你的其他设置)。
执行成功后返回的内容如下(摘自 update-distribution.rst):
{ "ETag": "E2QWRUHEXAMPLE", "Distribution": { "Id": "EDFDVBD6EXAMPLE", "ARN": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE", "Status": "InProgress", "LastModifiedTime": "2019-12-06T18:55:39.870Z", "InProgressInvalidationBatches": 0, "DomainName": "d111111abcdef8.cloudfront.net", "ActiveTrustedSigners": { "Enabled": false, "Quantity": 0 }, "DistributionConfig": { "CallerReference": "6b10378d-49be-4c4b-a642-419ccaf8f3b5", "Aliases": { "Quantity": 0 }, "DefaultRootObject": "index.html", "Origins": { "Quantity": 1, "Items": [ { "Id": "example-website", "DomainName": "www.example.com", "OriginPath": "", "CustomHeaders": { "Quantity": 0 }, "CustomOriginConfig": { "HTTPPort": 80, "HTTPSPort": 443, "OriginProtocolPolicy": "match-viewer", "OriginSslProtocols": { "Quantity": 2, "Items": [ "SSLv3", "TLSv1" ] }, "OriginReadTimeout": 30, "OriginKeepaliveTimeout": 5 } } ] }, "OriginGroups": { "Quantity": 0 }, "DefaultCacheBehavior": { "TargetOriginId": "example-website", "ForwardedValues": { "QueryString": false, "Cookies": { "Forward": "none" }, "Headers": { "Quantity": 1, "Items": [ "*" ] }, "QueryStringCacheKeys": { "Quantity": 0 } }, "TrustedSigners": { "Enabled": false, "Quantity": 0 }, "ViewerProtocolPolicy": "allow-all", "MinTTL": 0, "AllowedMethods": { "Quantity": 2, "Items": [ "HEAD", "GET" ], "CachedMethods": { "Quantity": 2, "Items": [ "HEAD", "GET" ] } }, "SmoothStreaming": false, "DefaultTTL": 86400, "MaxTTL": 31536000, "Compress": false, "LambdaFunctionAssociations": { "Quantity": 0 }, "FieldLevelEncryptionId": "" }, "CacheBehaviors": { "Quantity": 0 }, "CustomErrorResponses": { "Quantity": 0 }, "Comment": "", "Logging": { "Enabled": false, "IncludeCookies": false, "Bucket": "", "Prefix": "" }, "PriceClass": "PriceClass_All", "Enabled": true, "ViewerCertificate": { "CloudFrontDefaultCertificate": true, "MinimumProtocolVersion": "TLSv1", "CertificateSource": "cloudfront" }, "Restrictions": { "GeoRestriction": { "RestrictionType": "none", "Quantity": 0 } }, "WebACLId": "", "HttpVersion": "http1.1", "IsIPV6Enabled": true } } }注意响应中的几个关键信号:
ETag:更新后的配置版本标识,下次再更新时必须使用它作为IfMatch;Status: "InProgress":配置修改后 CloudFront 正在向全球边缘节点传播新配置,属正常中间状态;LastModifiedTime:本次配置变更的时间戳;InProgressInvalidationBatches:正在进行的失效批次数(此处为 0,与失效操作无关)。
方式二:用 --distribution-config + --if-match 提交完整配置
update-distribution的原生语义是“提交一份完整的新配置”。原文档第二个示例演示了如何禁用一个分发——把 JSON 文件中Enabled字段设为false,再连同--if-match一起提交:
aws cloudfront update-distribution \ --id EMLARXS9EXAMPLE \ --if-match E2QWRUHEXAMPLE \ --distribution-config file://dist-config-disable.json关于--if-match,文档特别强调:更新分发时必须用它提供分发的ETag;获取ETag的命令是 get-distribution-config(或get-distribution)。同时文档提示,禁用成功后即可用 delete-distribution 删除该分发——这也是 CloudFront 官方推荐的删除流程:先禁用、再删除。
dist-config-disable.json的完整内容如下(摘自 update-distribution.rst):
{ "CallerReference": "cli-1574382155-496510", "Aliases": { "Quantity": 0 }, "DefaultRootObject": "index.html", "Origins": { "Quantity": 1, "Items": [ { "Id": "amzn-s3-demo-bucket.s3.amazonaws.com-1574382155-273939", "DomainName": "amzn-s3-demo-bucket.s3.amazonaws.com", "OriginPath": "", "CustomHeaders": { "Quantity": 0 }, "S3OriginConfig": { "OriginAccessIdentity": "" } } ] }, "OriginGroups": { "Quantity": 0 }, "DefaultCacheBehavior": { "TargetOriginId": "amzn-s3-demo-bucket.s3.amazonaws.com-1574382155-273939", "ForwardedValues": { "QueryString": false, "Cookies": { "Forward": "none" }, "Headers": { "Quantity": 0 }, "QueryStringCacheKeys": { "Quantity": 0 } }, "TrustedSigners": { "Enabled": false, "Quantity": 0 }, "ViewerProtocolPolicy": "allow-all", "MinTTL": 0, "AllowedMethods": { "Quantity": 2, "Items": [ "HEAD", "GET" ], "CachedMethods": { "Quantity": 2, "Items": [ "HEAD", "GET" ] } }, "SmoothStreaming": false, "DefaultTTL": 86400, "MaxTTL": 31536000, "Compress": false, "LambdaFunctionAssociations": { "Quantity": 0 }, "FieldLevelEncryptionId": "" }, "CacheBehaviors": { "Quantity": 0 }, "CustomErrorResponses": { "Quantity": 0 }, "Comment": "", "Logging": { "Enabled": false, "IncludeCookies": false, "Bucket": "", "Prefix": "" }, "PriceClass": "PriceClass_All", "Enabled": false, "ViewerCertificate": { "CloudFrontDefaultCertificate": true, "MinimumProtocolVersion": "TLSv1", "CertificateSource": "cloudfront" }, "Restrictions": { "GeoRestriction": { "RestrictionType": "none", "Quantity": 0 } }, "WebACLId": "", "HttpVersion": "http2", "IsIPV6Enabled": true }关键字段速查
| 字段 | 作用 | 示例值 |
|---|---|---|
CallerReference | 配置的唯一标识,更新时不可更改 | cli-1574382155-496510 |
Aliases | 备用域名(CNAME)列表 | {"Quantity": 0} |
DefaultRootObject | 根请求返回的默认对象 | index.html |
Origins | 源站定义,S3 源用S3OriginConfig,自定义源用CustomOriginConfig | DomainName: "amzn-s3-demo-bucket.s3.amazonaws.com" |
DefaultCacheBehavior | 默认缓存行为(TTL、HTTP 方法、转发策略等) | MinTTL: 0, DefaultTTL: 86400, MaxTTL: 31536000 |
CacheBehaviors | 按路径匹配的附加缓存行为 | {"Quantity": 0} |
CustomErrorResponses | 自定义错误响应 | {"Quantity": 0} |
PriceClass | 价格等级,可选PriceClass_All/PriceClass_100/PriceClass_200 | PriceClass_All |
Enabled | 是否启用分发,禁用需设为false | false |
ViewerCertificate | 证书与 TLS 最低版本配置 | MinimumProtocolVersion: "TLSv1" |
Restrictions | 地理限制 | RestrictionType: "none" |
WebACLId | 关联的 WAF ACL ID | "" |
HttpVersion | HTTP 版本,示例从http1.1改到http2 | http2 |
IsIPV6Enabled | 是否启用 IPv6 | true |
提交后的响应(摘自 update-distribution.rst)中,DistributionConfig.Enabled已变为false,且生成了新的ETag: "E9LHASXEXAMPLE":
{ "ETag": "E9LHASXEXAMPLE", "Distribution": { "Id": "EMLARXS9EXAMPLE", "ARN": "arn:aws:cloudfront::123456789012:distribution/EMLARXS9EXAMPLE", "Status": "InProgress", "LastModifiedTime": "2019-12-06T18:32:35.553Z", "InProgressInvalidationBatches": 0, "DomainName": "d111111abcdef8.cloudfront.net", "ActiveTrustedSigners": { "Enabled": false, "Quantity": 0 }, "DistributionConfig": { "CallerReference": "cli-1574382155-496510", "Aliases": { "Quantity": 0 }, "DefaultRootObject": "index.html", "Origins": { "Quantity": 1, "Items": [ { "Id": "amzn-s3-demo-bucket.s3.amazonaws.com-1574382155-273939", "DomainName": "amzn-s3-demo-bucket.s3.amazonaws.com", "OriginPath": "", "CustomHeaders": { "Quantity": 0 }, "S3OriginConfig": { "OriginAccessIdentity": "" } } ] }, "OriginGroups": { "Quantity": 0 }, "DefaultCacheBehavior": { "TargetOriginId": "amzn-s3-demo-bucket.s3.amazonaws.com-1574382155-273939", "ForwardedValues": { "QueryString": false, "Cookies": { "Forward": "none" }, "Headers": { "Quantity": 0 }, "QueryStringCacheKeys": { "Quantity": 0 } }, "TrustedSigners": { "Enabled": false, "Quantity": 0 }, "ViewerProtocolPolicy": "allow-all", "MinTTL": 0, "AllowedMethods": { "Quantity": 2, "Items": [ "HEAD", "GET" ], "CachedMethods": { "Quantity": 2, "Items": [ "HEAD", "GET" ] } }, "SmoothStreaming": false, "DefaultTTL": 86400, "MaxTTL": 31536000, "Compress": false, "LambdaFunctionAssociations": { "Quantity": 0 }, "FieldLevelEncryptionId": "" }, "CacheBehaviors": { "Quantity": 0 }, "CustomErrorResponses": { "Quantity": 0 }, "Comment": "", "Logging": { "Enabled": false, "IncludeCookies": false, "Bucket": "", "Prefix": "" }, "PriceClass": "PriceClass_All", "Enabled": false, "ViewerCertificate": { "CloudFrontDefaultCertificate": true, "MinimumProtocolVersion": "TLSv1", "CertificateSource": "cloudfront" }, "Restrictions": { "GeoRestriction": { "RestrictionType": "none", "Quantity": 0 } }, "WebACLId": "", "HttpVersion": "http2", "IsIPV6Enabled": true } } }必须理解的三个核心语义
配置是整体替换,不是字段合并。服务端模型文档明确写道:"The values that you specify in an UpdateDistribution request are not merged into your existing configuration. Make sure to include all fields: the ones that you modified and also the ones that you didn't."(你在 UpdateDistribution 请求中指定的值不会与现有配置合并,必须包含所有字段——既包括你修改的,也包括未修改的)。因此凡是走
--distribution-config路线,必须基于get-distribution-config的输出做增量修改,绝不能只提交想改的几个字段,否则其余配置会被清空或导致校验失败。CallerReference不可变更。模型文档明确指出"You can't change the value of CallerReference.",任何试图修改它的更新都会被拒绝。ETag 是乐观并发控制的关键。
IfMatch请求头携带当前配置的版本号(ETag),若在此期间配置被其他人修改过(ETag 不匹配),服务端会拒绝本次提交,从而避免“丢失更新”。这也是 aws-cli 在--default-root-object快捷模式下自动取 ETag 的原因——既简化了操作,也保证了并发安全。若发生版本冲突,模型错误列表中对应PreconditionFailed、InvalidIfMatchVersion等错误。
两种方式的取舍与互斥约束
--default-root-object:适合“只改一个字段”的场景,CLI 自动完成 get-distribution-config → 合并 → 提交全流程,不会覆盖其他配置(源码见 UpdateDefaultRootObject)。--distribution-config:适合批量修改或多个字段联动的场景,需要自行维护完整 JSON;建议通过file://前缀从文件加载(如--distribution-config file://dist-config-disable.json),避免超长命令行与转义问题。- 两者互斥:aws-cli 注册了
operation-args-parsed.cloudfront.update-distribution事件,通过validate_mutually_exclusive_handler(['default_root_object'], ['distribution_config'])校验(见 cloudfront.py),同时指定两者会报错退出。
底层实现与测试验证
该命令的定制逻辑集中在 awscli/customizations/cloudfront.py,要点如下:
register()中为update-distribution挂载了两类事件:参数互斥校验(operation-args-parsed)与自定义参数注入(building-argument-table);UpdateDefaultRootObject继承自CreateDefaultRootObject(后者用于create-distribution的同类快捷参数),但额外注入了“自动 get-distribution-config 并保留其他配置”的行为;create_distribution与update_distribution共用一套distribution_config_template()模板(CallerReference由unique_string()生成,含时间戳与随机数,见 cloudfront.py)。
自动化测试 tests/functional/cloudfront/test_update_distribution.py 验证了四条关键行为,可作为理解语义的参考:
--default-root-object index.html:mock 的get_distribution_config响应返回ETag与DistributionConfig,最终请求参数包含DistributionConfig(含新DefaultRootObject)、Id与IfMatch(值为 mock 的__etag__);--distribution-config:仍按原生方式直接透传配置结构;- 同时指定两者:退出码 255,错误信息提示“cannot be specified when one of the following”(不可与其他参数同时指定);
- 不提供任何配置输入:退出码 255,参数校验失败。
常见错误与排查建议
| 报错方向 | 触发场景 | 建议 |
|---|---|---|
PreconditionFailed/InvalidIfMatchVersion | --if-match的 ETag 过期或错误 | 重新执行 get-distribution-config 获取最新 ETag 后重试 |
IllegalUpdate | 尝试修改CallerReference,或试图更新已删除/非活动分发 | 保持CallerReference原值不变 |
InvalidDefaultRootObject | 默认根对象以/开头或为空 | 传入不带前导斜杠的对象路径,如index.html |
MissingBody/ 校验失败 | --distribution-config提交的 JSON 不完整或必填字段缺失 | 基于get-distribution-config输出逐字段核对,补齐全部配置项 |
NoSuchDistribution | 分发 ID 不存在 | 用list-distributions核对 ID |
结语
update-distribution是管理 CloudFront 分发配置的必经入口,其精髓在于三点:完整配置整体替换(不合并)、ETag/If-Match 并发控制(防覆盖)、CLI 快捷参数与原生 JSON 两种模式(按场景取舍)。需要临时改一个字段时,--default-root-object这类快捷参数会自动加载并保留现有配置;需要批量变更时,先get-distribution-config导出完整配置、修改后以file://JSON 提交,同时带上最新 ETag。掌握这两条路线,即可安全、可复现地完成 CloudFront 分发的日常运维。如需进一步了解配置导出,可对照阅读 get-distribution-config.rst;涉及分发的创建与删除,可参考 create-distribution.rst 与 delete-distribution.rst。
【免费下载链接】aws-cliUniversal Command Line Interface for Amazon Web Services项目地址: https://gitcode.com/GitHub_Trending/aw/aws-cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考