AWS構築をCI/CDパイプラインで自動化する(Terraform)

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でリソースを全て記述するのは大変かもしれません。
しかし、一度作成したリソースが今後も使いまわせると考えると努力は報われますよね!

私の記事が少しでも皆様のご参考になれば幸いです!

この記事をシェアする
著者:nakao
IoT、サーバーレスな開発に興味深々。AWSエンジニア。