1. この記事について(6回シリーズ)
この記事は【AWS Fargate】実務的な設計フロー学習シリーズの第4弾です。
| 記事 | タイトル | 内容 |
|---|---|---|
| 第1弾 | 設計思考編 | なぜ設計が先なのか |
| 第2弾 | 図解編 | 構成図・フロー図・シーケンス図 |
| 第3弾 | パラメータ設計編 | Terraformパラメータ設計 |
| 第4弾 | Terraform実装編 | コード作成からplan成功まで |
| 第5-1弾 | 実装編 | apply〜アクセス確認 |
| 第5-2弾 | 運用・削除編 | トラブルシューティング〜destroy |
📁 完全なコードはGitHubで公開:GitHub: fargate-iac01
2. はじめに
2-1. この記事の位置づけ
第1弾で学んだ8ステップのうち、この記事では**STEP 8(実装の前半)**を扱います。
| STEP | 工程 | 記事 | 状態 |
|---|---|---|---|
| 1-7 | グランドデザイン〜IaC設計 | 第1弾〜第3弾 | ✅ 完了 |
| 8(前半) | 実装(コード作成〜plan) | 第4弾(この記事) | 📝 今ここ |
| 8(中盤) | 実装(apply〜アクセス確認) | 第5-1弾 | 📋 次回 |
| 8(後半) | トラブルシューティング〜destroy | 第5-2弾 | 📋 次回以降 |
今回のゴール:
- Terraformコードを作成する
- terraform planで実行計画を確認する
- エラーなく plan が成功する状態にする
2-2. 前回までのおさらい
第1弾〜第3弾で、設計を完了しました。
| 記事 | 完了した内容 | 成果物 |
|---|---|---|
| 第1弾 | グランドデザイン | 要件表 |
| 第2弾 | 可視化 | 4種類の図 |
| 第3弾 | パラメータ・IaC設計 | パラメータ表、ファイル構成 |
ここまでで決まったこと:
- 何を作るか(Apache on Fargate)
- どう繋がるか(VPC → Subnet → ECS → ECR)
- 具体的な値(CIDR: 10.0.0.0/16、CPU: 256等)
- Terraformのファイル構成(10ファイル)
次は、実際にTerraformコードを書いていきます。
2-3. この記事で得られるもの
| 得られること | 説明 |
|---|---|
| ✅ ファイル構成 | 10ファイルの役割と命名規則 |
| ✅ 主要コード | main.tf、variables.tf、vpc.tf、ecs.tf のポイント |
| ✅ 実行手順 | terraform init/fmt/validate/plan |
| ✅ planの読み方 | どこを確認すればいいか |
3. ファイル構成の確認
3-1. 作業ディレクトリの確認
第3弾で作成した作業ディレクトリを使用します。
# 作業ディレクトリに移動
$ cd ~/fargate-terraform
# 現在のディレクトリを確認
$ pwd
/home/username/fargate-terraform
# ディレクトリが空であることを確認
$ ls -la
total 8
drwxr-xr-x 2 username username 4096 Dec 18 10:00 .
drwxr-xr-x 25 username username 4096 Dec 18 09:55 ..
重要: 以降、特に指示がない限り、全てのコマンドは ~/fargate-terraform/ で実行します。
3-2. 10ファイルの役割
これから以下のファイルを作成します。
~/fargate-terraform/ # ← 作業ディレクトリ
├── main.tf # プロバイダー設定、共通タグ(21行)
├── variables.tf # 変数定義(90行)
├── terraform.tfvars # 変数の値(24行)
├── vpc.tf # VPC、Subnet、IGW、Route Table(61行)
├── security.tf # Security Group(38行)
├── iam.tf # ECS Task Execution Role(34行)
├── ecr.tf # ECR Repository(16行)
├── ecs.tf # Cluster、Task Definition、Service(86行)
├── outputs.tf # 出力値(63行)
└── .gitignore # 除外ファイル設定(30行)
合計: 463行
3-3. ファイル別の責務
| ファイル | 責務 | 料理の例え |
|---|---|---|
| main.tf | AWS接続設定、共通タグ | 調理器具のセットアップ |
| variables.tf | 変数定義、validation | 材料リスト(分量の範囲) |
| terraform.tfvars | 変数の実際の値 | 実際の分量 |
| vpc.tf | ネットワーク基盤 | レストランの建物 |
| security.tf | ファイアウォール | 防犯設備 |
| iam.tf | 権限設定 | スタッフの権限 |
| ecr.tf | イメージ保管 | レシピ本の本棚 |
| ecs.tf | コンテナ実行 | 調理器具と調理ルール |
| outputs.tf | 結果の出力 | 完成品の情報 |
3-4. 命名規則のポイント
| ポイント | 説明 | 例 |
|---|---|---|
| ✅ リソース種別で分割 | VPC関連はvpc.tf、ECS関連はecs.tf | vpc.tf、ecs.tf |
| ✅ 数字をつけない | 01_vpc.tf より vpc.tf | vpc.tf(⭕)、01_vpc.tf(❌) |
| ✅ 複数形にしない | securities.tf より security.tf | security.tf(⭕) |
| ✅ 動詞を使わない | create_vpc.tf より vpc.tf | vpc.tf(⭕) |
4. 主要コードの実装
実際のTerraformコードを作成します。
全コードは長いので、重要部分を抜粋します。
4-1. main.tf - プロバイダー設定
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
# 全リソースに自動適用される共通タグ
default_tags {
tags = {
Project = var.project_name
Environment = "learning"
ManagedBy = "terraform"
}
}
}
ポイント:
-
required_versionでTerraformバージョンを指定 -
default_tagsで全リソースに共通タグを自動付与 - 各リソースでは
Nameタグだけ設定すればOK
4-2. variables.tf - 変数定義(抜粋)
# 共通設定
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "ap-northeast-1"
}
variable "project_name" {
description = "Project name for resource naming and tagging"
type = string
default = "fargate-terraform"
}
# ネットワーク設定
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "VPC CIDR must be a valid IPv4 CIDR block."
}
}
# ECS設定
variable "cpu" {
description = "CPU units for Fargate task (256 = 0.25 vCPU)"
type = number
default = 256
validation {
condition = contains([256, 512, 1024, 2048, 4096], var.cpu)
error_message = "CPU must be 256, 512, 1024, 2048, or 4096."
}
}
variable "memory" {
description = "Memory (MB) for Fargate task"
type = number
default = 512
validation {
condition = var.memory >= 512 && var.memory <= 30720
error_message = "Memory must be between 512 and 30720 MB."
}
}
ポイント:
-
validationでFargateの制約をチェック -
descriptionで変数の意味を明記 -
typeで型を明示(string、number、bool)
4-3. vpc.tf - ネットワーク基盤
# ====================================
# VPC(Virtual Private Cloud)
# ====================================
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr # IPアドレス範囲(10.0.0.0/16)
enable_dns_hostnames = true # DNSホスト名を有効化
enable_dns_support = true # DNS解決を有効化
tags = {
Name = "${var.project_name}-vpc" # タグ名:fargate-terraform-vpc
}
}
# ====================================
# Internet Gateway(インターネットへの出入口)
# ====================================
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id # 上記VPCにアタッチ
tags = {
Name = "${var.project_name}-igw" # タグ名:fargate-terraform-igw
}
}
# ====================================
# Public Subnet(パブリックサブネット)
# ====================================
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # VPCに紐付け
cidr_block = var.subnet_cidr # IPアドレス範囲(10.0.1.0/24)
availability_zone = var.availability_zone # AZ(ap-northeast-1a)
map_public_ip_on_launch = true # パブリックIP自動割当を有効化
tags = {
Name = "${var.project_name}-public-subnet" # タグ名:fargate-terraform-public-subnet
}
}
# ====================================
# Route Table(ルートテーブル)
# ====================================
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id # VPCに紐付け
# インターネット向け通信のルーティング
route {
cidr_block = "0.0.0.0/0" # すべての宛先
gateway_id = aws_internet_gateway.main.id # IGW経由で外部へ
}
tags = {
Name = "${var.project_name}-public-rt" # タグ名:fargate-terraform-public-rt
}
}
# ====================================
# Route Table Association(サブネットとルートテーブルの関連付け)
# ====================================
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id # 上記Public Subnetに
route_table_id = aws_route_table.public.id # 上記Route Tableを関連付け
}
ポイント:
-
aws_vpc.main.idでリソース間を参照(Terraformが依存関係を自動解決) -
map_public_ip_on_launch = trueでパブリックIP自動割当(Fargateに必須) -
routeブロックで 0.0.0.0/0 → IGW のルーティング(インターネットアクセス用)
4-4. security.tf - Security Group
# ================================
# Security Group for Fargate Task
# ================================
# セキュリティグループ(仮想ファイアウォール)の作成
resource "aws_security_group" "fargate" {
name = "${var.project_name}-fargate-sg"
description = "Security group for Fargate task"
vpc_id = aws_vpc.main.id # VPCに所属
tags = {
Name = "${var.project_name}-fargate-sg"
}
}
# =================================================
# Inbound Rules(インバウンドルール = 外部からの接続)
# =================================================
# HTTPアクセス(ポート80)を許可
resource "aws_vpc_security_group_ingress_rule" "http" {
security_group_id = aws_security_group.fargate.id
description = "Allow HTTP from anywhere"
ip_protocol = "tcp" # TCPプロトコル
from_port = 80 # ポート80
to_port = 80 # ポート80
cidr_ipv4 = "0.0.0.0/0" # すべてのIPアドレスから許可
}
# =================================================
# Outbound Rules(アウトバウンドルール = 外部への接続)
# =================================================
# すべてのアウトバウンド通信を許可(ECRからイメージをpullするために必要)
resource "aws_vpc_security_group_egress_rule" "all" {
security_group_id = aws_security_group.fargate.id
description = "Allow all outbound (for ECR pull)"
ip_protocol = "-1" # すべてのプロトコル
cidr_ipv4 = "0.0.0.0/0" # すべてのIPアドレスへ許可
}
ポイント:
- AWS Provider 5.x以降の推奨方式: Security Groupとルールを別リソースで定義
-
aws_vpc_security_group_ingress_ruleでインバウンドルール(HTTP:80) -
aws_vpc_security_group_egress_ruleでアウトバウンドルール(全許可) -
ip_protocol = "-1"で全プロトコル許可
4-5. iam.tf - IAM Role
# ====================================
# ECS Task Execution Role
# ====================================
# Fargateタスクが起動時にECRからイメージをpullするために必要な権限
resource "aws_iam_role" "ecs_task_execution" {
name = "${var.project_name}-ecs-task-execution-role"
# このロールを引き受けられるサービスを定義(信頼ポリシー)
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole" # ロールの引き受けを許可
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com" # ECSサービスに許可
}
}
]
})
tags = {
Name = "${var.project_name}-ecs-task-execution-role"
}
}
# ====================================
# AWS管理ポリシーをアタッチ
# ====================================
# AmazonECSTaskExecutionRolePolicy(AWS提供の標準ポリシー)
# - ECRからイメージをpull
# - CloudWatch Logsへログ送信
resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
ポイント:
- Task Execution Role: Fargateタスクが起動時に使用するロール
- assume_role_policy: 「ECSサービスがこのロールを使える」という信頼関係
- AmazonECSTaskExecutionRolePolicy: ECR pullとCloudWatch Logs送信の権限を含む
4-6. ecr.tf - ECR Repository
resource "aws_ecr_repository" "main" {
name = var.repository_name
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = false
}
tags = {
Name = "${var.project_name}-ecr-repo"
}
}
ポイント:
-
image_tag_mutability = "MUTABLE"でlatestタグの上書き可能 -
scan_on_push = falseで学習用のためスキャン無効
4-7. ecs.tf - ECS Cluster、Task Definition、Service(抜粋)
# ====================================
# ECS Cluster(Fargateタスクを動かす箱)
# ====================================
resource "aws_ecs_cluster" "main" {
name = "${var.project_name}-cluster"
tags = {
Name = "${var.project_name}-cluster"
}
}
# ====================================
# CloudWatch Log Group(ログ保管場所)
# ====================================
resource "aws_cloudwatch_log_group" "ecs" {
name = "/ecs/${var.project_name}-task" # ロググループ名
retention_in_days = 7 # 7日間保持
tags = {
Name = "${var.project_name}-ecs-logs"
}
}
# ====================================
# ECS Task Definition(コンテナの設計書)
# ====================================
resource "aws_ecs_task_definition" "main" {
family = "${var.project_name}-task" # タスク定義名
requires_compatibilities = ["FARGATE"] # Fargate使用
network_mode = "awsvpc" # awsvpcモード(必須)
cpu = var.cpu # CPU(256)
memory = var.memory # メモリ(512)
execution_role_arn = aws_iam_role.ecs_task_execution.arn # 実行ロール
# コンテナの定義(JSON形式)
container_definitions = jsonencode([
{
name = "${var.project_name}-container" # コンテナ名
image = "${aws_ecr_repository.main.repository_url}:${var.image_tag}" # ECRのイメージURL
essential = true # 必須コンテナ
# ポートマッピング(コンテナの80番ポートを公開)
portMappings = [
{
containerPort = 80 # コンテナ内のポート
protocol = "tcp" # プロトコル
}
]
# ログ設定(CloudWatch Logsへ送信)
logConfiguration = {
logDriver = "awslogs" # awslogsドライバーを使用
options = {
"awslogs-group" = aws_cloudwatch_log_group.ecs.name # ロググループ
"awslogs-region" = var.aws_region # リージョン
"awslogs-stream-prefix" = "ecs" # ログストリームの接頭辞
}
}
}
])
tags = {
Name = "${var.project_name}-task"
}
}
# ====================================
# ECS Service(タスクを常に稼働させる仕組み)
# ====================================
resource "aws_ecs_service" "main" {
name = "${var.project_name}-service" # Service名
cluster = aws_ecs_cluster.main.id # 上記Clusterで動かす
task_definition = aws_ecs_task_definition.main.arn # 上記Task Definitionを使用
desired_count = var.desired_count # 常に1つ稼働
launch_type = "FARGATE" # Fargateで起動
# ネットワーク設定
network_configuration {
subnets = [aws_subnet.public.id] # Public Subnetに配置
security_groups = [aws_security_group.fargate.id] # Security Groupを適用
assign_public_ip = true # Public IPを自動割当
}
tags = {
Name = "${var.project_name}-service"
}
}
ポイント:
-
network_mode = "awsvpc"はFargate必須 -
container_definitionsはJSON形式(jsonencode使用) -
assign_public_ip = trueでインターネットアクセス可能 - ECRのURLは
aws_ecr_repository.main.repository_urlで参照
4-8. outputs.tf - 出力値
# ====================================
# Outputs(出力値)
# ====================================
# ECR Repository URL
output "ecr_repository_url" {
description = "ECR repository URL for Docker push"
value = aws_ecr_repository.main.repository_url
}
# ECS Cluster名
output "ecs_cluster_name" {
description = "ECS cluster name"
value = aws_ecs_cluster.main.name
}
# ECS Service名
output "ecs_service_name" {
description = "ECS service name"
value = aws_ecs_service.main.name
}
ポイント:
-
terraform apply後に自動的に表示される値 - 第5弾でECRへのpush時に使用
-
terraform outputコマンドで再表示可能
出力例:
Apply complete! Resources: 15 added, 0 changed, 0 destroyed.
Outputs:
ecr_repository_url = "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/apache-repo"
ecs_cluster_name = "fargate-terraform-cluster"
ecs_service_name = "fargate-terraform-service"
4-9. .gitignore
# Terraform
.terraform/
*.tfstate
*.tfstate.*
crash.log
*.tfvars
.terraform.lock.hcl
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
ポイント:
-
*.tfvarsをignore(機密情報の可能性) -
.terraform/をignore(プラグインキャッシュ) -
*.tfstateをignore(状態ファイルは別管理)
5. terraform init/fmt/validate
5-1. 初期化(terraform init)
fargate-terraform $ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0
Terraform has been successfully initialized!
何をしているか:
- プロバイダープラグインのダウンロード
-
.terraform/ディレクトリの作成 -
.terraform.lock.hclの作成
5-2. フォーマット(terraform fmt)
fargate-terraform $ terraform fmt
main.tf
vpc.tf
何をしているか:
- コードの自動整形
- インデントの統一
- 修正されたファイルが表示される
5-3. 検証(terraform validate)
fargate-terraform $ terraform validate
Success! The configuration is valid.
何をしているか:
- 構文エラーのチェック
- リソース参照の整合性チェック
- validationルールの実行
エラー例:
fargate-terraform $ terraform validate
╷
│ Error: Invalid value for variable
│
│ on variables.tf line 45:
│ 45: variable "cpu" {
│
│ CPU must be 256, 512, 1024, 2048, or 4096.
5-4. プラン確認(terraform plan)
fargate-terraform $ terraform plan
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ arn = (known after apply)
+ cidr_block = "10.0.0.0/16"
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ tags = {
+ "Name" = "fargate-terraform-vpc"
}
+ tags_all = {
+ "Environment" = "learning"
+ "ManagedBy" = "terraform"
+ "Name" = "fargate-terraform-vpc"
+ "Project" = "fargate-terraform"
}
}
# aws_subnet.public will be created
+ resource "aws_subnet" "public" {
...
}
# ... (他のリソースも表示)
Plan: 12 to add, 0 to change, 0 to destroy.
planの読み方:
| 記号 | 意味 |
|---|---|
+ |
作成される |
- |
削除される |
~ |
変更される |
-/+ |
再作成される(削除→作成) |
確認ポイント:
-
Plan: 15 to add→ 15個のリソースが作成される -
tags_allにdefault_tagsが自動追加されている -
(known after apply)は実行後に決まる値(IDなど)
6. terraform plan
6-1. VPCの確認
+ resource "aws_vpc" "main" {
+ cidr_block = "10.0.0.0/16"
+ enable_dns_support = true
+ tags = {
+ "Name" = "fargate-terraform-vpc"
}
}
✅ CIDR、DNS設定が正しいか確認
6-2. Security Groupの確認
+ resource "aws_security_group" "fargate" {
+ name = "fargate-terraform-fargate-sg"
+ vpc_id = (known after apply)
}
+ resource "aws_vpc_security_group_ingress_rule" "http" {
+ cidr_ipv4 = "0.0.0.0/0"
+ from_port = 80
+ to_port = 80
+ ip_protocol = "tcp"
}
+ resource "aws_vpc_security_group_egress_rule" "all" {
+ cidr_ipv4 = "0.0.0.0/0"
+ ip_protocol = "-1"
}
✅ Security Groupとルール(ingress/egress)が別リソースで作成されることを確認
6-3. ECS Task Definitionの確認
+ resource "aws_ecs_task_definition" "main" {
+ cpu = "256"
+ memory = "512"
+ network_mode = "awsvpc"
+ container_definitions = jsonencode(...)
}
✅ CPU/メモリ、awsvpcモードを確認
7. planの読み方とトラブルシューティング
7-1. よくあるエラー
| エラー | 原因 | 対処法 |
|---|---|---|
Error: Invalid CIDR |
CIDR形式が不正 | variables.tfのvalidationを確認 |
Error: Invalid CPU value |
CPUが256/512/1024/2048/4096以外 | terraform.tfvarsを修正 |
Error: Reference to undeclared resource |
リソース参照ミス | リソース名を確認 |
Error: Provider configuration not present |
terraform init未実行 | terraform init を実行 |
7-2. plan実行時の注意点
| 注意点 | 説明 |
|---|---|
| ⚠️ AWSクレデンシャル | AWS CLIで aws configure 済みか確認 |
| ⚠️ リージョン | terraform.tfvarsのリージョンが正しいか |
| ⚠️ 既存リソース | 同名のVPCやECRリポジトリがないか |
| ⚠️ コスト | plan実行は無料だが、apply後は課金される |
8. コードの全文について
この記事では重要部分を抜粋しました。
全コードは以下で公開予定:
- GitHub:
https://github.com/your-repo/fargate-terraform(※実際に公開する場合) - 全10ファイル、463行
リポジトリ構成:
fargate-terraform/
├── README.md # 使い方
├── main.tf
├── variables.tf
├── terraform.tfvars.example # サンプル(実際の値は各自設定)
├── vpc.tf
├── security.tf
├── iam.tf
├── ecr.tf
├── ecs.tf
├── outputs.tf
└── .gitignore
9. まとめ
9-1. この記事で作成したもの
| 成果物 | 内容 |
|---|---|
| 10個のTerraformファイル | 合計463行 |
| terraform plan成功 | 12個のリソースが作成予定 |
9-2. 8ステップの進捗
この記事で、8ステップのうち**STEP 8(前半)**が完了しました!
| STEP | 工程 | 記事 | 状態 |
|---|---|---|---|
| 1-7 | グランドデザイン〜IaC設計 | 第1弾〜第3弾 | ✅ 完了 |
| 8(前半) | 実装(コード作成〜plan) | 第4弾 | ✅ 完了 |
| 8(中盤) | 実装(apply〜アクセス確認) | 第5-1弾 | 📋 次回 |
| 8(後半) | トラブルシューティング〜destroy | 第5-2弾 | 📋 次回以降 |
達成したこと:
- ✅ 10ファイルのTerraformコードを作成
- ✅ terraform initでプロバイダーを初期化
- ✅ terraform planで実行計画を確認
- ✅ エラーなくplan成功
次は、terraform applyでリソースを実際に作成します!
9-3. 実行したコマンド
| コマンド | 目的 |
|---|---|
terraform init |
プロバイダープラグインのダウンロード |
terraform fmt |
コードの自動整形 |
terraform validate |
構文チェック |
terraform plan |
実行計画の確認 |
9-4. planからわかったこと
| 項目 | 内容 |
|---|---|
| 作成されるリソース | 12個(VPC、Subnet、IGW、RT、SG、ECR、ECS、IAM、CloudWatch) |
| タグの自動付与 | default_tagsが全リソースに適用される |
| 変数の展開 | terraform.tfvarsの値が正しく反映される |
9-5. 料理で振り返ると
| 工程 | Terraformの対応 |
|---|---|
| レシピを見る | 設計(構成図、パラメータ表) |
| 材料を確認 | terraform plan(実行計画) |
| 実際に調理 | terraform apply(次回) |
plan(実行計画)が成功したので、次はいよいよapply(実行)です!
10. 次の記事
次回は「実践・運用編」です。
terraform apply でリソースを作成し、実際にアクセスしてみます。
10-1. 次回の内容
- terraform apply の実行
- ECRへDockerイメージのpush
- タスクのパブリックIPを取得してアクセス
- トラブルシューティング(タスクが起動しない、アクセスできない)
- terraform destroy でリソース削除
パラメータ表とコードが揃ったので、実際にリソースを作成しましょう!
11. 参考リンク
(次回へ続く)