Iacツールの「Terraform」でAWSインフラをコード化して再現可能な環境を構築してみよう

2025年11月4日(火)
田中 智明
第15回の今回は、第13回と第14回で手動構築したAWSリソースをIacツールの「Terraform」で再現し、インフラをコードで管理する方法について解説します。

はじめに

第13回第14回では「AWS ECR」と「EKS」を使ってコンテナアプリケーションの実行基盤を構築しました。これらの環境構築では、AWSマネジメントコンソールから手動でリソースを作成してきました。

しかし、手動での環境構築にはいくつかの課題があります。

  1. 再現性の欠如
    同じ環境を再度構築しようとしても、設定値を覚えていなかったり、手順を間違えたりして、完全に同じ環境を作るのが困難です。開発環境、ステージング環境、本番環境を別々に構築する場合、それぞれで微妙な差異が生まれてしまいます。
    これは環境の削除にも同じことが言えます。削除手順や依存関係などが複雑に絡み合った環境では環境1つ破棄するのも困難です。環境構築は手順を間違ったとしても他の環境に影響を出しづらいですが、環境の削除は間違って別の環境を削除してしまう恐れもあります。狙った環境のみを削除できる再現性が重要となってきます。
  2. 変更履歴の管理が難しい
    「誰が」「いつ」「何のために」インフラを変更したのかを追跡することが困難です。複数人でインフラを管理している場合は変更内容が属人化し、後から振り返ることができません。
  3. レビューができない
    コンソール操作では、変更内容を事前にチームでレビューすることができません。アプリケーションコードのようにプルリクエストを出して、チームで確認してからデプロイするという運用が難しくなります。
  4. スケールしにくい
    環境が増えるたびに手動で構築していては、時間がかかりすぎます。10個、20個の環境を管理することは現実的ではありません。

これらの課題を解決するのが「Infrastructure as Code(IaC)」の考え方です。そして、そのIaCを実現するための代表的なツールが「Terraform」です。

本記事では、第13回と第14回で手動構築したAWSリソース(ECR、EKS、VPC、IAMロールなど)をTerraformのコードで再現し、インフラをコードで管理する方法を学びます。

IaCとは

IaCとは、コードでインフラストラクチャを構築・管理する手法です。従来のように管理画面から手作業で設定するのではなく、設定内容をコードとして記述し、自動的にインフラを構築します。

IaCのメリット

  1. 再現性の確保
    コードがあれば、いつでも同じ環境を再構築できます。開発、ステージング、本番環境を同じコードから作成することで、環境差異によるトラブルを防げます。
  2. バージョン管理
    Gitなどのバージョン管理システムでインフラの変更履歴を管理できます。「誰が」「いつ」「なぜ」変更したのかが明確になり、問題が発生した際にも過去の状態に戻すことができます。
  3. レビュープロセスの導入
    インフラの変更をプルリクエストとして提出し、チームでレビューしてからデプロイできます。これにより、設定ミスや意図しない変更を事前に防ぐことができます。
  4. ドキュメントとしての役割
    コードそのものがインフラの設計書になります。どのようなリソースがどのように設定されているかが、コードを読めば一目瞭然です。
  5. 自動化の推進
    CI/CDパイプラインと統合することで、インフラの構築・更新を完全に自動化できます。これにより、手作業によるミスを排除し、デプロイのスピードと信頼性を向上させます。

IaCの代表的なツール

IaCを実現するツールはいくつかありますが、代表的なものは以下の通りです。

  • Terraform: HashiCorpが開発するクラウド非依存のIaCツール
  • AWS CloudFormation: AWSが提供するAWS専用のIaCツール
  • Pulumi: 汎用プログラミング言語でインフラを記述できるツール
  • Ansible: 構成管理ツールとしても使われるが、IaCとしても利用可能

本記事では、最も広く使われているTerraformを採用します。

Terraformとは

