CATHODE COASTaccess to tools
10 min read

AWSのクォータが通ってもGPUインスタンスは起動できなかった話

EC2でGPUインスタンスを立ててOllamaを動かすまでに踏んだ5つの罠。InsufficientInstanceCapacity、nvidia-utilsとドライバの違い、インスタンスストアの誘惑など。

shareB!

はじめに

メモリ1GBのVPSで0.5Bの軽量LLMを動かしたのが前回。今回はその続きで、もう少しまともなサイズのモデルをGPUで動かすことにしました。

やりたいことは単純です。EC2にGPUインスタンスを立てて、Ollamaを入れて、27BクラスのモデルをVRAMに載せる。それだけのはずでした。

結果から言うと、Ollamaを起動するところに辿り着くまでに5回転びました。しかもそのうち3つは、GPUともLLMとも関係のない場所です。

執筆時点(2026年7月)、Ubuntu 24.04 LTS / Ollama v0.32.5 / NVIDIA Driver 580.173.02 での記録です。


先にまとめ

  1. クォータの承認は「空きがある」保証ではない。 InsufficientInstanceCapacity はこちらの設定ミスではありません。AZ単位で在庫が違うので、サブネットを指定して総当たりするのが定石でした。
  2. nvidia-utils を入れてもGPUは動かない。 aptが親切に提案してくるパッケージは、ドライバ本体を含んでいません。
  3. 一番大きいディスクは、停止すると消える。 232GBのインスタンスストアにモデルを置くと、次回起動時に消滅します。

最終的に動いた構成は以下です。

項目
インスタンスg6.xlarge(NVIDIA L4 / VRAM 23,034MiB)
リージョン / AZap-northeast-1 / 1c
単価$1.1672/時(東京・オンデマンド)
OSUbuntu 24.04 LTS
ドライバnvidia-driver-580-server
ストレージルート30GB(EBS) + データ100GB(EBS) + swap 232GB(インスタンスストア)

罠1: クォータが通っていても起動できない

GPUインスタンスを使うには、まずService Quotasで上限緩和の申請が要ります。新規アカウントは G系インスタンスのvCPUクォータが0 なので、申請しないと1台も起動できません。

申請は数日で通り、確認したところ64まで引き上げられていました。

$ aws service-quotas get-service-quota \
    --service-code ec2 --quota-code L-DB2E81BA \
    --region ap-northeast-1 --query 'Quota.Value'
64.0

g5.xlargeは4 vCPUなので、16台ぶんの余裕があります。安心して起動コマンドを叩きました。

An error occurred (InsufficientInstanceCapacity) when calling the RunInstances
operation (reached max retries: 2): Insufficient capacity.

最初は自分の設定ミスを疑いましたが、違いました。これはAWS側にGPUの空きが無いという意味です。

ここで理解したことが2つあります。

クォータと在庫は別物です。 Service Quotasの承認は「あなたはここまで使ってよい」という許可であって、「マシンが空いている」という保証ではありません。GPUは特に逼迫しやすく、承認が下りていても起動できないことがあります。

在庫はAZ単位です。 run-instances でサブネットを指定しないと、デフォルトVPCの特定のAZ1箇所だけを試して諦めます。他のAZには空きがあるかもしれません。

対処: AZを総当たりする

まず、そのインスタンスタイプがどのAZで提供されているかを確認します。

aws ec2 describe-instance-type-offerings \
  --location-type availability-zone \
  --filters "Name=instance-type,Values=g5.xlarge" \
  --region ap-northeast-1 \
  --query 'InstanceTypeOfferings[].Location' --output text

そのうえで、サブネットを順に指定してループさせます。

