Terragrunt は、Terraform を使用してインフラストラクチャを管理するための作業を簡素化するための Terraform Wrapper として知られるオープンソースのツールです。Terragrunt は Terraform が提供する機能を拡張し、モジュールのコード再利用、柔軟な構成、再利用性の向上を可能にします。特に、Terraform を使用して複数の環境やアカウントを管理する際に Terragrunt は非常に便利です。
$ brew install tfenv # Installs Terraform (version specified in .terraform-version)
$ brew install tgenv # Installs Terragrunt (version specified in .terragrunt-version)
### フォルダ構成
このフォルダ構成は、他の記事を参考に作成されました。この構造の利点は、各環境ごとに異なる変数を指定できるため、コードの再利用性が向上することです。
$ tree .
.
├── README.md
├── docs
│   └── graph-dependencies.png
├── envs
│   ├── prod
│   │   ├── ResourceGroupA
│   │   │   └── terragrunt.hcl
│   │   ├── ResourceGroupB
│   │   │   └── terragrunt.hcl
│   │   └── env.hcl
│   └── terragrunt.hcl
└── modules
    ├── ResourceGroupA
    │   └── xx.tf
    └── ResourceGroupB
        └── xx.tf
このファイルには、バックエンドとして AWS S3 バケットに*.tfstate ファイルを保存するための設定が含まれており、各環境に独自の*.tfstate ファイルを S3 バケット内に保存することができます。また、AWS リソースを作成するために Terraform が使用するプロバイダの定義も含まれています。設定では、各 AWS リージョンごとに異なるプロバイダ設定が指定されています。
*.tfstate ファイルを使用してリソースの状態を管理することで、複数の人がプロジェクトに取り組んでいても競合を回避することができます。
# Configuration for storing *.tfstate files for each environment
remote_state {
  backend = "s3"
  config = {
    bucket = "tfstate-xxxxxxxxxxxxxx"
    # Stored in `stg/modA.tfstate`
    key    = "${path_relative_to_include()}.tfstate"
    region = "ap-northeast-1"
    encrypt  = true
  }
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
}
generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents = <<EOF
terraform {
  required_version = ">= 1.3.7"
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 4.53.0"
    }
  }
}
# Tokyo region
provider "aws" {
  region   = "ap-northeast-1"
  alias  = "tokyo"
}
# Virginia region
provider "aws" {
  region  = "us-east-1"
  alias = "virginia"
}
# ... Other regions
EOF
}
このファイルは、Terragrunt の環境固有のファイルであり、prod 環境の設定が含まれています。これらの変数は、他の Terragrunt ファイルで使用され、環境固有の設定を定義するために使用されます。具体的には、envs/prod/ResourceGroupA の hcl ファイルで呼び出され、Terraform コードの module/ResourceGroupA にパラメータとして渡されます。
locals {
  ENV    = "prod"
  PROJECT_NAME = "test"
  ACCOUNT_ID   = "123456789"
  VPC_CIDE     = "10.0.0.0/24"
}
「ResourceGroupA」と「ResourceGroupB」の HCL ファイルの例を示しましょう。
最初は依存関係がないと仮定し、A というクリーンなスレートのシナリオを考えます。B は A に依存するリソースグループとします。
vim envs/prod/ResourceGroupA/terragrunt.hcllocals {
  ENV = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}
# Include definition of all environments (envs/terragrunt.hcl)
include {
  path = find_in_parent_folders()
}
terraform {
  # Reference the module
  source = "../../../modules//ResourceGroupA"
}
# Specify the input values for the module
inputs = {
  ENV          = local.ENV.locals.ENV
  PROJECT_NAME = local.ENV.locals.PROJECT_NAME
  ACCOUNT_ID   = local.ENV.locals.ACCOUNT_ID
}
vim envs/prod/ResourceGroupB/terragrunt.hcllocals {
  ENV = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}
# Include definition of all environments (envs/terragrunt.hcl)
include {
  path = find_in_parent_folders()
}
terraform {
  # Reference the module
  source = "../../../modules//ResourceGroupB"
}
# Specify the input values for the module
inputs = {
  ENV          = local.ENV.locals.ENV
  PROJECT_NAME = local.ENV.locals.PROJECT_NAME
  ACCOUNT_ID   = local.ENV.locals.ACCOUNT_ID
  VPC_ID       = local.ENV.locals.VPC_ID
}
variable "ENV" {
  description = "Environment"
  type = string
}
variable "PROJECT_NAME" {
  description = "Project Name"
  type = string
}
variable "vpc_id" {
  description = "The ID of the VPC"
  type = string
}
output "vpc_id" {
  value = aws_vpc.main.id
}
cd envs/prod
terragrunt run-all hclfmt
terragrunt run-all fmt
cd envs/prod
terragrunt validate-all
cd envs/prod
terragrunt run-all plan
cd envs/prod
terragrunt run-all apply
brew install graphvizcd envs/terragrunt-prod
terragrunt graph-dependencies | dot -Tpng > graph-dependencies.png
name: Terragrunt Actions
on:
  push:
    branches:
      - master
