AWS構築をCI/CDパイプラインで自動化する(Terraform)
目次
はじめに
こんにちは!スカイアーチHRソリューションズのnakaoです!
最近はTerraformやCI/CDパイプラインを使う機会が多く、アウトプットしたい!と思ったので記事にしました。
テーマとしてAWSリソースの構築をCI/CDパイプラインで自動化してみたいと思います。
アーキテクチャ
全体のアーキテクチャはこちらです!
ソース管理上GitLabを使用していますが、適宜GitHubもしくはCodeCommitなどに置き換えてください。
今回使用するリポジトリは2つあります。
・インフラパイプライン用リポジトリ
CI/CDパイプライン用のリソースを管理するリポジトリです。リソースはTerraformで管理します。
・インフラ用リポジトリ
構築したいAWSインフラリソースのリポジトリです。リソースはTerraformで管理します。
処理の流れとしては、以下の順番で実行されます。
①GitLab(インフラ用リポジトリ)にソースコード(Terraform)をPushする。
②ミラーリングが実行され、CodeCommitリポジトリにソースコードがミラーリングされる。
③CodeCommitへのミラーリングをトリガーに、CodePipelineが実行される。
④CodePipeline上でCodeBuildが実行される。
⑤CodePipeline上でTerraformのデプロイが実行される。
⑥AWSリソースを構築する。
今回はCI/CDパイプラインを構築するAWSアカウントとAWSリソースを構築するAWSアカウントは同一ですが、デプロイ先を別AWSアカウントにすることも可能です。
手動構築するリソース
CodeCommitリポジトリ、GitLabリポジトリは手動で構築します。
以前執筆したこちらの記事「GitLabリポジトリをCodeCommitにミラーリングしてS3に配置する」を参考に、CodeCommitリポジトリの作成、IAMユーザーの作成、GitLabリポジトリを作成してください。
注意点ですが、GitLabリポジトリ作成時のミラーリング設定で「Authentication method」が「Username and Password」しか選択できなくなりました。UsernameにはGit認証情報のユーザー名を設定してください。
また、「Mirror only protected branches」はチェックしてませんでしたが、チェックした方が良いでしょう。
開発はdevブランチで、「mainブランチにpushした場合のみミラーリングが実施されAWSリソースがデプロイされる」という使い分けができます。
手動構築するリソースに関しては以上です。
インフラパイプライン用リソース作成からapplyまで
まずはインフラパイプライン用のリソースを作成します。作成後、ローカル上から手動でapplyします。
インフラパイプライン用のリソース作成
フォルダ構成はこちらです。
ベストプラクティスを参考にした構成を心掛けました。
spa-infra-pipeline
├── terraform
│ ├── envs
│ │ └── dev
│ │ ├── backend.tf
│ │ ├── data.tf
│ │ ├── locals.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ ├── provider.tf
│ │ └── version.tf
│ ├── modules
│ │ ├── codebuild
│ │ │ ├── codebuild_deploy.tf
│ │ │ ├── codebuild_deployplan.tf
│ │ │ ├── iam.tf
│ │ │ ├── outputs.tf
│ │ │ └── variables.tf
│ │ ├── codepipeline
│ │ │ ├── codepipeline.tf
│ │ │ ├── iam.tf
│ │ │ ├── outputs.tf
│ │ │ └── variables.tf
│ │ ├── eventbridge
│ │ │ ├── eventbridge.tf
│ │ │ ├── iam.tf
│ │ │ ├── outputs.tf
│ │ │ └── variables.tf
│ │ └── s3_artifact
│ │ ├── outputs.tf
│ │ ├── s3_artifact.tf
│ │ └── variables.tf
│ └── setup
│ └── dev
│ ├── dynamodb.tf
│ ├── provider.tf
│ └── s3.tf
├── .gitignore
├── Makefile
└── README.md
「terraform」フォルダにterraformのファイルを集約しています。
まず、「envs」で環境毎にリソースを分けるようにしています。
また、terraformではstateファイルを用いて管理しているリソースの現在の状態を表しています。
今回はstateファイルをAWS上(S3とdynamodb)で管理します。
詳細に関しては以下、公式ドキュメントを参照してください。
terraform {
backend "s3" {
bucket = "dev-spa-infra-pipeline-terraform-state"
key = "terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "DevSpaInfraPipelineTerraformStateLock"
}
}
data "aws_codecommit_repository" "source_repository" {
repository_name = local.codecommit_repository_name
}
locals {
environment = "dev"
target = "spa-infra-pipeline"
codecommit_repository_name = "spa-infra"
codecommit_branch_name = "main"
deploy_image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
}
モジュールとしてリソースをファイルで切り分けるようにしています。
module "s3_artifact" {
source = "../../modules/s3_artifact"
environment = local.environment
target = local.target
}
module "codebuild" {
source = "../../modules/codebuild"
environment = local.environment
target = local.target
deploy_image = local.deploy_image
}
module "codepipeline" {
source = "../../modules/codepipeline"
environment = local.environment
target = local.target
s3_artifact_bucket = module.s3_artifact.s3_artifact_bucket
codecommit_repository_name = local.codecommit_repository_name
codecommit_branch_name = local.codecommit_branch_name
codebuild_deployplan_name = module.codebuild.codebuild_deployplan_name
codebuild_deploy_name = module.codebuild.codebuild_deploy_name
}
module "eventbridge" {
source = "../../modules/eventbridge"
environment = local.environment
target = local.target
codecommit_branch_name = local.codecommit_branch_name
codecommit_arn = data.aws_codecommit_repository.source_repository.arn
codepipeline_arn = module.codepipeline.codepipeline.arn
}
provider "aws" {
region = "ap-northeast-1"
}
terraform {
required_version = "1.8.3"
}
次に、「modules」で作成するAWSリソースを記述しています。
codebuildのリソースを作成します。
resource "aws_codebuild_project" "deployplan" {
name = "${var.environment}-${var.target}-deployplan-project"
service_role = aws_iam_role.codebuild_role.arn
source {
type = "CODEPIPELINE"
buildspec = "buildspec_deployplan.yml"
}
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = var.deploy_image
type = "LINUX_CONTAINER"
environment_variable {
name = "ENV"
type = "PLAINTEXT"
value = var.environment
}
}
}
resource "aws_codebuild_project" "deploy" {
name = "${var.environment}-${var.target}-deploy-project"
service_role = aws_iam_role.codebuild_role.arn
source {
type = "CODEPIPELINE"
buildspec = "buildspec_deploy.yml"
}
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = var.deploy_image
type = "LINUX_CONTAINER"
environment_variable {
name = "ENV"
type = "PLAINTEXT"
value = var.environment
}
}
}
resource "aws_iam_role" "codebuild_role" {
name = "${var.environment}-${var.target}-codebuild-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_policy" "codebuild_policy" {
name = "${var.environment}-${var.target}-codebuild-policy"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
"dynamoDB:*"
]
Resource = [
"*",
]
},
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"codecommit:GitPull"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:UpdateReport",
"codebuild:BatchPutTestCases",
"codebuild:BatchPutCodeCoverages"
]
Resource = ["*"]
},
]
})
}
resource "aws_iam_role_policy_attachment" "codebuild_policy_attach" {
role = aws_iam_role.codebuild_role.name
policy_arn = aws_iam_policy.codebuild_policy.arn
}
output "codebuild_deployplan_name" {
value = aws_codebuild_project.deployplan.name
}
output "codebuild_deploy_name" {
value = aws_codebuild_project.deploy.name
}
variable "environment" {
type = string
}
variable "target" {
type = string
}
variable "deploy_image" {
type = string
}
codepipelineのリソースを作成します。
resource "aws_codepipeline" "codepipeline" {
name = "${var.environment}-${var.target}-codepipeline"
role_arn = aws_iam_role.codepipeline_role.arn
artifact_store {
location = var.s3_artifact_bucket.id
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["source_output"]
configuration = {
RepositoryName = var.codecommit_repository_name
BranchName = var.codecommit_branch_name
PollForSourceChanges = "false"
}
}
}
stage {
name = "DeployPlan"
action {
name = "DeployPlan"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source_output"]
output_artifacts = ["deploy_plan_output"]
version = "1"
configuration = {
ProjectName = var.codebuild_deployplan_name
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["deploy_plan_output"]
version = "1"
configuration = {
ProjectName = var.codebuild_deploy_name
}
}
}
}
resource "aws_iam_role" "codepipeline_role" {
name = "${var.environment}-${var.target}-codepipeline-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_policy" "codepipeline_policy" {
name = "${var.environment}-${var.target}-codepipeline-policy"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow"
Action = [
"iam:PassRole"
]
Resource = [
"*",
]
Condition = {
"StringEqualsIfExists" = {
"iam:PassedToService" = [
"cloudformation.amazonaws.com",
"elasticbeanstalk.amazonaws.com",
"ec2.amazonaws.com",
"ecs-tasks.amazonaws.com"
]
}
}
},
{
Effect = "Allow"
Action = [
"codecommit:CancelUploadArchive",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetRepository",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"codedeploy:CreateDeployment",
"codedeploy:GetApplication",
"codedeploy:GetApplicationRevision",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"codestar-connections:UseConnection"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"elasticbeanstalk:*",
"ec2:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"s3:*",
"sns:*",
"cloudformation:*",
"rds:*",
"sqs:*",
"ecs:*"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"lambda:InvokeFunction",
"lambda:ListFunctions"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"opsworks:CreateDeployment",
"opsworks:DescribeApps",
"opsworks:DescribeCommands",
"opsworks:DescribeDeployments",
"opsworks:DescribeInstances",
"opsworks:DescribeStacks",
"opsworks:UpdateApp",
"opsworks:UpdateStack"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"cloudformation:CreateStack",
"cloudformation:DeleteStack",
"cloudformation:DescribeStacks",
"cloudformation:UpdateStack",
"cloudformation:CreateChangeSet",
"cloudformation:DeleteChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:SetStackPolicy",
"cloudformation:ValidateTemplate"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"codebuild:BatchGetBuilds",
"codebuild:StartBuild",
"codebuild:BatchGetBuildBatches",
"codebuild:StartBuildBatch"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"devicefarm:ListProjects",
"devicefarm:ListDevicePools",
"devicefarm:GetRun",
"devicefarm:GetUpload",
"devicefarm:CreateUpload",
"devicefarm:ScheduleRun"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"servicecatalog:ListProvisioningArtifacts",
"servicecatalog:CreateProvisioningArtifact",
"servicecatalog:DescribeProvisioningArtifact",
"servicecatalog:DeleteProvisioningArtifact",
"servicecatalog:UpdateProduct"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"cloudformation:ValidateTemplate"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"ecr:DescribeImages"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"states:DescribeExecution",
"states:DescribeStateMachine",
"states:StartExecution"
]
Resource = ["*"]
},
{
Effect = "Allow"
Action = [
"appconfig:StartDeployment",
"appconfig:StopDeployment",
"appconfig:GetDeployment"
]
Resource = ["*"]
},
]
})
}
resource "aws_iam_role_policy_attachment" "codepipeline_policy_attach" {
role = aws_iam_role.codepipeline_role.name
policy_arn = aws_iam_policy.codepipeline_policy.arn
}
output "codepipeline" {
value = aws_codepipeline.codepipeline
}
variable "environment" {
type = string
}
variable "target" {
type = string
}
variable "s3_artifact_bucket" {
type = object({
id = string
arn = string
})
}
variable "codecommit_repository_name" {
type = string
}
variable "codecommit_branch_name" {
type = string
}
variable "codebuild_deployplan_name" {
type = string
}
variable "codebuild_deploy_name" {
type = string
}
eventbridgeはマネコン上でcodepipelineを作成すると自動で作成されますが、今回は自動作成されません。codecommitへのpushをトリガーとしてcodepipelineが動作するeventbridgeを作成して紐づけます。
resource "aws_cloudwatch_event_rule" "eventbridge_rule" {
name = "${var.environment}-${var.target}-eventbridge-rule"
event_pattern = jsonencode({
source = ["aws.codecommit"],
detail-type = ["CodeCommit Repository State Change"],
resources = ["${var.codecommit_arn}"],
detail = {
event = ["referenceCreated", "referenceUpdated"],
referenceType = ["branch"],
referenceName = ["${var.codecommit_branch_name}"]
}
})
}
resource "aws_cloudwatch_event_target" "eventbridge_target" {
rule = aws_cloudwatch_event_rule.eventbridge_rule.name
arn = var.codepipeline_arn
role_arn = aws_iam_role.eventbridge_role.arn
}
resource "aws_iam_role" "eventbridge_role" {
name = "${var.environment}-${var.target}-eventbridge-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_policy" "eventbridge_policy" {
name = "${var.environment}-${var.target}-eventbridge-policy"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow"
Action = [
"codepipeline:StartPipelineExecution"
]
Resource = [
"${var.codepipeline_arn}"
]
},
]
})
}
resource "aws_iam_role_policy_attachment" "eventbridge_policy_attach" {
role = aws_iam_role.eventbridge_role.name
policy_arn = aws_iam_policy.eventbridge_policy.arn
}
variable "environment" {
type = string
}
variable "target" {
type = string
}
variable "codecommit_branch_name" {
type = string
}
variable "codecommit_arn" {
type = string
}
variable "codepipeline_arn" {
type = string
}
codepipelineのArtifact用にs3のリソースを作成します。
output "s3_artifact_bucket" {
value = aws_s3_bucket.artifact-bucket
}
resource "aws_s3_bucket" "artifact-bucket" {
bucket = "${var.environment}-${var.target}-artifact-bucket"
}
resource "aws_s3_bucket_public_access_block" "artifact-bucket" {
bucket = aws_s3_bucket.artifact-bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
variable "environment" {
type = string
}
variable "target" {
type = string
}
次に、「setup」でstateファイル管理用のAWSリソースを記述しています。
resource "aws_dynamodb_table" "lock" {
name = "DevSpaInfraPipelineTerraformStateLock"
hash_key = "LockID"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "LockID"
type = "S"
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "bucket" {
bucket = "dev-spa-infra-pipeline-terraform-state"
}
resource "aws_s3_bucket_public_access_block" "bucket" {
bucket = aws_s3_bucket.bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
また、今回はMakefileを用いていますが、terraformコマンドを直接入力しても問題ないです。
fmt:
terraform fmt --recursive
dev-init:
cd terraform/envs/dev ; terraform init
dev-plan:
cd terraform/envs/dev ; terraform plan --parallelism=30
dev-apply:
cd terraform/envs/dev ; terraform apply --auto-approve
dev-destroy:
cd terraform/envs/dev ; terraform destroy
インフラパイプライン用のリソース作成については以上です。
インフラパイプライン用のリソースapply
インフラパイプライン用のリソースを手動でapplyします。
terraformを使用するためにtfenvを使用します。install方法は割愛します。
また前提としてaws cliのinstallおよび、configureは設定済みとします。
state管理用のリソースapply
まずは、state管理用のリソースを個別でapplyします。
状態を管理するstateファイルは先に作成しておく必要があるからです。
stateファイルとリソースを同時に作成しようとするとうまくいかず、作成できませんでした。
$ tfenv --version
tfenv 3.0.0-49-g39d8c27
$ tfenv list
* 1.8.3 (set by /c/Users/nakao/.tfenv/version)
$ terraform --version
Terraform v1.8.3
on windows_amd64
+ provider registry.terraform.io/hashicorp/aws v5.48.0
$ cd terraform/setup/dev/
$ terraform init
$ terraform plan
$ terraform apply
state管理用のリソースapplyについては以上です。
インフラパイプライン用のリソースapply
次に、インフラパイプライン用のリソースをapplyします。
makeを使用しない場合は直接Makefile内のコマンドを入力してください。
$ make --version
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
$ make dev-init
$ make dev-plan
$ make dev-apply
成功した場合、AWSリソース(CI/CDパイプライン)が作成されているのがAWSマネコン上でも確認できます。
インフラパイプライン用のリソースapplyについては以上です。
インフラ用リソース作成からpushまで
次にインフラ用のリソースを作成します。
pushすると、CI/CDパイプラインが動作してAWSリソースが構築されます。
インフラ用のリソース作成
フォルダ構成はこちらです。
spa-infra
├── terraform
│ ├── envs
│ │ └── dev
│ │ ├── backend.tf
│ │ ├── data.tf
│ │ ├── locals.tf
│ │ ├── main.tf
│ │ ├── output.tf
│ │ ├── provider.tf
│ │ └── version.tf
│ ├── modules
│ │ └── s3
│ │ ├── outputs.tf
│ │ ├── s3.tf
│ │ └── variables.tf
│ └── setup
│ └── dev
│ ├── dynamodb.tf
│ ├── provider.tf
│ └── s3.tf
├── .gitignore
├── buildspec_deploy.yml
├── buildspec_deployplan.yml
├── Makefile
└── README.md
「envs」で環境毎にリソースを分けるようにします。
terraform {
backend "s3" {
bucket = "dev-spa-infra-terraform-state"
key = "terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "DevSpaInfraTerraformStateLock"
}
}
locals {
environment = "dev"
target = "spa-infra-pipeline"
}
module "s3" {
source = "../../modules/s3"
environment = local.environment
target = local.target
}
provider "aws" {
region = "ap-northeast-1"
}
terraform {
required_version = "1.8.3"
}
「modules」で構築するAWSリソースを記述しています。
今回は試しにS3バケットを作成します。
resource "aws_s3_bucket" "source_bucket" {
bucket = "${var.environment}-${var.target}-source-bucket"
}
resource "aws_s3_bucket_public_access_block" "source_bucket" {
bucket = aws_s3_bucket.source_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
variable "environment" {
type = string
}
variable "target" {
type = string
}
「setup」でstateファイル管理用のAWSリソースを記述しています。
resource "aws_dynamodb_table" "lock" {
name = "DevSpaInfraTerraformStateLock"
hash_key = "LockID"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "LockID"
type = "S"
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "bucket" {
bucket = "dev-spa-infra-terraform-state"
}
resource "aws_s3_bucket_public_access_block" "bucket" {
bucket = aws_s3_bucket.bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
codepipeline上のcodebuildで読み込むbuildspec.ymlをインフラ用のリポジトリ直下に配置します。
Makefileはインフラパイプライン用リソースと同様のファイルを配置します。
version: 0.2
phases:
install:
commands:
- echo install tfenv...
- git clone https://github.com/tfutils/tfenv.git ~/.tfenv
- echo 'export PATH=$PATH:$HOME/.tfenv/bin' >> ~/.bashrc
- source ~/.bashrc
- tfenv --version
- tfenv list-remote
- tfenv install 1.8.3
- tfenv list
- tfenv use 1.8.3
- terraform --version
- echo installed tfenv...
build:
commands:
- echo build start...
- make --version
- aws sts get-caller-identity
- make ${ENV}-init
- make ${ENV}-plan
- echo build completed...
artifacts:
files:
- '**/*'
version: 0.2
phases:
install:
commands:
- echo install tfenv...
- git clone https://github.com/tfutils/tfenv.git ~/.tfenv
- echo 'export PATH=$PATH:$HOME/.tfenv/bin' >> ~/.bashrc
- source ~/.bashrc
- tfenv --version
- tfenv list-remote
- tfenv install 1.8.3
- tfenv list
- tfenv use 1.8.3
- terraform --version
- echo installed tfenv...
build:
commands:
- echo build start...
- make --version
- aws sts get-caller-identity
- make ${ENV}-apply
- echo build completed...
artifacts:
files:
- '**/*'
インフラ用のリソース作成については以上です。
インフラ用のリソースapply
state管理用のリソースはインフラパイプライン用のリソースと同様、個別で先にapplyしておく必要があります。
$ tfenv --version
tfenv 3.0.0-49-g39d8c27
$ tfenv list
* 1.8.3 (set by /c/Users/nakao/.tfenv/version)
$ terraform --version
Terraform v1.8.3
on windows_amd64
+ provider registry.terraform.io/hashicorp/aws v5.48.0
$ cd terraform/setup/dev/
$ terraform init
$ terraform plan
$ terraform apply
インフラ用のリソースapplyについては以上です。
インフラ用のリソースpush
それでは最後に、インフラ用のリソースをpushします。
pushをトリガーにcodepipelineが起動し、AWSリソースが作成されていることが確認できました!
インフラ用のリソースpushについては以上です。
おわりに
お疲れ様でした!
AWS構築をCI/CDパイプラインで自動化することができましたね!
慣れるまではterraformでリソースを全て記述するのは大変かもしれません。
しかし、一度作成したリソースが今後も使いまわせると考えると努力は報われますよね!
私の記事が少しでも皆様のご参考になれば幸いです!