はじめに
インフラのリソースをコードで管理したい。
さて、Terraformを使おう… となった際に、どのようなディレクトリ構成で、どのようにワークスペースを区切るべきか、悩むことかと思います。
今回は小規模な組織がサクッとシンプルにTerraformを扱う際の個人的ベストな構成を紹介します。
前提
本記事では、以下のような方々をターゲットとしております。
- AWSのリソースをTerraformで管理したい
- 全てのインフラの変更を1, 2人で面倒見きれる程度の組織規模である(多少のコンフリクトは問題にならない)
- なるべくお金をかけたくないので。なのでTerraform Cloudは利用しないで、AWS上のS3でTerraformのstateを管理する
逆に、大規模な組織においてTerraform導入を考えている場合、このケースは適さないと思います。
理由としては以下の通りです。
多人数の組織の場合は頻繁にTerraform Applyが発生し、コンフリクトや意図せぬリソース変更の原因となる
- 現状、Terraform Apply時にS3上のTerraformのstateファイルにロックをかけることはできる
- とはいえ、(CI/CDを構築した場合)mainブランチの状態を元にTerraform applyが適用されることになる
- 複数人がmainにマージしてしまうと、意図しない事故が発生する可能性が高区なる
- この場合には、素直にTerraform Cloudに移行して、state管理を任せましょう
概要
結論として、以下のような構成をお勧めします。
link: https://github.com/gonkunkun/terraform-template
本構成におけるポイントは以下の通りです。
- TerraformのstateファイルはTerraformで管理せずに、Cloudformationを利用
- これは言わずもがなです。
- TerraformからTerraformのstateファイルを消せるのは論外
 
- 環境はstgとprdの2環境を想定
- 各環境毎にstateファイルを分けて管理
それぞれの環境毎にtfファイルを分けない
- ここは一番結論が分かれるポイントだと思います
- tfファイルを環境毎に分けないことで以下のメリットが生まれます
- stgとprd環境の差異をなるべく発生させない
- 仮に環境差異がある場合、コードをパッと見れば分かる
- 2重メンテが発生しない
 
- 反面、デメリットもあります
- stgにのみ、prdにのみ適用したい変更がある場合には手間が増える
- 具体的には、都度都度条件分岐やcountを利用して片面にのみ変更を当てる必要がある
 
詳細
ディレクトリ構成
ディレクトリ構成としては以下の通りです。
~/D/g/terraform-template ❯❯❯ tree .                                                                                                                                                                                               main
.
├── README.md
├── cloudformation
│   └── S3.yaml
└── workloads
    ├── data.tf
    ├── envs
    │   ├── prd
    │   │   ├── backend.hcl
    │   │   └── terraform.tfvars
    │   └── stg
    │       ├── backend.hcl
    │       └── terraform.tfvars
    ├── locals.tf
    ├── main.tf
    ├── providers.tf
    ├── terraform.tfvars
    ├── variables.tf
    ├── versions.tf
    └── vpc.tfそれでは、いくつかのポイント毎に見ていきましょう。
Stateファイルの配置先
本来はこの設定もCI/CDから変更したいですが、そこまで考えられていないです。
バケット名やTerraformの権限を持ったユーザを設定した上で、CloudformationでS3バケットを作成します。
Resources:
  StateBucketStg:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: "xxxxx-terraform-state-stg"
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
  StateFileBucketPolicyForStg:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref StateBucketStg
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "AllowSpecificIAMUser"
            Effect: "Allow"
            Principal:
              AWS: "arn:aws:iam::xxxxxxxxx:user/terraform"
            Action:
              - "s3:GetObject"
              - "s3:PutObject"
              - "s3:ListBucket"
              - "s3:DeleteObject"
            Resource:
              - !Sub "arn:aws:s3:::${StateBucketStg}/*"
              - !Sub "arn:aws:s3:::${StateBucketStg}"
  TerraformPlanOnlyPolicyForStg:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: "TerraformPlanOnlyPolicy"
      Roles:
        - !Ref TerraformUserRole 
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "AllowPlanActions"
            Effect: "Allow"
            Action:
              - "s3:GetObject"
              - "s3:ListBucket"
            Resource:
              - !Sub "arn:aws:s3:::${StateBucket}"
              - !Sub "arn:aws:s3:::${StateBucket}/*"
          - Sid: "DenyApplyAndDestroy"
            Effect: "Deny"
            Action:
              - "s3:PutObject"
              - "s3:DeleteObject"
            Resource:
              - !Sub "arn:aws:s3:::${StateBucket}"
              - !Sub "arn:aws:s3:::${StateBucket}/*"
  StateBucketPrd:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: "xxxxx-terraform-state-prd"
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
  StateFileBucketPolicyForPrd:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref StateBucketPrd
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "AllowSpecificIAMUser"
            Effect: "Allow"
            Principal:
              AWS: "arn:aws:iam::xxxxxxxxx:user/terraform"
            Action:
              - "s3:GetObject"
              - "s3:PutObject"
              - "s3:ListBucket"
              - "s3:DeleteObject"
            Resource:
              - !Sub "arn:aws:s3:::${StateBucketPrd}/*"
              - !Sub "arn:aws:s3:::${StateBucketPrd}"