Terraformは、HashiCorp社が開発したオープンソースのIaCツールです。HCL(HashiCorp Configuration Language)というDSL(ドメイン特化言語)でコードを記述し、AWS、Azure、Google Cloudなど、さまざまなクラウドプロバイダーに対応しています。

Terraformの特徴

  1. マルチクラウド対応
    Terraformは単一のツールで複数のクラウドプロバイダーを管理できます。AWSとGoogle Cloudを併用している場合でも、同じツールとワークフローで管理できます。
  2. 宣言的な記述
    「何を作るか」を記述するだけで、Terraformが自動的に「どうやって作るか」を判断します。これにより、複雑な依存関係も自動的に解決されます。
  3. 状態管理
    Terraformは現在のインフラの状態を「stateファイル」として保存します。このファイルにより既存のリソースとコードの差分を検出し、必要な変更だけを適用できます。
  4. プランとアプライの分離
    実際にリソースを変更する前に「何が変更されるか」をプレビューできます(terraform plan)。確認してから実行できるため、意図しない変更を防げます。
  5. モジュール化
    再利用可能なモジュールを作成し、コードの重複を避けることができます。例えば、VPCの構成を1つのモジュールとして定義し、複数のプロジェクトで使い回すことが可能です。

なお、TerraformはAPIが公開されているクラウドリソースの管理に優れていますが、サーバー内部の設定管理(nginxのインストールや設定ファイルの配置など)はあまり得意ではありません。そのような操作には「Ansible」などの構成管理ツールを併用するのが一般的です。

Terraformのインストール

Terraformをインストールします。インストール方法は公式ドキュメントを参照してください。

●Terraformのインストール
https://developer.hashicorp.com/terraform/install

インストール後、以下のコマンドでバージョンを確認できます。

$ terraform version

執筆時点の最新版はv1.13.4でした。

Terraformのベストプラクティス

実際にコードを書く前に、Terraformを使う上で押さえておきたいベストプラクティスを紹介します。これらは「Terraform公式のスタイルガイド」に基づいています。

コードフォーマットの統一

Terraformには、コードを自動整形するterraform fmtコマンドが用意されています。

$ terraform fmt

このコマンドを実行することで、以下が自動的に整えられます。

  • インデントは2スペース
  • ブロック内の属性は=で揃える
  • 空行の適切な配置

コミット前やCI/CDパイプラインで自動実行することにより、チーム全体でコードスタイルを統一できます。

ディレクトリ構成の工夫

プロジェクトの規模に応じて、適切なディレクトリ構成を選択します。

小規模プロジェクト向け(フラット構成)
terraform/
├── main.tf          # メインの構成ファイル
├── variables.tf     # 変数定義
├── outputs.tf       # 出力値定義
├── terraform.tfvars # 変数の値
└── .gitignore
大規模プロジェクト向け(モジュール構成)
terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       └── terraform.tfvars
└── modules/
    ├── vpc/
    ├── eks/
    └── ecr/

本記事では、学習のしやすさを優先してフラット構成を採用します。

状態ファイルの管理

Terraformの状態ファイル(terraform.tfstate)には、管理しているリソースの情報が含まれています。このファイルの管理方法には注意が必要です。

ローカル管理(開発・学習用)
個人の学習や検証環境では、ローカルに状態ファイルを保存しても問題ありません。

リモート管理(チーム開発・本番環境)
チームで開発する場合や本番環境では、状態ファイルをS3などのリモートバックエンドに保存します。これには、以下のメリットがあります。

  • 複数人で同じ状態を共有できる
  • 状態ファイルのロック機能により同時実行を防げる
  • バージョン管理により過去の状態に戻せる
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "eks-cluster/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

本記事では学習のためローカル管理を使用しますが、本番環境では必ずリモートバックエンドを使用してください。

機密情報の扱い

APIキーやパスワードなどの機密情報はコードに直接書かず、環境変数や専用の管理サービスを使用します。

悪い例
variable "db_password" {
  default = "password123"  # ハードコーディングはNG
}
良い例
variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true  # センシティブ情報としてマーク
  # 値は環境変数やtfvarsファイルで指定
}
環境変数で渡す場合:
$ export TF_VAR_db_password="secure_password"
$ terraform apply

