ブログ

AWSでWindows EC2の物理メモリ使用率をメトリクス監視する

AWSでシステムを運用する際、CPU使用率やディスクI/Oと同じくらい重要なのが「メモリ使用率」です。しかし、CloudWatchが標準で収集するメトリクスには注意点があります。

CloudWatch Agent が提供する「Memory % Committed Bytes In Use」などは、ページファイル(仮想メモリ)を含むため、実際の物理メモリ逼迫値を正確には表しません。

そのため、実際の物理メモリ(RAM)の使用率を把握したい場合には不十分なケースがあります。アプリケーションのパフォーマンスに直結するのは物理メモリの逼迫度合いであるため、運用上は「物理メモリだけの使用率」を監視することが重要となります。

そこで本記事では、純粋な物理メモリ使用率をCloudWatchに送信する仕組みを紹介します。

物理メモリ使用率を取得する仕組み

まず、どのようにして「物理メモリだけの利用率」を計算するかを見ていきましょう。

手順では、以下の工夫を加えています。

1. PowerShell で OS から生データを取得
Get-CimInstance Win32_OperatingSystemを利用し、TotalVisibleMemorySize(物理RAM合計)、FreePhysicalMemory(未使用RAM)を呼び出します。

2. 物理メモリ利用率を再計算

利用率(%) = (合計 - 空き) ÷ 合計 × 100

これによりページファイルやキャッシュを含まない、純粋な物理RAMの使用率が得られます。

3. StatsD形式で CloudWatch Agent に送信
ローカルの127.0.0.1:8125にUDPでゲージ値を送信し、CloudWatch Agentが取り込みます。

4. CloudWatch AgentがメトリクスとしてCloudWatchに登録
受け取った値をCloudWatchに転送し、カスタムメトリクスとして反映。

5. SSM State Managerでスクリプトを自動実行し、監視を継続
スクリプトを自動で呼び出すことで、継続的な監視を実現します。

この方法を使えば、CloudWatch標準メトリクスでは得られない「物理メモリの使用率」をダッシュボードに表示でき、さらにインスタンスタイプを変更しても自動的に正しい利用率を取得できます。

なぜ標準メトリクスだけでは不十分なのか?

ここで改めて、標準メトリクスの限界を整理してみましょう。
CloudWatch Agentが提供する代表的な指標「Memory % Committed Bytes In Use」は、ページファイルを含んだ「仮想メモリ全体」の使用率です。Windowsは物理メモリが不足すると自動的にページファイルに退避しますが、これはアプリケーションから見ればパフォーマンス低下を招く動作です。

つまり、この値だけを監視していると「物理メモリは逼迫しているのに数値は健全に見える」 という状態が発生します。
結果として、気づかないうちにスワップ多発によるレスポンス低下や障害を招くリスクがあります。

そのため、実運用ではページファイル込みの全体像とあわせて「物理メモリ使用率」という指標を追加することが有効です。

物理メモリ監視の設定

では、実際にこの仕組みを構築する際の手順を紹介いたします。

Step 0:前提条件

あらかじめ以下を準備しておきます。

  • Windows ServerのEC2(SSM Agentが稼働)
  • EC2アタッチのIAMロールに以下のポリシーを付与済み
    AmazonSSMManagedInstanceCore
    CloudWatchAgentServerPolicy
  • SNSトピックを事前作成し、通知先メールを購読承認済み
  • AWS CLI実行環境(CloudShellを推奨)
  • 環境変数設定
export REGION=ap-northeast-1
export TARGET_TAG_KEY=MemWatch
export TARGET_TAG_VAL=true
export PARAM_NAME="AmazonCloudWatch-windows-statsd"
export NAMESPACE=CWAgent
export METRIC_KEY=windows.mem.physical.used_percent
export TOPIC_ARN="arn:aws:sns:ap-northeast-1:アカウントID:mem-alarms"

これらを満たしていれば、以降のステップをスムーズに進められます。

Step 1:監視対象をタグで指定

まずは監視対象インスタンスをタグで管理します。

