CodePipelineからS3へのデプロイをトリガーにCloudFrontのキャッシュをクリアする

こんにちは、SCSK澤村です。

AWS CodePipelineからAmazon S3へのデプロイをトリガーにして、更新したファイルのみを判定してAmazon CloudFrontのキャッシュをクリアする実装を行いましたのでご紹介いたします。

概要

構成のイメージは下図の通りです。

流れは以下の通りです。(今回の記事で扱うのは赤枠内の部分です。)

  1. ユーザがAWS CodeCommitに更新ファイルをpushする
  2. AWS CodeCommitからAWS CodePipelineを経てAmazon S3のファイルが更新される
  3. Amazon S3のPUTをトリガーとしてAWS LambdaのInvalidation(キャッシュ削除)処理を実行
  4. Amazon CloudFrontのキャッシュが削除される

AWS Lambda設定方法

今回はAWS CodeCommit、AWS CodePipelineの設定は割愛します。

IAMポリシー・IAMロールの作成

AWS Lambdaに設定するIAMポリシーを3つ作成します。

  • Amazon CloudFrontキャッシュ削除ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "cloudfront:CreateInvalidation",
            "Resource": "arn:aws:cloudfront::(AWSアカウントID):distribution/*"
        }
    ]
}
  • CloudWatchログ出力ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}
  • CodeCommit取得ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "codecommit:GetCommit",
                "codecommit:GetDifferences",
                "codecommit:GetBranch"
            ],
            "Resource": "arn:aws:codecommit:*:(AWSアカウントID):*"
        }
    ]
}

上記3つのポリシーを登録したロールを作成します。

AWS Lambdaの作成

以下の設定でLambda関数を作成します。

  • 「一から作成」を選択
  • 関数名:任意の名前を入力
  • ランタイム:Python3.9を選択
  • アーキテクチャ:x86_64を選択
  • 実行ロール:上記で作成したLambda用のロールを選択

 

以下の設定でトリガーを追加します。

  • トリガーの選択:S3
  • バケット:指定のCodeCommit、CloudFrontと紐づくS3を選択
  • 再起呼び出しにチェックを入れる

 

「設定」→「環境変数」を選択し、「環境変数の編集」をクリックし、下記3つの環境変数を入力します。

  • キー:repository_name、値:リポジトリ名を入力
  • キー:branch_name、値:push実行の際に参照するCodeCommitのブランチ名を入力
  • キー:dest_id、値:CloudFrontのディストリビューションIDを入力

 

Lambdaのコードソースに下記を貼り付けます。

import urllib.parse
import boto3
import os
from datetime import datetime

# 最新commitIDの取得
def get_commit(codecommit, repository, branch):

    try:
        responce = codecommit.get_branch(
            repositoryName = str(repository),
            branchName = str(branch)
        )
        commitID = str(responce['branch']['commitId'])
        return commitID
    except Exception as e:
        print("get branch error: " +str(e))

# 一つ前のcommitID取得
def get_old_commit(codecommit, repository, commitID):
    try:
        response = codecommit.get_commit(
            repositoryName = str(repository),
            commitId = commitID
        )
        old_commitID = str(response['commit']['parents'][0])
        return old_commitID
    except Exception as e:
        print("get old commit error: " + str(e))

# 変更したファイルの取得
def get_differences(codecommit, repository, commitID, old_commitID):
    try:
        response = codecommit.get_differences(
            repositoryName = str(repository),
            beforeCommitSpecifier = old_commitID,
            afterCommitSpecifier = commitID,
        )
        return response
    except Exception as e:
        print("get differences id error: " + str(e))

# キャッシュクリア実行
def create_invalidation(put_file):
    #CloudFrontキャッシュ削除
    cf = boto3.client('cloudfront')
    dest_id = os.environ.get('dest_id')
    nowtime = datetime.now().strftime('%Y%m%d%H%M%S%f')
    try:
        response = cf.create_invalidation(
                DistributionId = str(dest_id),
                InvalidationBatch = {
                    'Paths': {
                        'Quantity': 1,
                        'Items': ['/'+ put_file]
                    },
                    'CallerReference': nowtime
                }
            )
        return response
    except Exception as e:
        print("create invalidation error: " + str(e))

def lambda_handler(event, context):

    print ('=== FUNCTION START ===')

    # codecommit環境変数の呼び出し
    codecommit = boto3.client('codecommit')
    repository = os.environ.get('repository_name')
    branch = os.environ.get('branch_name')

    # 更新ファイルを取得
    commitID = get_commit(codecommit, repository, branch)
    old_commitID = get_old_commit(codecommit, repository, commitID)
    res = get_differences(codecommit, repository, commitID, old_commitID)

    # トリガーから取得したS3ファイルと比較
    for path_list in res['differences']:
        put_file = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
        before_file = ''
        after_file = ''

        if 'beforeBlob' in path_list and 'afterBlob' in path_list:
            before_file = str(path_list['beforeBlob']['path'])
            after_file = str(path_list['afterBlob']['path'])
            print('before file: ' + before_file)
            print('after file: ' + after_file)
        elif 'beforeBlob' in path_list:
            before_file = str(path_list['beforeBlob']['path'])
            print('before file: ' + before_file)
        elif 'afterBlob' in path_list:
            after_file = str(path_list['afterBlob']['path'])
            print('after file: ' + after_file)
        else:
            print('This file is not subject to cache deletion.')

        # キャッシュ削除
        if put_file == before_file and put_file == after_file:
            print('cache clear: ' + put_file)
            create_invalidation(put_file)
        elif put_file == before_file and after_file == '':
            print('cache clear: ' + put_file)
            create_invalidation(put_file)
        else:
            print('This file is not subject to cache deletion.')
        
    print ('=== FUNCTION END ===')

※ソースコード概要※

  1. 指定のCodeCommitのブランチから最新のCommitIDを取得します。
  2. 最新のCommitIDを引用して1つ前のCommitIDを取得します。
  3. 最新のCommitIDと1つ前のCommitIDから差分のあるファイル名を取得します。
  4. S3にPUTされたファイルと比較して差分のあるファイル、削除されたファイルのキャッシュをクリアします。

設定は以上です。

最後に

今回はCloudFrontのキャッシュクリアの自動化についてご紹介いたしました。

AWSでWebサイトを構築される際に、是非ご活用いただけますと幸いです。

タイトルとURLをコピーしました