はじめに
前回の記事「TerraformでFramePackをAWS EC2(g6.2xlarge)にデプロイする」では、g6.2xlarge のオンデマンドインスタンスで FramePack を動かす構成を紹介しました。
g6.2xlarge のオンデマンド料金は東京リージョンで 約 $1.0/時間。動画生成を数時間やると $3〜5 くらいかかります。
今回はこれをスポットインスタンスに切り替えて、同じ GPU を約 70% 安く使う方法を紹介します。Terraform の変更は数行だけです。
スポットインスタンスとは
AWS の未使用 EC2 キャパシティを入札形式で利用できる仕組みです。オンデマンドより大幅に安い代わりに、AWS 側のキャパシティ需要が高まると 2分前通知のうえ強制終了(中断) されます。
| オンデマンド | スポット | |
|---|---|---|
| 料金(g6.2xlarge 東京) | 約 $1.0/時間 | 約 $0.25〜0.40/時間 |
| 削減率 | - | 約 60〜75% |
| 中断リスク | なし | あり(事前2分通知) |
| 適したユースケース | 本番・長時間連続稼働 | 動画生成など中断可能なバッチ処理 |
動画生成は「途中で止まっても再実行すればよい」性質の作業なので、スポットと相性が良いです。
Terraformの変更点
前回の main.tf からの差分は aws_instance リソースに instance_market_options ブロックを追加するだけです。
resource "aws_instance" "framepack" {
ami = data.aws_ami.dlami.id
instance_type = local.instance_type
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.framepack.id]
associate_public_ip_address = true
key_name = var.key_name
# ↓ この block を追加するだけ
instance_market_options {
market_type = "spot"
spot_options {
instance_interruption_behavior = "terminate"
spot_instance_type = "one-time"
}
}
user_data = templatefile("${path.module}/userdata.sh.tftpl", {
eichi_github_username = var.eichi_github_username
eichi_github_token = var.eichi_github_token
})
root_block_device {
volume_type = "gp3"
volume_size = 150
encrypted = true
}
tags = { Name = "framepack-spot" }
lifecycle {
precondition {
condition = contains(data.aws_ec2_instance_type_offerings.g6.locations, local.az)
error_message = "指定した AZ ${local.az} では g6.2xlarge が利用できません。"
}
}
}
パラメータの説明
market_type = "spot"
スポットインスタンスとして起動することを指定します。
instance_interruption_behavior = "terminate"
中断時にインスタンスを終了します。stop にすると停止(EBS は保持)もできますが、one-time タイプでは terminate のみ有効です。
spot_instance_type = "one-time"
スポットリクエストのタイプです。one-time はリクエストが一度だけ処理されます(中断後に自動で再起動しない)。persistent にすると中断後に自動でリクエストが再発行されますが、管理が複雑になるため one-time を推奨します。
max_price は省略
最高入札価格を省略するとオンデマンド料金が上限になります。明示的に指定するより省略した方が、急激な価格高騰時でもオンデマンド以上は払わずに済むため安全です。
完全なmain.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
locals {
instance_type = "g6.2xlarge"
vpc_cidr = "10.0.0.0/16"
az = "${var.aws_region}a"
}
data "aws_ec2_instance_type_offerings" "g6" {
filter {
name = "instance-type"
values = [local.instance_type]
}
location_type = "availability-zone"
}
resource "aws_vpc" "framepack" {
cidr_block = local.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "framepack-vpc" }
}
resource "aws_internet_gateway" "framepack" {
vpc_id = aws_vpc.framepack.id
tags = { Name = "framepack-igw" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.framepack.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.framepack.id
}
tags = { Name = "framepack-public-rt" }
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.framepack.id
cidr_block = "10.0.1.0/24"
availability_zone = local.az
map_public_ip_on_launch = true
tags = { Name = "framepack-public-subnet" }
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
data "aws_ami" "dlami" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["Deep Learning OSS Nvidia Driver AMI GPU PyTorch 2.10 (Ubuntu 24.04) *"]
}
filter { name = "architecture" values = ["x86_64"] }
filter { name = "state" values = ["available"] }
filter { name = "virtualization-type" values = ["hvm"] }
}
resource "aws_security_group" "framepack" {
name = "framepack-sg"
description = "FramePack SSH access"
vpc_id = aws_vpc.framepack.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.my_ip_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "framepack-sg" }
}
resource "aws_instance" "framepack" {
ami = data.aws_ami.dlami.id
instance_type = local.instance_type
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.framepack.id]
associate_public_ip_address = true
key_name = var.key_name
instance_market_options {
market_type = "spot"
spot_options {
instance_interruption_behavior = "terminate"
spot_instance_type = "one-time"
}
}
user_data = templatefile("${path.module}/userdata.sh.tftpl", {
eichi_github_username = var.eichi_github_username
eichi_github_token = var.eichi_github_token
})
root_block_device {
volume_type = "gp3"
volume_size = 150
encrypted = true
}
tags = { Name = "framepack-spot" }
lifecycle {
precondition {
condition = contains(data.aws_ec2_instance_type_offerings.g6.locations, local.az)
error_message = "指定した AZ ${local.az} では g6.2xlarge が利用できません。"
}
}
}
output "public_ip" { value = aws_instance.framepack.public_ip }
output "public_dns" { value = aws_instance.framepack.public_dns }
output "ami_id" { value = data.aws_ami.dlami.id }
現在のスポット価格を確認する
デプロイ前に現在の価格を確認しておくと安心です。
aws ec2 describe-spot-price-history \
--instance-types g6.2xlarge \
--product-descriptions "Linux/UNIX" \
--region ap-northeast-1 \
--max-items 5 \
--query 'SpotPriceHistory[*].{AZ:AvailabilityZone,Price:SpotPrice,Time:Timestamp}' \
--output table
実行例:
-----------------------------------------------------------------------
| DescribeSpotPriceHistory |
+--------------------+----------+------------------------------------+
| AZ | Price | Time |
+--------------------+----------+------------------------------------+
| ap-northeast-1a | 0.3200 | 2026-05-xx... |
| ap-northeast-1c | 0.3100 | 2026-05-xx... |
+--------------------+----------+------------------------------------+
$0.30〜0.32/時間 なら、オンデマンド($1.0)比で 約 68% 削減です。
デプロイ手順
前回と同じです。
cd framepack/
terraform init
terraform plan
terraform apply
apply 成功後にスポットリクエストが受諾されていることを確認します。
aws ec2 describe-spot-instance-requests \
--region ap-northeast-1 \
--query 'SpotInstanceRequests[*].{State:State,Status:Status.Code,IP:LaunchedAvailabilityZone}' \
--output table
State: active になっていれば起動完了です。
SSH トンネルで接続する
前回と同じく SSH トンネルでアクセスします。
ssh -i your-key.pem \
-L 7860:127.0.0.1:7860 \
ubuntu@$(terraform output -raw public_dns)
ブラウザで http://127.0.0.1:7860 を開けば FramePack-eichi の UI が表示されます。
スポット中断への対応
中断通知を確認する
EC2 のメタデータエンドポイントで中断通知を取得できます。インスタンス上で以下を実行すると、中断予告がある場合に応答が返ります。
# 中断通知がない場合は 404 が返る
curl -s http://169.254.169.254/latest/meta-data/spot/termination-time
systemd サービスは自動的に再起動しない
userdata.sh.tftpl の framepack-eichi.service は Restart=always を設定しているため、プロセスがクラッシュした場合は自動復帰します。ただしインスタンス自体が中断されると当然サービスも停止します。
中断後に再利用したい場合は terraform apply を再度実行するだけで同じ設定の新しいスポットインスタンスが起動します。モデルの重みは Hugging Face から都度ダウンロードされますが、ダウンロード先(/home/ubuntu/hf_cache)を S3 と同期する仕組みを入れると初回以降のセットアップを短縮できます。
使い終わったら削除する
terraform destroy
まとめ
-
instance_market_optionsブロック3行を追加するだけでスポットインスタンスに切り替えられます - g6.2xlarge の場合、東京リージョンで約 $0.30/時間(オンデマンドの約 30%)で利用できます
- 動画生成のような中断可能なワークロードにはスポットが最適です
- 中断リスクが気になる場合は
max_priceを設定してオンデマンド料金以下を保証しつつ、価格の上昇局面では起動しない設計も取れます