for SUBNET in $(aws ec2 describe-subnets --region $AWS_REGION \
                  --query 'Subnets[].SubnetId' --output text); do
  AZ=$(aws ec2 describe-subnets --subnet-ids $SUBNET --region $AWS_REGION \
         --query 'Subnets[0].AvailabilityZone' --output text)
  echo "--- 試行: $AZ ---"
  if aws ec2 run-instances \
      --region $AWS_REGION \
      --image-id $AMI_ID \
      --instance-type g6.xlarge \
      --key-name my-key \
      --security-group-ids $SG_ID \
      --subnet-id $SUBNET \
      --associate-public-ip-address \
      --block-device-mappings '[
        {"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3","DeleteOnTermination":true}},
        {"DeviceName":"/dev/sdf","Ebs":{"VolumeSize":100,"VolumeType":"gp3","DeleteOnTermination":false}}
      ]' \
      --query 'Instances[0].InstanceId' --output text 2>/dev/null; then
    echo "✅ 成功: $AZ"; break
  fi
  echo "❌ 容量なし"
done

サブネットを明示する場合は --associate-public-ip-address も必要です。設定によってはパブリックIPが付きません。

結果: g5を諦めてg6にしたら安くなった

g5.xlargeは全AZで容量不足でした。そこで**g6.xlarge(NVIDIA L4)**に変更したところ、ap-northeast-1c であっさり通りました。

--- 試行: ap-northeast-1c (subnet-xxxxxxxx) ---
i-0xxxxxxxxxxxxxxxx
✅ 成功: ap-northeast-1c

意外だったのは価格です。

インスタンスGPUVRAM東京・オンデマンド
g5.xlargeA10G22GB$1.4590/時
g6.xlargeL424GB$1.1672/時

新しい世代のほうが20%安く、VRAMも多い。 容量不足で弾かれた結果、条件の良いほうに流れ着きました。

料金はPricing APIで確認できます。エンドポイントはus-east-1固定なので、--regionは変えずにフィルタで対象リージョンを指定します。

aws pricing get-products \
  --region us-east-1 --service-code AmazonEC2 \
  --filters \
    "Type=TERM_MATCH,Field=instanceType,Value=g6.xlarge" \
    "Type=TERM_MATCH,Field=location,Value=Asia Pacific (Tokyo)" \
    "Type=TERM_MATCH,Field=operatingSystem,Value=Linux" \
    "Type=TERM_MATCH,Field=preInstalledSw,Value=NA" \
    "Type=TERM_MATCH,Field=tenancy,Value=Shared" \
    "Type=TERM_MATCH,Field=capacitystatus,Value=Used" \
  --query 'PriceList[0]' --output text | python3 -c "
import json,sys
d = json.load(sys.stdin)
for t in d['terms']['OnDemand'].values():
    for p in t['priceDimensions'].values():
        print(p['pricePerUnit']['USD'], '/', p['unit'])
"

コスト感

$1.1672/時が何を意味するか、月額と並べると分かりやすいです。

期間USD円(@155円)
1時間$1.17約181円
24時間$28.01約4,340円
30日連続$840.38約130,300円

前回のさくらVPSが月1,000円弱だったので、放置すると130倍です。GPUインスタンスは「必要なときだけ起こす」以外の運用が成立しません。


罠2: nvidia-utils を入れてもGPUは使えない

インスタンスにSSHして nvidia-smi を叩くと、Ubuntuが親切に候補を並べてくれます。

$ nvidia-smi
Command 'nvidia-smi' not found, but can be installed with:
sudo apt install nvidia-utils-470
sudo apt install nvidia-utils-535
sudo apt install nvidia-utils-580
...

この提案に従ってはいけません。

nvidia-utils-XXXnvidia-smi などのユーティリティ群だけで、カーネルドライバを含みません。インストールするとコマンドは存在するようになりますが、実行すると NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver で落ちます。「コマンドが無い」から「コマンドが動かない」に症状が変わるだけです。

必要なのは nvidia-driver-XXX-server のほうでした。

sudo apt update
sudo apt install -y nvidia-driver-580-server
sudo reboot

-server サフィックスはヘッドレス向けで、デスクトップ用のパッケージ群が入りません。推論サーバー用途ならこちらです。

再起動後、無事に認識されました。

+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.173.02             Driver Version: 580.173.02     CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
|   0  NVIDIA L4                      Off |   00000000:31:00.0 Off |                    0 |
| N/A   43C    P0             29W /   72W |       0MiB /  23034MiB |      4%      Default |
+-----------------------------------------+------------------------+----------------------+

