2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

基本概念・用語編

Q1: Terraformとは何ですか?他のインフラ管理ツールとの違いは?

A1: TerraformはHashiCorpが開発したInfrastructure as Code(IaC)ツールです。主な特徴は:

  • 宣言的設定: 最終的な状態を記述し、Terraformが現状との差分を自動計算
  • マルチクラウド対応: AWS、Azure、GCP、Kubernetes等、300以上のプロバイダーをサポート
  • 状態管理: terraform.tfstateファイルで実際のインフラ状態を追跡
  • 実行計画: 変更前に詳細なplanを表示し、予期しない変更を防止

他ツールとの比較:

  • CloudFormation: AWS専用、JSONベース
  • Ansible: 手続き型、設定管理が主目的
  • Pulumi: 実際のプログラミング言語を使用

Q2: HCL(HashiCorp Configuration Language)の基本構文を説明してください

A2: HCLはTerraformで使用される宣言的設定言語です。

# 基本構文
resource "resource_type" "resource_name" {
  argument = "value"
  nested_block {
    argument = "value"
  }
}

# 例:EC2インスタンス
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"
  
  tags = {
    Name = "WebServer"
    Environment = "Production"
  }
}

# データソース
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}

# 変数
variable "instance_count" {
  description = "Number of instances to create"
  type        = number
  default     = 1
  
  validation {
    condition     = var.instance_count >= 1 && var.instance_count <= 10
    error_message = "Instance count must be between 1 and 10."
  }
}

# 出力
output "instance_ip" {
  description = "Public IP of the instance"
  value       = aws_instance.web.public_ip
  sensitive   = false
}

Q3: Terraformの状態ファイル(tfstate)とは何ですか?なぜ重要ですか?

A3: terraform.tfstateは、Terraformが管理するインフラの現在の状態を記録するJSONファイルです。

重要性

  1. 現実とのマッピング: 設定ファイルと実際のリソースの対応関係を保存
  2. パフォーマンス: 大規模インフラでもAPI呼び出しを最小化
  3. 協調作業: チーム間での状態共有
  4. 依存関係追跡: リソース間の依存関係を記録

注意点

# 状態ファイルには機密情報が含まれる可能性
{
  "version": 4,
  "terraform_version": "1.6.0",
  "resources": [
    {
      "type": "aws_db_instance",
      "instances": [
        {
          "attributes": {
            "password": "supersecretpassword"  # 平文で保存される
          }
        }
      ]
    }
  ]
}

ベストプラクティス

  • リモートバックエンドを使用(S3 + DynamoDB)
  • 状態ファイルの暗号化
  • 定期的なバックアップ
  • アクセス制御の実装

Q4: Terraformの主要コマンドと使用場面を説明してください

A4:

# 1. 初期化
terraform init
# - プロバイダープラグインのダウンロード
# - バックエンドの設定
# - モジュールのダウンロード

# 2. 検証
terraform validate
# - 構文エラーのチェック
# - 設定の論理的整合性確認

# 3. フォーマット
terraform fmt
# - コードの自動整形
# - 一貫したスタイル適用

# 4. 計画立案
terraform plan
# - 変更内容の事前確認
# - リソースの作成/変更/削除を表示

# 5. 適用
terraform apply
# - 実際の変更実行
# - 状態ファイルの更新

# 6. 破棄
terraform destroy
# - 管理対象リソースの削除

# 7. 状態管理
terraform state list           # リソース一覧表示
terraform state show <resource> # リソース詳細表示
terraform state mv <src> <dst> # リソース名変更
terraform state rm <resource>  # 状態からリソース削除

# 8. インポート
terraform import <resource> <id>
# - 既存リソースをTerraform管理下に

# 9. 出力表示
terraform output
# - 定義済み出力値の表示

中級編:実践的な設定

Q5: 変数(Variables)の種類と使い分けを説明してください

A5: Terraformには複数の変数定義方法があります。

# 1. 基本的な変数定義
variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

# 2. 複雑な型
variable "vpc_config" {
  description = "VPC configuration"
  type = object({
    cidr_block           = string
    enable_dns_hostnames = bool
    availability_zones   = list(string)
  })
  
  default = {
    cidr_block           = "10.0.0.0/16"
    enable_dns_hostnames = true
    availability_zones   = ["us-west-2a", "us-west-2b"]
  }
}

# 3. 検証ルール付き変数
variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  
  validation {
    condition = can(regex("^t[2-4]\\.(nano|micro|small|medium|large)$", var.instance_type))
    error_message = "Instance type must be a valid t2, t3, or t4 type."
  }
}

# 4. 機密変数
variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true
  
  validation {
    condition     = length(var.db_password) >= 8
    error_message = "Password must be at least 8 characters long."
  }
}

変数の設定方法

# 1. terraform.tfvarsファイル
environment = "production"
instance_type = "t3.medium"

# 2. 環境変数
export TF_VAR_environment="production"

# 3. コマンドライン
terraform apply -var="environment=production"

# 4. .tfvarsファイル指定
terraform apply -var-file="production.tfvars"

Q6: ローカル値(Locals)と変数の違い、使い分けは?

A6:

# Locals: 計算や参照値の定義
locals {
  # 共通タグの定義
  common_tags = {
    Environment = var.environment
    Project     = var.project_name
    ManagedBy   = "terraform"
    CreatedAt   = timestamp()
  }
  
  # 条件分岐
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
  
  # 文字列操作
  bucket_name = "${var.project_name}-${var.environment}-${random_string.suffix.result}"
  
  # 複雑な計算
  subnet_cidrs = [
    for i in range(length(var.availability_zones)) : 
    cidrsubnet(var.vpc_cidr, 8, i)
  ]
}

# 使用例
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.instance_type
  
  tags = merge(local.common_tags, {
    Name = "web-server"
  })
}

違い

  • Variables: 外部から値を受け取る(入力)
  • Locals: 内部で計算・加工された値(中間処理)

Q7: データソース(Data Sources)の活用方法を教えてください

A7: データソースは既存のリソースから情報を取得するために使用します。

# 1. 最新のAMIを取得
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
  
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# 2. 現在のAWSアカウント情報
data "aws_caller_identity" "current" {}

# 3. 利用可能なアベイラビリティゾーン
data "aws_availability_zones" "available" {
  state = "available"
}

# 4. 既存のVPCを取得
data "aws_vpc" "existing" {
  filter {
    name   = "tag:Name"
    values = ["main-vpc"]
  }
}

# 5. Route53ホストゾーン
data "aws_route53_zone" "main" {
  name         = "example.com"
  private_zone = false
}

# 使用例
resource "aws_instance" "web" {
  ami               = data.aws_ami.ubuntu.id
  availability_zone = data.aws_availability_zones.available.names[0]
  
  tags = {
    Name      = "web-${data.aws_caller_identity.current.account_id}"
    OwnerID   = data.aws_caller_identity.current.user_id
  }
}

# 出力で情報表示
output "account_info" {
  value = {
    account_id = data.aws_caller_identity.current.account_id
    user_id    = data.aws_caller_identity.current.user_id
    arn        = data.aws_caller_identity.current.arn
  }
}

Q8: 条件分岐(Conditional Expressions)の実装方法は?

A8: Terraformでの条件分岐は主に三項演算子を使用します。

# 1. 基本的な条件分岐
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
  
  # 環境に応じて暗号化の有無を決定
  root_block_device {
    encrypted = var.environment == "production" ? true : false
  }
}

# 2. 複数条件
locals {
  instance_type = var.environment == "production" ? "t3.large" : (
    var.environment == "staging" ? "t3.medium" : "t3.micro"
  )
}

# 3. リソースの条件付き作成
resource "aws_cloudwatch_log_group" "app_logs" {
  count = var.enable_logging ? 1 : 0
  
  name              = "/aws/ec2/${var.app_name}"
  retention_in_days = var.environment == "production" ? 90 : 7
}

# 4. 動的ブロック
resource "aws_security_group" "web" {
  name = "web-sg"
  
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# After: モジュール化されたセキュリティグループ
module "security_groups" {
  source = "./modules/security-groups"
  
  vpc_id = module.vpc.vpc_id
}

# リファクタリング手順
# 1. 新しいモジュールを作成
# 2. 既存リソースを新しい場所に移動
terraform state mv aws_security_group.web module.security_groups.aws_security_group.web

# 3. 設定ファイルを更新
# 4. plan で差分がないことを確認
terraform plan

Q15: Terraformの依存関係管理について説明してください

A15:

# 1. 暗黙的依存関係(Implicit Dependencies)
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id  # 暗黙的にVPCに依存
  cidr_block = "10.0.1.0/24"
}

# 2. 明示的依存関係(Explicit Dependencies)
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id
  
  # 明示的にインターネットゲートウェイの作成を待つ
  depends_on = [aws_internet_gateway.main]
}

# 3. 複雑な依存関係の例
resource "aws_iam_role" "ec2_role" {
  name = "ec2-role"
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_instance_profile" "ec2_profile" {
  name = "ec2-profile"
  role = aws_iam_role.ec2_role.name
}

