S3とCloudFrontをCloudFormation(YAML)でデプロイしてOAIを設定

もくじ

前書き

やること

今回は完全に趣味の投稿となります。実用性はほぼ無いと思いますので興味のある方のみご覧ください。

やることは以下です。すべてCloudFormation単体で行います。CDKは使用しません。

  1. S3バケットを作成。(パブリックアクセスブロックを設定)
  2. CloudFrontをデプロイしてオリジンに❶で作ったバケットを設定。同時にOAI(Origin Access Identity)を作って設定する。
  3. ❶で作ったS3バケットのバケットポリシーに、❷で作ったOAIからの接続のみを許可する設定を投入する。
  4. ❶〜❸で作ったリソースを削除しようとするが、エラーが発生する。
  5. エラーの原因を特定して、解消してリソースを削除する。

リソース削除エラーへの対処

❹〜❺に書きましたら、エラーが発生してリソースが削除できなくなりました。

原因は以下でした。

AWS CloudFormation の「Export EXPORT_NAME cannot be updated as it is in use by STACK_NAME」(エクスポート EXPORT_NAME は、STACK_NAME により使用されているため更新できません) というエラーを解決するにはどうすればよいですか。

要するに、スタックから別のスタックの値をインポート(Fn::ImportValue)しているので、依存関係のようなものが発生して削除できなくなった、というものでした。

こんなトラップ的なエラー(そもそもデプロイの仕方が悪いのかもしれませんが)があるものかと、備忘録をかねて記載したいと思います。

やってみる

S3バケット作成

使用するCloudFormationテンプレートは以下になります。

  • S3.yml
---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for S3"

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: "formatsushi0"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: "true"
        BlockPublicPolicy: "true"
        IgnorePublicAcls: "true"
        RestrictPublicBuckets: "true"

Outputs:
  NameOfS3Bucket:
    Description: "Name of S3 Bucket"
    Value: !Ref S3Bucket
    Export:
      Name: "ExS3BucketName"

簡単に説明します。

“formatsushi0″という名前でS3バケットを作成しています。

パブリックアクセスはすべてブロックしています。

S3バケット名を、”ExS3BucketName”という名前でエクスポートしています。

% aws cloudformation create-stack --template-body file:///Users/atsushi/Document/AWS/CloudFormation/S3.yml --stack-name Stack-S3

上記コマンドでデプロイします。

CloudFrontのデプロイ

テンプレートは以下になります。

  • CloudFront.yml
---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for CloudFront"

Resources:
  OriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Sub
          - "OAI-${S3Bucket}"
          - S3Bucket: {"Fn::ImportValue": !Sub "ExS3BucketName"}
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: "Deployed by CloudFormation"
        HttpVersion: "http2"
        Aliases: 
          - "atsushinotes.work"
        ViewerCertificate:
          AcmCertificateArn: "arn:aws:acm:us-east-1:XXXXXXXXXXXX:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
          MinimumProtocolVersion: "TLSv1.2_2021"
          SslSupportMethod: "sni-only"
        Origins:
          - S3OriginConfig:
              OriginAccessIdentity: !Sub
                - "origin-access-identity/cloudfront/${OAIID}"
                - OAIID: !Ref "OriginAccessIdentity"
            DomainName: !Sub
              - "${ExS3BucketName}.s3.ap-northeast-1.amazonaws.com"
              - ExS3BucketName: {'Fn::ImportValue': !Sub 'ExS3BucketName'}
            Id: !Sub
              - "${ExS3BucketName}.s3.ap-northeast-1.amazonaws.com"
              - ExS3BucketName: {'Fn::ImportValue': !Sub 'ExS3BucketName'}
        DefaultCacheBehavior:
          TargetOriginId: !Sub
            - "${ExS3BucketName}.s3.ap-northeast-1.amazonaws.com"
            - ExS3BucketName: {'Fn::ImportValue': !Sub 'ExS3BucketName'}
          ViewerProtocolPolicy: allow-all
          CachePolicyId: "6xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx6"
        Enabled: true

Outputs:
  IdOfOAI:
    Description: "ID of OAI"
    Value: !Ref OriginAccessIdentity
    Export:
      Name: "ExOAIID"

簡単に説明します。

OAIを”OAI-[S3バケット名]”という名前で作成しています。

代替ドメインを”atsushinotes.work”という名前で作成しています。作成済みのACMををしています。(このドメインは使いません。検証で設定してみただけです)

“sni-only”でレガシークライアントサポートは無効にしています。

オリジンは先ほど作成したS3バケットにしています([S3バケット名].s3.ap-northeast-1.amazonaws.com)。先ほどのS3のスタックからS3バケット名はインポートしています。

キャッシュの動作はマネージドの「CachingOptimized」に設定しました。IDをマネージメントコンソールで確認してコードで指定しています。