VRAMは23,034MiB。 「24GB」と言われますが、実際に使えるのは約22.5GiBです。この差は後でモデルを載せるときに効いてきます。

そして TDPが72W。GPUとしては驚くほど低い数字ですが、これが後々の性能に関わってきます(別記事に書きます)。


罠3: 一番大きいディスクは、停止すると消える

ボリュームをマウントしようとして lsblk を叩いたら、3つ出てきました。

$ lsblk -o NAME,SIZE,MODEL
NAME           SIZE MODEL
nvme0n1         30G Amazon Elastic Block Store
├─nvme0n1p1     29G
nvme1n1        100G Amazon Elastic Block Store
nvme2n1      232.8G Amazon EC2 NVMe Instance Storage

作ったのはルート30GBとデータ100GBの2本だけです。232.8GBの nvme2n1 は覚えがありません。

これはインスタンスストアでした。g6.xlargeに標準で付属する物理NVMe SSDです。EBSとは性質が根本的に違います。

デバイス種別インスタンス停止時
nvme0n1 (30G)EBS ルート保持される
nvme1n1 (100G)EBS データ保持される
nvme2n1 (232.8G)インスタンスストア完全に消える

一番大きくて一番速いディスクが、一番消えやすい。 ここに17GBのモデルを置いたら、停止するたびに再ダウンロードです。

MODEL 列を見れば区別できます。Amazon Elastic Block Store がEBS、Amazon EC2 NVMe Instance Storage がインスタンスストアです。lsblk を単体で叩くと表示されないので、-o NAME,SIZE,MODEL を付ける習慣をつけたほうがよさそうです。

使い道はある

消えて困らないデータなら、物理NVMeなので高速です。今回はswapに割り当てました。

sudo mkswap /dev/nvme2n1
sudo swapon /dev/nvme2n1
$ free -h
               total        used        free      shared  buff/cache   available
Mem:            15Gi       869Mi        13Gi       2.9Mi       511Mi        14Gi
Swap:          232Gi          0B       232Gi

メモリ15GiBに対してswap 232GiB。VRAMから溢れても落ちない構成になりました。

/etc/fstab には書かないこと。 インスタンスストアは停止・起動でデバイスが初期化されるため、fstabに書くと次回起動時にマウントに失敗します。必要なら起動スクリプトで毎回 mkswap します。

一方、EBSのデータボリュームは永続化します。

sudo mkfs -t ext4 /dev/nvme1n1
sudo mkdir -p /mnt/models
sudo mount /dev/nvme1n1 /mnt/models
UUID=$(sudo blkid -s UUID -o value /dev/nvme1n1)
echo "UUID=$UUID /mnt/models ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab

nofail は必ず付けてください。 ボリュームが無い状態で起動したとき、これが無いと起動に失敗してSSHもできなくなります。


罠4: systemctl edit が余計な文字を残した

Ollamaを入れて、モデルの保存先をデータボリュームに変更します。デフォルトのままだとルートの29GBに17GBのモデルが落ちて容量不足になるためです。

sudo systemctl edit ollama

エディタで以下を記述しました。

[Service]
Environment="OLLAMA_MODELS=/mnt/models"

反映を確認します。

$ systemctl show ollama -p Environment | grep OLLAMA_MODELS
Environment=PATH=/usr/local/sbin:... OLLAMA_MODELS=/mnt/models:

末尾にコロンが付いています。

OLLAMA_MODELS=/mnt/models:
                         ↑ これ

案の定、Ollamaは起動していませんでした。

$ curl http://localhost:11434/
curl: (7) Failed to connect to localhost port 11434 after 0 ms

/mnt/models: という存在しないパスとして解釈されていたわけです。編集時に余計な文字が混入したのか、コメント行との境界を踏んだのかは特定できていませんが、systemctl edit の対話エディタは編集位置を間違えやすいという教訓になりました。

対処: ドロップインを直接置く

対話エディタを使わず、ファイルとして書き込むほうが確実でした。

sudo mkdir -p /etc/systemd/system/ollama.service.d
 
sudo tee /etc/systemd/system/ollama.service.d/override.conf > /dev/null << 'EOF'
[Service]
Environment="OLLAMA_MODELS=/mnt/models"
EOF
 