resource "aws_iam_role_policy_attachment" "s3_access" {
  role       = aws_iam_role.ec2_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}

resource "aws_launch_template" "web" {
  name_prefix   = "web-"
  image_id      = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  iam_instance_profile {
    name = aws_iam_instance_profile.ec2_profile.name
  }
  
  # ポリシーアタッチメントが完了してから作成
  depends_on = [aws_iam_role_policy_attachment.s3_access]
}

# 4. 循環依存の回避
# 悪い例:循環依存
resource "aws_security_group" "web" {
  name = "web-sg"
  
  ingress {
    from_port                = 80
    to_port                  = 80
    protocol                 = "tcp"
    source_security_group_id = aws_security_group.alb.id
  }
}

resource "aws_security_group" "alb" {
  name = "alb-sg"
  
  egress {
    from_port                = 80
    to_port                  = 80
    protocol                 = "tcp"
    source_security_group_id = aws_security_group.web.id  # 循環依存!
  }
}

# 良い例:循環依存の回避
resource "aws_security_group" "web" {
  name = "web-sg"
}

resource "aws_security_group" "alb" {
  name = "alb-sg"
}

resource "aws_security_group_rule" "web_from_alb" {
  type                     = "ingress"
  from_port                = 80
  to_port                  = 80
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.alb.id
  security_group_id        = aws_security_group.web.id
}

resource "aws_security_group_rule" "alb_to_web" {
  type                     = "egress"
  from_port                = 80
  to_port                  = 80
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.web.id
  security_group_id        = aws_security_group.alb.id
}

Q16: Terraformでのセキュリティベストプラクティスは?

A16:

# 1. 機密情報の管理
# 悪い例:パスワードをハードコード
resource "aws_db_instance" "bad_example" {
  password = "supersecretpassword"  # 平文で保存される
}

# 良い例:外部シークレット管理
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "myapp/db/password"
}

resource "aws_db_instance" "good_example" {
  password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

# 2. 変数でのセンシティブ情報
variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true  # ログに出力されない
}

# 3. 状態ファイルの暗号化
terraform {
  backend "s3" {
    bucket         = "terraform-state-bucket"
    key            = "app/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true  # 暗号化必須
    kms_key_id     = "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012"
    dynamodb_table = "terraform-locks"
  }
}

# 4. IAMポリシーの最小権限原則
resource "aws_iam_role" "ec2_role" {
  name = "ec2-role"
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
        Condition = {
          StringEquals = {
            "aws:RequestedRegion" = "us-west-2"  # リージョン制限
          }
        }
      }
    ]
  })
}

resource "aws_iam_policy" "s3_limited_access" {
  name        = "s3-limited-access"
  description = "Limited S3 access policy"
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject"
        ]
        Resource = [
          "arn:aws:s3:::my-app-bucket/*"  # 特定バケットのみ
        ]
        Condition = {
          StringEquals = {
            "s3:x-amz-server-side-encryption" = "AES256"  # 暗号化必須
          }
        }
      }
    ]
  })
}

# 5. ネットワークセキュリティ
resource "aws_security_group" "web" {
  name_prefix = "web-sg-"
  vpc_id      = aws_vpc.main.id
  
  # 最小限のアクセスのみ許可
  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # HTTP は HTTPS にリダイレクトのみ
  ingress {
    description = "HTTP redirect"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # 明示的なアウトバウンドルール
  egress {
    description = "HTTPS outbound"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  tags = {
    Name = "web-security-group"
  }
}

# 6. リソースの暗号化強制
resource "aws_s3_bucket" "app_bucket" {
  bucket = "my-app-bucket-${random_string.suffix.result}"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "app_bucket" {
  bucket = aws_s3_bucket.app_bucket.id
  
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.s3.arn
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_public_access_block" "app_bucket" {
  bucket = aws_s3_bucket.app_bucket.id
  
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# 7. 監査ログの有効化
resource "aws_cloudtrail" "main" {
  name           = "main-cloudtrail"
  s3_bucket_name = aws_s3_bucket.cloudtrail.id
  
  include_global_service_events = true
  is_multi_region_trail        = true
  enable_logging               = true
  
  event_selector {
    read_write_type                 = "All"
    include_management_events       = true
    
    data_resource {
      type   = "AWS::S3::Object"
      values = ["arn:aws:s3:::*/*"]
    }
  }
}

DevOps・CI/CD編

Q17: Terraform CI/CDパイプラインのベストプラクティスは?

A17:

# GitHub Actions例
name: Terraform CI/CD

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

env:
  TF_VERSION: 1.6.0
  AWS_REGION: us-west-2

jobs:
  validate:
    name: Validate
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}
          
      - name: Terraform Format Check
        run: terraform fmt -check -recursive
        
      - name: Terraform Init
        run: terraform init -backend=false
        
      - name: Terraform Validate
        run: terraform validate
        
      - name: Security Scan (tfsec)
        uses: aquasecurity/tfsec-action@v1.0.3
        
      - name: Cost Estimation (Infracost)
        uses: infracost/infracost-comment-action@v1
        with:
          api-key: ${{ secrets.INFRACOST_API_KEY }}

  plan:
    name: Plan
    runs-on: ubuntu-latest
    needs: validate
    if: github.event_name == 'pull_request'
    environment: development
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}
          
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}
          
      - name: Terraform Init
        run: terraform init
        
      - name: Terraform Plan
        run: terraform plan -no-color -out=tfplan
        
      - name: Save Plan
        uses: actions/upload-artifact@v3
        with:
          name: tfplan
          path: tfplan
          
      - name: Comment Plan
        uses: actions/github-script@v7
        with:
          script: |
            const plan = require('fs').readFileSync('tfplan.txt', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '```terraform\n' + plan + '\n```'
            });

  deploy-dev:
    name: Deploy to Development
    runs-on: ubuntu-latest
    needs: [validate, plan]
    if: github.ref == 'refs/heads/main'
    environment: development
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}
          
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}
          
      - name: Terraform Init
        run: terraform init
        
      - name: Terraform Apply
        run: terraform apply -auto-approve

  deploy-prod:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: deploy-dev
    if: github.ref == 'refs/heads/main'
    environment: production
    
    steps:
      - name: Manual Approval Required
        run: echo "Manual approval required for production deployment"
        
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}
          
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_PROD_ARN }}
          aws-region: ${{ env.AWS_REGION }}
          
      - name: Terraform Init
        run: terraform init
        
      - name: Terraform Plan
        run: terraform plan -out=prod-plan
        
      - name: Terraform Apply
        run: terraform apply prod-plan
        
      - name: Notify Success
        if: success()
        run: |
          curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"✅ Production deployment completed successfully"}' \
            ${{ secrets.SLACK_WEBHOOK_URL }}

Q18: Terraformのテスト戦略について説明してください

A18:

// Terratest例(Go)
package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/gruntwork-io/terratest/modules/aws"
    "github.com/stretchr/testify/assert"
)

func TestTerraformWebApp(t *testing.T) {
    t.Parallel()

    // Terraformオプション設定
    terraformOptions := &terraform.Options{
        TerraformDir: "../examples/complete",
        
        Vars: map[string]interface{}{
            "region":           "us-west-2",
            "environment":      "test",
            "instance_type":    "t3.micro",
        },
        
        EnvVars: map[string]string{
            "AWS_DEFAULT_REGION": "us-west-2",
        },
    }

    // テスト後のクリーンアップ
    defer terraform.Destroy(t, terraformOptions)

    // Terraformの実行
    terraform.InitAndApply(t, terraformOptions)

    // 出力値の検証
    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)

    // AWSリソースの検証
    vpc := aws.GetVpcById(t, vpcId, "us-west-2")
    assert.Equal(t, "10.0.0.0/16", vpc.CidrBlock)

    // EC2インスタンスの検証
    instanceId := terraform.Output(t, terraformOptions, "instance_id")
    instance := aws.GetEc2Instance(t, instanceId, "us-west-2")
    assert.Equal(t, "t3.micro", instance.InstanceType)
    assert.Equal(t, "running", instance.State)

    // セキュリティグループの検証
    sgId := terraform.Output(t, terraformOptions, "security_group_id")
    sg := aws.GetSecurityGroupById(t, sgId, "us-west-2")
    
    // HTTPSアクセスが許可されているか確認
    httpsFound := false
    for _, rule := range sg.IngressRules {
        if rule.FromPort == 443 && rule.ToPort == 443 {
            httpsFound = true
            break
        }
    }
    assert.True(t, httpsFound, "HTTPS access should be allowed")
}

// ユニットテスト例
func TestValidateVariables(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../",
        
        Vars: map[string]interface{}{
            "instance_type": "invalid.type",
        },
    }
    
    // 無効な変数でvalidateが失敗することを確認
    _, err := terraform.InitAndValidateE(t, terraformOptions)
    assert.Error(t, err)
}
# Terraform組み込みテスト
# test/main.tftest.hcl
run "valid_vpc_cidr" {
  command = plan
  
  variables {
    vpc_cidr = "10.0.0.0/16"
  }
  
  assert {
    condition     = can(cidrhost(var.vpc_cidr, 0))
    error_message = "VPC CIDR must be valid"
  }
}

