1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform初心者が抑えるべき基本・ノウハウまとめ

Last updated at Posted at 2025-09-01

初めてTerraformを触るエンジニアに向けて、
自分が初めてTerraformを触ってから二週間でキャッチアップした内容を共有します

前提

  • AWSとTerraformを使う
  • GitHub Actionsを多少知っている

今回の内容まとめ

  • 基本コマンド
    • init → plan → apply の基本
  • AWSへの連携
    • State を S3 / Lock を DynamoDB
  • CICD
    • PR で plan、main で apply
    • Trivyでセキュリティスキャン
  • ブランチ戦略
    • トランクベースパターン
    • 環境ブランチパターンで起こるトラップ
  • ディレクトリ構造
    • modules × environments 構成

1. Terraform の基本コマンドと State

Terraform はコードクラウドの差分をterraform.tfstateというファイルで管理する。
このとき、Stateを管理するS3バケットをBackendと呼ぶ。

以下が基本コマンド三種とTerraformがどのように動くのかをまとめたもの。

  • init: Provider/Module 取得、バックエンド初期化
  • plan: コードと実態の差分をプレビュー
  • apply: 差分を適用、State を更新

❗ ローカル State(terraform.tfstate)のまま運用すると上書き・紛失・競合の温床。必ず次の項目で説明するやり方のdynamo+S3で管理しておこう。


2. State は S3、Lock は DynamoDB(チーム前提の必須構成)

前項で説明した通り、Terraformはクラウドの状態を.tfstateファイルで管理する。
もちろんチーム開発では複数人でTerraformコードを管理するため、
Stateファイルは皆が参照できる場所においておく必要がある。
また、複数人が同時にterraform applyを実行しないようにするために
競合を防ぐ必要がある。

これをS3とDynamo DBで実現することができる。

2-1. まず初めにやるべきこと

  • 先に S3 バケット & DynamoDB テーブルを手動で作成する
  • Terraform の backend を書き、 S3 + DynamoDB に向ける
  • terraform initを実行し、連携できたか確認
     →AWS CLIの設定が必要

これにより、複数の開発者が
terraform applyを同時実行してもにより競合を防止することができる

2-2. Backend 設定サンプルコード

terraform {
  backend "s3" {
    bucket         = "mycompany-tfstate-apne1"
    key            = "envs/prod/terraform.tfstate" # 環境ごとにパスを分ける
    region         = "ap-northeast-1"
    dynamodb_table = "sample-tfstate-lock"
    encrypt        = true
  }
  required_version = ">= 1.6.0"
}

3. CI/CD:PRで plan&trivy、mainにマージ前に apply

コマンドと手順の流れ

1.ローカルでterraform initterraform planを実行
 →planで出てきた変更が意図したものかチェック

2.プルリクエストを作成し以下のActionsを実行する

  • Actions1 再度init→planし、レビュアが変更内容をチェック
  • Actions2 trivyでセキュリティスキャン

trivyとは
コンテナ/IaCの脆弱性を検出するOSSで、特にAWSやTerraformになれないうちに意図せず脆弱な構成にしてしまうことを防げます。

3.レビュー後本番環境にApply&Mainブランチにマージ

おすすめはterraform applyが成功した後でmainブランチにマージ。
成功した後にmainブランチへマージすることで、以下のようなメリットがある

  • terraform applyが失敗するterraformコードが他のメンバーに渡らない
  • 次の項目で話す厄介なトラブルを防ぐ
  • terraform applyが失敗しても元に戻しやすい

最小の GitHub Actions 例(抜粋)

Trivyはこちらを参照
https://github.com/aquasecurity/trivy-action/blob/master/README.md

init plan→ applyのActions

name: terraform-premerge-prod-apply

on:
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened, labeled]
    paths:
      - 'environments/**'
      - 'modules/**'
      - '.github/workflows/terraform-premerge-prod-apply.yml'

permissions:
  id-token: write        # for GitHub OIDC → AWS
  contents: read
  pull-requests: write   # to comment plan result

concurrency:
  group: terraform-prod-${{ github.event.pull_request.number }}
  cancel-in-progress: false

env:
  TF_IN_AUTOMATION: "true"
  AWS_REGION: ap-northeast-1
  TF_WORKING_DIR: environments/prod