リソースの命名規則

リソース名は一貫性のある命名規則に従います。公式スタイルガイドでは、リソース名にリソースタイプを繰り返さないことが推奨されています。

# 悪い例: リソースタイプの重複
resource "aws_vpc" "eks_vpc" {
  # ...
}

resource "aws_security_group" "eks_cluster_sg" {
  # ...
}

# 良い例: リソースタイプを繰り返さない
resource "aws_vpc" "main" {
  # ...
}

resource "aws_security_group" "cluster" {
  # ...
}

ただし、本記事では学習のしやすさを優先し、分かりやすさのため一部でリソースタイプを含めた命名を使用しています。

タグの活用

AWSリソースには必ずタグを付け、管理しやすくします。

tags = {
  Name        = "my-eks-cluster"
  Environment = "production"
  ManagedBy   = "terraform"
  Project     = "devops-demo"
}

バージョン管理

Terraformのバージョンとプロバイダーのバージョンを明示的に指定します。

terraform {
  required_version = ">= 1.13.4"

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

なぜバージョンを固定する必要があるのか
Terraformやプロバイダーのバージョンを固定しないと、以下のような問題が発生する可能性があります。

  1. 既存のコードが動かなくなる
    プロバイダーのバージョンアップでリソースの属性名が変更されたり、必須パラメータが追加されたりすることがあります。バージョンを固定していないと、ある日突然terraform applyが失敗する可能性があります。
  2. チームメンバー間で挙動が異なる
    チームメンバーがそれぞれ異なるバージョンのTerraformやプロバイダーを使用していると、同じコードでも実行結果が異なる場合があります。これにより、予期しないリソースの変更や削除が発生する危険性があります。
  3. 破壊的変更への対応
    Terraformやプロバイダーはメジャーバージョンアップ時に破壊的変更を導入することがあります。バージョンを固定することで計画的にアップグレードを行い、必要なコード修正を事前に準備できます。

バージョン指定の演算子
バージョン指定には以下の演算子を使用できます。

  • >=: 指定したバージョン以上であればどのバージョンでも使用可能です。例えば、>= 1.3.4はバージョン1.3.4、1.3.5、1.4.0、2.0.0などすべてが許容されます。
  • ~>:「悲観的バージョン制約」と呼ばれ、右端のバージョン番号のみ増加を許可します。例えば、~> 6.16.0は6.16.0、6.16.5、6.16.10などパッチバージョンの増加は許容されますが、6.17.0のようにマイナーバージョンの増加は許可されません。これにより、セキュリティフィックスなどの修正は受け入れつつ、マイナーアップデートを防ぐことができます。

本番環境では、予期しない変更を防ぐため~>を使用することが推奨されます。

TerraformでAWSにリソースを作成

それでは、第13回第14回で作成したAWSリソースをTerraformで再現していきましょう。

前提条件

  • Terraformがインストール済み
  • AWS CLIが設定済み(aws configure完了)
  • 第13回、第14回で作成したリソースは削除済み

ステップ1: プロジェクトのセットアップ

作業用ディレクトリを作成します。

$ mkdir terraform-eks
$ cd terraform-eks

ステップ2: プロバイダーの設定

provider.tfファイルを作成し、AWSプロバイダーを設定します。

provider.tf
terraform {
  required_version = ">= 1.13.4"

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

provider "aws" {
  region = var.aws_region
}

ステップ3: 変数の定義

variables.tfファイルを作成し、環境ごとに変わる値を変数として定義します。

variables.tf
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "ap-northeast-1"
}

variable "cluster_name" {
  description = "EKS cluster name"
  type        = string
  default     = "my-cluster"
}

variable "ecr_repository_name" {
  description = "ECR repository name"
  type        = string
  default     = "my-app"
}

variable "node_instance_type" {
  description = "EC2 instance type for EKS nodes"
  type        = string
  default     = "t3.medium"
}

variable "node_desired_size" {
  description = "Desired number of nodes"
  type        = number
  default     = 2
}

variable "node_min_size" {
  description = "Minimum number of nodes"
  type        = number
  default     = 1
}

variable "node_max_size" {
  description = "Maximum number of nodes"
  type        = number
  default     = 3
}

ステップ4: VPCの作成

vpc.tfファイルを作成し、EKS用のVPCを定義します。

vpc.tf
# VPCの作成
resource "aws_vpc" "eks_vpc" {
  cidr_block           = "192.168.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.cluster_name}-vpc"
  }
}

