mcpField
T
terraform-service-scaffold

terraform-service-scaffold

github.com/c0x12cクラウド・DevOps109104 回インストール19 週間前に更新
リポジトリ

terraform-service-scaffold について

サービス基盤を新規構築するチーム向けに、要件からサービス単位の Terraform 構成を生成するスキルです。ライブオーケストレーション、再利用可能なモジュール、環境設定、CI/CDを扱い、バージョン固定と状態管理のルールに沿います。

主な機能

  1. 01サービス要件に応じた Terraform 構成生成
  2. 02ECS または EKS のコンテナホストに対応
  3. 03RDS、Redis、S3、SQS の設定
  4. 04環境変数と状態設定の作成
  5. 05CI/CD ワークフローの生成
  6. 06サービスモジュールのリソースをファイル分割

活用シーン

  1. 01新しいサービスリポジトリへの Terraform 導入
  2. 02マイクロサービス基盤の初期構築
  3. 03サービス環境とクラウドリソースの設定
  4. 04インフラ変更用 CI/CD フローの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/c0x12c/ai-toolkit --skill terraform-service-scaffold

インストール後、Claude Code で/terraform-service-scaffoldを入力して読み込まれたか確認します。

詳細

Terraform サービスのひな形

ライブオーケストレーション、再利用可能なモジュール、環境設定、CI/CD ワークフローを含む、サービスレベルの完全な Terraform 構成を生成します。

使用する場面

  • 新しいサービスリポジトリに Terraform を追加する場合
  • サービスインフラストラクチャを一から構築する場合
  • 標準的なクラウドリソースを使って新しいマイクロサービスをセットアップする場合

手順

1. 要件を確認する

ユーザーに次の内容を確認します。

  • サービス名(例:{service})
  • コンテナホスト:ECS または EKS
  • 必要なリソース:RDS、Redis、S3、SQS、またはその組み合わせ
  • 環境:ひな形を作成する環境(デフォルト:dev)
2. インフラストラクチャのリモートステートを読み込む
# Check existing shared infrastructure outputs
data "terraform_remote_state" "infra" {
  backend = "s3"

  config = {
    bucket = "{project}-terraform-state"
    key    = "infra/terraform.tfstate"
    region = var.region
  }
}
3. ライブオーケストレーションを作成する

場所:terraform/live/

terraform.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {}
}

provider "aws" {
  region = var.region

  default_tags {
    tags = {
      Project     = var.project
      Service     = var.service
      Environment = var.env
      ManagedBy   = "terraform"
    }
  }
}
variables.tf
variable "project" {
  description = "Project identifier"
  type        = string
}

variable "service" {
  description = "Service name"
  type        = string
}

variable "env" {
  description = "Environment (dev, staging, prod)"
  type        = string
}

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}
locals.tf
locals {
  name_prefix = "${var.project}-${var.service}-${var.env}"

  vpc_id             = data.terraform_remote_state.infra.outputs.vpc_id
  private_subnet_ids = data.terraform_remote_state.infra.outputs.private_subnet_ids
  public_subnet_ids  = data.terraform_remote_state.infra.outputs.public_subnet_ids

  common_tags = {
    Project     = var.project
    Service     = var.service
    Environment = var.env
  }
}
outputs.tf
output "ecr_repository_url" {
  description = "ECR repository URL for {service}"
  value       = module.{service}.ecr_repository_url
}

output "service_endpoint" {
  description = "Service endpoint URL"
  value       = module.{service}.service_endpoint
}
4. サービスモジュールを作成する

場所:terraform/modules/{service}/

リソースごとにファイルを分けます。

main.tf
# Module entry point — locals and data sources only
locals {
  name_prefix = "${var.project}-${var.service}-${var.env}"
}
ecr.tf
module "ecr_backend" {
  source = "git::https://github.com/{project}/terraform-modules.git//ecr?ref=v1.0.0"

  name                 = "${local.name_prefix}-backend"
  image_tag_mutability = "IMMUTABLE"

  lifecycle_policy_rules = [
    {
      description   = "Keep last 20 images"
      count_number  = 20
      tag_status    = "any"
      count_type    = "imageCountMoreThan"
    }
  ]
}

module "ecr_worker" {
  source = "git::https://github.com/{project}/terraform-modules.git//ecr?ref=v1.0.0"

  name                 = "${local.name_prefix}-worker"
  image_tag_mutability = "IMMUTABLE"

  lifecycle_policy_rules = [
    {
      description   = "Keep last 20 images"
      count_number  = 20
      tag_status    = "any"
      count_type    = "imageCountMoreThan"
    }
  ]
}
rds.tf
module "rds" {
  source = "git::https://github.com/{project}/terraform-modules.git//rds?ref=v1.0.0"

  name                = "${local.name_prefix}-db"
  engine              = "postgres"
  engine_version      = "15.4"
  instance_class      = var.rds_instance_class
  allocated_storage   = var.rds_allocated_storage
  db_name             = replace(var.service, "-", "_")
  master_username     = var.db_username
  master_password     = var.db_password
  subnet_ids          = var.private_subnet_ids
  vpc_id              = var.vpc_id
  deletion_protection = var.env == "prod" ? true : false

