03. EC2 とコンピュート
AWS の代表サービス EC2(Elastic Compute Cloud)。仮想マシンを 1 台立てるだけのシンプル例から、AMI を最新で取得する/起動スクリプトを流す/IAM ロールを付ける、までを順に。
この章の目次
最小例
# EC2 を 1 台立ち上げる最小コード(VPC/SG 未指定 → デフォルト VPC が使われる)
# ami と instance_type の 2 つだけが事実上必須
resource "aws_instance" "web" {
ami = "ami-0c4a35bf6c1f8c39d" # AMI ID(リージョン固有・要置き換え)
instance_type = "t3.micro" # サイズ(vCPU/メモリの組合せ)
tags = {
Name = "web"
}
}
これだけで EC2 1 台が起動します。ただしデフォルト VPC の中、デフォルト SG という不便な構成。実用には次のように VPC や SG を指定します。
# 実用的な EC2 設定: VPC 内に配置し SG / 鍵 / IAM ロールを指定
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id # AMI を data で取得(次節)
instance_type = "t3.micro"
subnet_id = aws_subnet.public["a"].id # どの subnet に置くか
vpc_security_group_ids = [aws_security_group.web.id] # 適用する SG(list)
key_name = aws_key_pair.deployer.key_name # SSH 公開鍵
iam_instance_profile = aws_iam_instance_profile.ec2.name # この EC2 の AWS 権限
tags = { Name = "web" }
}
AMI を最新で取得(data ソース)
AMI ID をコードに固定で書くと、月が変わると古い AMI のまま起動し続けて、セキュリティパッチが当たりません。data "aws_ami" で「最新の Amazon Linux 2023」を毎回引いてくるのが定石。
# Amazon Linux 2023 の最新 AMI を毎回検索(ID ハードコード回避)
# 2 つの filter で「名前パターン × 仮想化タイプ」を絞り込み
data "aws_ami" "al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# data で取得した AMI ID をそのまま EC2 の ami に渡す
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
# ...
}
terraform plan 時の挙動
data ソースは plan のたびに最新を引いてくるので、AMI が更新されると差分が出ます。「AMI が更新されたから再作成しろ」と Terraform が言ってきます。これを避けたい時は
lifecycle.ignore_changes = [ami] を使う。
user_data で起動時スクリプト
EC2 起動時に 1 度だけ流すシェルスクリプト。
# user_data = EC2 起動時に 1 度だけ実行されるシェルスクリプト
# ヒアドキュメント (<<-EOT) で複数行を書ける
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
user_data = <<-EOT
#!/bin/bash
dnf update -y
dnf install -y nginx
systemctl enable --now nginx
echo "Hello from $(hostname)" > /usr/share/nginx/html/index.html
EOT
# user_data を変更したら EC2 を作り直す(変更を確実に反映)
user_data_replace_on_change = true
}
テンプレ化するなら templatefile():
# 別ファイルに user_data を切り出して、変数を差し込んでレンダリングする例
# templates/web-init.sh.tpl の中の ${name} が var.environment の値で置き換わる
# # templates/web-init.sh.tpl の中身:
# # #!/bin/bash
# # echo "Hello, ${name}!" > /tmp/hello.txt
resource "aws_instance" "web" {
user_data = templatefile("${path.module}/templates/web-init.sh.tpl", {
name = var.environment # ← テンプレ内の ${name} に展開される
})
}
IAM ロールを付ける
EC2 から AWS API を叩くには IAM ロール を「インスタンスプロファイル」経由で attach します。詳細は 05 章。最小はこれ:
# EC2 用 IAM ロール: assume_role_policy で「EC2 サービスがこのロールを assume できる」と宣言
# jsonencode() で HCL の map を JSON 文字列に変換
resource "aws_iam_role" "ec2" {
name = "ec2-web"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
# AWS 管理ポリシー(SSM 用)を上のロールにアタッチ
# SSM Session Manager で SSH なしで EC2 に入れるようになる
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# IAM Instance Profile = IAM ロールを EC2 にアタッチするためのラッパー(EC2 特有)
# EC2 から IAM ロールを「直接」ではなく Instance Profile 経由で参照する必要がある
resource "aws_iam_instance_profile" "ec2" {
name = "ec2-web"
role = aws_iam_role.ec2.name
}
# EC2 に Instance Profile を割り当て
resource "aws_instance" "web" {
# ...
iam_instance_profile = aws_iam_instance_profile.ec2.name
}
SSM 経由で SSH レス運用
上の
AmazonSSMManagedInstanceCore を付けると、SSH キーや 22 番ポート開放なしで aws ssm start-session --target i-xxx で入れるようになります。22 番を開けない がベストプラクティス。
ディスク設定(root_block_device)
# EC2 のディスク詳細設定
# root_block_device: ルートディスク(OS が入る)の設定
# ebs_block_device: 追加のデータディスク(必要に応じて 1 つ以上付ける)
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = "t3.medium"
root_block_device {
volume_size = 30 # GB
volume_type = "gp3" # 推奨(gp2 より安く速い)
iops = 3000 # gp3 は IOPS を独立に設定可能
throughput = 125 # MB/s
encrypted = true # 暗号化(本番では必須)
delete_on_termination = true # EC2 終了時にディスクも削除
}
# 追加データディスクをアタッチする例
ebs_block_device {
device_name = "/dev/sdf" # OS 内で /dev/nvme1n1 等として見える
volume_size = 100
volume_type = "gp3"
encrypted = true
}
}
複数台を一括(for_each)
# 複数台の EC2 を「役割が違う」設定で一括作成する例
# まず map で「キー = サーバ名、値 = 設定」を定義
locals {
servers = {
api = { type = "t3.small", subnet = "a" }
worker = { type = "t3.micro", subnet = "c" }
cron = { type = "t3.nano", subnet = "a" }
}
}
# for_each で map の各エントリを 1 つの EC2 に展開
# 結果: aws_instance.fleet["api"], ["worker"], ["cron"] の 3 リソース
resource "aws_instance" "fleet" {
for_each = local.servers
ami = data.aws_ami.al2023.id
instance_type = each.value.type # 各サーバの type を参照
subnet_id = aws_subnet.private[each.value.subnet].id # subnet も各設定から
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile = aws_iam_instance_profile.ec2.name
tags = { Name = each.key } # api / worker / cron
}
# for_each のリソースは map のキーでアクセス
output "api_private_ip" {
value = aws_instance.fleet["api"].private_ip
}
EC2 以外の選択肢
| 用途 | EC2 ではなく |
|---|---|
| Web アプリ/API | Lambda + API Gateway または ECS Fargate |
| 定常稼働のサービス | ECS Fargate(インスタンス管理が要らない) |
| Auto Scaling したいバッチ | aws_launch_template + aws_autoscaling_group |
| SSH せずに作業 | AWS Cloud9 / SSM Session Manager |
「サーバの OS を自由にいじりたい」「特殊なソフトウェアを動かす」場合だけ EC2、それ以外は Lambda か ECS Fargate を第一候補 にするのが 2026 年のセオリー。