# インターネットゲートウェイ
resource "aws_internet_gateway" "eks_igw" {
  vpc_id = aws_vpc.eks_vpc.id

  tags = {
    Name = "${var.cluster_name}-igw"
  }
}

# パブリックサブネット
resource "aws_subnet" "public_subnet_1" {
  vpc_id                  = aws_vpc.eks_vpc.id
  cidr_block              = "192.168.0.0/18"
  availability_zone       = "${var.aws_region}a"
  map_public_ip_on_launch = true

  tags = {
    Name                                        = "${var.cluster_name}-public-subnet-1"
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
    "kubernetes.io/role/elb"                    = "1"
  }
}

resource "aws_subnet" "public_subnet_2" {
  vpc_id                  = aws_vpc.eks_vpc.id
  cidr_block              = "192.168.64.0/18"
  availability_zone       = "${var.aws_region}c"
  map_public_ip_on_launch = true

  tags = {
    Name                                        = "${var.cluster_name}-public-subnet-2"
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
    "kubernetes.io/role/elb"                    = "1"
  }
}

# プライベートサブネット
resource "aws_subnet" "private_subnet_1" {
  vpc_id            = aws_vpc.eks_vpc.id
  cidr_block        = "192.168.128.0/18"
  availability_zone = "${var.aws_region}a"

  tags = {
    Name                                        = "${var.cluster_name}-private-subnet-1"
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
    "kubernetes.io/role/internal-elb"           = "1"
  }
}

resource "aws_subnet" "private_subnet_2" {
  vpc_id            = aws_vpc.eks_vpc.id
  cidr_block        = "192.168.192.0/18"
  availability_zone = "${var.aws_region}c"

  tags = {
    Name                                        = "${var.cluster_name}-private-subnet-2"
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
    "kubernetes.io/role/internal-elb"           = "1"
  }
}

# NAT Gateway用のElastic IP
resource "aws_eip" "nat_eip_1" {
  domain = "vpc"

  tags = {
    Name = "${var.cluster_name}-nat-eip-1"
  }
}

resource "aws_eip" "nat_eip_2" {
  domain = "vpc"

  tags = {
    Name = "${var.cluster_name}-nat-eip-2"
  }
}

# NAT Gateway
resource "aws_nat_gateway" "nat_gateway_1" {
  allocation_id = aws_eip.nat_eip_1.id
  subnet_id     = aws_subnet.public_subnet_1.id

  tags = {
    Name = "${var.cluster_name}-nat-gateway-1"
  }

  depends_on = [aws_internet_gateway.eks_igw]
}

resource "aws_nat_gateway" "nat_gateway_2" {
  allocation_id = aws_eip.nat_eip_2.id
  subnet_id     = aws_subnet.public_subnet_2.id

  tags = {
    Name = "${var.cluster_name}-nat-gateway-2"
  }

  depends_on = [aws_internet_gateway.eks_igw]
}

# パブリックルートテーブル
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.eks_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.eks_igw.id
  }

  tags = {
    Name = "${var.cluster_name}-public-route-table"
  }
}

# パブリックサブネットへのルートテーブル関連付け
resource "aws_route_table_association" "public_subnet_1_association" {
  subnet_id      = aws_subnet.public_subnet_1.id
  route_table_id = aws_route_table.public_route_table.id
}

