CUEBiC TEC BLOG

キュービックTECチームの技術ネタを投稿しております。

Terraformのdynamicブロックを使って動的にリソースパラメーターを分けてみた

概要

こんにちは、キュービックでSREをやっているYuhta28です。キュービック内のテック技術について発信します。
以前弊社でのTerraformの取り組みについて紹介しました。

cuebic.hatenablog.com

この中で環境によってリソース数の差分が生じる、例えば本番環境なら冗長化のためNAT Gatewayを3台稼働させるが、検証環境ならコスト最適化のため1台のみ稼働させると言ったケースではfor_eachを使って動的にリソース数を管理する方法について紹介しました。
ではAWS CodePipelineで本番環境のみにApprovalステージを追加したいというような要望のときはどうすればよろしいでしょうか🤔

本番環境ではデプロイ直前に承認作業がある

CodePipelineのステージはパラメーターの一つで複数行に渡り記述されるのでfor_eachで分岐するとなるとうまくいきません。

# CodePipeline作成Terraform(一部パラメーター省略)
resource "aws_codepipeline" "example-pipeline" {
  artifact_store {
    location = var.s3-location
    type     = "S3"
  }
  name     = "pipeline-name"

  # ソースステージ
  stage {
    name = "Source"
    action {
      category = "Source"

      configuration = {
        Branch               = "main"
        Owner                = "master"
        PollForSourceChanges = "false"
        Repo                 = "pipeline-repo"
      }

      name             = "Source"
      namespace        = "SourceVariables"
      output_artifacts = ["SourceArtifact"]
      owner            = "ThirdParty"
      provider         = "GitHub"
      region           = "ap-northeast-1"
      run_order        = "1"
      version          = "1"
    }
  }

  # ビルドステージ
  stage {
    name = "Build"

    action {
      category = "Build"

      configuration = {
        ProjectName = "build-project"
      }

      input_artifacts  = ["SourceArtifact"]
      name             = "Build"
      output_artifacts = ["BuildArtifact"]
      owner            = "AWS"
      provider         = "CodeBuild"
      region           = "ap-northeast-1"
      run_order        = "1"
      version          = "1"
    }
  }

  # 承認ステージ(本番環境のみ作成)
  stage {
      name = "Confirm"
      action {
        category  = "Approval"
        name      = "RequestApprove"
        owner     = "AWS"
        provider  = "Manual"
        region    = "ap-northeast-1"
        run_order = "1"
        version   = "1"
      }
  }

  # デプロイステージ
  stage {
    name = "Deploy"

    action {
      category = "Deploy"


      input_artifacts = ["BuildArtifact"]
      name            = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      region          = "ap-northeast-1"
      run_order       = "1"
      version         = "1"
    }
  }
}

承認ステージ部分が本番環境だけに必要なステージです。ここだけを環境ごとに分割したいという状況は一般的にあると思います。
この問題を解決してくれるのがdynamicブロックとよばれるものです。今回このdynamicブロックを用いて環境毎に異なるリソースパラメータの動的作成について紹介します。

dynamicブロックについて

www.terraform.io

dynamicはブロック単位のループ処理を実装したいときに使います。
例えばセキュリティグループの作成でdynamicブロックを使ったとします。

# セキュリティグループ作成
locals {
  ingress_web = [
  # [description, from_port, to_port, protocol, cidr_blocks]
    ["HTTPS from VPC", 443, 443, "tcp", "10.0.0.0/8"],
    ["HTTP from VPC", 80, 80, "tcp", "192.168.0.0/16"],
  ]
}

resource "aws_security_group" "web" {
  name        = "allow-web"
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = local.ingress_web
    content {
      description = ingress.value[0]
      from_port   = ingress.value[1]
      to_port     = ingress.value[2]
      protocol    = ingress.value[3]
      cidr_blocks = ingress.value[4]
    }
  }

  egress = [
    {
      from_port        = 0
      to_port          = 0
      protocol         = "-1"
      cidr_blocks      = ["0.0.0.0/0"]
    }
  ]

  tags = {
    Name = "allow-web"
  }
}

ここではingressルールでHTTP通信とHTTPS通信の許可設定を付与する箇所でdynamicブロックを使っています。ingressのパラメータはポートが異なっても設定項目は同じなので設定値をingress_webという変数でまとめればingressの記述回数が一回で済みます。
これを応用してdynamicでループするかどうかをTrue or Falseで分岐するようにすれば本番環境のみに承認ステージを追加することができます。

dynamicブロックによる分岐設定

先程のTerraformファイルを以下のように修正します。

# CodePipeline作成Terraform(一部パラメーター省略)
resource "aws_codepipeline" "example-pipeline" {
  artifact_store {
    location = var.s3-location
    type     = "S3"
  }
  name     = "pipeline-name"

  # ソースステージ
  stage {
    name = "Source"
    action {
      category = "Source"

      configuration = {
        Branch               = "main"
        Owner                = "master"
        PollForSourceChanges = "false"
        Repo                 = "pipeline-repo"
      }

      name             = "Source"
      namespace        = "SourceVariables"
      output_artifacts = ["SourceArtifact"]
      owner            = "ThirdParty"
      provider         = "GitHub"
      region           = "ap-northeast-1"
      run_order        = "1"
      version          = "1"
    }
  }

  # ビルドステージ
  stage {
    name = "Build"

    action {
      category = "Build"

      configuration = {
        ProjectName = "build-project"
      }

      input_artifacts  = ["SourceArtifact"]
      name             = "Build"
      output_artifacts = ["BuildArtifact"]
      owner            = "AWS"
      provider         = "CodeBuild"
      region           = "ap-northeast-1"
      run_order        = "1"
      version          = "1"
    }
  }

  # dyanmicを追加、論理型変数でTrue時のみリソースを作成する
  dynamic "stage" {
    for_each = var.production_approval ? [1] : []
    content {
      name = "Confirm"
      action {
        category  = "Approval"
        name      = "RequestApprove"
        owner     = "AWS"
        provider  = "Manual"
        region    = "ap-northeast-1"
        run_order = "1"
        version   = "1"
      }
    }
  }

  # デプロイステージ
  stage {
    name = "Deploy"

    action {
      category = "Deploy"


      input_artifacts = ["BuildArtifact"]
      name            = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      region          = "ap-northeast-1"
      run_order       = "1"
      version         = "1"
    }
  }
}

dynamicブロックを追加し、production_approvalという変数がTrueの時リソースを一回作成するように設定します。
production_approvalはTrue/Falseの論理型変数なので、variables.tfには以下のように記載します。

# variables.tf
variable "production_approval" {
  type        = bool
  default     = false
  description = "本番環境のみにApprovalステージを設定するためのif分岐"
}

本番環境のterraform apply実行先のCodePipelineモジュールファイルにtrueを加えます。

module "cba-codepipeline" {
  source              = "../../modules/codepipeline

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
  # trueとすることでfor_eachで承認ステージを作成する
  production_approval = true
}

これによって本番環境では承認ステージ付きのCodePipelineが作成され、検証環境では承認ステージなしのCodePipelineを作成できます。
(注:production_approvalのデフォルト値をfalseにしているので検証環境に変数をセット不要です)

所感

dynamicブロックを使ってCodePipelineの承認ステージの作成可否を環境ごとに分岐できるようにしました。
Moduleで汎用化する際、パラメータ単位でリソースを作り分けたいというケースもあると思います。そうしたケースではdynamicブロックを使って環境別の条件別分岐を実装すると良いと思います。
みなさんもぜひ試してみてください。

参考文献

future-architect.github.io

stackoverflow.com

kleinblog.net