  backup_retention_period = var.env == "prod" ? 30 : 7
  multi_az                = var.env == "prod" ? true : false
}
redis.tf
module "redis" {
  source = "git::https://github.com/{project}/terraform-modules.git//elasticache?ref=v1.0.0"

  name               = "${local.name_prefix}-cache"
  engine             = "redis"
  node_type          = var.redis_node_type
  num_cache_nodes    = 1
  subnet_ids         = var.private_subnet_ids
  vpc_id             = var.vpc_id
  transit_encryption = true
  at_rest_encryption = true
  auth_token         = var.redis_auth_token
}
s3.tf
module "s3" {
  source = "git::https://github.com/{project}/terraform-modules.git//s3?ref=v1.0.0"

  bucket_name = "${local.name_prefix}-assets"
  versioning  = true

  server_side_encryption = {
    sse_algorithm = "aws:kms"
  }

  block_public_access = {
    block_public_acls       = true
    block_public_policy     = true
    ignore_public_acls      = true
    restrict_public_buckets = true
  }
}
sqs.tf
module "sqs" {
  source = "git::https://github.com/{project}/terraform-modules.git//sqs?ref=v1.0.0"

  name                       = "${local.name_prefix}-queue"
  visibility_timeout_seconds = 300
  message_retention_seconds  = 1209600
  receive_wait_time_seconds  = 20

  dead_letter_queue = {
    enabled             = true
    max_receive_count   = 3
    retention_seconds   = 1209600
  }
}
ecs.tf(コンテナホストが ECS の場合)
module "ecs_service" {
  source = "git::https://github.com/{project}/terraform-modules.git//ecs-service?ref=v1.0.0"

  name            = local.name_prefix
  cluster_id      = var.ecs_cluster_id
  task_definition = module.ecs_task.arn
  desired_count   = var.desired_count
  subnet_ids      = var.private_subnet_ids
  security_groups = [aws_security_group.service.id]

  target_group_arn = aws_lb_target_group.service.arn
  container_name   = "${local.name_prefix}-backend"
  container_port   = var.container_port
}

module "ecs_task" {
  source = "git::https://github.com/{project}/terraform-modules.git//ecs-task?ref=v1.0.0"

  family           = local.name_prefix
  cpu              = var.task_cpu
  memory           = var.task_memory
  execution_role   = var.ecs_execution_role_arn
  task_role        = var.ecs_task_role_arn
  image            = "${module.ecr_backend.repository_url}:${var.image_tag}"
  container_port   = var.container_port
  environment      = var.container_environment
  secrets          = var.container_secrets
}

resource "aws_lb_target_group" "service" {
  name        = local.name_prefix
  port        = var.container_port
  protocol    = "HTTP"
  vpc_id      = var.vpc_id
  target_type = "ip"

  health_check {
    path                = "/health"
    healthy_threshold   = 2
    unhealthy_threshold = 3
    interval            = 30
  }
}
eks.tf(コンテナホストが EKS の場合)
resource "kubernetes_namespace" "service" {
  metadata {
    name = var.service
    labels = {
      project = var.project
      env     = var.env
    }
  }
}

resource "kubernetes_service_account" "service" {
  metadata {
    name      = var.service
    namespace = kubernetes_namespace.service.metadata[0].name
    annotations = {
      "eks.amazonaws.com/role-arn" = module.irsa.role_arn
    }
  }
}

module "irsa" {
  source = "git::https://github.com/{project}/terraform-modules.git//irsa?ref=v1.0.0"

  name                = "${local.name_prefix}-irsa"
  oidc_provider_arn   = var.oidc_provider_arn
  namespace           = var.service
  service_account     = var.service
  policy_arns         = var.irsa_policy_arns
}

resource "kubernetes_secret" "service" {
  metadata {
    name      = "${var.service}-secrets"
    namespace = kubernetes_namespace.service.metadata[0].name
  }

  data = var.k8s_secrets
}

resource "kubernetes_config_map" "service" {
  metadata {
    name      = "${var.service}-config"
    namespace = kubernetes_namespace.service.metadata[0].name
  }

  data = var.k8s_config
}
5. 環境設定を作成する

場所:terraform/envs/dev/

state.config
bucket         = "{project}-terraform-state"
key            = "{service}/dev/terraform.tfstate"
region         = "us-east-1"
dynamodb_table = "{project}-terraform-locks"
encrypt        = true
terraform.tfvars
project = "{project}"
service = "{service}"
env     = "dev"
region  = "us-east-1"

# RDS
rds_instance_class  = "db.t3.micro"
rds_allocated_storage = 20
db_username         = "{service}_admin"

# Redis
redis_node_type = "cache.t3.micro"

# Container
desired_count  = 1
task_cpu       = 256
task_memory    = 512
container_port = 8080
image_tag      = "latest"
secrets.tfvars
# Encrypted via git-secret-protector — committed to git, decrypted at CI/CD time
db_password      = ""
redis_auth_token = ""
git-secret-protector のセットアップ