resource "aws_route_table_association" "public_subnet_2_association" {
  subnet_id      = aws_subnet.public_subnet_2.id
  route_table_id = aws_route_table.public_route_table.id
}

# プライベートルートテーブル
resource "aws_route_table" "private_route_table_1" {
  vpc_id = aws_vpc.eks_vpc.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gateway_1.id
  }

  tags = {
    Name = "${var.cluster_name}-private-route-table-1"
  }
}

resource "aws_route_table" "private_route_table_2" {
  vpc_id = aws_vpc.eks_vpc.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gateway_2.id
  }

  tags = {
    Name = "${var.cluster_name}-private-route-table-2"
  }
}

# プライベートサブネットへのルートテーブル関連付け
resource "aws_route_table_association" "private_subnet_1_association" {
  subnet_id      = aws_subnet.private_subnet_1.id
  route_table_id = aws_route_table.private_route_table_1.id
}

resource "aws_route_table_association" "private_subnet_2_association" {
  subnet_id      = aws_subnet.private_subnet_2.id
  route_table_id = aws_route_table.private_route_table_2.id
}

ステップ5: IAMロールの作成

iam.tfファイルを作成し、EKSクラスターとノードグループ用のIAMロールを定義します。

iam.tf
# EKSクラスター用IAMロール
resource "aws_iam_role" "eks_cluster_role" {
  name = "${var.cluster_name}-cluster-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "eks.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  role       = aws_iam_role.eks_cluster_role.name
}

# EKSノードグループ用IAMロール
resource "aws_iam_role" "eks_node_role" {
  name = "${var.cluster_name}-node-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
  role       = aws_iam_role.eks_node_role.name
}

resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
  role       = aws_iam_role.eks_node_role.name
}

resource "aws_iam_role_policy_attachment" "eks_container_registry_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
  role       = aws_iam_role.eks_node_role.name
}

ステップ6: ECRリポジトリの作成

ecr.tfファイルを作成し、コンテナイメージを保存するECRリポジトリを定義します。

ecr.tf
resource "aws_ecr_repository" "app_repository" {
  name                 = var.ecr_repository_name
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  tags = {
    Name = var.ecr_repository_name
  }
}

# ライフサイクルポリシー(古いイメージを自動削除)
resource "aws_ecr_lifecycle_policy" "app_repository_policy" {
  repository = aws_ecr_repository.app_repository.name

  policy = jsonencode({
    rules = [
      {
        rulePriority = 1
        description  = "Keep last 10 images"
        selection = {
          tagStatus   = "any"
          countType   = "imageCountMoreThan"
          countNumber = 10
        }
        action = {
          type = "expire"
        }
      }
    ]
  })
}

ステップ7: EKSクラスターの作成

eks.tfファイルを作成し、EKSクラスターとノードグループを定義します。

eks.tf
# EKSクラスター
resource "aws_eks_cluster" "main" {
  name     = var.cluster_name
  role_arn = aws_iam_role.eks_cluster_role.arn
  version  = "1.33"

  vpc_config {
    subnet_ids = [
      aws_subnet.public_subnet_1.id,
      aws_subnet.public_subnet_2.id,
      aws_subnet.private_subnet_1.id,
      aws_subnet.private_subnet_2.id
    ]
    endpoint_private_access = true
    endpoint_public_access  = true
  }

  depends_on = [
    aws_iam_role_policy_attachment.eks_cluster_policy
  ]
}

# EKSノードグループ
resource "aws_eks_node_group" "main" {
  cluster_name    = aws_eks_cluster.main.name
  node_group_name = "${var.cluster_name}-node-group"
  node_role_arn   = aws_iam_role.eks_node_role.arn
  subnet_ids = [
    aws_subnet.private_subnet_1.id,
    aws_subnet.private_subnet_2.id
  ]

  scaling_config {
    desired_size = var.node_desired_size
    min_size     = var.node_min_size
    max_size     = var.node_max_size
  }

  instance_types = [var.node_instance_type]
  disk_size      = 20

  depends_on = [
    aws_iam_role_policy_attachment.eks_worker_node_policy,
    aws_iam_role_policy_attachment.eks_cni_policy,
    aws_iam_role_policy_attachment.eks_container_registry_policy
  ]
}

