terraform-service-scaffold について
サービス基盤を新規構築するチーム向けに、要件からサービス単位の Terraform 構成を生成するスキルです。ライブオーケストレーション、再利用可能なモジュール、環境設定、CI/CDを扱い、バージョン固定と状態管理のルールに沿います。
主な機能
- 01サービス要件に応じた Terraform 構成生成
- 02ECS または EKS のコンテナホストに対応
- 03RDS、Redis、S3、SQS の設定
- 04環境変数と状態設定の作成
- 05CI/CD ワークフローの生成
- 06サービスモジュールのリソースをファイル分割
活用シーン
- 01新しいサービスリポジトリへの Terraform 導入
- 02マイクロサービス基盤の初期構築
- 03サービス環境とクラウドリソースの設定
- 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
コメント
Markdown 対応