GitLabリポジトリをCodeCommitにミラーリングしてCloudFront+S3に配置する(Vue.js+CloudFormation 編)
目次
はじめに
こんにちは!スカイアーチHRソリューションズのnakaoです!
前回、こちらの記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する(Vue.js 編)」でVue.jsのプロジェクトをローカル上からGitLabにPushしてS3に配置するミラーリングを試しました。
今回は構成をCloudFront+S3に変更して、Vue.jsのソースコードをミラーリングしてみようと思います。
また、前回まで手動でAWSマネジメントコンソールから作成していたリソースをCloudFormationで管理してみようと思い、そちらも挑戦してみました!
アーキテクチャ
全体のアーキテクチャはこちらです!
こちらに記載してあるパイプラインはアプリケーション用のパイプラインです。
処理の流れとしては、以下の順番で実行されます。
①GitLabにソースコード(Vue.js)をPushする。
②ミラーリングが実行され、CodeCommitリポジトリにソースコードがミラーリングされる。
③CodeCommitへのミラーリングをトリガーに、CodePipelineが実行される。
④CodePipeline上でCodeBuildが実行される。
⑤CodePipeline上でS3へのデプロイが実行される。
⑥CodePipeline上でCloudFrontのキャッシュを削除するlambdaが実行される。
今回は構成をCloudFront+S3に変更したので、CloudFrontのキャッシュを考慮する必要がありました。
デフォルトではS3オブジェクトのキャッシュを24時間保持します。
今回はキャッシュを削除するlambdaを用意し、S3にコンテンツが配置されたらキャッシュを削除するようにしました。
以下、参考にした記事です。
また、クライアント側のブラウザキャッシュに関しても即時反映するようにしたかったため、CloudFrontのレスポンスヘッダーにCache-Control:no-cacheを設定するようにしました。
以下、参考にした記事です。
アプリケーション用のパイプラインとは別で、インフラリソース用のパイプラインをもう一つ用意します。
リソースのデプロイはより慎重に実施したかったので、手動デプロイを途中の手順として組み込んでいます。
デプロイを自動化してない、CIパイプラインにしました。
処理の流れとしては、以下の順番で実行されます。
①GitLabにソースコード(CloudFormationの.ymlファイル)をPushする。
②ミラーリングが実行され、CodeCommitリポジトリにソースコードがミラーリングされる。
③CodeCommitへのミラーリングをトリガーに、CodePipelineが実行される。
④CodePipeline上でCodeBuildが実行され、CloudFormationのChange setを作成する。
⑤AWSマネジメントコンソールからChange setを確認し、Change setを手動で実行、リソースのデプロイを行う。
分かりやすい記事にしたかったため、アプリケーション用のパイプライン、インフラリソース用のパイプラインは手動で作成します。
パイプラインも含めた完全自動化、インフラリソースのCI/CDパイプラインはまた別記事で紹介したいと思います。
インフラリソース用のパイプライン作成~実行
まずはじめに、インフラリソース用のパイプライン作成から実行まで実施します。
CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリの作成まで
以前執筆したこちらの記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する」と同様に、CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリを作成してください。
CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリの作成に関しては以上です。
CodeBuildビルドプロジェクトの作成と設定まで
こちらの記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する」と同様に、CodeBuildビルドプロジェクトを作成してください。IAMロールの修正と環境変数の設定を追加で実施します。
CodeBuild用IAMロールの修正
今回新しく作成したCodeBuild用のIAMロールだと、Buildspec実行時にCloudFormationへのアクセス権限が不足してしまいます。
「許可を追加」から「ポリシーをアタッチ」して、AWSCloudFormationFullAccess権限を付与しておきましょう。
※本来FullAccess権限のポリシーを付与するのは良くないですが、今回はこのままFullAccess権限を付与して進めます。必要最低限のポリシーを付与するように心掛けましょう。
CodeBuild用IAMロールの修正に関しては以上です。
CodeBuild用環境変数の設定
Buildspec実行時に指定するCloudFormationスタック名、S3バケット名、CloudFrontのキャッシュポリシーを環境変数に設定しておきましょう。
CodeBuild用環境変数の設定に関しては以上です。
CodePipelineの作成まで
CodePipelineを作成していきます。
こちらの記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する」と同様に、ソースステージ、ビルドステージを追加します。
デプロイステージのみ、必要ないのでスキップして作成してください。
CodePipelineの作成に関しては以上です。
GitLabリポジトリにソースコードをPushしてChange Setの作成
以降の作業は先ほど作成したインフラリソース用のGitLabリポジトリをローカル上にcloneして作業しているとします。
以下の構成でGitLabにソースコードを作成します。
「cfn」ディレクトリ以下にCloudFormationでリソースの設定を.ymlファイルで記述しています。
設定値の詳細までは説明しませんが、前回の記事で手動作成した際のデフォルト値と同じになるよう意識して記述しました。
AWSTemplateFormatVersion: 2010-09-09
Description:
Create CloudFront and S3
Mappings:
CachePolicyMap:
Managed-CachingOptimized:
Id: 658327ea-f89d-4fab-a63d-7e88639e58f6
Parameters:
BucketName:
Type: String
CachePolicyName:
Type: String
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
s3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
BucketKeyEnabled: true
WebsiteConfiguration:
IndexDocument: index.html
s3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref s3Bucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowCloudFrontServicePrincipalReadOnly
Effect: Allow
Principal:
Service:
- cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub ${s3Bucket.Arn}/*
Condition:
StringEquals:
AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${cloudfrontDistribution.Id}
# ------------------------------------------------------------#
# CloudFront
# ------------------------------------------------------------#
cloudfrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt s3Bucket.RegionalDomainName
Id: myS3Origin
S3OriginConfig:
OriginAccessIdentity: ''
OriginAccessControlId: !GetAtt OAC.Id
DefaultCacheBehavior:
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- GET
- HEAD
Compress: true
CachePolicyId: !FindInMap [ CachePolicyMap, !Ref CachePolicyName, Id]
ResponseHeadersPolicyId: !GetAtt cacheControl.Id
TargetOriginId: myS3Origin
DefaultRootObject: index.html
HttpVersion: http2
Enabled: true
OAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: OAC
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
cacheControl:
Type: AWS::CloudFront::ResponseHeadersPolicy
Properties:
ResponseHeadersPolicyConfig:
Name: add-cache-control
CustomHeadersConfig:
Items:
- Header: cache-control
Value: no-cache
Override: false
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
lambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: clear-clf-cache
Role: !GetAtt lambdaRole.Arn
Runtime: python3.11
Handler: index.lambda_handler
Environment:
Variables:
DistributionId: !GetAtt cloudfrontDistribution.Id
Code:
ZipFile: |
import json
import os
import boto3
import time
codepipeline = boto3.client('codepipeline')
cloudfront = boto3.client('cloudfront')
def lambda_handler(event, context):
try:
job_id = event['CodePipeline.job']['id']
invalidation = cloudfront.create_invalidation(DistributionId=os.environ['DistributionId'],
InvalidationBatch = {
'Paths': {
'Items': ['/*'],
'Quantity': 1
},
'CallerReference': str(time.time())
}
)
codepipeline.put_job_success_result(jobId = job_id)
except Exception as e:
codepipeline.put_job_failure_result(
jobId = job_id,
failureDetails={
'type': 'JobFailed',
'message': str(e)
}
)
return {
'statusCode': 200,
'body': json.dumps('cloudfront cache deletion completed')
}
lambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CloudFrontCreateInvalidationPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- cloudfront:CreateInvalidation
Resource: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/*
- PolicyName: CodePilelinePutJobResultPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- codepipeline:PutJobSuccessResult
- codepipeline:PutJobFailureResult
Resource: "*"
CloudFormation記述に関して、弊社社員の技術記事も参考にしました。ありがとうございます!
ルートディレクトリ直下に「buildspec.yml」を配置しています。
CloudFormationの構文チェックを実施してから、デプロイするようにしています。
デプロイコマンドに–no-execute-changesetオプションを付与することで、Change Setの作成・更新で処理が止まるようになります。オプションを付与しない場合は、Change Setの作成・更新からChange Setの実行まで自動で行います。
version: 0.2
phases:
install:
commands:
- aws --version
pre_build:
commands:
- echo Check syntax errors in cloudformation template file...
- aws cloudformation validate-template --template-body file://cfn/cl-s3.yml
build:
commands:
- echo cloudformation deploy start...
- aws cloudformation deploy --template-file cfn/cl-s3.yml --stack-name ${STACK_NAME} --parameter-overrides BucketName=${S3_BUCKET_NAME} CachePolicyName=${CACHE_POLICY_NAME} --capabilities CAPABILITY_IAM --no-execute-changeset
- echo cloudformation deploy completed...
それでは、GitLabにPushします!
GitLabへのPush成功です!
CodeCommitリポジトリを確認します。
GitLabから、CodeCommitへのミラーリング成功です!
CodePipelineを確認します。
CodePipelineのステージ全て成功してますね!
インフラリソース用のパイプライン作成から実行までは以上です。
Change Setの手動デプロイ
インフラリソース用のパイプラインで作成したChange Setを手動デプロイします。
AWSマネジメントコンソールからCloudFormationを開き、作成したスタック名からChange Setを確認します。
作成するリソースに間違いがないかを確認して、Change Setを実行して手動デプロイします。
リソースが全て作成されました!
Change Setの手動デプロイに関しては以上です。
アプリケーション用のパイプライン作成~実行
次に、アプリケーション用のパイプライン作成から実行まで実施します。
CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリの作成、CodeBuildプロジェクトの作成まで
前回の記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する(Vue.js 編)」と同様に、CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリの作成、CodeBuildプロジェクトを作成してください。
CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリの作成、CodeBuildプロジェクトの作成に関しては以上です。
CodePipelineの作成まで
CodePipelineを作成していきます。
こちらの記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する」と同様に、ソースステージ、ビルドステージ、デプロイステージを追加して作成します。
作成したCodePipelineを編集して、Deployステージの後に、CloudFrontのキャッシュを削除するClearCacheステージを追加します。
ステージを追加したら、アクショングループを追加します。
アクションプロパイダーとしてlambdaを選択し、インフラリソース用のパイプラインで作成したlambda関数名を選択して、完了を押下します。
CodePipelineの作成に関しては以上です。
GitLabリポジトリにソースコードをPushして静的ウェブサイトの確認
以降の作業は先ほど作成したアプリケーション用のGitLabリポジトリをローカル上にcloneして作業しているとします。
前回の記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する(Vue.js 編)」と同様に、ローカル上にVue.jsのプロジェクトとbuildspec.ymlを作成してください。
それでは、GitLabにPushします!
GitLabへのPush成功です!
CodeCommitリポジトリを確認します。
GitLabから、CodeCommitへのミラーリング成功です!
CodePipelineを確認します。
CodePipelineのステージ全て成功してますね!
CloudFrontの確認をしていきましょう!
作成したディストリビューションのキャッシュ削除を確認すると、S3デプロイ後、キャッシュが削除されているのを確認できますね!
ディストリビューションドメインにアクセスして、ブラウザキャッシュも確認してみましょう。
Google ChromeデベロッパーツールのNetworkタブ、Response Headersを確認するとCache-Control:no-cacheで設定されていますね。
アプリケーション用のパイプライン作成から実行までは以上です。
再度アプリケーション用のリソースをGitで編集してPushしてみてください。編集したリソースが即時反映されることを確認できると思います。
おわりに
お疲れ様でした!
インフラリソース、アプリケーションそれぞれを自動化して管理することができましたね。
今回はブラウザのキャッシュをCache-Control:no-cacheにしましたが、サーバーへ毎回アクセスする必要があるデメリットもあり、コスト面などで問題があるかと思いました。
ブラウザのキャッシュ保持時間をある程度設けてコスト面の対応もしつつ、デプロイ後の反映時間のラグ対応としてはメンテナンス時間を設けて反映確認後、クライアント側へ伝えるなどの設計が良いのではないかと思いました。
また、パイプラインを手動で作成しましたが、パイプラインも含めて自動化したり、CloudFormationをCDKに変更して管理してみるなどまだまだ活用方法はありそうです!
CloudFrontのステータスが有効で公開状態になっているため、無効に設定するかリソースを削除しておきましょう。
私の記事が少しでも皆様のご参考になれば幸いです。