CloudFront の設定をエクスポートしてほぼ同じ設定で復元してみた
CloudFront の設定をエクスポートしてコピー(ほぼ同じ設定で復元)してみた記録です。
後述するやり方で調整すれば CLI から同じようなディストリビューションが新規に作成できます。
検証する際はテスト環境で行うことをお勧めいたします。
前提
AWS CLI の設定や CloudFront の設定は完了している前提でこの記事の内容はコンパクトにまとめています。
状況
以前コンソールから設定した CloudFront のディストリビューションがありました。
コンソールから設定しているので特にコード化されていない状態でした。
AWS CLI でバックアップ
AWS CLI を使用すると JSON 形式で設定が吐き出せるというのでやってみました。
aws cloudfront get-distribution-config --id <ExistingDistID> > dist-config.json
実行すると以下のような JSON がレスポンスで返ってきます。
{
  "ETag": "E2QWRUHEXAMPLE",
  "DistributionConfig": {
    "CallerReference": "cli‑example",
    "Aliases": {
      "Quantity": 1,
      "Items": [
        "api.example.com"
      ]
    },
    "DefaultRootObject": "index.html",
    "Origins": {
      "Quantity": 1,
      "Items": [
        {
          "Id": "myS3Origin",
          "DomainName": "my-bucket.s3.amazonaws.com",
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "S3OriginConfig": {
            "OriginAccessIdentity": "origin-access-identity/cloudfront/E74FTE3AEXAMPLE"
          }
        }
      ]
    },
    "DefaultCacheBehavior": {
      "TargetOriginId": "myS3Origin",
      "ViewerProtocolPolicy": "redirect-to-https",
      "AllowedMethods": {
        "Quantity": 2,
        "Items": [
          "GET",
          "HEAD"
        ],
        "CachedMethods": {
          "Quantity": 2,
          "Items": [
            "GET",
            "HEAD"
          ]
        }
      },
      "ForwardedValues": {
        "QueryString": false,
        "Cookies": {
          "Forward": "none"
        },
        "Headers": {
          "Quantity": 0
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        }
      },
      "MinTTL": 0,
      "DefaultTTL": 86400,
      "MaxTTL": 31536000
    },
    "CacheBehaviors": {
      "Quantity": 0,
      "Items": []
    },
    "Comment": "My CloudFront distribution",
    "Logging": {
      "Enabled": true,
      "IncludeCookies": false,
      "Bucket": "my-log-bucket.s3.amazonaws.com",
      "Prefix": "logs/"
    },
    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": false,
      "ACMCertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/abcd‑efgh‑1234",
      "SSLSupportMethod": "sni-only",
      "MinimumProtocolVersion": "TLSv1.2_2019"
    },
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0,
        "Items": []
      }
    },
    "WebACLId": "arn:aws:wafv2:us-east-1:123456789012:global/webacl/MyWebACL/abcd1234",
    "HttpVersion": "http2",
    "IsIPV6Enabled": true
  }
}
以下主な項目です。コンソールの設定が JSON になっているので不明な項目はコンソールを確認してみると良いと思います。
| 項目 | 説明 | 
|---|---|
| ETag | 現在の設定バージョンを示すタグ | 
| DistributionConfig | ディストリビューションの設定本体 | 
| CallerReference | 一意性チェック用の文字列 | 
| Aliases | CNAME(Alternate Domain Names)情報 | 
| Origins | 起点オリジン(S3 や HTTP サーバーなど)情報 | 
| DefaultCacheBehavior | デフォルトのキャッシュ動作、プロトコルポリシー、HTTP メソッド、TTL 設定など | 
| CacheBehaviors | パスパターンごとの追加キャッシュ動作(なければ空) | 
| Logging | アクセスログ設定(有効・ログ先バケット・プレフィックスなど) | 
| ViewerCertificate | SSL/TLS 設定(ACM 証明書 ARN、SNI 方法、最小プロトコルなど) | 
| Restrictions | ジオ制限設定 | 
| WebACLId | このディストリビューションに紐付けられている WAF Web ACL の ARN | 
| HttpVersion | サポートする HTTP バージョン(例:http2) | 
| IsIPV6Enabled | IPv6 の有効/無効フラグ | 
似た環境を JSON から立ち上げる
前述の手順で取得した JSON からほぼ同じ環境を立ち上げてみたいと思います。ここで注意しなければならない点がいくつかあります。
代替ドメインは一意に設定が必須
以下のように CloudFront の代替ドメインは1意になっていなければなりません。つまり、稼働中のディストリビューションをコピーする場合などは注意が必要になります。
JSON はそのまま使えない
エクスポートした JSON は create-distribution コマンドでそのまま使うことはできません。
似たディストリビューションを作成
まずはコマンドを確認してみます。
$ aws cloudfront create-distribution --distribution-config file://dist-config-modified.json
上記コマンドではエクスポートした json ファイルを指定しています。しかしこのままだと後述するエラーが出ますのでいくつか修正をしましょう。
ETag と DistributionConfig(キー部分)を削除
以下の部分を削除します。
{
  "ETag": "...",
  "DistributionConfig": { ...本体... }
}
この { ...本体... }部分を残し、最上位の階層とした JSON にすることでこのエラーは解消できます。
{ ...本体... }
CallerReference を変更する
JSON の中に CallerReference という項目があります。これはディストリビューションを1意に指定するものなのでここを独自の値に変更します。
値は日時スタンプなどを含めてわかりやすく、かつ機密情報は入れないように作成するのがよいとのことでした。以下のような値に変更します。
projA-oac-migration-2024-03-06T12-34-56Z-7d32f8
"CallerReference": "projA-oac-migration-2024-03-06T12-34-56Z-7d32f8"
代替ドメイン部分を空に設定
この Items に代替ドメインが格納されています。注意の部分でも書いたようにこの値は1意でなければならず今回は稼働中のディストリビューションがあるという状況を想定している為ここを空にして新しいものを立ち上げます。
"Aliases": { "Quantity": 1, "Items": ["xxxxxxxxx.com"] }
以下のように修正します。
"Aliases": { "Quantity": 0 }
ディストリビューションのステータスを無効にして立ち上げる
JSON の中に DistributionConfig.Enabled という項目があります。これがディストリビューションのステータスを決定している部分になります。この項目を false に設定します。
ここまで JSON を修正できたら前述のコマンドを実行できます。
$ aws cloudfront create-distribution --distribution-config file://dist-config-modified.json
コンソールから確認すると新しいディストリビューションが登録されていると思います。私のケースでは数分のダウンタイムを許容し、ここから既存のディストリビューション設定の代替ドメインを削除しディストリビューションを停止、新しいものに代替ドメインを設定してディストリビューションを起動、Route53 からのルーティングを新しいものに向けるという流れで移行しました。
エラー
代替ドメインについてのエラーには以下のようなものがあります。
以下は私が出会ったエラーです。
JSON をそのまま指定した
get-distribution-config でエクスポートした JSON をそのまま指定して create-distribution を実行したら以下のエラーになりました。
Parameter validation failed:
Missing required parameter in DistributionConfig: "CallerReference"
Missing required parameter in DistributionConfig: "Origins"
Missing required parameter in DistributionConfig: "DefaultCacheBehavior"
Missing required parameter in DistributionConfig: "Comment"
Missing required parameter in DistributionConfig: "Enabled"
Unknown parameter in DistributionConfig: "ETag", must be one of: CallerReference, Aliases, DefaultRootObject, Origins, OriginGroups, DefaultCacheBehavior, CacheBehaviors, CustomErrorResponses, Comment, Logging, PriceClass, Enabled, ViewerCertificate, Restrictions, WebACLId, HttpVersion, IsIPV6Enabled, ContinuousDeploymentPolicyId, Staging
Unknown parameter in DistributionConfig: "DistributionConfig", must be one of: CallerReference, Aliases, DefaultRootObject, Origins, OriginGroups, DefaultCacheBehavior, CacheBehaviors, CustomErrorResponses, Comment, Logging, PriceClass, Enabled, ViewerCertificate, Restrictions, WebACLId, HttpVersion, IsIPV6Enabled, ContinuousDeploymentPolicyId, Staging
エラーを読むとパラメータが足らないと言われていますが、実は余分な要素が含まれていることが原因です。解消方法としては以下の部分を削除します。
{
  "ETag": "...",
  "DistributionConfig": { ...本体... }
}
この { ...本体... }部分を残し、最上位の階層とした JSON にすることでこのエラーは解消できます。
{ ...本体... }
CallerReference がコピー元と同じため
JSON の中に CallerReference という項目があります。これはディストリビューションを1意に指定するものなのでここを独自の値に変更しないと以下のエラーがでます。
An error occurred (DistributionAlreadyExists) when calling the CreateDistribution operation: The caller reference that you are using to create a distribution is associated with another distribution. Already exists:
値は日時スタンプなどを含めてわかりやすく、かつ機密情報は入れないように作成するのがよいとのことでした。
"CallerReference": "projA-oac-migration-2024-03-06T12-34-56Z-7d32f8"
items パメータ指定方法間違い
An error occurred (InvalidArgument) when calling the CreateDistribution operation: The parameter CNAME contains one or more parameters that are too small.
これは JSON の指定を以下のように指定していた為でした。
"Aliases": { "Quantity": 1, "Items": [""] }
以下のように修正したら直りました。
"Aliases": { "Quantity": 0 }
稼働中の代替ドメインを移動する
CloudFront の代替ドメインは同じものを設定できない仕様になっています。
公式では以下のようなやり方で稼働中ディストリビューションの代替ドメインを移動する方法が用意されていました。
AWS アカウントが別でも代替ドメインを移動できるようです。
今回は同じ AWS アカウント間でしたので以下のコマンドで移動することができました。
aws cloudfront update-domain-association \
  --domain "www.example.com" \
  --target-resource DistributionId="$NEW_ID" \
  --if-match "$ETAG"
上記コマンドには ETAG という情報が必要なので以下のコマンドを実行して取得します。この ETAG はコンソールから確認できない値なので AWS CLI で取得するほかありません。
新しいディストリビューションの ID を<ExistingDistID>に当てはめて実行します。
aws cloudfront get-distribution-config --id <ExistingDistID> > dist-config.json
ダウンロードした JSON の先頭に ETAG があるのでメモして update-domain-association を実行します。
ここで usage のエラーがでたら AWS CLI のバージョンが古いので以下のページを参考にアップデートしてください。
コマンド実行が完了するまでには 1 分もかかりませんでした。最終変更も変化がなかったのでうまくいけばダウンタイムもかなり少なくすみそうです。(厳密に検証してはおりません)
update-domain-association の実行でエラー
update-domain-association の実行時に usage のエラーがでたら AWS CLI 自体のバージョンが古いかもしれません。
以下のページにしたがってアップデートすると解消できました。
あとがき
Terraform などの IaC 以前につくられたリソースはいずれかのタイミングで IaC 化するとその後の管理コストが劇的に減らせる実感がありました。とはいえ CLI で色々できるのは本当にすごいですね。クラウドの凄さも実感できる体験でした。
Discussion