Published
- 10 min read
AWS CodePipelineを使ってAstroの静的サイトをS3にデプロイする
はじめに
今回はAWS CodePipelineを使って、前回Astroで作成した静的サイト(ブログ)をデプロイしていきます。
パイプラインの構成イメージは以下の通りです。
また、前回すでにS3は作成済みのため、本記事ではS3の作成部分は割愛します。

CodeCommmitの準備
今回はブログのコード、コンテンツをすべてCodeCommitに格納しました。
GitHub等CodePipelineのソースにできるものであれば何でもいいです。
CodeCommitはマネジメントコンソールで作成しました。
CodePipelineの設定
CodePipelineは以下のステージを作成します。
- Source: CodeCommitをソースプロバイダーとしてソースコードを取得する
- Build: CodeBuildをアクションプロバイダーとしてAstroブログをデプロイする
- Deploy: S3をアクションプロバイダーとしてビルドアーティファクトをプットする
CodeCommitからコードを取得、CodeBuildでAstroをビルド、最後にS3にコンテンツをアップロードする流れです。
CodeBuildの設定
CodeBuildにおけるLambdaの使用について
CodeBuildのコンピューティングにLambdaが使用できるようになったため、今回はそちらを使用していきます。
(参考)AWS CodeBuildでのAWS Lambdaコンピューティングの使用
今回のユースケースではnpmを使用して必要なパッケージのインストールとビルド(astro build)を行います。
そのため、ランタイムはNode.jsコンテナを使用します。
もちろん従来のEC2タイプでも実装可能ですがLambdaの方が圧倒的に早いので、ユースケースにマッチする場合はLambdaの使用がおすすめです。
今回に似たようなユースケースとして、公式ドキュメントではReactアプリのビルドサンプルが紹介されています。
(参考)CodeBuild Lambda Node.js で単一ページの React アプリを作成する
buildspec
buildspecファイル(CodeBuildで実行するコマンドを記述したファイル)は以下の通りです。
version: 0.2
phases:
pre_build:
commands:
- node -v
- npm -v
build:
commands:
- npm install -g npm@latest
- npm install
- npm run build
artifacts:
name: "blog_content"
base-directory: "dist"
files:
- "**/*"
exclude-paths:
- "**/admin/*"
- "**/sitemap*.xml"
- "**/robots.txt"
- "**/openblog-lighthouse-score.svg"
- pre_buildフェーズではnodeやnpmのバージョン確認をしていますが、必須ではありません。
- buildフェーズでは、npm自身のアップデート後、
npm installとnpm run build(astro build)をしています。 - ビルドアーティファクトには、ビルドしたコンテンツが出力されている
distディレクトリを指定します。
しかし、中身には不要なファイルが含まれているため、exclude-pathsにパスの指定を行います。
buildspecのリファレンスについては以下の公式ドキュメントを参照ください。
(参考)CodeBuildのビルド仕様リファレンス
作成したファイルをCodeCommmitに格納します。
今回のケースでは、cicdというフォルダを作成し、そこに配置しています。
※CodeBuildの設定でファイルパスを指定します。
Terraformコード
上記の設定を踏まえたTerraformコードは以下の通りです。
使用する場合は各自の環境に応じて書き換えてください。
また、今回CodeCommitはマネジメントコンソールで作成し、Terraformでは管理しません。
# CodePipeline Settings
resource "aws_codepipeline" "blog" {
name = "astro-blog-pipeline"
role_arn = aws_iam_role.blog["codepipeline"].arn
artifact_store {
location = aws_s3_bucket.codepipeline_artifact.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["SourceArtifact"]
configuration = {
RepositoryName = var.application_repository_name # Codecommitのリポジトリ名
BranchName = "release" # リリース対象のブランチ名
PollForSourceChanges = false
OutputArtifactFormat = "CODE_ZIP"
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["SourceArtifact"]
output_artifacts = ["BuildArtifact"]
version = "1"
configuration = {
ProjectName = aws_codebuild_project.blog.name
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "S3"
input_artifacts = ["BuildArtifact"]
version = "1"
configuration = {
BucketName = "YOUR_BLOG_S3_BUCKET_NAME" # デプロイするS3バケット名
Extract = true # ビルドアーティファクトを展開してputする
}
}
}
}
# CodeBuild Settings
resource "aws_codebuild_project" "blog" {
name = "astro-blog-codebuild"
description = "run astro build with lambda"
build_timeout = 15 # Lambdaなのでタイムアウトは15分まで
service_role = aws_iam_role.blog["codebuild"].arn
artifacts {
type = "CODEPIPELINE"
}
environment {
# メモリの設定(1GBでも動くが遅い)
compute_type = "BUILD_LAMBDA_2GB"
# Node.jsのLambdaコンテナイメージを使用する
image = "aws/codebuild/amazonlinux-x86_64-lambda-standard:nodejs20"
type = "LINUX_LAMBDA_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = false
}
source {
type = "CODEPIPELINE"
buildspec = "cicd/buildspec.yml" # CodeCOmmitのルートから見たbuildspecファイルのパス
}
}
# S3 Settings (Artifact Store)
resource "aws_s3_bucket" "codepipeline_artifact" {
# CodePipelineのアーティファクトストアのS3バケット
bucket = "YOUR_ARTIFACT_S3_BUCKET_NAME"
}
resource "aws_s3_bucket_public_access_block" "codepipeline_artifact" {
bucket = aws_s3_bucket.codepipeline_artifact.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# IAM Roles
# for_eachで使用するmap(名前は良くなかったかも)
locals {
service_name = {
codepipeline = "codepipeline.amazonaws.com"
codebuild = "codebuild.amazonaws.com"
}
}
## IAM Role
resource "aws_iam_role" "blog" {
# for_eachを使用して、CodePipelineロールとCodeBuildロールをそれぞれ定義する
for_each = tomap(local.service_name)
name = "astro-blog-role-${each.key}"
# 信頼関係ポリシーに設定するAWSサービス名を注入する
assume_role_policy = templatefile("${path.module}/template/assume_role_policy.json",
{
service_name = each.value
})
}
## IAM Policy
resource "aws_iam_policy" "blog" {
# for_eachを使用して、CodePipelineロールとCodeBuildロールをそれぞれ定義する
for_each = tomap(local.service_name)
name = "astro-blog-policy-${each.key}"
# 各ポリシーに記載された変数に値を注入する
policy = templatefile("${path.module}/template/${each.key}_role_policy.json",
{
application_repository_name = var.application_repository_name
blog_content_s3_bucket_arn = aws_s3_bucket.blog.arn
artifact_s3_bucket_arn = aws_s3_bucket.codepipeline_artifact.arn
codebuild_arn = aws_codebuild_project.blog.arn
}
)
}
## IAM Role Policy Attachment
resource "aws_iam_role_policy_attachment" "blog" {
# for_eachを使用して、CodePipelineロールとCodeBuildロールをそれぞれ定義する
for_each = tomap(local.service_name)
role = aws_iam_role.blog[each.key].name
policy_arn = aws_iam_policy.blog[each.key].arn
}
信頼関係ポリシー
IAMロールの信頼関係ポリシーです。
このファイルはtemplateフォルダ内に配置します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "${service_name}"
},
"Action": "sts:AssumeRole"
}
]
}
CodePipelineロールポリシー
CodePipelineのサービスロールに設定するポリシーです。
CodeCommmit、CodeBuild、S3に対する各操作の許可を記載します。
このファイルはtemplateフォルダ内に配置します。
YOUR_AWS_ACCOUNT_ID は書き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CodeCommit",
"Effect": "Allow",
"Action": [
"codecommit:CancelUploadArchive",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive"
],
"Resource": "arn:aws:codecommit:ap-northeast-1:YOUR_AWS_ACCOUNT_ID:${application_repository_name}"
},
{
"Sid": "S3",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"${artifact_s3_bucket_arn}/*",
"${blog_content_s3_bucket_arn}/*"
]
},
{
"Sid": "CodeBuild",
"Effect": "Allow",
"Action": [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
],
"Resource": [
"${codebuild_arn}"
]
}
]
}
CodeBuildロールポリシー
CodeBuildのサービスロールに設定するポリシーです。
アーティファクトストアのS3に対する操作の許可を記載します。
その他ログやECRへの許可も記載します。
サービスロールの権限サンプルは公式ドキュメントを参照ください。
(参考)CodeBuild サービスロールの作成
このファイルはtemplateフォルダ内に配置します。
YOUR_AWS_ACCOUNT_ID は書き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudWatchLogsPolicy",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Sid": "S3Policy",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
"s3:GetBucketAcl",
"s3:GetBucketLocation"
],
"Resource": [
"${artifact_s3_bucket_arn}",
"${artifact_s3_bucket_arn}/*"
]
},
{
"Sid": "ECRPullPolicy",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
},
{
"Sid": "ECRAuthPolicy",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
}
]
}
動作確認
作成したCodePipeineを実行し、S3に想定通りファイルがデプロイされていれば成功です。
また、実際にサイトにアクセスして、コンテンツが表示されることを確認しましょう。

課題
ゴミファイルの残存
S3へのファイル配置の際に、既存ファイルは上書きされますが、一部ビルドの都度新規作成されるJSファイルがあるようです。
上書きされない古いファイルは放置するとゴミが永遠に残り続けてしまいます。
ライフサイクルルールの有効期限設定以外で削除をする方法を検討します。
CloudFrontキャッシュ
前回作成したCloudFrontはオリジンのキャッシュを有効にしていました。
デプロイしてもファイルの更新が正常に反映されないため、例えば以下のような対策を行う必要があります。
- そもそもCloudFrontのキャッシュを無効にする
- キャッシュの削除を手動(コンソール/CLI)で実行する
- キャッシュの削除を行うLambda/StepFunctions等をパイプラインに組み込む
※キャッシュの削除はaws cloudfront create-invalidationコマンドで実行できます。
(参考)キャッシュされたファイルを CloudFront から削除する方法を教えてください。