run "instance_has_correct_type" {
  command = plan
  
  variables {
    environment = "test"
  }
  
  assert {
    condition     = aws_instance.web.instance_type == "t3.micro"
    error_message = "Test environment should use t3.micro instances"
  }
}

run "security_group_blocks_ssh" {
  command = plan
  
  assert {
    condition = !contains([
      for rule in aws_security_group.web.ingress : rule.from_port
    ], 22)
    error_message = "SSH should not be allowed from internet"
  }
}

# テスト実行
terraform test

Q19: Terraformでのコスト最適化手法は?

A19:

# 1. 環境別リソースサイジング
locals {
  environment_config = {
    dev = {
      instance_type     = "t3.micro"
      min_size         = 1
      max_size         = 2
      volume_size      = 10
      backup_retention = 3
    }
    staging = {
      instance_type     = "t3.small"
      min_size         = 1
      max_size         = 3
      volume_size      = 20
      backup_retention = 7
    }
    production = {
      instance_type     = "t3.large"
      min_size         = 3
      max_size         = 10
      volume_size      = 50
      backup_retention = 30
    }
  }
  
  config = local.environment_config[var.environment]
}

# 2. スポットインスタンスの活用
resource "aws_launch_template" "web" {
  name_prefix   = "${var.environment}-web-"
  image_id      = data.aws_ami.ubuntu.id
  instance_type = local.config.instance_type
  
  # スポットインスタンスリクエスト(開発・ステージング環境)
  dynamic "instance_market_options" {
    for_each = var.environment != "production" ? [1] : []
    content {
      market_type = "spot"
      spot_options {
        max_price                      = "0.05"
        spot_instance_type            = "one-time"
        instance_interruption_behavior = "terminate"
      }
    }
  }
}

# 3. 自動スケジューリング
resource "aws_autoscaling_schedule" "scale_down_evening" {
  count = var.environment != "production" ? 1 : 0
  
  scheduled_action_name  = "scale-down-evening"
  min_size              = 0
  max_size              = 0
  desired_capacity      = 0
  recurrence            = "0 18 * * MON-FRI"  # 平日18時にスケールダウン
  autoscaling_group_name = aws_autoscaling_group.web.name
}

resource "aws_autoscaling_schedule" "scale_up_morning" {
  count = var.environment != "production" ? 1 : 0
  
  scheduled_action_name  = "scale-up-morning"
  min_size              = local.config.min_size
  max_size              = local.config.max_size
  desired_capacity      = local.config.min_size
  recurrence            = "0 9 * * MON-FRI"   # 平日9時にスケールアップ
  autoscaling_group_name = aws_autoscaling_group.web.name
}

# 4. ストレージ最適化
resource "aws_ebs_volume" "data" {
  availability_zone = data.aws_availability_zones.available.names[0]
  size              = local.config.volume_size
  
  # 環境に応じたストレージタイプ
  type = var.environment == "production" ? "gp3" : "gp2"
  
  # 本番環境のみ高いIOPS
  iops       = var.environment == "production" ? 3000 : null
  throughput = var.environment == "production" ? 125 : null
  
  tags = {
    Name        = "${var.environment}-data-volume"
    Environment = var.environment
    CostCenter  = var.cost_center
  }
}

# 5. RDS最適化
resource "aws_db_instance" "main" {
  identifier = "${var.environment}-database"
  
  allocated_storage     = local.config.volume_size
  max_allocated_storage = local.config.volume_size * 2
  storage_type         = "gp2"
  
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = var.environment == "production" ? "db.t3.medium" : "db.t3.micro"
  
  # 本番環境のみMulti-AZ
  multi_az = var.environment == "production"
  
  backup_retention_period = local.config.backup_retention
  backup_window          = "03:00-04:00"
  maintenance_window     = "sun:04:00-sun:05:00"
  
  # 開発環境はスナップショットなしで削除可能
  skip_final_snapshot = var.environment != "production"
  deletion_protection = var.environment == "production"
}

# 6. Lambda予約済み並行性(本番のみ)
resource "aws_lambda_function" "api" {
  filename         = "api.zip"
  function_name    = "${var.environment}-api"
  role            = aws_iam_role.lambda.arn
  handler         = "index.handler"
  runtime         = "nodejs18.x"
  
  # 本番環境のみ予約済み並行性を設定
  reserved_concurrent_executions = var.environment == "production" ? 100 : -1
}

# 7. コストアラート
resource "aws_budgets_budget" "cost_alert" {
  name         = "${var.environment}-cost-budget"
  budget_type  = "COST"
  limit_amount = var.environment == "production" ? "1000" : "100"
  limit_unit   = "USD"
  time_unit    = "MONTHLY"
  
  cost_filters = {
    Tag = ["Environment:${var.environment}"]
  }
  
  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                 = 80
    threshold_type            = "PERCENTAGE"
    notification_type         = "ACTUAL"
    subscriber_email_addresses = [var.alert_email]
  }
}

Q20: 大規模Terraformプロジェクトの構成管理手法は?

A20:

# 大規模プロジェクトのディレクトリ構造
terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── staging/
│   └── production/
├── modules/
│   ├── networking/
│   │   ├── vpc/
│   │   ├── subnets/
│   │   └── security-groups/
│   ├── compute/
│   │   ├── ec2/
│   │   ├── autoscaling/
│   │   └── load-balancer/
│   ├── data/
│   │   ├── rds/
│   │   ├── elasticache/
│   │   └── s3/
│   └── monitoring/
│       ├── cloudwatch/
│       └── alerting/
├── shared/
│   ├── data.tf
│   ├── locals.tf
│   └── versions.tf
└── scripts/
    ├── deploy.sh
    ├── validate.sh
    └── cleanup.sh
# 環境固有の設定 - environments/production/main.tf
terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-state-locks"
    encrypt        = true
  }
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# 共通設定の読み込み
module "shared_config" {
  source = "../../shared"
}

# ネットワーク層
module "networking" {
  source = "../../modules/networking"
  
  environment        = "production"
  vpc_cidr          = "10.0.0.0/16"
  availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
  
  tags = local.common_tags
}

# コンピュート層
module "compute" {
  source = "../../modules/compute"
  
  environment    = "production"
  vpc_id        = module.networking.vpc_id
  subnet_ids    = module.networking.private_subnet_ids
  instance_type = "t3.large"
  min_size      = 3
  max_size      = 10
  
  depends_on = [module.networking]
}

# データ層
module "data" {
  source = "../../modules/data"
  
  environment         = "production"
  vpc_id             = module.networking.vpc_id
  database_subnet_ids = module.networking.database_subnet_ids
  
  depends_on = [module.networking]
}

# 監視層
module "monitoring" {
  source = "../../modules/monitoring"
  
  environment = "production"
  resources = {
    vpc_id      = module.networking.vpc_id
    instance_ids = module.compute.instance_ids
    db_instance = module.data.db_instance_id
  }
  
  depends_on = [module.compute, module.data]
}

locals {
  common_tags = {
    Environment = "production"
    Project     = "myapp"
    ManagedBy   = "terraform"
    Owner       = "platform-team"
    CostCenter  = "engineering"
  }
}
# デプロイメントスクリプト - scripts/deploy.sh
#!/bin/bash

set -euo pipefail

ENVIRONMENT="${1:-dev}"
DRY_RUN="${2:-false}"

echo "🚀 Deploying to $ENVIRONMENT environment"

# 環境ディレクトリに移動
cd "environments/$ENVIRONMENT"

# 初期化
echo "📦 Initializing Terraform..."
terraform init -upgrade

# 検証
echo "✅ Validating configuration..."
terraform validate

# フォーマットチェック
echo "🎨 Checking format..."
terraform fmt -check

# セキュリティスキャン
echo "🔒 Running security scan..."
tfsec .

# プラン生成
echo "📋 Generating plan..."
terraform plan -out=tfplan

# ドライランでない場合は適用
if [[ "$DRY_RUN" != "true" ]]; then
    echo "⚠️  Applying changes in 10 seconds..."
    sleep 10
    
    terraform apply tfplan
    
    echo "✅ Deployment completed successfully!"
else
    echo "ℹ️  Dry run completed - no changes applied"
fi

高度なトピック編

Q21: Terraformプロバイダーの開発について説明してください

A21:

// カスタムプロバイダーの例(Go)
package main

import (
    "context"
    "github.com/hashicorp/terraform-plugin-framework/provider"
    "github.com/hashicorp/terraform-plugin-framework/provider/schema"
    "github.com/hashicorp/terraform-plugin-framework/types"
)

type customProvider struct{}

func (p *customProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
    resp.TypeName = "custom"
}

func (p *customProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "api_endpoint": schema.StringAttribute{
                MarkdownDescription: "API endpoint URL",
                Optional:           true,
            },
            "api_token": schema.StringAttribute{
                MarkdownDescription: "API authentication token",
                Optional:           true,
                Sensitive:          true,
            },
        },
    }
}

