目的
・AWS上の静的Webサイトホスティングを有効にしたS3をCloudFrontで公開。
・S3のコンテンツを更新した際に、CloudFrontのキャッシュ削除を行うLambdaを実装。
前提条件
・SAMを使用してAWS上にリソースを作成する。
・Pythonを使用してLambdaを実装する。
完成イメージ
SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
WebBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::StackName}-webbucket-${AWS::AccountId}
VersioningConfiguration:
Status: Enabled
WebsiteConfiguration:
IndexDocument: index.html
PublicAccessBlockConfiguration:
IgnorePublicAcls: false
WebBucketBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WebBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Principal:
Service: cloudfront.amazonaws.com
Action:
- s3:GetObject
Effect: Allow
Resource:
- !Sub ${WebBucket.Arn}/*
Condition:
StringEquals:
AWS:SourceArn:
- !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${WebBucketDistribution}
WebBucketDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
TargetOriginId: WebBucketOrigin
Compress: true
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
CachePolicyId: !Ref WebBucketCachePolicy
OriginRequestPolicyId: !Ref WebBucketOriginRequestPolicy
PriceClass: PriceClass_200
DefaultRootObject: index.html
Enabled: true
Origins:
- DomainName: !GetAtt WebBucket.RegionalDomainName
Id: WebBucketOrigin
OriginAccessControlId: !GetAtt WebBucketOriginAccessControl.Id
S3OriginConfig:
OriginAccessIdentity: ''
WebBucketOriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
SigningBehavior: always
OriginAccessControlOriginType: s3
SigningProtocol: sigv4
Name: !Sub ${AWS::StackName}-OAC-${AWS::AccountId}
WebBucketCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
Name: WebBucketCachePolicy
DefaultTTL: 86400
MaxTTL: 31536000
MinTTL: 3600
ParametersInCacheKeyAndForwardedToOrigin:
EnableAcceptEncodingGzip: true
EnableAcceptEncodingBrotli: true
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
WebBucketOriginRequestPolicy:
Type: AWS::CloudFront::OriginRequestPolicy
Properties:
OriginRequestPolicyConfig:
Name: WebBucketOriginRequestPolicy
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
DeleteCacheLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: LambdaS3Policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- cloudfront:CreateInvalidation
- cloudfront:ListDistributions
Resource: '*'
DeleteCache:
Type: AWS::Serverless::Function
Properties:
Handler: index.lambda_handler
Runtime: python3.11
Role: !GetAtt DeleteCacheLambdaRole.Arn
Environment:
Variables:
Stack_NAME: !Sub ${AWS::StackName}
CodeUri: ./DeleteCache
Events:
WebBucket:
Type: S3
Properties:
Bucket: !Ref WebBucket
Events:
- s3:ObjectCreated:*
- s3:ObjectRemoved:*
Filter:
S3Key:
Rules:
- Name: suffix
Value: index.html
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref DeleteCache
Action: lambda:InvokeFunction
Principal: s3.amazonaws.com
SourceAccount: !Ref AWS::AccountId
SourceArn: !Sub arn:aws:s3:::${WebBucket}
Outputs:
WebsiteURLonCF:
Description: URL for website hosted on CloudFront
Value: !GetAtt WebBucketDistribution.DomainName
①以下、前回記事より設定内容に変更なし
・WebBucket(S3バケット)
・WebBucketBucketPolicy(バケットポリシー)
・WebBucketDistribution(CloudFrontディストリビューション)
・WebBucketOriginAccessControl(OAC)
・WebBucketCachePolicy(キャッシュポリシー)
・WebBucketOriginRequestPolicy(オリジンリクエストポリシー)
②DeleteCacheLambdaRole(Lambda用IAMロール)
・ログを書き込むための権限を付与(logs:~)
・CloudFrontのキャッシュ削除を行うための権限を付与(cloudfront:CreateInvalidation)
・CloudFrontディストリビューションの一覧を取得するための権限を付与(cloudfront:ListDistributions)
③DeleteCache(キャッシュ削除用Lambda)
・ディストリビューションIDやS3ドメイン名を直接環境変数に設定すると循環参照が発生してしまうため、Lambda内でS3ドメイン名を作成しディストリビューションIDを取得する。そのためにはバケット名が必要となるためLambdaから取得できないStack名を環境変数に設定(バケット名:Stack名-webbucket-アカウントId)
・WebBucketで「index.html」の作成・削除が行われた際に実行されるよう設定(Events:~)
④LambdaInvokePermission(Lambdaに付与するリソースベースのポリシー)
Lambda
import os
import boto3
def lambda_handler(event, context):
#アカウントIDの取得
sts_client = boto3.client("sts")
account_id = sts_client.get_caller_identity()["Account"]
#S3ドメイン名の設定
s3_domain_name = os.environ['Stack_NAME'] + '-webbucket-' + account_id + '.s3.' + os.environ['AWS_REGION'] + '.amazonaws.com'
paths_to_invalidate = ['/index.html']
#CloudFrontディストリビューションの一覧を取得
cloudfront_client = boto3.client('cloudfront')
response = cloudfront_client.list_distributions()
distributions = response['DistributionList']['Items']
#S3ドメイン名が一致するCloudFrontディストリビューションのIDを取得
for distribution in distributions:
if distribution['Origins']['Items'][0]['DomainName'] == s3_domain_name:
distribution_id = distribution['Id']
break
#CloudFrontのキャッシュ削除
cloudfront_client.create_invalidation(
DistributionId=distribution_id,
InvalidationBatch={
'Paths': {
'Quantity': len(paths_to_invalidate),
'Items': paths_to_invalidate
},
'CallerReference': str(context.aws_request_id)
}
)
動作確認
①作成されたS3バケットへ「index.html」をアップロードする
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>My Website Home Page</title>
</head>
<body>
<h1>Welcome to my website(CloudFront)</h1>
<p>Now hosted on Amazon S3(Before)!</p>
</body>
</html>
③S3バケットへ「index.html」をアップロードする(更新)
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>My Website Home Page</title>
</head>
<body>
<h1>Welcome to my website(CloudFront)</h1>
<p>Now hosted on Amazon S3(After)!</p>
</body>
</html>
④ブラウザの更新
・表示される画面が更新され最新のコンテンツが配信されていることを確認
⑤ログ(CloudWatch)にエラーが出ていないことの確認
参考(前回記事)