jobs:
  plan:
    name: Terraform Plan (PR)
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      # ここでシークレットを使った連携をしないこと!
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/gha-terraform-prod
          aws-region: ${{ env.AWS_REGION }}

      - name: Terraform Init
        run: terraform -chdir=${{ env.TF_WORKING_DIR }} init -input=false

      - name: Terraform Fmt
        run: terraform -chdir=${{ env.TF_WORKING_DIR }} fmt -check

      - name: Terraform Validate
        run: terraform -chdir=${{ env.TF_WORKING_DIR }} validate

      - name: Terraform Plan
        id: plan
        run: |
          set -e
          terraform -chdir=${{ env.TF_WORKING_DIR }} plan -no-color -lock-timeout=5m | tee plan.txt
          echo "PLAN<<EOF" >> $GITHUB_OUTPUT
          sed -e 's/`/\\`/g' plan.txt >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Comment plan to PR
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          header: terraform-plan-prod
          message: |
            ## Terraform Plan (prod)
            <details><summary>Show</summary>

            ```
            ${{ steps.plan.outputs.PLAN }}
            ```

            </details>

  apply_premerge:
    name: ✅ Apply to Production (Pre-merge)
    needs: plan
    runs-on: ubuntu-latest

    # ▶️ Apply は「apply-prod」ラベルが付いた PR のみで実行
    if: contains(github.event.pull_request.labels.*.name, 'apply-prod')

    # 環境保護(Required reviewers)で人の承認を必須に
    environment:
      name: production

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/gha-terraform-prod
          aws-region: ${{ env.AWS_REGION }}

      - name: Terraform Init
        run: terraform -chdir=${{ env.TF_WORKING_DIR }} init -input=false

      # 👇 直前の状態から不整合がないか安全のため再プラン
      - name: Safety Plan before Apply
        run: terraform -chdir=${{ env.TF_WORKING_DIR }} plan -no-color -lock-timeout=5m

      - name: Terraform Apply (Pre-merge to prod)
        run: terraform -chdir=${{ env.TF_WORKING_DIR }} apply -auto-approve -lock-timeout=5m

4. ブランチ戦略

自分がやらかした落とし穴(環境ブランチパターンにて)

結果としてrevert が優先されてしまい、 結果的に staging までrevertされた状態になる…ことに

  • stagingmainブランチをマージ
  • mainブランチマージ後にterraform applyで問題発生
  • maingit revert
  • staging ブランチを修正
  • 再度stagingに再度mainをマージしコンフリクトを解消しようとした

自分のGitの使い方が悪いだけかも知れないが、これを防ぐために
以下の二つを実施してほしい。

  • トランクベースパターンで
  • マージ前にterarform applyし、成功後にMainにマージ

トランクベースパターンとは
Mainブランチとそこから派生するFeatureブランチを作り、Fatureから随時Mainにマージをかけるブランチ戦略。
検索すると色々出てくるので検索してみてほしい。

こうしておけば、Mainブランチに入る前のコードでterraform applyが失敗してもMainブランチを再度Applyすることで元に戻すことができる。

5. ディレクトリ構成:modules/ × environments/

.
├── modules/
│   ├── network/
│   │   ├── main.tf        # VPCやサブネットなどの関連するリソース
│   │   ├── variables.tf  # CIDRブロックや名前のプレフィックスなどの引数
│   │   └── outputs.tf     # サブネットIDなどの他のモジュールに渡す値
│   ├── alb/
│   ├── ecs_service/
│   ├── rds/
│   └── waf/
└── environments/
    ├── prod/
    │   ├── backend.tf     # S3+DynamoDB backend
    │   ├── main.tf        # modulesを組み合わせる
    │   ├── locals.tf    # 各環境で利用する値
    │   └── terraform.tfvars # terraformやawsなどのバージョン定義
    └── staging/
        └── ...

例:environments/prod/main.tf(一部)

module "network" {
  #各modulesのvariable.tfに書いた引数に値を渡す
  source = "../../modules/vpc"
  env    = local.environment
  cidr   = local.cidr
  product_name = local.product_name
  tag    = local.tag
}

module "alb" {
  source = "../../modules/alb"
  product_name = local.product_name
  env          = local.environment
  #他のモジュールと連携する場合にこのように書く
  subnet_id    = module.network.subnet.id
  cognito_client_id = module.cognito.client_id
  #依存関係明確化
  depends_on   = [module.network]
  tag          = local.tag
}

例:environments/prod/locals.tf(一部)

locals {
  product_name = "my_product"
  environment = "production"
  cidr_list = ["10.1.0.0/16", "10.2.0.0/16"]
}

locals {
  tags = {
    Project     = local.product_name
    Environment = local.environment
    ManagedBy   = "Terraform"
  }
}

例:modules/network/variables.tf(一部)

variable "vpc_cidr_block" {
  type        = string
  description = "VPCのCIDRブロック"
}

variable "product_name" {
  type        = string
  description = "名前のプレフィックスにつけるプロダクト名"
}

variable "tags" {
  type        = map(string)
  description = "全てのリソースにつける共通タグ"
}

例:modules/network/output.tf(一部)

output "vpc_id" {
  value       = aws_vpc.main_vpc.id
  description = "メインのVPCのVPCID"
}

6. ブートストラップの具体手順(安全第一の導線)

  1. 手動で事前に以下を作成

    • S3 バケット(Versioning + SSE。名前は org-tfstate-<region> など)
    • DynamoDB テーブル(mycompany-tfstate-lock、PK=LockID
  2. IaC リポジトリの backend を S3/DDB に切り替え

    • terraform init 時に State 移行のプロンプトに従う
  3. PR で planmain で apply の CI を導入

    • マージ前にapplyし、成功したらmainにマージ
    • 並行開発しても DDB Lock で安全
  4. modules/environments/ を分離

    • まずは最小セット(VPC → ALB → App → DB)から
    • 監視・WAF・Route53 などは段階的に追加

まとめ

  • State を中央集約し、DynamoDBで競合を防ぎ、PRでplan&Trivy
  • Mainマージ前にapplyしapply成功後にMainブランチへマージ。
  • トランクベースで履歴の一貫性を保ち、modules × environments の構造で安全に切り戻せる設計に。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?