type customProviderModel struct {
    ApiEndpoint types.String `tfsdk:"api_endpoint"`
    ApiToken    types.String `tfsdk:"api_token"`
}

func (p *customProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
    var config customProviderModel
    
    diags := req.Config.Get(ctx, &config)
    resp.Diagnostics.Append(diags...)
    
    if resp.Diagnostics.HasError() {
        return
    }
    
    // クライアント設定
    client := &APIClient{
        Endpoint: config.ApiEndpoint.ValueString(),
        Token:    config.ApiToken.ValueString(),
    }
    
    resp.DataSourceData = client
    resp.ResourceData = client
}
# カスタムプロバイダーの使用
terraform {
  required_providers {
    custom = {
      source  = "company/custom"
      version = "~> 1.0"
    }
  }
}

provider "custom" {
  api_endpoint = "https://api.example.com"
  api_token    = var.custom_api_token
}

resource "custom_resource" "example" {
  name        = "example-resource"
  description = "Example custom resource"
  
  configuration = {
    setting1 = "value1"
    setting2 = "value2"
  }
}

Q22: Terraform Cloudと自己管理の比較、選択基準は?

A22:

項目 Terraform Cloud 自己管理 選択基準
初期設定 簡単、すぐに開始可能 複雑、インフラ構築が必要 チームサイズ、技術力
状態管理 自動、暗号化、バックアップ 手動設定、S3+DynamoDB等 セキュリティ要件
コスト 従量課金、チーム規模に応じて インフラコスト、運用コスト 予算、チームサイズ
カスタマイズ 制限あり 完全にカスタマイズ可能 特殊要件の有無
セキュリティ SOC2準拠、エンタープライズ機能 自社責任 コンプライアンス要件
# Terraform Cloud設定例
terraform {
  cloud {
    organization = "my-company"
    
    workspaces {
      name = "production-infrastructure"
    }
  }
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# 環境変数の設定(Terraform Cloud UI)
# TF_VAR_aws_region = us-west-2
# AWS_ACCESS_KEY_ID = [sensitive]
# AWS_SECRET_ACCESS_KEY = [sensitive]

Q23: Terraformのパフォーマンス最適化手法は?

A23:

# 1. プロバイダーの並列実行制限
provider "aws" {
  region = "us-west-2"
  
  # API制限に応じて調整
  max_retries = 10
}

# 2. リソースのターゲット実行
# 特定のリソースのみ適用
terraform apply -target=module.database
terraform apply -target=aws_instance.web[0]

# 3. 並列実行数の調整
# デフォルト: 10並列
terraform apply -parallelism=5

# 4. 大量リソースの効率的な管理
resource "aws_instance" "web" {
  for_each = toset(var.instance_names)
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  # ライフサイクル管理
  lifecycle {
    create_before_destroy = true
    ignore_changes = [
      tags["LastUpdated"],  # 頻繁に変更されるタグを無視
    ]
  }
  
  tags = {
    Name = each.key
    LastUpdated = timestamp()
  }
}

# 5. データソースのキャッシュ活用
data "aws_availability_zones" "available" {
  state = "available"
  
  # 結果をローカルにキャッシュ
  lifecycle {
    postcondition {
      condition     = length(self.names) >= 2
      error_message = "At least 2 AZs must be available"
    }
  }
}

locals {
  # 計算結果をローカルに保存
  az_names = data.aws_availability_zones.available.names
  subnet_cidrs = [
    for i, az in local.az_names :
    cidrsubnet(var.vpc_cidr, 8, i)
  ]
}
# パフォーマンス監視
export TF_LOG=INFO
export TF_LOG_PATH=terraform.log

# 実行時間の測定
time terraform apply

# リソース数の確認
terraform state list | wc -l

# 大きな状態ファイルの分割
terraform state mv 'module.old_module' 'module.new_module'

Q24: Terraformの高度なセキュリティ機能について説明してください

A24:

# 1. Sentinel Policy as Code (Terraform Cloud/Enterprise)
# policy/aws-security.sentinel
import "tfplan/v2" as tfplan

# 必須暗号化チェック
mandatory_encryption = rule {
  all tfplan.resource_changes as _, changes {
    changes.type is "aws_s3_bucket" implies 
      changes.change.after.server_side_encryption_configuration != null
  }
}

# インスタンスタイプ制限
allowed_instance_types = ["t3.micro", "t3.small", "t3.medium"]

instance_type_allowed = rule {
  all tfplan.resource_changes as _, changes {
    changes.type is "aws_instance" implies
      changes.change.after.instance_type in allowed_instance_types
  }
}

main = rule {
  mandatory_encryption and instance_type_allowed
}

# 2. OPA (Open Policy Agent) 統合
# policy/security.rego
package terraform.security

import data.terraform.plan

# セキュリティグループルール検証
deny[msg] {
  resource := plan.resource_changes[_]
  resource.type == "aws_security_group_rule"
  resource.change.after.type == "ingress"
  resource.change.after.from_port == 22
  "0.0.0.0/0" in resource.change.after.cidr_blocks
  
  msg := sprintf("SSH access from 0.0.0.0/0 is not allowed in %v", [resource.address])
}

# S3バケット暗号化必須
deny[msg] {
  resource := plan.resource_changes[_]
  resource.type == "aws_s3_bucket"
  not has_encryption_configuration(resource.change.after)
  
  msg := sprintf("S3 bucket %v must have encryption enabled", [resource.address])
}

has_encryption_configuration(bucket) {
  bucket.server_side_encryption_configuration != null
}
# 3. 外部セキュリティツールとの統合

# Checkov - Python製セキュリティスキャナー
pip install checkov
checkov -d . --framework terraform

# TFSec - Go製セキュリティスキャナー
tfsec .

# Terrascan - 複数IaCツール対応
terrascan scan -t terraform

# Infracost - コスト分析
infracost breakdown --path .

# TFLint - Terraformリンター
tflint --init
tflint
# 4. 動的シークレット管理
# Vault統合例
data "vault_generic_secret" "db_password" {
  path = "secret/myapp/database"
}

resource "aws_db_instance" "main" {
  identifier = "myapp-db"
  
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.t3.micro"
  
  username = "admin"
  password = data.vault_generic_secret.db_password.data["password"]
  
  # パスワードローテーション
  manage_master_user_password = true
  master_user_secret_kms_key_id = aws_kms_key.rds.key_id
}

# 5. 条件付きアクセス制御
resource "aws_iam_policy" "ec2_access" {
  name        = "ec2-conditional-access"
  description = "EC2 access with conditions"
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ec2:DescribeInstances",
          "ec2:StartInstances",
          "ec2:StopInstances"
        ]
        Resource = "*"
        Condition = {
          # IPアドレス制限
          IpAddress = {
            "aws:SourceIp" = var.trusted_ip_ranges
          }
          # 時間制限
          DateGreaterThan = {
            "aws:CurrentTime" = "09:00:00Z"
          }
          DateLessThan = {
            "aws:CurrentTime" = "18:00:00Z"
          }
          # MFA必須
          Bool = {
            "aws:MultiFactorAuthPresent" = "true"
          }
        }
      }
    ]
  })
}

Q25: Terraformの国際化・多地域展開戦略は?

A25:

# 1. マルチリージョン対応の構造
# global/
# ├── main.tf
# ├── variables.tf
# └── regions/
#     ├── us-west-2/
#     ├── eu-west-1/
#     └── ap-northeast-1/

# global/main.tf
locals {
  regions = {
    "us-west-2" = {
      primary = true
      zones   = ["us-west-2a", "us-west-2b", "us-west-2c"]
      cidr    = "10.0.0.0/16"
    }
    "eu-west-1" = {
      primary = false
      zones   = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
      cidr    = "10.1.0.0/16"
    }
    "ap-northeast-1" = {
      primary = false
      zones   = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
      cidr    = "10.2.0.0/16"
    }
  }
}

# 各リージョンにモジュール展開
module "regions" {
  source   = "./modules/region"
  for_each = local.regions
  
  region_name        = each.key
  is_primary         = each.value.primary
  availability_zones = each.value.zones
  vpc_cidr          = each.value.cidr
  
  # グローバル設定
  domain_name = var.domain_name
  environment = var.environment
}

# 2. リージョン間接続
resource "aws_vpc_peering_connection" "cross_region" {
  for_each = {
    for pair in setproduct(keys(local.regions), keys(local.regions)) :
    "${pair[0]}-${pair[1]}" => {
      requester = pair[0]
      accepter  = pair[1]
    }
    if pair[0] != pair[1] && pair[0] < pair[1]  # 重複を避ける
  }
  
  provider = aws.requester
  
  vpc_id        = module.regions[each.value.requester].vpc_id
  peer_vpc_id   = module.regions[each.value.accepter].vpc_id
  peer_region   = each.value.accepter
  auto_accept   = false
  
  tags = {
    Name = "vpc-peering-${each.value.requester}-${each.value.accepter}"
  }
}

# 3. グローバルサービスの設定
resource "aws_route53_zone" "main" {
  name = var.domain_name
  
  tags = {
    Environment = var.environment
  }
}

