基本概念・用語編
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ファイルです。
重要性:
- 現実とのマッピング: 設定ファイルと実際のリソースの対応関係を保存
- パフォーマンス: 大規模インフラでもAPI呼び出しを最小化
- 協調作業: チーム間での状態共有
- 依存関係追跡: リソース間の依存関係を記録
注意点:
# 状態ファイルには機密情報が含まれる可能性
{
"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で統一管理するプロジェクトを担当しました。」
課題:
- 既存のインフラが手動構築で管理されていない状態
- 複数のクラウドプロバイダーにリソースが散在
- チーム間での設定の不整合
- コンプライアンス要件への対応
アプローチ:
- 現状分析: 既存リソースの棚卸しとTerraform化の優先度付け
- 段階的移行: リスクの低いリソースから順次Terraform化
- モジュール設計: 再利用可能なモジュールの設計・実装
- 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対応:
- 影響範囲の特定: 影響を受けたリソースの洗い出し
- ロールバック実行: 以前の設定に戻す
- 手動での設定復旧: 失われた設定の手動復旧
根本原因分析:
- テスト環境での検証が不十分
- プロバイダー更新時のマイグレーションガイドの見落とし
- 状態ファイルのバックアップ戦略が不完全
改善策の実装:
- 段階的更新プロセス: dev → staging → production
- 自動化されたテストスイート: Terratest導入
- 状態ファイルバックアップ強化: 複数世代のバックアップ
- チェックリスト作成: 更新前の必須確認事項
# 実装した検証スクリプト例
./scripts/validate-migration.sh
./scripts/backup-state.sh
terraform plan -detailed-exitcode
# Exit code 2 = changes exist, require review
学んだこと:
- 「動作しているものを変更する時は、十分すぎるほどの慎重さが必要」
- 自動化と手動確認のバランスの重要性
- チーム全体でのベストプラクティス共有の価値
Q28: 現在のTerraformトレンドやアップデートについてどう思いますか?
A28:
最新トレンド:
-
Terraform 1.6+ の新機能
-
terraform testコマンドの正式リリース - Improved configuration validation
- Enhanced state management
-
-
クラウドネイティブ統合
- Kubernetes Provider の成熟
- Helm Chart との連携強化
- GitOps ツールとの統合
-
セキュリティ・コンプライアンス
- 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: 本格運用(継続)
- 本番環境への適用
- 運用プロセスの最適化
- 継続的改善
成功要因:
- 経営層のサポート: ROIの明確化と予算確保
- 段階的アプローチ: 一度にすべてを変えない
- 知識共有: 定期的な勉強会・事例共有
- ツール整備: 開発環境・CI/CDの整備
- 文化醸成: 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:
技術スキルの向上:
-
クラウドプラットフォームの深い理解
- AWS/Azure/GCP の新サービス・機能の習得
- マルチクラウド・ハイブリッドクラウドの実践経験
-
関連技術の習得
- Kubernetes, Helm
- Ansible, Chef
- Docker, Container技術
- CI/CD ツール(GitHub Actions, GitLab CI, Jenkins)
-
プログラミングスキル
- Go(プロバイダー開発用)
- Python(自動化スクリプト)
- Shell scripting
業務スキルの向上:
-
アーキテクチャ設計能力
- Well-Architected Framework の理解
- セキュリティ・コンプライアンス要件の理解
- コスト最適化戦略
-
チームリーダーシップ
- メンタリング・教育スキル
- プロジェクトマネジメント
- ステークホルダーとのコミュニケーション
学習計画:
# 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 として、組織全体のインフラ戦略に貢献できるエンジニアになりたいと考えています。特に、セキュリティとコスト最適化を両立させたクラウドアーキテクチャの設計・実装において専門性を高めていきたいです。」
まとめ:面接成功のポイント
面接官が評価するポイント
- 技術的深さ: 表面的でない、実践に基づいた理解
- 問題解決能力: トラブルシューティングの系統的アプローチ
- ベストプラクティス: セキュリティ、パフォーマンス、保守性への配慮
- チームワーク: 他職種との協働、知識共有への積極性
- 継続学習: 最新技術への関心と学習への取り組み
準備すべきこと
- 実践経験の整理: 具体的なプロジェクト事例とその成果
- 失敗談と学び: 問題解決プロセスと改善策
- コード例の準備: 説明できるTerraformコードの用意
- 最新動向の把握: Terraformとクラウド技術のトレンド
- 質問の準備: 逆質問で関心と積極性をアピール
面接での成功を祈っています! 🚀
追加練習問題:実践シナリオ編
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が完成しました!基本概念から実践的なトラブルシューティング、チーム開発での課題解決まで、幅広くカバーしています。
面接成功のコツ:
- 実体験を交える: 「実際に〜のプロジェクトで〜」
- 具体的なコード例: 口頭だけでなく、実際のコードで説明
- 失敗談も含める: トラブル経験とその学びを語る
- 最新動向への関心: 継続学習の姿勢をアピール
- ビジネス観点: 技術だけでなく、コストや運用効率の視点
頑張ってください!🚀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"