OAIのIDを”ExOAIID”という名前でエクスポートしています。

% aws cloudformation create-stack --template-body file:///Users/atsushi/Document/AWS/CloudFormation/CloudFront.yml --stack-name Stack-CloudFront

上記コマンドでデプロイします。

S3バケットポリシーの更新

テンプレートは以下になります。

  • S3-2.yml
---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for S3"

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: "formatsushi0"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: "true"
        BlockPublicPolicy: "true"
        IgnorePublicAcls: "true"
        RestrictPublicBuckets: "true"
  ##### 追記ここから #####
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Sid: "OAI"
            Action:
            - "s3:GetObject"
            Effect: "Allow"
            Resource: !Join
              - ""
              - - "arn:aws:s3:::"
                - !Ref S3Bucket
                - "/*"
            Principal:
              AWS: !Sub 
                - "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${IDofOAI}"
                - IDofOAI: {"Fn::ImportValue": !Sub "ExOAIID"}
  ##### 追記ここまで #####

Outputs:
  NameOfS3Bucket:
    Description: "Name of S3 Bucket"
    Value: !Ref S3Bucket
    Export:
      Name: "ExS3BucketName"

先ほどの”S3.yml”に追記を行っています。

内容は、以下のバケットポリシーを追加するものです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "OAI",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XXXXXXXXXXXXXX"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::formatsushi0/*"
        }
    ]
}

“XXXXXXXXXXXXXX”のところには、CloudFrontのスタックからインポートしたOAIのIDが入ります。

% aws cloudformation update-stack --template-body file:///Users/atsushi/Document/AWS/CloudFormation/S3-2.yml --stack-name Stack-S3

上記コマンドでS3のスタックを更新します。

動作確認

S3バケットにHTMLファイルを配置して、CloudFront経由で表示できることを確認します。

“form.html”という自作のファイルをアップロードします。

% aws s3 cp form.html s3://formatsushi0/
upload: ./form.html to s3://formatsushi0/form.html

表示することができました。

後かたづけ

HTMLファイル削除

とりあえず先に、作成したS3バケットの中を空にします。

% aws s3 rb s3://formatsushi0 --force
delete: s3://formatsushi0/form.html
remove_bucket: formatsushi0

CloudFrontスタックを削除しようとすると。。

CloudFrontスタックから削除します。

スタックはすぐに削除できると思っていたのですができませんでした。

以下のコマンドでCloudFrontスタックを削除しようとしました。

% aws cloudformation delete-stack --stack-name Stack-CloudFront

エラーは以下のとおりで、内容は「エクスポートしている値(ここでは”ExOAIID”)がS3スタックに使用されているので削除できません」です。

S3スタックが参照している箇所を実際の値に置き換える

ですので、S3スタック側でCloudFrontスタックの値を参照している箇所を、実際の値に置き換えます。

テンプレートは以下です。

  • S3-delete.yml
---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for S3"

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: "formatsushi0"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: "true"
        BlockPublicPolicy: "true"
        IgnorePublicAcls: "true"
        RestrictPublicBuckets: "true"
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Sid: "OAI"
            Action:
            - "s3:GetObject"
            Effect: "Allow"
            Resource: !Join
              - ""
              - - "arn:aws:s3:::"
                - !Ref S3Bucket
                - "/*"
            Principal:
              ##### 変更箇所は下記の一行 #####
              AWS: "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XXXXXXXXXXXXX"

Outputs:
  NameOfS3Bucket:
    Description: "Name of S3 Bucket"
    Value: !Ref S3Bucket
    Export:
      Name: "ExS3BucketName"

OAIのIDをAWSコンソールで確認して、上記のXXXXXXXXXXXXXの箇所に埋め込んでスタックを更新します。つまり、これでCloudFrontスタックの値を参照することがなくなります。

スタック更新のコマンド。

% aws cloudformation update-stack --template-body file:///Users/atsushi/Document/AWS/CloudFormation/S3-delete.yml --stack-name Stack-S3

CloudFrontスタックの削除のリトライ

S3スタック更新が成功したら、もう一度CloudFrontスタックの削除を試みます。

% aws cloudformation delete-stack --stack-name Stack-CloudFront

削除が始まりました。

S3スタックの削除

最後に、S3スタックを削除します。

% aws cloudformation delete-stack --stack-name Stack-S3

削除が始まりました。

今回の検証で作成したリソースはすべて削除されました。

参考サイト

AWS::S3::Bucket

AWS::CloudFront::Distribution

AWS CloudFormation の「Export EXPORT_NAME cannot be updated as it is in use by STACK_NAME」(エクスポート EXPORT_NAME は、STACK_NAME により使用されているため更新できません) というエラーを解決するにはどうすればよいですか。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

コメント

コメントする

コメントは日本語で入力してください。(スパム対策)

CAPTCHA

もくじ
閉じる