# 地理的ルーティング
resource "aws_route53_record" "regional" {
  for_each = local.regions
  
  zone_id = aws_route53_zone.main.zone_id
  name    = var.domain_name
  type    = "A"
  
  set_identifier = each.key
  
  geolocation_routing_policy {
    continent = each.key == "us-west-2" ? "NA" : (
      each.key == "eu-west-1" ? "EU" : "AS"
    )
  }
  
  health_check_id = aws_route53_health_check.regional[each.key].id
  
  alias {
    name                   = module.regions[each.key].alb_dns_name
    zone_id               = module.regions[each.key].alb_zone_id
    evaluate_target_health = true
  }
}

# 4. データレプリケーション
resource "aws_s3_bucket_replication_configuration" "global" {
  count = local.regions["us-west-2"].primary ? 1 : 0
  
  role   = aws_iam_role.replication[0].arn
  bucket = module.regions["us-west-2"].s3_bucket_id
  
  dynamic "rule" {
    for_each = {
      for region, config in local.regions :
      region => config
      if !config.primary
    }
    
    content {
      id     = "replicate-to-${rule.key}"
      status = "Enabled"
      
      destination {
        bucket        = module.regions[rule.key].s3_bucket_arn
        storage_class = "STANDARD_IA"
        
        encryption_configuration {
          replica_kms_key_id = module.regions[rule.key].kms_key_arn
        }
      }
    }
  }
}

# 5. 監視・アラート(グローバル)
resource "aws_cloudwatch_metric_alarm" "global_health" {
  for_each = local.regions
  
  alarm_name          = "global-health-${each.key}"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "TargetResponseTime"
  namespace           = "AWS/ApplicationELB"
  period              = "300"
  statistic           = "Average"
  threshold           = "1.0"
  alarm_description   = "This metric monitors ALB response time in ${each.key}"
  
  dimensions = {
    LoadBalancer = module.regions[each.key].alb_arn_suffix
  }
  
  alarm_actions = [aws_sns_topic.global_alerts.arn]
}
# コンプライアンス対応(GDPR、SOX等)
# modules/compliance/gdpr.tf
resource "aws_s3_bucket_public_access_block" "gdpr_compliance" {
  bucket = var.bucket_id
  
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_logging" "gdpr_audit" {
  bucket = var.bucket_id
  
  target_bucket = aws_s3_bucket.audit_logs.id
  target_prefix = "access-logs/"
}

# データ削除ポリシー(忘れられる権利)
resource "aws_s3_bucket_lifecycle_configuration" "data_retention" {
  bucket = var.bucket_id
  
  rule {
    id     = "gdpr_data_retention"
    status = "Enabled"
    
    # 個人データの自動削除
    expiration {
      days = var.data_retention_days  # 例: 2555日(7年)
    }
    
    # バージョンも削除
    noncurrent_version_expiration {
      noncurrent_days = 30
    }
  }
}

面接対策編

Q26: あなたがTerraformで行った最も複雑なプロジェクトについて説明してください

A26: (これは実体験を話す質問です。以下は回答の構造例)

状況設定
「前職でマルチクラウド(AWS + Azure)のハイブリッド環境を Terraformで統一管理するプロジェクトを担当しました。」

課題

  • 既存のインフラが手動構築で管理されていない状態
  • 複数のクラウドプロバイダーにリソースが散在
  • チーム間での設定の不整合
  • コンプライアンス要件への対応

アプローチ

  1. 現状分析: 既存リソースの棚卸しとTerraform化の優先度付け
  2. 段階的移行: リスクの低いリソースから順次Terraform化
  3. モジュール設計: 再利用可能なモジュールの設計・実装
  4. CI/CD統合: GitOpsワークフローの構築

技術的詳細

# 実装例を含める
module "aws_infrastructure" {
  source = "./modules/aws"
  providers = {
    aws = aws.us_west_2
  }
}

module "azure_infrastructure" {
  source = "./modules/azure" 
  providers = {
    azurerm = azurerm.west_us_2
  }
}

結果・成果

  • デプロイ時間: 8時間 → 30分(96%削減)
  • 設定エラー: 月10件 → 月1件以下(90%削減)
  • 新環境構築: 2週間 → 1日(95%削減)

学んだこと

  • 大規模移行では段階的アプローチが重要
  • チーム間のコミュニケーションとトレーニングが成功のカギ
  • 十分なテストとロールバック戦略の必要性

Q27: Terraformプロジェクトでの失敗経験とその対処について話してください

A27: (実体験ベースの回答例)

失敗事例
「AWS Provider のメジャーアップデート時に、十分な検証なしで本番環境に適用し、RDSインスタンスの設定が意図せず変更される問題が発生しました。」

具体的な問題

  • プロバイダーv3からv4への更新で破壊的変更が発生
  • RDSのバックアップ設定が初期化される
  • 状態ファイルと実際のリソースに不整合が発生

immediate対応

  1. 影響範囲の特定: 影響を受けたリソースの洗い出し
  2. ロールバック実行: 以前の設定に戻す
  3. 手動での設定復旧: 失われた設定の手動復旧

根本原因分析

  • テスト環境での検証が不十分
  • プロバイダー更新時のマイグレーションガイドの見落とし
  • 状態ファイルのバックアップ戦略が不完全

改善策の実装

  1. 段階的更新プロセス: dev → staging → production
  2. 自動化されたテストスイート: Terratest導入
  3. 状態ファイルバックアップ強化: 複数世代のバックアップ
  4. チェックリスト作成: 更新前の必須確認事項
# 実装した検証スクリプト例
./scripts/validate-migration.sh
./scripts/backup-state.sh
terraform plan -detailed-exitcode
# Exit code 2 = changes exist, require review

学んだこと

  • 「動作しているものを変更する時は、十分すぎるほどの慎重さが必要」
  • 自動化と手動確認のバランスの重要性
  • チーム全体でのベストプラクティス共有の価値

Q28: 現在のTerraformトレンドやアップデートについてどう思いますか?

A28:

最新トレンド

  1. Terraform 1.6+ の新機能

    • terraform test コマンドの正式リリース
    • Improved configuration validation
    • Enhanced state management
  2. クラウドネイティブ統合

    • Kubernetes Provider の成熟
    • Helm Chart との連携強化
    • GitOps ツールとの統合
  3. セキュリティ・コンプライアンス

    • Policy as Code の普及(Sentinel, OPA)
    • セキュリティスキャンツールの統合
    • Zero-trust architecture への対応

個人的見解
「特に注目しているのは terraform test の正式リリースです。これまでTerratestなど外部ツールに依存していたテスト機能が、Terraform本体に組み込まれることで、テスト駆動インフラ開発がより身近になると期待しています。」

今後の展望

  • AI/MLワークロードに特化したプロバイダーの充実
  • マルチクラウド管理の更なる簡素化
  • Infrastructure as CodeからPlatform as Codeへの進化

Q29: チームでTerraformを導入する際のアプローチは?

A29:

導入フェーズ

Phase 1: 準備・教育(1-2ヶ月)

# チーム教育計画
Week 1-2: Terraform基礎学習
Week 3-4: ハンズオン実習
Week 5-6: 既存環境の分析
Week 7-8: パイロットプロジェクト選定

Phase 2: パイロット実装(2-3ヶ月)

  • 低リスクな環境(開発環境)から開始
  • 小規模なリソース群での実証
  • ベストプラクティスの策定

Phase 3: 段階的展開(3-6ヶ月)

  • ステージング環境への適用
  • CI/CDパイプライン構築
  • 監視・アラート機能追加

Phase 4: 本格運用(継続)

  • 本番環境への適用
  • 運用プロセスの最適化
  • 継続的改善

成功要因

  1. 経営層のサポート: ROIの明確化と予算確保
  2. 段階的アプローチ: 一度にすべてを変えない
  3. 知識共有: 定期的な勉強会・事例共有
  4. ツール整備: 開発環境・CI/CDの整備
  5. 文化醸成: Infrastructure as Code文化の定着
# チーム用スターターテンプレート例
module "starter_template" {
  source = "git::https://github.com/company/terraform-modules.git//starter?ref=v1.0"
  
  project_name = "my-new-project"
  environment  = "development"
  team_name    = "platform-team"
  
  # 組織標準の設定が自動適用される
}

Q30: Terraformエンジニアとして今後どのようにスキルアップしていきますか?

A30:

技術スキルの向上

  1. クラウドプラットフォームの深い理解

    • AWS/Azure/GCP の新サービス・機能の習得
    • マルチクラウド・ハイブリッドクラウドの実践経験
  2. 関連技術の習得

    • Kubernetes, Helm
    • Ansible, Chef
    • Docker, Container技術
    • CI/CD ツール(GitHub Actions, GitLab CI, Jenkins)
  3. プログラミングスキル

    • Go(プロバイダー開発用)
    • Python(自動化スクリプト)
    • Shell scripting

業務スキルの向上

  1. アーキテクチャ設計能力

    • Well-Architected Framework の理解
    • セキュリティ・コンプライアンス要件の理解
    • コスト最適化戦略
  2. チームリーダーシップ

    • メンタリング・教育スキル
    • プロジェクトマネジメント
    • ステークホルダーとのコミュニケーション

