03. EC2 とコンピュート
AWS の代表サービス EC2(Elastic Compute Cloud)。仮想マシンを 1 台立てるだけのシンプル例から、AMI を最新で取得する/起動スクリプトを流す/IAM ロールを付ける、までを順に。
この章の目次
最小例
resource "aws_instance" "web" {
ami = "ami-0c4a35bf6c1f8c39d" # AMI ID(リージョン固有)
instance_type = "t3.micro"
tags = {
Name = "web"
}
}
これだけで EC2 1 台が起動します。ただしデフォルト VPC の中、デフォルト SG という不便な構成。実用には次のように VPC や SG を指定します。
resource "aws_instance" "web" {
ami = data.aws_ami.al2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public["a"].id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.deployer.key_name
iam_instance_profile = aws_iam_instance_profile.ec2.name
tags = { Name = "web" }
}
AMI を最新で取得(data ソース)
AMI ID をコードに固定で書くと、月が変わると古い AMI のまま起動し続けて、セキュリティパッチが当たりません。data "aws_ami" で「最新の Amazon Linux 2023」を毎回引いてくるのが定石。
data "aws_ami" "al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
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 度だけ流すシェルスクリプト。
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_replace_on_change = true # user_data を変えたら EC2 を作り直す
}
テンプレ化するなら templatefile():
# 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
})
}
IAM ロールを付ける
EC2 から AWS API を叩くには IAM ロール を「インスタンスプロファイル」経由で attach します。詳細は 05 章。最小はこれ:
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"
}]
})
}
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "ec2" {
name = "ec2-web"
role = aws_iam_role.ec2.name
}
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)
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
throughput = 125
encrypted = true
delete_on_termination = true
}
ebs_block_device {
device_name = "/dev/sdf"
volume_size = 100
volume_type = "gp3"
encrypted = true
}
}
複数台を一括(for_each)
locals {
servers = {
api = { type = "t3.small", subnet = "a" }
worker = { type = "t3.micro", subnet = "c" }
cron = { type = "t3.nano", subnet = "a" }
}
}
resource "aws_instance" "fleet" {
for_each = local.servers
ami = data.aws_ami.al2023.id
instance_type = each.value.type
subnet_id = aws_subnet.private[each.value.subnet].id
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile = aws_iam_instance_profile.ec2.name
tags = { Name = each.key }
}
# 参照
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 年のセオリー。