env:
  TERRAGRUNT_CACHE_DIR: ${{ github.workspace }}/tool
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      TARGET_ENV: ''
    steps:
      - name: Set Production
        run: |
          mkdir -p ${GITHUB_WORKSPACE}/deploy
          echo 'export TARGET_ENV="prod"' >> ${GITHUB_WORKSPACE}/deploy/.env
        if: github.ref == 'refs/heads/master'
        env:
          GITHUB_WORKSPACE: ${{ github.workspace }}
      - name: Set Staging
        run: |
          mkdir -p ${GITHUB_WORKSPACE}/deploy
          echo 'export TARGET_ENV="stg"' >> ${GITHUB_WORKSPACE}/deploy/.env
        if: github.ref == 'refs/heads/staging'
        env:
          GITHUB_WORKSPACE: ${{ github.workspace }}
      - name: Terragrunt Plan
        env:
          TERRAGRUNT_DOWNLOAD: "https://github.com/gruntwork-io/terragrunt/releases/download/v0.34.0/terragrunt_linux_amd64"
        run: |
          source ${GITHUB_WORKSPACE}/deploy/.env
          sudo apt-get update && sudo apt-get install -y curl unzip git
          curl -Lo /tmp/terragrunt.zip ${TERRAGRUNT_DOWNLOAD}
          sudo unzip -d /usr/local/bin /tmp/terragrunt.zip
          cd ${GITHUB_WORKSPACE}/envs/${TARGET_ENV}
          terragrunt run-all plan
        if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/staging'
        env:
          GITHUB_WORKSPACE: ${{ github.workspace }}
          TERRAGRUNT_CACHE_DIR: ${{ env.TERRAGRUNT_CACHE_DIR }}
      - name: Terragrunt Apply
        if: github.ref == 'refs/heads/master'
        env:
          TERRAGRUNT_DOWNLOAD: "https://github.com/gruntwork-io/terragrunt/releases/download/v0.34.0/terragrunt_linux_amd64"
        run: |
          source ${GITHUB_WORKSPACE}/deploy/.env
          sudo apt-get update && sudo apt-get install -y curl unzip git
          curl -Lo /tmp/terragrunt.zip ${TERRAGRUNT_DOWNLOAD}
          sudo unzip -d /usr/local/bin /tmp/terragrunt.zip
          cd ${GITHUB_WORKSPACE}/envs/${TARGET_ENV}
          terragrunt run-all apply --terragrunt-non-interactive
        env:
          GITHUB_WORKSPACE: ${{ github.workspace }}
          TERRAGRUNT_CACHE_DIR: ${{ env.TERRAGRUNT_CACHE_DIR }}
image: hashicorp/terraform:1.3.7
definitions:
  caches:
    terragrunt: ${BITBUCKET_CLONE_DIR}/tool
  steps:
    - step: &set-production
        name: Set Production
        script:
          - mkdir -p ${BITBUCKET_CLONE_DIR}/deploy
          - echo 'export TARGET_ENV="prod"' >> ${BITBUCKET_CLONE_DIR}/deploy/.env
        artifacts:
          - deploy/**
    - step: &set-staging
        name: Set Staging
        script:
          - mkdir -p ${BITBUCKET_CLONE_DIR}/deploy
          - echo 'export TARGET_ENV="stg"' >> ${BITBUCKET_CLONE_DIR}/deploy/.env
        artifacts:
          - deploy/**
    - step: &terragrunt-plan
        name: terragrunt Plan
        caches:
          - terragrunt
        script:
          - source ${BITBUCKET_CLONE_DIR}/deploy/.env
          - apk update && apk add bash curl groff jq less unzip git
          - if [ ! -d ${BITBUCKET_CLONE_DIR}/tool/tfenv ]; then git clone https://github.com/tfutils/tfenv.git ${BITBUCKET_CLONE_DIR}/tool/tfenv; fi
          - if [ ! -d ${BITBUCKET_CLONE_DIR}/tool/tgenv ]; then git clone https://github.com/cunymatthieu/tgenv.git ${BITBUCKET_CLONE_DIR}/tool/tgenv; fi
          - export PATH=${BITBUCKET_CLONE_DIR}/tool/tfenv/bin:$PATH
          - export PATH=${BITBUCKET_CLONE_DIR}/tool/tgenv/bin:$PATH
          - cd ${BITBUCKET_CLONE_DIR}/envs/${TARGET_ENV}
          - tfenv install && tgenv install
          - terragrunt run-all plan
    - step: &terragrunt-apply
        name: terragrunt Apply
        trigger: manual
        caches:
          - terragrunt
        script:
          - source ${BITBUCKET_CLONE_DIR}/deploy/.env
          - apk update && apk add bash curl groff jq less unzip git
          - if [ ! -d ${BITBUCKET_CLONE_DIR}/tool/tfenv ]; then git clone https://github.com/tfutils/tfenv.git ${BITBUCKET_CLONE_DIR}/tool/tfenv; fi
          - if [ ! -d ${BITBUCKET_CLONE_DIR}/tool/tgenv ]; then git clone https://github.com/cunymatthieu/tgenv.git ${BITBUCKET_CLONE_DIR}/tool/tgenv; fi
          - export PATH=${BITBUCKET_CLONE_DIR}/tool/tfenv/bin:$PATH
          - export PATH=${BITBUCKET_CLONE_DIR}/tool/tgenv/bin:$PATH
          - cd ${BITBUCKET_CLONE_DIR}/envs/${TARGET_ENV}
          - tfenv install && tgenv install
          - terragrunt run-all apply --terragrunt-non-interactive
pipelines:
  default:
    - step: *set-production
    - step: *terragrunt-plan
  branches:
    master:
      - step: *set-production
      - step: *terragrunt-plan
      - step:
          <<: *terragrunt-apply
          deployment: production