学習計画

# 6ヶ月学習計画
Month 1-2: 
- AWS Advanced Networking 学習
- Terraform Associate 資格取得

Month 3-4:
- Kubernetes + Terraform 実践プロジェクト
- セキュリティスキャンツール習得

Month 5-6:
- Go言語学習開始
- オープンソースプロジェクト参加

情報収集源

  • HashiCorp公式ブログ・ドキュメント
  • Cloud provider のアップデート情報
  • GitHub trending repositories
  • Tech conference(HashiConf, re:Invent等)
  • コミュニティ参加(Reddit, Discord, Slack)

アウトプット

  • 社内勉強会での知見共有
  • ブログ・Qiita での記事執筆
  • オープンソース貢献
  • 外部イベントでの登壇

長期的目標
「単なるツールユーザーから、Infrastructure as Code の evangelist として、組織全体のインフラ戦略に貢献できるエンジニアになりたいと考えています。特に、セキュリティとコスト最適化を両立させたクラウドアーキテクチャの設計・実装において専門性を高めていきたいです。」


まとめ:面接成功のポイント

面接官が評価するポイント

  1. 技術的深さ: 表面的でない、実践に基づいた理解
  2. 問題解決能力: トラブルシューティングの系統的アプローチ
  3. ベストプラクティス: セキュリティ、パフォーマンス、保守性への配慮
  4. チームワーク: 他職種との協働、知識共有への積極性
  5. 継続学習: 最新技術への関心と学習への取り組み

準備すべきこと

  1. 実践経験の整理: 具体的なプロジェクト事例とその成果
  2. 失敗談と学び: 問題解決プロセスと改善策
  3. コード例の準備: 説明できるTerraformコードの用意
  4. 最新動向の把握: Terraformとクラウド技術のトレンド
  5. 質問の準備: 逆質問で関心と積極性をアピール

面接での成功を祈っています! 🚀


追加練習問題:実践シナリオ編

Q31: 「本番環境で terraform destroy が誤実行されました。どう対処しますか?」

A31:

緊急対応フェーズ(最初の30分)

# 1. 即座に実行を停止(実行中の場合)
Ctrl+C  # 実行中の場合は中断

# 2. 被害状況の把握
terraform show  # 現在の状態確認
aws ec2 describe-instances --query 'Reservations[].Instances[].{ID:InstanceId,State:State.Name}' --output table
aws rds describe-db-instances --query 'DBInstances[].[DBInstanceIdentifier,DBInstanceStatus]' --output table

# 3. 状態ファイルのバックアップ確認
aws s3 ls s3://terraform-state-bucket/backups/ --recursive | tail -10

復旧フェーズ(1-4時間)

# 4. 最新のバックアップから状態復旧
aws s3 cp s3://terraform-state-bucket/backups/terraform.tfstate.2024-01-15-14-30 terraform.tfstate

# 5. 削除されたリソースの再作成
terraform plan  # 何が再作成されるか確認
terraform apply  # 承認後実行

# 6. データ復旧(データベース等)
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier myapp-db-restored \
  --db-snapshot-identifier myapp-db-snapshot-2024-01-15

予防策の実装

# destroy防止設定
resource "aws_db_instance" "production" {
  deletion_protection = true
  
  lifecycle {
    prevent_destroy = true
  }
}

# IAMポリシーでdestroy権限を制限
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "ec2:TerminateInstances",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "ec2:ResourceTag/Environment": "production"
        }
      }
    }
  ]
}

Q32: 「既存のAWSアカウントに散在する手動作成リソースをTerraform化してください」

A32:

Step 1: 現状調査・分析

# リソース一覧の取得
aws ec2 describe-instances --output table
aws rds describe-db-instances --output table
aws elbv2 describe-load-balancers --output table
aws s3api list-buckets

# タグによる分類
aws resourcegroupstaggingapi get-resources \
  --tag-filters Key=Environment,Values=production \
  --output table

Step 2: 優先度付けとリスク評価

# リソース分類表
| リソース | 重要度 | 複雑さ | 依存関係 | 移行優先度 |
|----------|--------|--------|----------|------------|
| VPC/Subnet | High | Low | Many | 1 |
| Security Groups | High | Medium | Many | 2 |
| EC2 Instances | High | High | Medium | 3 |
| RDS | Critical | High | Low | 4 |
| Load Balancers | High | Medium | High | 5 |

Step 3: インポート戦略

# 段階的インポート例
# Phase 1: ネットワーク基盤
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  # 既存VPCの設定に合わせる
}

# インポートコマンド
# terraform import aws_vpc.main vpc-12345678
# 自動化インポートスクリプト
#!/bin/bash
# bulk-import.sh

VPC_ID="vpc-12345678"
SUBNET_IDS=("subnet-11111111" "subnet-22222222")

# VPCのインポート
terraform import aws_vpc.main $VPC_ID

# サブネットの一括インポート
for i in "${!SUBNET_IDS[@]}"; do
  terraform import "aws_subnet.public[$i]" "${SUBNET_IDS[$i]}"
done

# インポート後の設定調整
terraform plan  # 差分確認

Step 4: 段階的移行

# 移行用設定テンプレート
locals {
  # 既存リソースIDを変数化
  existing_resources = {
    vpc_id = "vpc-12345678"
    subnet_ids = ["subnet-11111111", "subnet-22222222"]
    security_group_ids = ["sg-11111111", "sg-22222222"]
  }
}

# データソースで既存リソースを参照
data "aws_vpc" "existing" {
  id = local.existing_resources.vpc_id
}

# 新しいリソースは既存リソースを参照
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = local.existing_resources.subnet_ids[0]
  
  vpc_security_group_ids = local.existing_resources.security_group_ids
}

Q33: 「Terraform state が破損しました。復旧手順を説明してください」

A33:

破損パターン別対応

パターン1: 状態ファイル構文エラー

# エラー例
Error: Failed to load state: state snapshot could not be decoded: invalid character 'x' looking for beginning of value

# 対処法
# 1. バックアップから復旧
cp terraform.tfstate.backup terraform.tfstate

# 2. S3バージョニングから復旧
aws s3api list-object-versions --bucket terraform-state-bucket --prefix terraform.tfstate
aws s3 cp s3://terraform-state-bucket/terraform.tfstate?versionId=VERSION_ID terraform.tfstate

パターン2: リソースIDの不整合

# エラー例
Error: Resource 'aws_instance.web' not found

# 対処法: 状態の手動修正
terraform state rm aws_instance.web  # 問題リソースを削除
terraform import aws_instance.web i-1234567890abcdef0  # 再インポート

パターン3: 完全な状態ファイル損失

# 段階的復旧プロセス
# 1. 空の状態から開始
rm terraform.tfstate

# 2. 重要なリソースから順次インポート
terraform import aws_vpc.main vpc-12345678
terraform import aws_subnet.public[0] subnet-11111111
terraform import aws_instance.web i-1234567890abcdef0

# 3. 状態整合性チェック
terraform plan  # 差分を確認し、設定を調整

復旧自動化スクリプト

#!/usr/bin/env python3
# state-recovery.py
import boto3
import json
import subprocess

class StateRecovery:
    def __init__(self, region):
        self.ec2 = boto3.client('ec2', region_name=region)
        self.rds = boto3.client('rds', region_name=region)
        
    def discover_resources(self):
        """既存リソースを自動発見"""
        resources = {}
        
        # EC2インスタンス
        instances = self.ec2.describe_instances()
        for reservation in instances['Reservations']:
            for instance in reservation['Instances']:
                if instance['State']['Name'] != 'terminated':
                    resources[f"aws_instance.{instance['InstanceId']}"] = instance['InstanceId']
        
        # RDSインスタンス
        db_instances = self.rds.describe_db_instances()
        for db in db_instances['DBInstances']:
            resources[f"aws_db_instance.{db['DBInstanceIdentifier']}"] = db['DBInstanceIdentifier']
            
        return resources
    
    def generate_import_commands(self, resources):
        """インポートコマンドを生成"""
        commands = []
        for tf_resource, aws_id in resources.items():
            commands.append(f"terraform import {tf_resource} {aws_id}")
        return commands
    
    def execute_recovery(self):
        """復旧実行"""
        resources = self.discover_resources()
        commands = self.generate_import_commands(resources)
        
        for cmd in commands:
            print(f"Executing: {cmd}")
            result = subprocess.run(cmd.split(), capture_output=True, text=True)
            if result.returncode != 0:
                print(f"Error: {result.stderr}")
            else:
                print("Success")

# 使用例
recovery = StateRecovery('us-west-2')
recovery.execute_recovery()

Q34: 「チーム開発でTerraformのマージコンフリクトが頻発しています。解決策は?」

A34:

問題の分析