# IAMロール付きのWindows EC2を自動検出
INSTANCE_IDS=$(aws ec2 describe-instances \
  --region $REGION \
  --filters "Name=platform,Values=windows" \
    "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[?IamInstanceProfile!=`null`].InstanceId' \
  --output text)

if [ -z "$INSTANCE_IDS" ]; then
  echo "エラー: IAMロール付きのWindows EC2が見つかりません"
  exit 1
fi

echo "タグを設定するインスタンス:"
echo "$INSTANCE_IDS" | tr ' ' '\n'

# タグ設定実行
for IID in $INSTANCE_IDS; do
  aws ec2 create-tags \
    --region $REGION \
    --resources $IID \
    --tags Key=$TARGET_TAG_KEY,Value=$TARGET_TAG_VAL
  echo "タグ設定完了: $IID"
done

タグを付与しておけば、State ManagerやRun Commandの対象を一括で指定でき、今後の運用も楽になります。

Step 2:CloudWatch AgentのStatsD設定をParameter Storeに保存

次に、CloudWatch Agentの設定をSSM Parameter Storeに格納します。

cwagent-statsd.json を作成し、以下のように保存します。

# StatsD受信設定作成
cat > cwagent-statsd.json <<'JSON'
{
  "agent": {
    "metrics_collection_interval": 60,
    "logfile": "c:\\ProgramData\\Amazon\\AmazonCloudWatchAgent\\Logs\\amazon-cloudwatch-agent.log"
  },
  "metrics": {
    "namespace": "CWAgent",
    "append_dimensions": {
      "InstanceId": "${aws:InstanceId}"
    },
    "aggregation_dimensions": [["InstanceId"]],
    "metrics_collected": {
      "statsd": {
        "service_address": ":8125",
        "metrics_collection_interval": 60,
        "metrics_aggregation_interval": 60,
        "metric_separator": "."
      }
    }
  }
}
JSON

# Parameter Store保存
aws ssm put-parameter \
  --region $REGION \
  --name "$PARAM_NAME" \
  --type String \
  --overwrite \
  --value "file://cwagent-statsd.json" \
  --description "CloudWatch Agent config for StatsD memory metrics"

echo "Parameter Store保存完了: $PARAM_NAME"

Parameter Storeを使うことで、設定を一元管理し、複数インスタンスにも容易に展開できます。

Step 3:CloudWatch Agentをインストール・設定適用

Agentをインストールし、Parameter Storeに保存した設定を反映します。

# CloudWatch Agentインストール
echo "CloudWatch Agentインストール開始..."
INSTALL_CMD_ID=$(aws ssm send-command \
  --region $REGION \
  --document-name "AWS-ConfigureAWSPackage" \
  --targets "Key=tag:${TARGET_TAG_KEY},Values=${TARGET_TAG_VAL}" \
  --parameters '{"action":["Install"],"installationType":["Uninstall and reinstall"],"name":["AmazonCloudWatchAgent"],"version":["latest"]}' \
  --comment "Install CloudWatch Agent for memory monitoring" \
  --query 'Command.CommandId' \
  --output text)

echo "インストール中... (Command ID: $INSTALL_CMD_ID)"
sleep 30

# 既存Agent停止
aws ssm send-command \
  --region $REGION \
  --document-name "AmazonCloudWatch-ManageAgent" \
  --targets "Key=tag:${TARGET_TAG_KEY},Values=${TARGET_TAG_VAL}" \
  --parameters '{"action":["stop"],"mode":["ec2"]}' \
  --no-cli-pager > /dev/null 2>&1

sleep 5

# 設定適用して起動
echo "CloudWatch Agent設定適用..."
CONFIG_CMD_ID=$(aws ssm send-command \
  --region $REGION \
  --document-name "AmazonCloudWatch-ManageAgent" \
  --targets "Key=tag:${TARGET_TAG_KEY},Values=${TARGET_TAG_VAL}" \
  --parameters "{\"action\":[\"configure\"],\"mode\":[\"ec2\"],\"optionalConfigurationSource\":[\"ssm\"],\"optionalConfigurationLocation\":[\"${PARAM_NAME}\"],\"optionalRestart\":[\"yes\"]}" \
  --comment "Configure CloudWatch Agent with StatsD" \
  --query 'Command.CommandId' \
  --output text)

echo "CloudWatch Agent設定完了 (Command ID: $CONFIG_CMD_ID)"

既存の環境でもこの方法で再適用すれば、常にクリーンな設定で統一できます。

Step 4:PowerShellスクリプトをState Managerで定期実行

以下のスクリプトは29分間ループして1分ごとに物理メモリ使用率を送信します。

# 既存Association削除(存在する場合)
EXISTING_ASSOC=$(aws ssm describe-association \
  --region $REGION \
  --association-name "CWAgent-PhysicalMem-UsedPercent" \
  --query 'AssociationDescription.AssociationId' \
  --output text 2>/dev/null || echo "")

if [ ! -z "$EXISTING_ASSOC" ] && [ "$EXISTING_ASSOC" != "None" ]; then
  echo "既存Association削除: $EXISTING_ASSOC"
  aws ssm delete-association \
    --region $REGION \
    --association-id "$EXISTING_ASSOC"
  sleep 5
fi

# 30分間継続的にメトリクスを送信するPowerShellスクリプト
cat > parameters.json <<'EOF'
{
  "commands": [
    "$endTime = (Get-Date).AddMinutes(29); while ((Get-Date) -lt $endTime) { try { $os=Get-CimInstance Win32_OperatingSystem; $totalMB=[double]($os.TotalVisibleMemorySize/1024); $freeMB=[double]($os.FreePhysicalMemory/1024); if($totalMB -eq 0) { throw 'Total memory is zero' }; $usedPct=100.0-(($freeMB/$totalMB)*100.0); $metric=('windows.mem.physical.used_percent:{0}|g' -f [math]::Round($usedPct,2)); $udp=New-Object System.Net.Sockets.UdpClient; $bytes=[Text.Encoding]::ASCII.GetBytes($metric); [void]$udp.Send($bytes,$bytes.Length,'127.0.0.1',8125); $udp.Close(); Write-Host \"Sent: $metric at $(Get-Date)\" } catch { Write-Error $_.Exception.Message }; Start-Sleep -Seconds 60 }"
  ]
}
EOF

# Association作成(30分間隔)
ASSOC_ID=$(aws ssm create-association \
  --region $REGION \
  --name "AWS-RunPowerShellScript" \
  --association-name "CWAgent-PhysicalMem-UsedPercent" \
  --targets "Key=tag:${TARGET_TAG_KEY},Values=${TARGET_TAG_VAL}" \
  --schedule-expression "cron(0 0/30 * 1/1 * ? *)" \
  --parameters file://parameters.json \
  --max-errors "10%" \
  --max-concurrency "10" \
  --query 'AssociationDescription.AssociationId' \
  --output text)

echo "State Manager Association作成完了: $ASSOC_ID"
echo "30分ごとに起動し、各回29分間メトリクスを送信します"

# 一時ファイル削除
rm parameters.json

# 即座に実行
echo "初回実行を開始..."
aws ssm start-associations-once \
  --region $REGION \
  --association-ids "$ASSOC_ID"

State ManagerのAssociationを30分間隔で作成し、このスクリプトを呼び出すようにします。

Step 5:CloudWatchアラームを作成

次にアラームを作成します。

以下は物理メモリ使用率が 85%を5分連続で超過した場合 にSNS通知する例です。

# 対象インスタンス取得
IID_LIST=$(aws ec2 describe-instances \
  --region $REGION \
  --filters "Name=tag:${TARGET_TAG_KEY},Values=${TARGET_TAG_VAL}" \
    "Name=platform,Values=windows" \
    "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].InstanceId' \
  --output text)

echo "アラーム作成対象インスタンス:"
echo "$IID_LIST" | tr ' ' '\n'

# 各インスタンスにアラーム作成
for IID in $IID_LIST; do
  aws cloudwatch put-metric-alarm \
    --region $REGION \
    --alarm-name "High-PhysicalMem-UsedPercent-${IID}" \
    --alarm-description "Physical memory usage exceeds 85% for 5 minutes" \
    --namespace "$NAMESPACE" \
    --metric-name "$METRIC_KEY" \
    --dimensions Name=InstanceId,Value=$IID \
    --statistic Average \
    --period 60 \
    --evaluation-periods 5 \
    --threshold 85 \
    --comparison-operator GreaterThanOrEqualToThreshold \
    --treat-missing-data notBreaching \
    --alarm-actions "$TOPIC_ARN"
    
  echo "アラーム作成完了: $IID"
done

Step 6:動作確認

最後に、メトリクスが正しく送信されているか確認します。

aws cloudwatch get-metric-statistics \
  --region ap-northeast-1 \
  --namespace "CWAgent" \
  --metric-name "windows.mem.physical.used_percent" \
  --dimensions Name=InstanceId,Value=i-xxxxxxxx \
  --start-time $(date -u -d '-10 minutes' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 60 \
  --statistics Average

意図的にメモリ負荷を与えてしきい値を超えるようにし、SNS通知が届くかテストするとさらに安心です。

まとめ

AWSが提供する標準メトリクスはOS全体像の把握には便利ですが、アプリケーションの実効メモリを測るには不十分です。

今回の方法では、PowerShell+StatsD+CloudWatch Agentというシンプルな構成で、インスタンスタイプ変更に追従する物理メモリ監視を実現しています。

「どのくらいまで使えるのか?」「インスタンスタイプを変えるべきか?」といった判断を支援する強力な仕組みとなるでしょう。


元記事発行日: 2025年12月24日、最終更新日: 2025年12月11日