シークレットは .gitignore ではなく、git-secret-protector を使って Git で暗号化します。

# 1. Install git-secret-protector
pip install git-secret-protector

# 2. Initialize per-environment filters
git-secret-protector init --filter secrets-dev
git-secret-protector init --filter secrets-prod

# 3. Add .gitattributes rules for auto-encrypt on commit
cat >> .gitattributes <<'EOF'
terraform/live/envs/dev/secrets.tfvars filter=secrets-dev
terraform/live/envs/prod/secrets.tfvars filter=secrets-prod
EOF

# 4. Store encryption keys securely (CI/CD secrets, NOT in repo)
# GitHub Actions: store as repository secret GIT_SECRET_PROTECTOR_KEY_DEV / _PROD

シークレットファイルは暗号化された状態で Git にコミットします。チェックアウト時に、鍵が利用可能であれば smudge フィルターによって復号されます。 CI/CD では、terraform plan/apply の前に復号してください。

6. CI/CD ワークフローを生成する
# .github/workflows/terraform.yml
name: Terraform

on:
  pull_request:
    paths: ['terraform/**']
  push:
    branches: [main]
    paths: ['terraform/**']

env:
  TF_VERSION: '1.11'
  ENVIRONMENT: dev

jobs:
  plan:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}
      - name: Decrypt secrets
        run: |
          pip install git-secret-protector
          git-secret-protector reveal --filter secrets-${{ env.ENVIRONMENT }}
        env:
          GIT_SECRET_PROTECTOR_KEY: ${{ secrets.GIT_SECRET_PROTECTOR_KEY_DEV }}
      - name: Init
        run: |
          cd terraform/live
          terraform init -backend-config=envs/${{ env.ENVIRONMENT }}/state.config
      - name: Plan
        run: |
          cd terraform/live
          terraform plan \
            -var-file=envs/${{ env.ENVIRONMENT }}/terraform.tfvars \
            -var-file=envs/${{ env.ENVIRONMENT }}/secrets.tfvars \
            -no-color

  apply:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}
      - name: Decrypt secrets
        run: |
          pip install git-secret-protector
          git-secret-protector reveal --filter secrets-${{ env.ENVIRONMENT }}
        env:
          GIT_SECRET_PROTECTOR_KEY: ${{ secrets.GIT_SECRET_PROTECTOR_KEY_DEV }}
      - name: Init
        run: |
          cd terraform/live
          terraform init -backend-config=envs/${{ env.ENVIRONMENT }}/state.config
      - name: Apply
        run: |
          cd terraform/live
          terraform apply \
            -var-file=envs/${{ env.ENVIRONMENT }}/terraform.tfvars \
            -var-file=envs/${{ env.ENVIRONMENT }}/secrets.tfvars \
            -auto-approve
7. 出力チェックリスト

すべてのファイルを生成した後、次のチェックリストを作成します。

  • terraform/live/terraform.tf — バックエンドとプロバイダー
  • terraform/live/variables.tf — すべての入力変数
  • terraform/live/locals.tf — 計算値、リモートステート
  • terraform/live/outputs.tf — エクスポートする値
  • terraform/modules/{service}/ — リソースごとにファイルを分割
  • terraform/envs/dev/state.config — バックエンド設定
  • terraform/envs/dev/terraform.tfvars — 環境の値
  • terraform/envs/dev/secrets.tfvars — 機密情報(Git で無視)
  • .github/workflows/terraform.yml — CI/CD パイプライン
  • .gitignore に *.tfvars のシークレット、.terraform/、*.tfstate* が含まれている

対話スタイル

  • 生成前にサービス名、コンテナホスト、必要なリソースを確認します
  • すべてのファイルを一度に生成し、一部だけを出力することはありません
  • バージョンを固定したレジストリモジュールを使用し、インラインリソースは使用しません
  • プロバイダーは live/ にのみ配置し、モジュールには配置しません

ルール

  • {project}/terraform-modules のレジストリモジュールを ?ref=vX.Y.Z 付きで使用する
  • すべてのプロバイダーとモジュールのバージョンを固定する
  • locals はフラットにし、避けられない場合を除き、ネストしたマップは使用しない
  • プロバイダーは live/ にのみ定義し、モジュールには定義しない
  • モジュール内ではリソースごとにファイルを分ける
  • スタック間の参照にはリモートステートを使用する
  • 機密変数には sensitive = true を指定する
  • すべてのリソースに project、service、environment、ManagedBy のタグを付ける
  • ステートは DynamoDB のロックを使用して S3 に保存する
  • シークレットはコミットせず、.gitignore と secrets.tfvars を使用する

出力

次の完全なディレクトリツリーを生成します。

terraform/
  live/
    terraform.tf
    variables.tf
    locals.tf
    outputs.tf
  modules/{service}/
    main.tf
    ecr.tf
    rds.tf
    redis.tf
    s3.tf
    sqs.tf
    ecs.tf (or eks.tf)
    variables.tf
    outputs.tf
  envs/dev/
    state.config
    terraform.tfvars
    secrets.tfvars
.github/workflows/terraform.yml

コメント