# よくあるコンフリクト例
<<<<<<< HEAD
resource "aws_instance" "web" {
  count = 3
=======
resource "aws_instance" "web" {
  count = 5
>>>>>>> feature/scale-up

解決策1: ブランチ戦略の最適化

# Gitflow を Terraform向けにカスタマイズ
main                 # 本番環境
├── develop          # 開発統合ブランチ
├── feature/vpc      # ネットワーク機能
├── feature/compute  # コンピュート機能
└── feature/storage  # ストレージ機能

解決策2: 環境分離とモジュール化

# ディレクトリ構造の改善
terraform/
├── environments/
│   ├── dev/         # 開発者個別環境
│   ├── staging/     # 統合テスト環境
│   └── production/  # 本番環境
├── modules/         # 共通モジュール
└── shared/          # 共通設定

解決策3: リソース責任分割

# modules/networking/main.tf (Team A担当)
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
}

# modules/compute/main.tf (Team B担当)  
resource "aws_instance" "web" {
  vpc_id = var.vpc_id  # 他チームのリソースを参照
}

# 依存関係はmodule間で明確に定義

解決策4: 状態分離戦略

# infrastructure/networking/backend.tf
terraform {
  backend "s3" {
    bucket = "company-terraform-state"
    key    = "networking/terraform.tfstate"
    region = "us-west-2"
  }
}

# infrastructure/compute/backend.tf
terraform {
  backend "s3" {
    bucket = "company-terraform-state"  
    key    = "compute/terraform.tfstate"
    region = "us-west-2"
  }
}

解決策5: 自動化ワークフロー

# .github/workflows/terraform-pr.yml
name: Terraform PR Validation

on:
  pull_request:
    paths: ['terraform/**']

jobs:
  validate:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        environment: [networking, compute, storage]
        
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        
      - name: Terraform Init
        run: terraform init
        working-directory: terraform/${{ matrix.environment }}
        
      - name: Terraform Plan
        run: terraform plan -out=tfplan
        working-directory: terraform/${{ matrix.environment }}
        
      - name: Check for conflicts
        run: |
          if terraform show tfplan | grep -q "will be destroyed"; then
            echo "::error::Destructive changes detected"
            exit 1
          fi

Q35: 「コスト爆発が起きました。Terraformでコスト制御する方法は?」

A35:

緊急対応:現状把握

# AWS Cost Explorer APIでコスト分析
aws ce get-cost-and-usage \
  --time-period Start=2024-01-01,End=2024-01-31 \
  --granularity DAILY \
  --metrics BlendedCost \
  --group-by Type=DIMENSION,Key=SERVICE

# 高コストリソースの特定
aws ec2 describe-instances \
  --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,InstanceType,Tags[?Key==`Name`].Value|[0]]' \
  --output table

Terraformでの予防的コスト制御

# 1. コスト制限ポリシー
resource "aws_budgets_budget" "cost_control" {
  name     = "${var.environment}-cost-budget"
  budget_type = "COST"
  limit_amount = var.environment == "production" ? "5000" : "500"
  limit_unit   = "USD"
  time_unit    = "MONTHLY"
  
  cost_filters = {
    Tag = ["Environment:${var.environment}"]
  }
  
  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                 = 80
    threshold_type            = "PERCENTAGE"
    notification_type          = "FORECASTED"
    subscriber_email_addresses = [var.admin_email]
  }
  
  notification {
    comparison_operator        = "GREATER_THAN"  
    threshold                 = 100
    threshold_type            = "PERCENTAGE"
    notification_type          = "ACTUAL"
    subscriber_email_addresses = [var.admin_email]
  }
}

