はじめに
前回の記事では、「SSHを前提としない踏み台EC2」をどう設計するかにフォーカスしました。
本記事ではその続きとして、設計で分離した責務が、Terraform上でどのファイルに現れるのかを確認します。
Terraformの構文解説が目的ではありません。
- なぜ provider.tf が分離されているのか
- なぜ main.tf にすべてを書かないのか
- outputs.tf は誰のための情報なのか
こうした「設計とコードの対応関係」を読み解いていきます。
今回のファイル構成
terraform-aws-sample
├── provider.tf
├── main.tf
├── variables.tf
└── outputs.tf
この構成は、Terraformのベストプラクティスというより、 設計上の責務を意図的に分離した結果です。
以下では、それぞれのファイルが「何を責任として持っているか」に注目して見ていきます。
provider.tf:設計の前提条件を固定する
provider.tf は、 「この設計がどの世界で成立するか」 を定義するファイルです。
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
provider.tf の設計上の役割
- AWSを操作対象とすること
- TerraformとProviderのバージョン前提
- どのリージョンで成立する設計か
これらはリソース設計以前の前提条件です。
もしリージョンやプロバイダが変われば、 同じEC2・同じIAM Roleでも結果は変わります。
そのため provider.tf は、 設計の土台をコードとして固定する責務を持っています。
variables.tf:設計上「変えていいもの」を明示する
variables.tf は、 Terraformコード内で使用する入力変数を定義するファイルです。
# リージョン指定
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-northeast-1"
}
# EC2のインスタンスタイプを指定
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
# EC2のNameタグに使う値を指定
variable "instance_name" {
description = "EC2 Name tag"
type = string
default = "ssm-bastion"
}
variables.tf の設計上の役割
重要なのは、すべてを変数にしていない点です。
- インスタンスタイプ → 変えていい
- Nameタグ → 変えていい
- 接続方式(SSM) → 変えていない
variables.tf は、 設計上「調整可能な部分」だけを表に出す役割を持っています。
逆に言えば、変数になっていない要素は、 この構成において「触るべきでない前提条件」です。
main.tf:設計を成立させる最小構成を定義する
main.tf には、この構成が成立するために最低限必要なリソースのみを定義しています。
# 既存の default VPC を取得するための data ブロック
data "aws_vpc" "default" {
default = true
}
# IAM Role(SSM用)作成
resource "aws_iam_role" "ssm_role" {
name = "ssm-bastion-role"
# EC2 がこの Role を引き受けられるようにする設定
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
}
# SSM用ポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ssm_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# Instance Profile作成
resource "aws_iam_instance_profile" "ssm" {
name = "ssm-bastion-profile"
role = aws_iam_role.ssm_role.name
}
# 踏み台EC2専用の Security Group作成
resource "aws_security_group" "bastion_sg" {
name = "bastion-ssm-sg"
description = "Security group for SSM bastion"
vpc_id = data.aws_vpc.default.id
# egress(アウトバウンドのみ許可)
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "bastion-ssm-sg"
}
}
# AMI(Amazon Linux 2023)を取得
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
# EC2 Instance 作成
resource "aws_instance" "bastion" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
# IAM Role を付与
iam_instance_profile = aws_iam_instance_profile.ssm.name
# Security Group の指定
vpc_security_group_ids = [
aws_security_group.bastion_sg.id
]
# Nameタグ設定
tags = {
Name = var.instance_name
}
}
main.tf の設計上の役割
このファイルにあるリソースは、
- 1つでも欠けると SSM 接続が成立しない
- これ以上増やすと「踏み台」の責務を超える
という意味で、設計上の最小構成です。
Security Group にインバウンドルールが存在しない点も、 意図的な設計判断です。
接続可否は IAM に委ね、 ネットワークは「到達経路を作らない」ことで制御しています。
outputs.tf:Terraformの外との境界を定義する
outputs.tf は、Terraformで作成した情報を Terraformの外へ安全に渡すためのファイルです。
# EC2 Instance ID を出力
output "instance_id" {
description = "EC2 Instance ID"
value = aws_instance.bastion.id
}
# EC2 Name タグを出力
output "instance_name" {
description = "EC2 Name tag"
value = aws_instance.bastion.tags["Name"]
}
# AWSリージョンを出力
output "aws_region" {
description = "AWS Region"
value = var.aws_region
}
outputs.tf の設計上の役割
outputs.tf は、
- AWS CLI
- スクリプト
- 運用者
といったTerraformの外にいる存在との境界です。
特に instance_id は、SSM接続時に必須となります。
aws ssm start-session --target <instance-id>
Terraformの state に閉じたままにせず、 必要な情報だけを明示的に外へ出すことが重要です。
まとめ
本記事では、SSM踏み台EC2の構成を通して、 設計上の責務が Terraform のファイル分割にどう現れるかを見てきました。
- provider.tf:設計の前提条件を固定
- variables.tf:変えていい設計要素の明示
- main.tf:成立に必要な最小構成
- outputs.tf:Terraform外との境界
Terraformを単なる作成ツールとしてではなく、 設計をそのまま状態として管理するツールとして使う感覚を掴めれば、 IaCの理解は一段深まります。