Terraform + CodePipeline + CodeBuild + S3 で Cloudfront に配信する
みなさん、こんにちは!めもりー(@m3m0r7) です。
Terraform を初めて 2 週間ほどのインフラ初心者です。
主にやりたいこととしては下記のようになります。
そのために S3 に展開する設定を含めて Terraform でどう書けばよいのかしたためてみます。
CodeCommit にプッシュしてビルドして Lambda で配信するといった用途なら多く見かけたのですが、残念ながらこういった用途はあまり見かけないので少し苦戦していました。Terraform 難しい。
書いていく
CodePipeline で一連の流れを定義するにあたり、下記が必要になります。
- CodePipeline のためのロールの設定
- CodeBuild のためのロールの設定
- ロールをそれぞれ紐付ける
- CodeBuild のプロジェクトの設定
- CodePipeline のアプリケーションの設定
それぞれ見ていきます。
1. CodePipeline のためのロールの設定
CodePipeline で一体どういったロールを設定すればいいのか正直難しいところもあり beyond さんの Github + CodeBuild + CodePipelineを利用したFargateのデプロイフローをTerraformで構築する という記事を参考にさせていただきました。
当該の記事でデプロイに不要そうな情報をいくつか取り除き data
で以下のように IAM ポリシーを定義し、それを使用します。
data "aws_iam_policy_document" "codepipeline_policy" {
statement {
sid = ""
effect = "Allow"
actions = [
"codedeploy:CreateDeployment",
"codedeploy:GetApplication",
"codedeploy:GetApplicationRevision",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
]
resources = ["*"]
}
statement {
sid = ""
effect = "Allow"
actions = [
"elasticbeanstalk:*",
"ec2:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"s3:*",
"sns:*",
"cloudformation:*",
"rds:*",
"sqs:*",
"ecs:*"
]
resources = ["*"]
}
statement {
sid = ""
effect = "Allow"
actions = [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
]
resources = ["*"]
}
}
権限に関しては、こんなにいらないので、もう少し削っても良い気はしています。
次に AssumeRole の定義を下記のように行います。
data "aws_iam_policy_document" "codepipeline_assume_policy" {
statement {
sid = ""
effect = "Allow"
principals {
identifiers = [
"codepipeline.amazonaws.com",
]
type = "Service"
}
actions = [
"sts:AssumeRole",
]
}
}
2. CodeBuild のためのロールの設定
CodePipeline 同様に CodeBuild のための IAM ポリシーと AssumeRole を定義します。
data "aws_iam_policy_document" "codebuild_policy" {
statement {
sid = ""
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = ["*"]
}
statement {
sid = ""
effect = "Allow"
actions = [
"ec2:CreateNetworkInterface",
"ec2:DescribeDhcpOptions",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVpcs"
]
resources = ["*"]
}
statement {
sid = ""
effect = "Allow"
actions = [
"s3:*"
]
resources = ["*"]
}
}
data "aws_iam_policy_document" "codebuild_assume_policy" {
statement {
sid = ""
effect = "Allow"
principals {
identifiers = [
"codebuild.amazonaws.com",
]
type = "Service"
}
actions = [
"sts:AssumeRole",
]
}
}
3. ロールをそれぞれ紐付ける
data で定義したロールをそれぞれ下記のように紐付けます。
var.env
は予め、指定した現在の環境、例えば stg, prd などが代入される想定です。
Terraform を触っていて、私がものすごく分かりづらかったのが変数の使用である var.*** でした。
余談ですが、この var は variable
ブロックで定義した値が入ります。これらの値は terraform.tfvars
などで定義したり、環境変数で定義した値が入っていきます。
階層が深くなるにつれて、それをモジュールに対してプロパゲーションしていく必要があります。
Terraform の定義を Go で書ければものすごい楽なんだけどなと何回か思ったのは秘密です。
さて、下記のように IAM ポリシーを定義することでロールとポリシーの設定ができるようになります。
resource "aws_iam_role" "codepipeline_role" {
name = "${var.env}-codepipeline-role"
assume_role_policy = data.aws_iam_policy_document.codepipeline_assume_policy.json
}
resource "aws_iam_role_policy" "codepipeline_role_policy" {
name = "${var.env}-codepipeline-policy"
role = aws_iam_role.codepipeline_role.id
policy = data.aws_iam_policy_document.codepipeline_policy.json
}
resource "aws_iam_role" "codebuild_role" {
name = "${var.env}-codebuild-role"
assume_role_policy = data.aws_iam_policy_document.codebuild_assume_policy.json
}
resource "aws_iam_role_policy" "codebuild_policy" {
role = aws_iam_role.codebuild_role.name
policy = data.aws_iam_policy_document.codebuild_policy.json
}
4. CodeBuild のプロジェクトの設定
CodePipeline で使用するための CodeBuild のプロジェクトを設定します。
CodeBuild の定義は下記のようになります。
resource "aws_codebuild_project" "frontend" {
name = "${var.env}-codebuild-frontend"
description = "build frontend for ${var.env}"
build_timeout = "60"
service_role = aws_iam_role.codebuild_role.arn
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:3.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = false
}
source {
type = "CODEPIPELINE"
}
}
5. CodePipeline のアプリケーションの設定
次に CodePipeline の設定です。
resource "aws_codepipeline" "frontend" {
name = "${var.env}-codepipeline-frontend"
role_arn = aws_iam_role.codepipeline_role.arn
artifact_store {
location = aws_s3_bucket.deploy_bucket.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "S3"
version = "1"
output_artifacts = ["source"]
configuration = {
S3Bucket = aws_s3_bucket.deploy_bucket.bucket
S3ObjectKey = "frontend.zip"
PollForSourceChanges = true
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source"]
output_artifacts = ["build"]
version = "1"
configuration = {
ProjectName = aws_codebuild_project.frontend.name
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "S3"
version = "1"
input_artifacts = ["build"]
configuration = {
BucketName = var.storage.cloudfront_bucket.bucket
Extract = true
}
}
}
}
S3 にプッシュするときは aws_s3_bucket.deploy_bucket.bucket
で指定しているバケットに frontend.zip
という名前で下記のようにプッシュします。
aws s3 cp \
frontend.zip \
s3://<< バケット名 >>/frontend.zip
そうするとポーリングを行っている CodePipeline で自動的に処理が始まるようになります。
またステージの設定における Deploy は Extract
を true
にすることで指定したバケットに対してそのままファイル全てを展開することができるようになります。
var.storage.cloudfront_bucket.bucket
は Cloudfront に指定しているバケットの名前を指定します。
これで一連の処理ができるようになりました。
Discussion