はじめに
EC2のインスタンスタイプを選ぶとき、「とりあえずCPUとメモリが大きいもの」を選んでいないでしょうか。
EC2サイジングは、vCPUとメモリだけでは決められません。EBSのIOPSやスループット、EC2側のEBS帯域、ネットワーク、ピーク時間、バッチの完了時間も影響します。
この記事では、新規構築、既存EC2の見直し、オンプレミスからの移行で使える手順を、EBSも含めて具体的に説明します。
先に結論
EC2サイジングは、次の順番で進めます。
- 応答時間、処理量、バッチ完了時間などの要件を決める
- CPU、メモリ、ネットワーク、EBSの現状を測る
- 負荷の特徴からインスタンスファミリーを選ぶ
- ピークと成長分の余裕を加え、サイズを選ぶ
- EBSの容量、IOPS、スループットを別々に決める
- 負荷試験と本番メトリクスで再評価する
最初の選定を正解だと考えず、説明できる初期値を置き、測定して直すのがポイントです。
前提
- 対象読者: EC2とEBSのサイジングを基礎から学びたい人
- 対象環境: Amazon EC2、Amazon EBS、Amazon CloudWatch、AWS Compute Optimizer
- OS例: Amazon Linux 2023
- AWS CLI: AWS CLI v2
- CLI出力の集計:
jq - 扱わない範囲: GPU、HPC、SAP、Oracleライセンスの個別設計
- 仕様確認日: 2026年8月27日
AWSのインスタンス世代、利用可能なサイズ、帯域、料金は更新されます。実際の設計では、対象リージョンのAWSコンソールと公式ドキュメントで再確認してください。
用語の短い説明
| 用語 | 説明 |
|---|---|
| vCPU | EC2から見える論理CPU。多くのインスタンスでCPUコアのスレッドに相当する |
| IOPS | 1秒間に処理できる読み書き回数の目安 |
| スループット | 1秒間に転送できるデータ量の目安 |
| レイテンシ | 1回の処理やI/Oが完了するまでの待ち時間 |
| P95 | データの95%がその値以下に収まる境界 |
| ライトサイジング | 大きすぎる、または小さすぎるリソースを適切なサイズへ直すこと |
1. 最初に性能要件を数値化する
サイジングの目的は、CPU使用率を下げることではありません。システムが必要な性能を満たし、運用とコストのバランスを取ることが目的です。
| 確認項目 | 質問の例 |
|---|---|
| 応答時間 | Web APIのP95応答時間を1秒以内にするか |
| 同時利用 | 通常時とピーク時の同時接続数はいくつか |
| 処理量 | 1秒あたりのリクエスト数やファイル数はいくつか |
| バッチ | 夜間バッチを何時までに終える必要があるか |
| 成長率 | 6か月後、1年後に利用量はどれくらい増えるか |
| 障害時 | 1台が停止したとき、残りの台数で処理できるか |
| ライセンス | vCPU数、コア数、CPUメーカーによる制約があるか |
| 変更可能時間 | 停止を伴うサイズ変更をいつ行えるか |
「遅くないサーバー」では完了条件を判定できません。「ピーク200リクエスト/秒でP95応答時間1秒以内」のように数値化します。
2. 状況別に情報の集め方を変える
新規構築
実績値がないため、似たシステムの実績、想定リクエスト数、データ量から初期値を置き、負荷試験で確認します。Auto Scalingに対応できる構成なら、1台を過大にする方法と比較します。
既存EC2
CloudWatchとOS内のメトリクスを使います。平常日だけでなく、月末・月初、夜間バッチ、キャンペーン、リリース直後などの業務ピークを含めます。
取得日数に万能な正解はありません。少なくとも、定期処理と高負荷日が入る代表期間を取ります。
オンプレミスからの移行
オンプレの「8コア、32GBメモリ、1TBディスク」を、そのままEC2へ写しません。物理サーバーは数年先を見越して大きく購入していることがあるためです。
- CPUの平均、P95、最大、コア別使用率
- 利用可能メモリ、スワップ、OOMの有無
- ディスク容量と増加率
- 読み書きIOPS、転送量、レイテンシ、キュー
- ネットワークの送受信量と接続数
- ジョブの実行時間と締め切り
- CPUコア数に連動するソフトウェアライセンス
AWS Application Migration Serviceは移行を支援しますが、移行元の過剰サイズまで自動的に正しくなるわけではありません。移行後の再評価を計画に入れます。
調査前に変数を設定する
以降のコマンドでは、リージョン、インスタンスID、調査期間を変数に入れて使います。IDや期間を何度も手入力しないため、対象間違いを減らせます。
# 対象環境に合わせて変更する。
export AWS_REGION="ap-northeast-1"
export INSTANCE_ID="i-0123456789abcdef0"
# Amazon Linux 2023など、GNU dateを使う環境で直近14日を指定する。
export START_TIME="$(date -u -d '14 days ago' '+%Y-%m-%dT%H:%M:%SZ')"
export END_TIME="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
# macOSで実行する場合は、START_TIMEだけこちらを使う。
# export START_TIME="$(date -u -v-14d '+%Y-%m-%dT%H:%M:%SZ')"
# 14日分を1,440データポイント以内で取得するため、15分単位にする。
export PERIOD=900
# AWS CLIとjqを利用できることを確認する。
aws --version
jq --version
# 誤ったAWSアカウントを調査しないよう、実行主体とアカウントIDを確認する。
aws sts get-caller-identity \
--query '{Account:Account,Arn:Arn}' \
--output table
jqがない場合、macOSではbrew install jq、Amazon Linux 2023ではsudo dnf install -y jqで導入できます。
get-metric-statisticsは、1回の呼び出しで最大1,440データポイントを返します。14日分を5分単位で要求すると上限を超えるため、ここでは15分単位に集約します。1分単位の短いピークを調べるときは、調査期間を短くします。期間と粒度の条件は、GetMetricStatisticsのAPIリファレンスで確認できます。
EC2の現行構成を最初に確認する
メトリクスを見る前に、現在のインスタンスタイプ、CPUアーキテクチャ、モニタリング設定を確認します。
# 対象EC2の現在値を一覧表示する。
aws ec2 describe-instances \
--region "$AWS_REGION" \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,InstanceId:InstanceId,Type:InstanceType,State:State.Name,AZ:Placement.AvailabilityZone,Architecture:Architecture,CoreCount:CpuOptions.CoreCount,ThreadsPerCore:CpuOptions.ThreadsPerCore,Monitoring:Monitoring.State,EbsOptimized:EbsOptimized}' \
--output table
# 後続の比較コマンドで使うため、現在のインスタンスタイプを変数へ入れる。
export INSTANCE_TYPE="$(aws ec2 describe-instances \
--region "$AWS_REGION" \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].InstanceType' \
--output text)"
# 現在のインスタンスタイプのvCPU、メモリ、ネットワーク性能を確認する。
aws ec2 describe-instance-types \
--region "$AWS_REGION" \
--instance-types "$INSTANCE_TYPE" \
--query 'InstanceTypes[0].{Type:InstanceType,vCPU:VCpuInfo.DefaultVCpus,MemoryMiB:MemoryInfo.SizeInMiB,Network:NetworkInfo.NetworkPerformance}' \
--output table
Monitoringがdisabledなら基本モニタリング、enabledなら詳細モニタリングです。名称だけを見ると監視自体が無効に見えますが、disabledでも基本モニタリングのメトリクスは送信されます。
接続中のEBSも同じ段階で確認します。
# OSから見えるデバイス名とEBSボリュームIDの対応を確認する。
aws ec2 describe-instances \
--region "$AWS_REGION" \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].BlockDeviceMappings[].{DeviceName:DeviceName,VolumeId:Ebs.VolumeId,DeleteOnTermination:Ebs.DeleteOnTermination}' \
--output table
# 接続中EBSのタイプ、容量、IOPS、スループットを確認する。
aws ec2 describe-volumes \
--region "$AWS_REGION" \
--filters "Name=attachment.instance-id,Values=$INSTANCE_ID" \
--query 'Volumes[].{VolumeId:VolumeId,Type:VolumeType,SizeGiB:Size,IOPS:Iops,ThroughputMiBps:Throughput,Encrypted:Encrypted,State:State}' \
--output table
迷ったときの調査順
コマンドを闇雲に実行するのではなく、遅延やエラーが起きた時刻を起点に、次の順で調べます。
| 順番 | 確認すること | 主な手段 | 次の判断 |
|---|---|---|---|
| 1 | 現在のEC2・EBS設定 |
describe-instances、describe-volumes
|
想定した構成か |
| 2 | 遅かった時刻と処理 | アプリケーションログ、ジョブ履歴 | 調査する時間帯を絞れるか |
| 3 | CPU・メモリ・EBS・ネットワーク | CloudWatch | どのリソースが同時に変化したか |
| 4 | プロセス・コア・デバイス |
mpstat、pidstat、iostat
|
OS内の原因を特定できるか |
| 5 | EC2・EBSの性能上限 |
describe-instance-types、ExceededCheck |
設定値や上限へ到達したか |
| 6 | 変更候補 | Compute Optimizer、負荷試験 | 変更後も性能要件を満たすか |
CPUが高いという事実だけでサイズを上げません。応答時間や処理量が悪化した同じ時刻にCPUが高いのか、I/O待ちやメモリ不足が先に起きていないかを突き合わせます。
CloudWatchのコマンドでデータが返らないときは、次を順に確認します。
-
AWS_REGIONが対象EC2と同じか - インスタンスIDやボリュームIDを取り違えていないか
-
START_TIMEとEND_TIMEがUTCで、実データがある期間か - 名前空間、メトリクス名、ディメンション名が一致しているか
- CloudWatch Agentのカスタムメトリクスなら、AgentとIAMロールが正常か
3. CPUを見る
CPUは平均値だけでなく、ピークの高さと継続時間を見ます。
| パターン | 考えられる状態 | 次に確認すること |
|---|---|---|
| 常にCPUが高い | CPUボトルネックの可能性 | プロセス別使用率、スレッド数、スケールアップ/アウト |
| 短時間だけ高い | 一時的なピーク | 応答時間の悪化、T系のCPUクレジット |
| CPUは低いのに遅い | CPU以外がボトルネック | メモリ、EBS、ロック、外部API、ネットワーク |
| 1コアだけ高い | シングルスレッド処理 | vCPUを増やして速くなる実装か |
CloudWatchのEC2標準メトリクスは、デフォルトで5分間隔で送信されます。詳細モニタリングを有効にすると1分間隔にできます。短いピークを評価するときは取得間隔も確認します。詳しい対象メトリクスと間隔は、AWS公式のEC2メトリクス一覧で確認できます。
# CPU使用率を時刻順に表示する。
# Averageは期間内の平均、Maximumは期間内の最大値。
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--statistics Average Maximum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--query 'sort_by(Datapoints,&Timestamp)[].{Time:Timestamp,Average:Average,Maximum:Maximum}' \
--output table
期間全体を1つの数字で比較したい場合は、各15分平均の平均値、P95、最大値をjqで集計します。
# CPUUtilizationの15分平均をJSONで取得し、期間全体を要約する。
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--statistics Average \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--query 'Datapoints[].Average' \
--output json | \
jq 'sort as $values
| ($values | length) as $count
| if $count == 0 then
{samples: 0}
else
{
samples: $count,
average: ($values | add / $count),
p95: $values[((($count * 0.95) | ceil) - 1)],
max: $values[-1]
}
end'
ここで求めているP95は、CPU使用率の15分平均値を並べたP95です。Web APIの応答時間に対するP95とは別物です。Averageは全体傾向、P95は高負荷帯、Maximumは最も高かった期間を知るために使います。
CloudWatchでCPUが高かった時刻を特定したら、OS内でコア別・プロセス別に確認します。
mpstat、pidstat、vmstatの役割
3つともLinuxの稼働状況を調べるコマンドですが、見る範囲が異なります。
| コマンド | 主に見る範囲 | 分かること |
|---|---|---|
mpstat |
CPU全体・CPUコア別 | 各コアの使用率、I/O待ち、アイドル率。特定の1コアに処理が偏っていないか確認できる |
pidstat |
プロセス別 | どのPIDがCPU、メモリ、ディスクI/Oを使っているか確認できる |
vmstat |
サーバー全体 | 実行待ち、メモリ、スワップ、ブロックI/O、CPUを一度に確認できる |
mpstatとpidstatはsysstatパッケージに含まれます。vmstatは通常、procps-ngパッケージに含まれます。
# CPU調査用のsysstatをインストールする。
sudo dnf install -y sysstat
# 全CPUとコア別の使用率を1秒間隔で5回表示する。
mpstat -P ALL 1 5
# プロセスごとのCPU・メモリ・ディスクI/Oを1秒間隔で5回表示する。
pidstat -u -r -d 1 5
mpstat -P ALL 1 5の-P ALLは全CPUとコア別の統計、1は1秒間隔、5は5回出力する指定です。主に次の列を見ます。
-
%usr: アプリケーションなど、ユーザー空間で使ったCPU時間 -
%sys: OSカーネルで使ったCPU時間 -
%iowait: 未完了のディスクI/Oがある状態で、CPUが待っていた時間 -
%idle: CPUが使われていなかった時間
特定コアだけ%idleが継続的に低い場合は、シングルスレッド処理などの偏りを疑います。%iowaitが高い場合は、CPU不足と決めつけず、EBSやファイルシステムも確認します。
pidstat -u -r -d 1 5の-uはCPU使用率、-rはページフォールトとメモリ、-dはディスクI/Oの統計を表示します。CPU使用率が高いPIDや、読み書き量が大きいPIDを探すことで、原因となっているプロセスを絞り込めます。
T系ではCPUクレジットも見る
T3やT4gなどは、平常時は低負荷で、必要なときだけCPUをバーストする用途に向いています。
-
CPUCreditBalance: 利用できるCPUクレジットの残量 -
CPUCreditUsage: 使用したCPUクレジット -
CPUSurplusCreditBalance: Unlimitedモードでベースラインを超えて使用した分
T3やT4gは、通常の起動ではデフォルトでUnlimitedモードになります。ベースラインを超えるCPU使用が長時間続くと追加料金の対象になる可能性があるため、常時CPUを使う処理ではM系やC系とも比較します。Dedicated Hostなど、起動方法によって初期設定が異なる場合は個別に確認してください。
# T系インスタンスのクレジット設定がstandardかunlimitedか確認する。
aws ec2 describe-instance-credit-specifications \
--region "$AWS_REGION" \
--instance-ids "$INSTANCE_ID" \
--query 'InstanceCreditSpecifications[].{InstanceId:InstanceId,CpuCredits:CpuCredits}' \
--output table
# 調査期間内のCPUクレジット残高の最小値を確認する。
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EC2 \
--metric-name CPUCreditBalance \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--statistics Minimum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--query 'sort_by(Datapoints,&Timestamp)[].{Time:Timestamp,Minimum:Minimum}' \
--output table
T系以外では、CPUクレジットのデータは返りません。T系でCPUCreditBalanceが繰り返し低下する場合は、CPU使用の継続時間とCPUSurplusCreditBalanceも確認します。
4. メモリを見る
EC2がデフォルトでCloudWatchへ送るメトリクスには、OS内部のメモリ使用率は含まれません。CloudWatch Agentなどを使って取得します。
# 利用可能メモリとスワップを確認する。
free -h
# 1秒間隔でCPU、メモリ、スワップ、I/O待ちを5回確認する。
vmstat 1 5
# RSSが大きいプロセスを上位10件表示する。RSSの単位はKiB。
ps -eo pid,comm,%mem,rss --sort=-rss | head -n 11
# 直近14日間にOOM Killerが動いた形跡を探す。
sudo journalctl -k --since '14 days ago' \
| grep -Ei 'out of memory|oom-killer|killed process'
Linuxでは空きメモリをページキャッシュに使うため、usedだけを見て不足と判定しません。available、スワップ、OOM Killer、アプリケーションのヒープ使用量を一緒に確認します。
vmstat 1 5は、1秒間隔で5回出力します。1行目には起動後からの平均が含まれるため、現在の瞬間的な傾向は2行目以降を中心に見ます。
| 列 | 意味 | 確認のポイント |
|---|---|---|
r |
実行中またはCPUの実行待ちにいるプロセス数 | vCPU数を継続的に上回る場合は、CPU待ちが発生している可能性がある |
si / so
|
スワップからの読み込み量・スワップへの書き出し量 | 継続的に値が出る場合は、メモリ圧迫の可能性がある |
bi / bo
|
ブロックデバイスからの読み込み量・書き出し量 | I/O量の増加時刻とwa、EBSメトリクスを照合する |
wa |
CPUがI/O完了を待っていた時間の割合 | 継続的に高い場合は、EBSやファイルシステム側も調べる |
CloudWatch Agentで取得する例
Amazon Linux 2023での最小例です。エージェントがCloudWatchへメトリクスを送れるIAMロールも必要です。以下のJSONは/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.jsonとして保存します。
# CloudWatch Agentをインストールする。
sudo dnf install -y amazon-cloudwatch-agent
{
"agent": {
"metrics_collection_interval": 60
},
"metrics": {
"namespace": "CWAgent",
"append_dimensions": {
"InstanceId": "${aws:InstanceId}"
},
"metrics_collected": {
"mem": {
"measurement": ["used_percent"]
},
"swap": {
"measurement": ["used_percent"]
},
"disk": {
"measurement": ["used_percent"],
"resources": ["*"]
}
}
}
}
JSONにはコメントを書けないため、意図を補足します。memとswapのused_percentは、CloudWatch上でそれぞれmem_used_percentとswap_used_percentとして送信されます。diskはファイルシステム使用率を取得します。
# 作成したJSONを読み込み、EC2モードでAgentを起動する。
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config \
-m ec2 \
-s \
-c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
CloudWatch Agentから送信するメトリクスはカスタムメトリクスとして課金対象になります。必要な項目と取得間隔を選びます。
Agentを起動したら、CloudWatchへメモリ使用率が届いているか確認します。
# mem_used_percentが対象インスタンスのディメンションで存在するか確認する。
aws cloudwatch list-metrics \
--region "$AWS_REGION" \
--namespace CWAgent \
--metric-name mem_used_percent \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID"
# メモリ使用率の平均値と最大値を時刻順に表示する。
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace CWAgent \
--metric-name mem_used_percent \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--statistics Average Maximum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--query 'sort_by(Datapoints,&Timestamp)[].{Time:Timestamp,Average:Average,Maximum:Maximum}' \
--output table
list-metricsで見つからない場合は、Agentの起動状態、設定ファイル、EC2のIAMロール、リージョンを順に確認します。メモリ使用率だけでなく、freeのavailable、スワップ増加、OOM、アプリケーションのヒープ上限を合わせて判断します。
5. ネットワークを見る
CPUとメモリに余裕があっても、ネットワークが上限に近づくと遅延やパケットドロップにつながります。
-
NetworkIn/NetworkOut: 受信・送信バイト数 -
NetworkPacketsIn/NetworkPacketsOut: 受信・送信パケット数 -
conntrack_allowance_exceeded: 追跡可能な接続数の上限超過 -
bw_in_allowance_exceeded/bw_out_allowance_exceeded: 入出力帯域の上限超過 -
pps_allowance_exceeded: 1秒あたりパケット数の上限超過
allowance系はENAドライバのethtool統計などから確認します。送受信量だけでなく、パケット数や接続数の上限もボトルネックになります。
# NetworkOutを平均Mbpsへ変換して時刻順に表示する。
# CloudWatchのNetworkOutは、期間内に送信した合計バイト数。
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EC2 \
--metric-name NetworkOut \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--statistics Sum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--output json | \
jq --argjson period "$PERIOD" '
.Datapoints
| sort_by(.Timestamp)
| map({time: .Timestamp, average_mbps: (.Sum * 8 / $period / 1000000)})'
CloudWatchの送受信量に余裕があっても、PPSや接続追跡の上限超過が起きることがあります。ENAドライバの統計をEC2内で確認します。
# デフォルトルートで使うネットワークインターフェイス名を取得する。
INTERFACE="$(ip route show default | awk '{print $5; exit}')"
# 0より大きいallowance超過カウンターがないか確認する。
sudo ethtool -S "$INTERFACE" \
| grep -E 'bw_(in|out)_allowance_exceeded|pps_allowance_exceeded|conntrack_allowance_exceeded'
これらは累積カウンターです。1回の値だけでなく、時間を空けて再取得して増えているかを確認します。
6. インスタンスファミリーを選ぶ
次の表は、AWSの分類をもとに最初の候補を選ぶために簡略化したものです。
| 特徴 | 候補 | 用途例 | 注意 |
|---|---|---|---|
| CPUとメモリのバランス型 | M系 | Webサーバー、業務アプリ | 迷ったときの比較基準にしやすい |
| CPU比率が大きい | C系 | 計算処理、エンコード、高トラフィックWeb | メモリ容量を先に確認する |
| メモリ比率が大きい | R系 | インメモリDB、大きなキャッシュ | メモリ不足の根拠を確認する |
| 低負荷で一時的にCPUが必要 | T系 | 小規模Web、検証環境 | CPUクレジットとUnlimited料金を見る |
| ローカルNVMeのI/Oを重視 | I系など | 分散DB、一時キャッシュ | Instance Storeの永続性を別途考える |
ファミリー名だけで決めず、世代、x86/Arm、ローカルストレージの有無、ネットワークとEBS帯域を比較します。
# 東京リージョンでM/C/Rファミリーのlargeサイズを比較する。
# MemoryMiBはMiB表記のため、GiBで見る場合は1,024で割る。
aws ec2 describe-instance-types \
--region ap-northeast-1 \
--instance-types m7i.large c7i.large r7i.large \
--query 'InstanceTypes[].{Type:InstanceType,vCPU:VCpuInfo.DefaultVCpus,MemoryMiB:MemoryInfo.SizeInMiB,Network:NetworkInfo.NetworkPerformance,EBSBaselineMbps:EbsInfo.EbsOptimizedInfo.BaselineBandwidthInMbps,EBSMaxMbps:EbsInfo.EbsOptimizedInfo.MaximumBandwidthInMbps,EBSBaselineIOPS:EbsInfo.EbsOptimizedInfo.BaselineIops,EBSMaxIOPS:EbsInfo.EbsOptimizedInfo.MaximumIops}' \
--output table
NetworkPerformanceのUp toは、常にその速度を維持できるという意味ではありません。小さいサイズでは、ベースラインとバーストの扱いも確認します。
7. EBSは容量・IOPS・スループット・レイテンシで決める
EBSを「500GB」のように容量だけで決めると、空き容量があるのに遅いという状態が起きます。
| 観点 | 質問 | 主な確認値 |
|---|---|---|
| 容量 | 何GiB保存するか | 現在使用量、増加率、保持期間 |
| IOPS | 1秒に何回の読み書きが必要か | ReadOps、WriteOps、I/Oサイズ |
| スループット | 1秒に何MiB転送するか | ReadBytes、WriteBytes |
| レイテンシ | 1回のI/Oが何msで完了するか | ReadLatency、WriteLatency、QueueLength |
EBSボリュームタイプ
| タイプ | 特徴 | 最初に検討する用途 |
|---|---|---|
gp3 |
容量とIOPS・スループットを独立して設定できる汎用SSD | Web、業務アプリ、開発環境などの一般用途 |
gp2 |
容量と基準IOPSが連動する旧世代の汎用SSD | 既存環境の移行元として見かける |
io2 |
高IOPS、低レイテンシ、高い耐久性を重視するSSD | ミッションクリティカルなDB、gp3の上限を超える用途 |
st1 |
大きなデータの順次読み書きを重視するHDD | ログ処理、ビッグデータなど |
sc1 |
低頻度アクセス向けの低コストHDD | 頻繁に読み書きしないデータ |
多くの一般用途では、まずgp3を候補にします。gp3はストレージ料金に3,000 IOPSと125 MiB/sの基準性能が含まれます。
必要に応じてIOPSは最大80,000、スループットは最大2,000 MiB/sまで追加設定できます。上限にはボリュームサイズとIOPSの比率条件があり、追加分は課金対象です。
gp3はgp2のようなI/Oクレジットによるバースト方式ではなく、設定したIOPSとスループットを持続的に提供する設計です。
IOPSとスループットの関係
スループット = IOPS × 1回あたりのI/Oサイズ
1回16 KiBのI/Oを1秒間に6,000回行う場合は次のとおりです。
6,000 IOPS × 16 KiB = 96,000 KiB/s = 93.75 MiB/s
一方、256 KiBの大きなI/Oを1,000回行うと250 MiB/sになります。小さなランダムI/OではIOPS、大きな順次I/Oではスループットが先に上限へ達することがあります。
8. EBSのボトルネックを4層に分ける
EBSが遅いときは、ボリュームの設定だけを増やさず、次の4層に分けます。
1. OSとアプリケーション
ファイルシステムが満杯、メモリスワップが多い、アプリケーションが同期書き込みを多用するなどの要因を確認します。
# ブロックデバイスとマウント先を確認する。
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
# ファイルシステムの使用量と空き容量を確認する。
df -hT
# sysstatをインストールし、デバイス別のI/Oを1秒間隔で確認する。
sudo dnf install -y sysstat
iostat -xz 1
iostatではawait、aqu-sz、%utilなどを見ます。単一のしきい値だけで判定せず、応答時間とアプリケーションログと一緒に見ます。
2. EBSボリュームの上限
EBSのメトリクスはAWS/EBS名前空間へ1分間隔で自動送信されます。対応するNitroベースEC2では、IOPS、スループット、レイテンシを直接読めるメトリクスが追加されています。対象条件と統計値は、AWS公式のEBSメトリクス一覧で確認できます。
| 確認したいこと | Nitroで使える直接メトリクス | 従来メトリクスからの計算 |
|---|---|---|
| 読み書き合計IOPS | VolumeAvgIOPS |
VolumeReadOpsとVolumeWriteOpsのSum / Periodを合計 |
| 読み書き合計スループット | VolumeAvgThroughput |
VolumeReadBytesとVolumeWriteBytesのSum / Period / 1,048,576を合計 |
| 読みレイテンシ | VolumeAvgReadLatency |
VolumeTotalReadTime / VolumeReadOps × 1,000 |
| 書きレイテンシ | VolumeAvgWriteLatency |
VolumeTotalWriteTime / VolumeWriteOps × 1,000 |
| 平均キュー長 | - |
VolumeQueueLengthのAverage
|
VolumeAvgIOPSの単位はOps/s、VolumeAvgThroughputはKiB/s、レイテンシはミリ秒です。Multi-AttachではInstanceIdディメンションも使うなど条件が変わるため、通常の単一アタッチと同じクエリをそのまま使わないようにします。
対応するNitroベースEC2では、上限超過を直接確認できる次のメトリクスも利用できます。
-
VolumeIOPSExceededCheck: プロビジョンしたIOPSを継続的に超えようとしたか -
VolumeThroughputExceededCheck: プロビジョンしたスループットを継続的に超えようとしたか
値1が上限超過を示します。Magneticボリューム、Multi-Attach有効ボリュームなど、対象外の条件がある点に注意します。
# 調査するEBSボリュームIDを設定する。
export VOLUME_ID="vol-0123456789abcdef0"
# IOPS・スループット・レイテンシの最大値を時刻順に表示する。
# Nitro非対応などでメトリクスがない場合は、何も返らない。
for METRIC in \
VolumeAvgIOPS \
VolumeAvgThroughput \
VolumeAvgReadLatency \
VolumeAvgWriteLatency
do
echo "=== $METRIC ==="
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EBS \
--metric-name "$METRIC" \
--dimensions "Name=VolumeId,Value=$VOLUME_ID" \
--statistics Maximum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--query 'sort_by(Datapoints,&Timestamp)[].{Time:Timestamp,Maximum:Maximum}' \
--output table
done
上限超過チェックは、Maximumが1になった時刻がないかを見ます。
# IOPSまたはスループットの上限超過が発生した時刻を確認する。
for METRIC in VolumeIOPSExceededCheck VolumeThroughputExceededCheck
do
echo "=== $METRIC ==="
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EBS \
--metric-name "$METRIC" \
--dimensions "Name=VolumeId,Value=$VOLUME_ID" \
--statistics Maximum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--query 'sort_by(Datapoints[?Maximum==`1`],&Timestamp)[].{Time:Timestamp,Exceeded:Maximum}' \
--output table
done
直接メトリクスを利用できない環境では、従来メトリクスを期間の秒数で割ります。次は読み取りIOPSの例です。
# VolumeReadOpsの期間内合計を秒数で割り、平均Read IOPSへ変換する。
aws cloudwatch get-metric-statistics \
--region "$AWS_REGION" \
--namespace AWS/EBS \
--metric-name VolumeReadOps \
--dimensions "Name=VolumeId,Value=$VOLUME_ID" \
--statistics Sum \
--period "$PERIOD" \
--start-time "$START_TIME" \
--end-time "$END_TIME" \
--output json | \
jq --argjson period "$PERIOD" '
.Datapoints
| sort_by(.Timestamp)
| map({time: .Timestamp, average_read_iops: (.Sum / $period)})'
書き込みIOPSはVolumeWriteOps、読み取りスループットはVolumeReadBytes、書き込みスループットはVolumeWriteBytesへ置き換えます。Bytes系はさらに1,048,576で割るとMiB/sになります。
現在のEBS設定をもう一度確認するときは、対象ボリュームIDで絞ります。
# 対象EBSの現在設定と接続先を確認する。
aws ec2 describe-volumes \
--region "$AWS_REGION" \
--volume-ids "$VOLUME_ID" \
--query 'Volumes[].{VolumeId:VolumeId,Type:VolumeType,SizeGiB:Size,IOPS:Iops,ThroughputMiBps:Throughput,InstanceId:Attachments[0].InstanceId,Device:Attachments[0].Device}' \
--output table
3. EC2インスタンス側のEBS帯域
EBSボリュームに8,000 IOPSを設定しても、EC2側が持続できるIOPSやスループットを超えていれば性能を使い切れません。
実効EBS性能 = min(接続ボリュームの合計性能, EC2インスタンスのEBS性能上限)
小さいインスタンスでは、EBS帯域にベースラインと最大値がある場合があります。長時間バッチは、最大値だけでなくベースラインでも成立するか確認します。
# 現在のインスタンスタイプが持つEBS性能の基準値と最大値を確認する。
aws ec2 describe-instance-types \
--region "$AWS_REGION" \
--instance-types "$INSTANCE_TYPE" \
--query 'InstanceTypes[0].{Type:InstanceType,BaselineIOPS:EbsInfo.EbsOptimizedInfo.BaselineIops,MaximumIOPS:EbsInfo.EbsOptimizedInfo.MaximumIops,BaselineMBps:EbsInfo.EbsOptimizedInfo.BaselineThroughputInMBps,MaximumMBps:EbsInfo.EbsOptimizedInfo.MaximumThroughputInMBps}' \
--output table
ベースラインが0やnullになる場合は、そのインスタンスタイプの仕様表も確認します。接続する全EBSの合計負荷と比較し、1ボリュームだけで判断しません。
4. スナップショットから復元したEBS
スナップショットから作成したEBSは、初回アクセス時にブロックを取得するため、初期化が完了するまで性能が安定しないことがあります。
性能試験前に、Fast Snapshot Restore、ボリューム初期化レートの指定、手動初期化のいずれかを検討します。追加料金やI/O負荷があるため、本番要件と日程に合わせて選びます。
9. 具体的なサイジング例
ここからは、筆者の実務経験をもとにした説明用の例です。AWS公式の推奨しきい値ではありません。
| 項目 | 実測値 | 初期選定の考え方 |
|---|---|---|
| CPU | P95 55%、最大85% | ピーク時の応答時間を負荷試験で確認 |
| メモリ | P95 11 GiB | OS分も含め16 GiB程度を起点にする |
| EBS使用量 | 320 GiB | 1年の増加分と作業領域を含め500 GiBとする |
| EBS IOPS | P95 4,500、最大5,400 | 6,000 IOPSを候補にする |
| EBSスループット | P95 100 MiB/s、最大180 MiB/s | 250 MiB/sを候補にする |
| ネットワーク | 最大150 Mbps | インスタンス上限と十分に離れているか確認 |
初期候補は、M系の4 vCPU・16 GiBメモリ程度と、gp3 500 GiB・6,000 IOPS・250 MiB/sです。
この時点で決定ではありません。対象EC2のEBSベースラインで6,000 IOPSと250 MiB/sの必要時間を支えられるか確認します。不足する場合はEC2サイズ、ファミリー、I/O設計を見直します。
10. EBSの設定を変更する
Elastic Volumesに対応する構成では、EBSをデタッチせず、EC2を再起動せずに容量、ボリュームタイプ、IOPS、スループットを変更できます。
# 変更前にスナップショット、現在値、対象IDを確認する。
# gp3 500 GiB、6,000 IOPS、250 MiB/sへ変更する例。
aws ec2 modify-volume \
--region ap-northeast-1 \
--volume-id vol-0123456789abcdef0 \
--volume-type gp3 \
--size 500 \
--iops 6000 \
--throughput 250
# 変更の進行状態を確認する。
aws ec2 describe-volumes-modifications \
--region ap-northeast-1 \
--volume-ids vol-0123456789abcdef0
EBSの容量は増やせますが、同じボリュームを後から縮小できません。縮小する場合は、小さい新規ボリュームへデータを移行します。
容量を増やした後は、OS側のパーティションやファイルシステムの拡張が別途必要です。実行コマンドはXFS、ext4、Windowsで異なるため、対象を確認してから作業します。
ボリュームの変更はキャンセルできません。同じボリュームは、前の変更が完了している条件で、ローリング24時間内に最大4回まで変更できます。
11. Compute Optimizerを判断材料にする
AWS Compute OptimizerはCloudWatchメトリクスを分析し、EC2やEBSの最適化候補を示します。利用前にアカウントまたはOrganizations管理アカウントからオプトインします。
EC2とAuto Scalingグループの推奨には、過去14日間に少なくとも30時間のCloudWatchメトリクスが必要です。有料の拡張インフラストラクチャメトリクスを有効にすると、ルックバック期間を最大93日へ延長できます。
EBSの推奨は、ボリュームが実行中のEC2へ少なくとも30時間連続で接続されている必要があります。
- メモリを分析させるには、CloudWatch Agentの
mem_used_percentなどが必要 - 推奨の節約額を表示するにはCost Explorerの有効化が必要
- キャンペーン、新機能、未実行の月次処理は別途考慮する
- 変更前にテストし、変更後の応答時間とエラー率を確認する
# Compute Optimizerへオプトイン済みか確認する。
aws compute-optimizer get-enrollment-status \
--region "$AWS_REGION" \
--query '{Status:status,StatusReason:statusReason,MemberAccountsEnrolled:memberAccountsEnrolled}' \
--output table
# EC2とEBSのARNを組み立てる。
export ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export INSTANCE_ARN="arn:aws:ec2:${AWS_REGION}:${ACCOUNT_ID}:instance/${INSTANCE_ID}"
export VOLUME_ARN="arn:aws:ec2:${AWS_REGION}:${ACCOUNT_ID}:volume/${VOLUME_ID}"
# 現在のEC2に対する判定、理由、分析期間を確認する。
aws compute-optimizer get-ec2-instance-recommendations \
--region "$AWS_REGION" \
--instance-arns "$INSTANCE_ARN" \
--query 'instanceRecommendations[0].{CurrentType:currentInstanceType,Finding:finding,Reasons:findingReasonCodes,LookbackDays:lookBackPeriodInDays}' \
--output json
# 推奨候補を順位、性能リスク、移行難易度、推定削減額と一緒に表示する。
aws compute-optimizer get-ec2-instance-recommendations \
--region "$AWS_REGION" \
--instance-arns "$INSTANCE_ARN" \
--query 'instanceRecommendations[0].recommendationOptions[].{Rank:rank,Type:instanceType,PerformanceRisk:performanceRisk,MigrationEffort:migrationEffort,EstimatedMonthlySavings:savingsOpportunity.estimatedMonthlySavings.value}' \
--output table
# 対象EBSの推奨タイプ、容量、IOPS、スループットを表示する。
aws compute-optimizer get-ebs-volume-recommendations \
--region "$AWS_REGION" \
--volume-arns "$VOLUME_ARN" \
--query 'volumeRecommendations[0].volumeRecommendationOptions[].{Rank:rank,Type:configuration.volumeType,SizeGiB:configuration.volumeSize,IOPS:configuration.volumeBaselineIOPS,ThroughputMBps:configuration.volumeBaselineThroughput,PerformanceRisk:performanceRisk,EstimatedMonthlySavings:savingsOpportunity.estimatedMonthlySavings.value}' \
--output table
FindingがUnavailable、または推奨候補が空の場合は、オプトイン状態、分析に必要な時間、対象リソースの条件を確認します。EC2とAuto Scalingグループには過去14日間で少なくとも30時間、EBSには実行中EC2へ30時間連続で接続された実績が必要です。要件はCompute Optimizerの公式リソース要件で更新を確認してください。
PerformanceRiskは0から4の範囲で、値が高いほど推奨構成が性能要件を満たせない可能性が高いことを示します。削減額だけで並べず、FindingReasonCodes、移行難易度、将来のピークも合わせて確認します。
Compute Optimizerは正解を自動適用するサービスではなく、比較候補を出すサービスとして使います。
12. EC2サイズ変更の注意
EBS-backedインスタンスのインスタンスタイプを変更する場合、基本的にEC2の停止と起動が必要です。変更先との互換性も確認します。
- AMIのCPUアーキテクチャが一致するか
- ENAなどのネットワークドライバが対応しているか
- NitroベースのNVMeデバイス名の違いに対応できるか
- 接続済みEBSボリューム数が変更先の上限以内か
- ライセンス、CPUオプション、Dedicated Hostの制約がないか
- 停止・起動で自動割り当てのパブリックIPv4アドレスが変わる影響がないか
- 起動後にOS、エージェント、アプリケーションを確認できるか
Arm系のGravitonインスタンスへ変える場合は、x86_64のAMIのままインスタンスタイプだけ変更できません。Arm対応AMI、アプリケーション、ライブラリ、コンテナイメージで別EC2を構築し、移行試験します。
13. 負荷試験で確認する
サイジングの最終判断は、カタログ仕様ではなく、本番に近い条件の負荷試験で行います。
- 処理量と同時接続数
- P50、P95、P99の応答時間
- エラー率とタイムアウト数
- CPUとメモリ
- EBSのIOPS、スループット、レイテンシ、キュー
- ネットワークの送受信量と上限超過
- 外部API、DB、キャッシュの待ち時間
通常負荷、予想ピーク、1台障害やバッチ重複を含む高負荷の3パターンを分けます。負荷生成側が先に上限へ達しないようにし、本番データや外部サービスへ影響しない検証環境で行います。
14. サイジングシートの例
レビューでは、次のように実測と選定理由を残します。
| 項目 | 現状・予測 | ピーク | 初期選定 | 再確認日 |
|---|---|---|---|---|
| vCPU | 4 vCPUでP95 55% | 85% | 4 vCPUから負荷試験 | 本番開始2週間後 |
| メモリ | P95 11 GiB | 12 GiB | 16 GiB | 本番開始2週間後 |
| EBS容量 | 320 GiB | 340 GiB | 500 GiB | 毎月 |
| EBS IOPS | P95 4,500 | 5,400 | 6,000 IOPS | 本番開始2週間後 |
| EBSスループット | P95 100 MiB/s | 180 MiB/s | 250 MiB/s | 本番開始2週間後 |
| ネットワーク | P95 100 Mbps | 150 Mbps | 仕様と負荷試験で決定 | 本番開始2週間後 |
余裕は一律30%のように決めません。Auto Scalingで台数を増やせるか、月次処理が未計測か、1台障害時に全負荷を受けるか、変更調整にどれくらい時間がかかるかで変えます。
よくある失敗
オンプレのスペックをそのまま写す
オンプレの調達時点の余裕までAWSに移すと、使っていないサイズに毎月支払い続けることがあります。実測値と業務要件から選び直します。
CPU平均だけを見る
平均CPUが20%でも、業務ピークに100%が続いているかもしれません。Maximum、P95、ピークの継続時間を見ます。
メモリを測定しない
CPUしか見ずにサイズを下げると、スワップやOOMが発生する可能性があります。CloudWatch Agentまたは既存の監視ツールでメモリを測ります。
EBSの容量だけを増やす
gp3は容量とIOPS・スループットを分けて設定できます。遅さの原因がIOPSなのか、スループットなのか、EC2側の上限なのかを切り分けます。
カタログの最大値だけを見る
Up toやMaximumは、長時間維持できるベースラインと異なる場合があります。長時間バッチはベースライン値でも成立するか確認します。
Compute Optimizerの推奨をそのまま適用する
過去のメトリクスに含まれない未来のピーク、新機能、障害時の片系運転を別途考慮します。
実務チェックリスト
要件
- 応答時間、処理量、バッチ終了時間を数値化した
- 通常時とピーク時を分けた
- データとトラフィックの成長率を確認した
- 1台障害時の必要性能を確認した
EC2
- CPUのAverage、Maximum、P95、継続時間を確認した
- メモリ、スワップ、OOMの有無を確認した
- T系ではCPUクレジットを確認した
- ファミリー、世代、CPUアーキテクチャを比較した
- ネットワークの帯域、PPS、接続数を確認した
- 変更に必要な停止時間と互換性を確認した
EBS
- 容量、増加率、保持期間を確認した
- 読み書きIOPSを分けて確認した
- 読み書きスループットを分けて確認した
- I/Oサイズ、レイテンシ、QueueLengthを確認した
- EBSボリュームとEC2側の上限を両方確認した
- スナップショット復元後の初期化を考慮した
- 容量変更後のファイルシステム拡張手順を確認した
検証と運用
- 通常、ピーク、障害時の負荷試験を行った
- 変更前後の応答時間、エラー率、コストを比較した
- Compute Optimizerの推奨とパフォーマンスリスクを確認した
- 本番開始後の再評価日を決めた
- CloudWatchアラームと変更前へ戻す手順を用意した
参考・確認先
- Amazon EC2インスタンスタイプ
- EC2インスタンスで利用できるCloudWatchメトリクス
- CloudWatch Agentでメトリクスを収集する
- CloudWatch Agent設定ファイルの詳細
- Amazon EBSボリュームタイプ
- General Purpose SSDボリューム
- EBSのCloudWatchメトリクス
- EBS最適化インスタンス
- Elastic VolumesでEBSボリュームを変更する
- EBSボリュームを初期化する
- Compute Optimizerのリソース要件
- Compute OptimizerがEC2で分析するメトリクス
- EC2インスタンスタイプ変更の互換性
- AWS Well-Architected Framework Performance Efficiency Pillar
mpstatLinux manual pagepidstatLinux manual pagevmstatLinux manual page
仕様確認日: 2026年8月27日
関連記事
- AWSインフラ工程を5ステップで整理する 要件定義・設計・実装・検証
- AWS EC2を毎日18時に自動停止する EventBridge Scheduler設定手順
- AWS EC2ストレージ入門 EBS・IOPS・スナップショットを整理する(今後公開予定)
- AWSコスト試算入門 Pricing Calculatorで月額料金を見積もる手順
まとめ
- EC2サイジングは、要件を数値化してからCPU、メモリ、ネットワーク、EBSを測定する
- EBSは容量、IOPS、スループット、レイテンシに分けて考える
- EBSボリュームの設定だけでなく、EC2側のEBS帯域上限も確認する
- Compute Optimizerは判断材料として使い、負荷試験後に適用する
- 最初のサイズを正解にせず、本番開始後の再評価日まで決める
おわりに
EC2のサイジングは、「大きくして安心」ではなく、実測と性能要件をもとに選定理由を説明できることが大切です。EBSも容量だけではなく、IOPS、スループット、EC2側の帯域まで一緒に見ると、性能とコストの両方を改善できます。
Wealthy Designでは、AWS・Google Cloud・Azureの設計で、構築方法だけでなく性能、コスト、運用まで説明できるエンジニアリングを大切にしています。技術発信やエンジニア育成の取り組みは、会社サイトにまとめています。