# 2. インスタンスタイプ制限
variable "allowed_instance_types" {
  description = "Allowed EC2 instance types by environment"
  type = map(list(string))
  default = {
    dev     = ["t3.nano", "t3.micro", "t3.small"]
    staging = ["t3.micro", "t3.small", "t3.medium"]
    prod    = ["t3.medium", "t3.large", "t3.xlarge"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  
  # バリデーション
  lifecycle {
    precondition {
      condition = contains(
        var.allowed_instance_types[var.environment],
        var.instance_type
      )
      error_message = "Instance type ${var.instance_type} not allowed in ${var.environment}"
    }
  }
}

# 3. 自動シャットダウン
resource "aws_instance" "development" {
  count = var.environment == "dev" ? var.instance_count : 0
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  # 開発環境は夜間自動停止
  user_data = base64encode(templatefile("${path.module}/shutdown-schedule.sh", {
    shutdown_time = "18:00"
    startup_time  = "09:00"
  }))
  
  tags = {
    AutoShutdown = "enabled"
    Schedule     = "weekdays-only"
  }
}

# 4. スポットインスタンス活用
resource "aws_launch_template" "spot" {
  name_prefix   = "${var.environment}-spot-"
  image_id      = data.aws_ami.ubuntu.id
  instance_type = "t3.medium"
  
  # 非本番環境でスポットインスタンス使用
  dynamic "instance_market_options" {
    for_each = var.environment != "production" ? [1] : []
    content {
      market_type = "spot"
      spot_options {
        max_price                      = "0.05"  # 上限価格設定
        spot_instance_type            = "one-time"
        instance_interruption_behavior = "stop"
      }
    }
  }
}

# 5. リソース使用量監視
resource "aws_cloudwatch_metric_alarm" "high_cpu_cost_alert" {
  alarm_name          = "${var.environment}-unused-instance"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "24"  # 24時間
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "3600"  # 1時間
  statistic           = "Average"
  threshold           = "5"     # CPU使用率5%以下
  alarm_description   = "Instance appears to be unused"
  
  alarm_actions = [aws_sns_topic.cost_alerts.arn]
  
  dimensions = {
    InstanceId = aws_instance.web.id
  }
}

コスト最適化自動化

# cost-optimizer.py
import boto3
import json
from datetime import datetime, timedelta

class CostOptimizer:
    def __init__(self):
        self.ec2 = boto3.client('ec2')
        self.cloudwatch = boto3.client('cloudwatch')
        
    def find_unused_instances(self):
        """使用率の低いインスタンスを特定"""
        instances = self.ec2.describe_instances(
            Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
        )
        
        unused_instances = []
        end_time = datetime.utcnow()
        start_time = end_time - timedelta(days=7)
        
        for reservation in instances['Reservations']:
            for instance in reservation['Instances']:
                # CPU使用率を確認
                response = self.cloudwatch.get_metric_statistics(
                    Namespace='AWS/EC2',
                    MetricName='CPUUtilization',
                    Dimensions=[
                        {'Name': 'InstanceId', 'Value': instance['InstanceId']}
                    ],
                    StartTime=start_time,
                    EndTime=end_time,
                    Period=3600,
                    Statistics=['Average']
                )
                
                if response['Datapoints']:
                    avg_cpu = sum(d['Average'] for d in response['Datapoints']) / len(response['Datapoints'])
                    if avg_cpu < 5:  # 5%以下
                        unused_instances.append({
                            'InstanceId': instance['InstanceId'],
                            'InstanceType': instance['InstanceType'],
                            'AvgCPU': avg_cpu
                        })
        
        return unused_instances
    
    def generate_recommendations(self):
        """コスト最適化推奨事項を生成"""
        unused = self.find_unused_instances()
        
        recommendations = []
        for instance in unused:
            recommendations.append({
                'action': 'stop_or_terminate',
                'resource': instance['InstanceId'],
                'reason': f"Low CPU utilization: {instance['AvgCPU']:.2f}%",
                'estimated_savings': self.calculate_savings(instance['InstanceType'])
            })
            
        return recommendations

これで30問のTerraform技術面接Q&Aが完成しました!基本概念から実践的なトラブルシューティング、チーム開発での課題解決まで、幅広くカバーしています。

面接成功のコツ

  1. 実体験を交える: 「実際に〜のプロジェクトで〜」
  2. 具体的なコード例: 口頭だけでなく、実際のコードで説明
  3. 失敗談も含める: トラブル経験とその学びを語る
  4. 最新動向への関心: 継続学習の姿勢をアピール
  5. ビジネス観点: 技術だけでなく、コストや運用効率の視点

頑張ってください!🚀dynamic "ingress" {
for_each = var.enable_https ? [443] : []
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

dynamic "ingress" {
for_each = var.allowed_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = var.trusted_cidrs
}
}
}

5. count vs for_each

resource "aws_instance" "web_count" {
count = var.create_instances ? var.instance_count : 0

ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}

resource "aws_instance" "web_for_each" {
for_each = var.create_instances ? var.instance_configs : {}

ami = data.aws_ami.ubuntu.id
instance_type = each.value.instance_type

tags = {
Name = each.key
}
}


### Q9: ループ処理(count、for_each、for)の使い分けは?
**A9:** 

```hcl
# 1. count - 同じリソースを複数作成
variable "instance_count" {
  default = 3
}

resource "aws_instance" "web" {
  count = var.instance_count
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  tags = {
    Name = "web-${count.index + 1}"
  }
}

# 2. for_each - 個別設定が必要な複数リソース
variable "environments" {
  type = map(object({
    instance_type = string
    min_size      = number
    max_size      = number
  }))
  
  default = {
    dev = {
      instance_type = "t3.micro"
      min_size      = 1
      max_size      = 2
    }
    prod = {
      instance_type = "t3.large"
      min_size      = 2
      max_size      = 10
    }
  }
}

resource "aws_autoscaling_group" "app" {
  for_each = var.environments
  
  name                = "asg-${each.key}"
  min_size            = each.value.min_size
  max_size            = each.value.max_size
  desired_capacity    = each.value.min_size
  vpc_zone_identifier = data.aws_subnets.private.ids
  
  launch_template {
    id      = aws_launch_template.app[each.key].id
    version = "$Latest"
  }
}

# 3. for式 - データの変換
locals {
  # リストの変換
  uppercase_names = [for name in var.user_names : upper(name)]
  
  # マップの変換
  environment_tags = {
    for env, config in var.environments :
    env => {
      Environment = env
      InstanceType = config.instance_type
    }
  }
  
  # 条件付きfor式
  production_configs = {
    for env, config in var.environments :
    env => config
    if config.instance_type != "t3.micro"
  }
  
  # ネストしたfor式
  all_subnets = flatten([
    for vpc_name, vpc_config in var.vpcs : [
      for subnet_name, subnet_config in vpc_config.subnets : {
        vpc_name    = vpc_name
        subnet_name = subnet_name
        cidr_block  = subnet_config.cidr_block
      }
    ]
  ])
}

# 4. 動的ブロックでのfor_each
resource "aws_security_group" "web" {
  name = "web-sg"
  
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      description = ingress.value.description
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

使い分けの基準

  • count: 同一リソースの単純な複製
  • for_each: 個別設定が必要な複数リソース
  • for式: データの変換・フィルタリング

上級編:モジュール・状態管理

Q10: モジュール(Modules)の作成と使用方法を説明してください

A10:

# モジュールの構造
modules/
├── vpc/
   ├── main.tf
   ├── variables.tf
   ├── outputs.tf
   └── versions.tf
└── ec2/
    ├── main.tf
    ├── variables.tf
    ├── outputs.tf
    └── versions.tf

# modules/vpc/variables.tf
variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
  validation {
    condition     = can(cidrhost(var.vpc_cidr, 0))
    error_message = "VPC CIDR must be a valid IPv4 CIDR block."
  }
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "availability_zones" {
  description = "List of AZs"
  type        = list(string)
}

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
  }
}

resource "aws_subnet" "public" {
  count = length(var.availability_zones)
  
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = var.availability_zones[count.index]
  
  map_public_ip_on_launch = true
  
  tags = {
    Name = "${var.environment}-public-${count.index + 1}"
    Type = "public"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = {
    Name = "${var.environment}-igw"
  }
}

# modules/vpc/outputs.tf
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "IDs of the public subnets"
  value       = aws_subnet.public[*].id
}

output "vpc_cidr_block" {
  description = "CIDR block of the VPC"
  value       = aws_vpc.main.cidr_block
}

# モジュールの使用 - main.tf
module "vpc" {
  source = "./modules/vpc"
  
  vpc_cidr           = "10.0.0.0/16"
  environment        = "production"
  availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
}

module "web_servers" {
  source = "./modules/ec2"
  
  vpc_id            = module.vpc.vpc_id
  subnet_ids        = module.vpc.public_subnet_ids
  environment       = "production"
  instance_count    = 3
  
  depends_on = [module.vpc]
}

# リモートモジュールの使用
module "s3_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "~> 3.0"
  
  bucket = "my-app-bucket-${random_string.suffix.result}"
  
  versioning = {
    enabled = true
  }
  
  server_side_encryption_configuration = {
    rule = {
      apply_server_side_encryption_by_default = {
        sse_algorithm = "AES256"
      }
    }
  }
}

Q11: リモートバックエンドの設定と状態ロックについて説明してください

A11:

# S3 + DynamoDBバックエンド設定
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "webapp/terraform.tfstate"
    region         = "us-west-2"
    
    # 状態ロック用DynamoDBテーブル
    dynamodb_table = "terraform-state-locks"
    encrypt        = true
    
    # バージョニング・レプリケーション
    versioning     = true
  }
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  
  required_version = ">= 1.6.0"
}

# DynamoDBテーブルの作成(別途実行)
resource "aws_dynamodb_table" "terraform_locks" {
  name           = "terraform-state-locks"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"
  
  attribute {
    name = "LockID"
    type = "S"
  }
  
  tags = {
    Name = "Terraform State Lock Table"
  }
}

# S3バケットの設定(別途実行)
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state-bucket"
  
  tags = {
    Name = "Terraform State Bucket"
  }
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# 環境別の設定
# environments/dev/backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "environments/dev/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-state-locks"
    encrypt        = true
  }
}

# environments/prod/backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "environments/prod/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-state-locks"
    encrypt        = true
  }
}

状態ロックの仕組み

# 1. terraform applyを実行すると、DynamoDBに排他ロックが作成される
# 2. 他のユーザーが同時にapplyしようとするとロックエラーが発生
Error: Error acquiring the state lock

# 3. 緊急時のロック解除(注意して使用)
terraform force-unlock <lock-id>

Q12: ワークスペース(Workspaces)の使い方と環境分離戦略は?

A12:

# 1. ワークスペースの作成と切り替え
terraform workspace new development
terraform workspace new staging  
terraform workspace new production

# 2. ワークスペース一覧確認
terraform workspace list
  default
  development
* staging
  production

# 3. ワークスペース切り替え
terraform workspace select production

# 4. 現在のワークスペース確認
terraform workspace show
# ワークスペースを活用した環境分離
locals {
  environment = terraform.workspace
  
  # 環境別設定
  config = {
    development = {
      instance_type = "t3.micro"
      min_size      = 1
      max_size      = 2
    }
    staging = {
      instance_type = "t3.small"
      min_size      = 1
      max_size      = 3
    }
    production = {
      instance_type = "t3.large"
      min_size      = 3
      max_size      = 10
    }
  }
  
  current_config = local.config[local.environment]
}

# 環境名をリソース名に含める
resource "aws_instance" "web" {
  count = local.current_config.min_size
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.current_config.instance_type
  
  tags = {
    Name        = "${local.environment}-web-${count.index + 1}"
    Environment = local.environment
    Workspace   = terraform.workspace
  }
}

# 環境別バックエンド設定
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "webapp/${terraform.workspace}/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-state-locks"
  }
}

環境分離の戦略比較

方法 メリット デメリット 適用場面
Workspaces 単一コードベース、簡単切り替え 設定の差異が大きい場合は複雑 同一構成の環境
ディレクトリ分離 完全分離、独立管理 コード重複、同期が困難 構成が大きく異なる環境
変数ファイル コード共通化、設定外部化 変数管理が複雑 設定のみ異なる環境

実践編:トラブルシューティング

Q13: よくあるTerraformエラーとその対処法を教えてください

A13:

# 1. Provider version conflict
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider hashicorp/aws

# 対処法
rm -rf .terraform
rm .terraform.lock.hcl
terraform init

# 2. Resource already exists
Error: creating EC2 Instance: InvalidParameterValue: Instance i-xxx already exists

# 対処法:既存リソースをインポート
terraform import aws_instance.web i-1234567890abcdef0

# 3. State lock error
Error: Error acquiring the state lock

# 対処法:ロックIDを確認してforce-unlock
terraform force-unlock 12345678-1234-1234-1234-123456789012

# 4. Dependency cycle
Error: Cycle: aws_security_group.web, aws_security_group.db

# 対処法:依存関係を明示的に定義
resource "aws_security_group" "web" {
  # webからdbへの参照を削除し、別途ingress ruleで定義
}

resource "aws_security_group_rule" "web_to_db" {
  type                     = "egress"
  from_port                = 3306
  to_port                  = 3306
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.db.id
  security_group_id        = aws_security_group.web.id
}

# 5. Resource drift detection
# 状態ファイルと実際のリソースの差異を確認
terraform plan -detailed-exitcode
# Exit code: 0=no changes, 1=error, 2=changes present

# 6. Terraform version mismatch
Error: state snapshot was created by Terraform v1.6.0, which is newer than current v1.5.0

# 対処法:Terraformバージョンをアップデート
tfenv install 1.6.0
tfenv use 1.6.0

Q14: 状態ファイルの操作(state commands)について説明してください

A14:

# 1. リソース一覧表示
terraform state list
aws_instance.web
aws_security_group.web
module.vpc.aws_vpc.main

# 2. 特定リソースの詳細表示
terraform state show aws_instance.web

# 3. リソースの削除(状態ファイルからのみ)
terraform state rm aws_instance.web

# 4. リソースの移動・名前変更
terraform state mv aws_instance.old_name aws_instance.new_name

# 5. モジュール間でのリソース移動
terraform state mv module.old_module.aws_instance.web module.new_module.aws_instance.web

# 6. 状態ファイルの引っ越し
terraform state pull > backup.tfstate
terraform state push backup.tfstate

# 7. 既存リソースのインポート
terraform import aws_instance.web i-1234567890abcdef0

# 8. 状態ファイルの置き換え(危険な操作)
terraform state replace-provider hashicorp/aws registry.terraform.io/hashicorp/aws

# 実践例:リソースのリファクタリング
# Before: 単一のセキュリティグループ
resource "aws_security_group" "web" {
  name = "web-sg"
2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?