ステップ8: 出力値の定義

outputs.tfファイルを作成し、作成したリソースの情報を出力します。

outputs.tf
output "cluster_name" {
  description = "EKS cluster name"
  value       = aws_eks_cluster.main.name
}

output "cluster_endpoint" {
  description = "EKS cluster endpoint"
  value       = aws_eks_cluster.main.endpoint
}

output "cluster_security_group_id" {
  description = "Security group ID attached to the EKS cluster"
  value       = aws_eks_cluster.main.vpc_config[0].cluster_security_group_id
}

output "ecr_repository_url" {
  description = "ECR repository URL"
  value       = aws_ecr_repository.app_repository.repository_url
}

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.eks_vpc.id
}

ステップ9: Terraformの初期化

作成したファイルをもとに、Terraformを初期化します。

$ terraform init

このコマンドで、AWSプロバイダーのプラグインがダウンロードされます。

ステップ10: 実行計画の確認

実際にリソースを作成する前に、何が作成されるかを確認します。

$ terraform plan

このコマンドで、作成されるリソースの一覧が表示されます。以下のような出力が表示されるはずです。

Plan: 27 to add, 0 to change, 0 to destroy.

約27個のリソースが作成されることが確認できます。

terraform planの結果1

terraform planの結果2

ステップ11: リソースの作成

実行計画に問題がなければ、実際にリソースを作成します。

$ terraform apply

確認メッセージが表示されるので、yesと入力して実行します。

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

リソースの作成には10〜15分程度かかります。完了すると、以下のような出力が表示されます。

Apply complete! Resources: 27 added, 0 changed, 0 destroyed.

Outputs:

cluster_endpoint = "https://XXXXX.gr7.ap-northeast-1.eks.amazonaws.com"
cluster_name = "my-cluster"
cluster_security_group_id = "sg-XXXXX"
ecr_repository_url = "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-app"
vpc_id = "vpc-XXXXX"

terraform applyの結果1

terraform applyの結果2

terraform applyの結果3

ステップ12: kubectlの設定

作成したEKSクラスターにアクセスできるよう、kubectlを設定します。

$ aws eks update-kubeconfig --region ap-northeast-1 --name my-cluster

接続を確認します。

$ kubectl get nodes

以下のような出力が表示されれば成功です。

NAME                                              STATUS   ROLES    AGE   VERSION
ip-192-168-xxx-xxx.ap-northeast-1.compute.internal   Ready    <none>   2m    v1.33.x
ip-192-168-xxx-xxx.ap-northeast-1.compute.internal   Ready    <none>   2m    v1.33.x

ステップ13: 状態の確認

Terraformが管理しているリソースの一覧を確認できます。

$ terraform state list

以下のようなリソース一覧が表示されます。

aws_ecr_lifecycle_policy.app_repository_policy
aws_ecr_repository.app_repository
aws_eip.nat_eip_1
aws_eip.nat_eip_2
aws_eks_cluster.main
...

特定のリソースの詳細を確認する場合は以下のコマンドを使います。

$ terraform state show aws_eks_cluster.main

後片付けと注意点

リソースの削除

Terraformで作成したリソースを削除する場合は、以下のコマンドを実行します。

$ terraform destroy

重要: この操作は取り消せません。本番環境では十分に注意してください。

また、Kubernetesで作成したLoadBalancer(ELB)などは、Terraformの管理外のため自動削除されません。事前に手動で削除するか、またはterraform destroy実行後に残っているリソースを手動で削除してください。

terraform destroyの結果1

terraform destroyの結果2

terraform destroyの結果3

状態ファイルのバックアップ

terraform.tfstateファイルは非常に重要です。このファイルを失うと、Terraformがリソースを管理できなくなります。