sudo systemctl daemon-reload
sudo systemctl restart ollama

この方法ならファイルをリポジトリで管理できるので、再構築時にも同じものが置けます。対話エディタで手打ちするより、あらゆる面で優れていました。

確認するときは、変数を1行ずつ表示させると異常に気づきやすくなります。

$ systemctl show ollama -p Environment | tr ' ' '\n' | grep OLLAMA
OLLAMA_MODELS=/mnt/models

tr ' ' '\n' を挟むだけです。grep だけだと1行に全変数が並ぶので、末尾のコロンのような小さな異常を見落とします。


罠5: 起動しても、まだ入れない

これは小さな話ですが、地味に時間を無駄にしました。

run-instances が成功してインスタンスIDが返っても、すぐにSSHはできません。running 状態はハイパーバイザ上で起動したという意味で、OSの初期化は終わっていません。

# これを待つ
aws ec2 wait instance-status-ok --instance-ids $INSTANCE_ID --region $AWS_REGION

instance-status-ok はステータスチェックが2つとも通るまで待ちます。数分かかりますが、その間も課金されています


ハマりどころまとめ

症状原因対処
InsufficientInstanceCapacityAZにGPUの在庫が無いサブネットを指定してAZを総当たり。別ファミリー(g6等)も試す
VcpuLimitExceededクォータ未承認Service Quotasで申請。リージョンごとに独立
nvidia-smi が動かないnvidia-utils はドライバを含まないnvidia-driver-XXX-server を入れる
停止したらモデルが消えたインスタンスストアに置いたlsblk -o NAME,SIZE,MODEL でEBSか確認
再起動でマウントされない/etc/fstab の記述UUID指定+nofail
Ollamaが起動しない環境変数に余計な文字ドロップインを直接書く。tr ' ' '\n' で確認
SSHが繋がらないOS初期化が未完了aws ec2 wait instance-status-ok

やってみての所感

詰まった場所がGPUと無関係だった

5つの罠のうち、GPU固有と言えるのは1つ目(容量不足)と2つ目(ドライバ)だけです。残りはディスク、systemd、待ち時間。普通のLinuxサーバー構築の話でした。

前回のVPS構築でも、詰まったのはファイアウォールという枯れた領域でした。今回も同じで、新しい技術を触るときほど、足を引っ張るのは古い部分なのかもしれません。

エラーメッセージを正確に読む

InsufficientInstanceCapacityVcpuLimitExceeded は、症状が「起動できない」で同じですが、原因も対処もまったく違います。前者はAWS側の在庫、後者は自分の申請不足です。

前回の記事でも Timeout during connectConnection refused の違いが切り分けの鍵になりました。エラーメッセージは正確に書かれているので、雰囲気で読まずに文字どおり受け取るのが結局は近道でした。

CLIで組んでよかった

最初はコンソールで作ろうとしていましたが、AZ総当たりが必要になった時点でCLIに切り替えました。結果的にこれが正解で、インスタンスタイプを変えて再試行する作業が一行の書き換えで済みました

構築手順がそのままシェルスクリプトとして残るので、次に別リージョンで作り直すときにも使えます。GPUインスタンスは作っては壊す運用になりがちなので、この形にしておく価値は高そうです。


まとめ

GPUインスタンスを立ててOllamaが起動するところまで、実作業は1時間ほど。ただし詰まった時間を含めると2倍以上かかりました。

一番の学びは、Service Quotasの承認を「使える権利」だと思い込んでいたことです。承認されているのに起動できない、という状況を想定していませんでした。クラウドは無限にリソースがあるように見えて、GPUのような希少な資源では在庫の概念が顔を出します。

そして結果的に、容量不足で弾かれたおかげでより安くVRAMの多いインスタンスに落ち着きました。第一希望が通らなかったことが、そのまま最適解になった形です。

次はこのGPUで実際にモデルを動かし、速度を測ります。メモリ1GBのVPSで動かした0.5Bと、ほぼ同じ速度が出るという予想外の結果になったのですが、それはまた別の話です。


参考リンク