ref: https://github.com/gonkunkun/terraform-template/blob/main/cloudformation/S3.yaml
backend (stateファイルの保存先)の設定
環境毎にbackendの設定を記載しています。
bucket         = "xxxxx-terraform-state-stg"
region         = "ap-northeast-1"
key            = "stg/terraform.tfstate"
use_lockfile   = true
encrypt        = truestg: https://github.com/gonkunkun/terraform-template/blob/main/workloads/envs/stg/backend.hcl
bucket         = "xxxxx-terraform-state-prd"
region         = "ap-northeast-1"
key            = "prd/terraform.tfstate"
use_lockfile   = true
encrypt        = trueprd: https://github.com/gonkunkun/terraform-template/blob/main/workloads/envs/prd/backend.hcl
DynamoDBを用いたstate lockが不要になったのが嬉しいですね。
ref: https://github.com/hashicorp/terraform/blob/5279e432611752a9bcba27baaf657a3305c226f8/website/docs/language/backend/s3.mdx#state-locking
ちなみに、plan/applyを実行する際には以下のようなコマンドとなります。
# In the case of stg
## Specify the backend
terraform init -backend-config="envs/stg/backend.hcl"
## Plan
terraform plan -var="env=stg"
# In the case of prd
## Specify the backend
terraform init -backend-config="envs/prd/backend.hcl"
## Plan
terraform plan -var="env=prd"stgとprd環境のリソース定義方法
環境毎に変数を定義し、terraformリソースに渡します。
locals {
  cidr_block = {
    stg : "10.10.0.0/16",
    prd : "10.20.0.0/16",
  }[var.env]
}
#trivy:ignore:AVD-AWS-0178
resource "aws_vpc" "sample" {
  cidr_block = local.cidr_block
  tags = {
    "Name" = "sample-${var.env}-vpc"
  }
  tags_all = {
    "Name" = "sample-${var.env}-vpc"
  }
  lifecycle {
    prevent_destroy = true
  }
}ref: https://github.com/gonkunkun/terraform-template/blob/main/workloads/vpc.tf
セットアップ(Terraform planまで)
それでは、ローカル環境からplanを打てるようになるまでの設定を見ていきます。
事前準備
以下は事前に済ませておいてください。
- 対象となるAWSアカウントの作成
- state管理用のS3バケットの作成
- Terraform実行用のIAM or Roleの作成 / ローカル環境上でのプロファイルの設定
設定
リポジトリをCloneしてきます。
# Install Terraform (using brew)
brew install terraform
# Clone the repository
git clone https://github.com/gonkunkun/terraform-templateバックエンドのS3バケット名を正しく設定します。
stg: https://github.com/gonkunkun/terraform-template/blob/main/workloads/envs/stg/backend.hcl
prd: https://github.com/gonkunkun/terraform-template/blob/main/workloads/envs/prd/backend.hcl
自分の環境に合わせてregionを設定します。
https://github.com/gonkunkun/terraform-template/blob/main/workloads/providers.tf
バックエンドを選択して、initしましょう。
(このタイミングでS3上にstateファイルが生成されるはず)
# Initialize the provider (for the staging environment)
cd ./terraform-template/workloads
terraform init -backend-config="envs/stg/backend.hcl"あとはplanを打てることを確認します。
terraform plan -var="env=stg"おわりに
本記事では、Terraformのディレクトリ構成と初期設定についてまとめました。
CI/CDの構築については以下で紹介しています。


ここまで読んでいただきありがとうございます。
 
																	 
																	 
																	 
																	 
																	 
																	 
						
						
						
												 
						
						
						
												 
										
					