ローカルで作業する場合の対策
  1. .gitignoreterraform.tfstateを追加(機密情報が含まれるため)
  2. 定期的に別の場所にバックアップを取る
# .gitignore
.terraform/
terraform.tfstate
terraform.tfstate.backup
*.tfvars

本番環境での対策
S3バックエンドを使用し、バージョニングを有効化します。

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "eks-cluster/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-lock"
  }
}

コストの管理

クラウド上に作成したリソースは、使用していなくても課金されます。特に以下のリソースに注意してください。

  • EKSクラスター: $0.10/時間
  • EC2インスタンス(ノードグループ): インスタンスタイプに応じた料金
  • NAT Gateway: $0.062/時間 + データ転送料
  • Elastic IP(未使用時): $0.005/時間

検証が終わったら、必ずterraform destroyでリソースを削除してください。

エラーが発生した場合

  1. 権限エラー
    IAMユーザーに必要な権限が不足している可能性があります。以下のポリシーがアタッチされているか確認してください。
    • AmazonEC2FullAccess
    • AmazonEKSClusterPolicy
    • IAMFullAccess
    • AmazonVPCFullAccess
  2. リソースの依存関係エラー
    リソース間の依存関係が正しく解決されていない場合、depends_onを明示的に指定します。
    resource "aws_eks_cluster" "main" {
      # ...
    
      depends_on = [
        aws_iam_role_policy_attachment.eks_cluster_policy
      ]
    }
  3. タイムアウトエラー
    EKSクラスターの作成には時間がかかるため、タイムアウトが発生することがあります。その場合は再度terraform applyを実行してください。Terraformは差分を検出し、作成が完了していないリソースのみを処理します。

おわりに

本記事では、第13回と第14回で手動構築したAWSリソースを、Terraformのコードで再現する方法を学びました。IaCを導入することで、以下のメリットが得られます。

  • 再現性: 同じ環境をいつでも再構築できる
  • バージョン管理: インフラの変更履歴を追跡できる
  • レビュー: チームで変更内容を確認してからデプロイできる
  • 自動化: CI/CDパイプラインと統合し、デプロイを自動化できる
  • ドキュメント: コードそのものがインフラの設計書になる

最初は手動での構築に比べて手間がかかるように感じるかもしれませんが、環境が増えたり、変更が頻繁に発生したりする場合、IaCの効果は絶大です。

次回は、Terraformをチーム開発で使用する際のベストプラクティスについて解説します。リモートバックエンドの設定、モジュール化、CI/CDとの統合など、より実践的な内容に踏み込んでいきます。

今回作成したTerraformコードはそのまま本番環境でも使用できる品質ですが、環境に応じてカスタマイズが必要です。次回の記事を参考に、チーム開発に適した構成へと発展させていきましょう。

日本仮想化技術株式会社
ソーシャルゲーム業界で10年間インフラエンジニアとして活動し、現在は日本仮想化技術でOpsエンジニアを担当。DevOps支援サービス「かんたんDevOps」では仕組み作りや導入支援、技術調査などを行っている。

連載バックナンバー

システム開発技術解説
第15回

Iacツールの「Terraform」でAWSインフラをコード化して再現可能な環境を構築してみよう

2025/11/4
第15回の今回は、第13回と第14回で手動構築したAWSリソースをIacツールの「Terraform」で再現し、インフラをコードで管理する方法について解説します。
システム開発技術解説
第14回

「AWS EKS」でKubernetesクラスターを構築してコンテナアプリケーションをデプロイしてみよう

2025/10/14
第14回の今回は、「AWS EKS」でKubernetesクラスターを構築し、前回でECRにプッシュしたコンテナイメージをデプロイする方法について解説します。
システム開発技術解説
第13回

「GitHub Actions」と「AWS ECR」を連携してセキュアなCI/CDパイプラインを構築してみよう

2025/9/24
第13回の今回は、「GitHub Actions」と「AWS ECR」を連携して、セキュアで実用的なCI/CDパイプラインを構築する方法について解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています