こしあん
2024-12-22

CloudWatch MetricsによるEC2のディスク使用率の集計とDashboardの可視化を試す


50{icon} {views}


EC2でCloudWatch Agentをインストールし、ディスク使用率をCloudWatch Metricsに送信して可視化しました。Terraformでインフラをコード化し、標準メトリクスとあわせてダッシュボード上で一元的に監視できるように構築しています。

はじめに

  • EC2のCloudWatch Agentをインストールして、EC2のディスク使用率をCloudWatch Metricsとして発行してみました。
  • 前提として、CPU使用率やネットワーク転送量は何もしなくても標準メトリクスで実現できますが、ディスク使用率は標準メトリクスだけでは実装できません。CloudWatch Agentのインストールが必要になります。
  • Metricsとして公開しただけではCloudWatch Metricsの画面に飛ばないとグラフ化できないので、CloudWatch Dashboardを作成し、標準メトリクスとセットで可視化をしてみます。

流れ

Terraformのコードで記述しますが、以下の通りに進めます

  • EC2をプライベートサブネットにデプロイする(Session Managerで接続するため)。プライベートサブネットはNATゲートウェイやNATインスタンスと接続されているものとします。
  • EC2のユーザーデータでCloudWatch Agentをインストールし、ディスク使用率を出力するためのメトリクスを作成する。それをデーモン化する
  • CloudWatch Metricsから可視化ができているか確認する
  • それをCloudWatch Dashboardで標準メトリクスと統合して可視化する

Terraformのコード

var.subnet_idにサブネットIDを代入済みとします。var.instance_typet3.microとしました。

# IAMロールの作成
resource "aws_iam_role" "ec2_ssm_role" {
  name = "ec2_ssm_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Effect = "Allow",
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

# IAMポリシーのアタッチ
resource "aws_iam_role_policy_attachment" "ssm_managed_policy" {
  role       = aws_iam_role.ec2_ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "cloudwatch_agent_policy" {
  role       = aws_iam_role.ec2_ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}

# IAMインスタンスプロファイルの作成
resource "aws_iam_instance_profile" "ec2_instance_profile" {
  name = "ec2_instance_profile"
  role = aws_iam_role.ec2_ssm_role.name
}

# セキュリティグループの作成
resource "aws_security_group" "ec2_sg" {
  name        = "ec2_sg"
  description = "Security group for EC2 instance"
  vpc_id      = data.aws_subnet.selected.vpc_id

  # 必要に応じてインバウンドルールを追加
  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
    description      = "Allow all outbound traffic"
  }
}

# サブネット情報の取得
data "aws_subnet" "selected" {
  id = var.subnet_id
}

# 最新のUbuntu AMIを取得
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # CanonicalのAMI所有者ID

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# EC2インスタンスの作成
resource "aws_instance" "ubuntu_ec2" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [aws_security_group.ec2_sg.id]

  associate_public_ip_address = false
  iam_instance_profile        = aws_iam_instance_profile.ec2_instance_profile.name

  user_data = <<-EOF
#!/bin/bash
set -e

# パッケージインデックスの更新
apt-get update -y

# 必要なパッケージのインストール
apt-get install -y unzip wget

# CloudWatchエージェントのダウンロード
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb

# CloudWatchエージェントのインストール
dpkg -i amazon-cloudwatch-agent.deb

# CloudWatchエージェントの設定ファイル作成
cat <<EOT > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
{
  "agent": {
    "metrics_collection_interval": 60,
    "logfile": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
  },
  "metrics": {
    "metrics_collected": {
      "disk": {
        "measurement": [
          "used_percent"
        ],
        "resources": [
          "/"
        ],
        "ignore_file_system_types": [
          "sysfs", "proc", "tmpfs", "devtmpfs", "devfs"
        ]
      }
    }
  }
}
EOT

# CloudWatchエージェントの起動
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config \
  -m ec2 \
  -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json \
  -s
EOF


  tags = {
    Name = "Ubuntu-CloudWatch-EC2"
  }
}

# AWS Region
data "aws_region" "current" {}

# CloudWatchダッシュボードの作成
resource "aws_cloudwatch_dashboard" "ec2_dashboard" {
  dashboard_name = "EC2-Dashboard"

  dashboard_body = jsonencode({
    widgets = [
      {
        type = "metric",
        x    = 0,
        y    = 0,
        width = 12,
        height = 6,
        properties = {
          view = "timeSeries",
          title = "CPU Utilization",
          region = data.aws_region.current.name,
          metrics = [
            [ "AWS/EC2", "CPUUtilization", "InstanceId", aws_instance.ubuntu_ec2.id ]
          ],
          period = 300,
          stat = "Average",
          region = data.aws_region.current.name,
          yAxis = {
            left = {
              min = 0,
              max = 100
            }
          }
        }
      },
      {
        type = "metric",
        x    = 12,
        y    = 0,
        width = 12,
        height = 6,
        properties = {
          view = "timeSeries",
          title = "Network In/Out",
          region = data.aws_region.current.name,
          metrics = [
            [ "AWS/EC2", "NetworkIn", "InstanceId", aws_instance.ubuntu_ec2.id ],
            [ ".", "NetworkOut", ".", "." ]
          ],
          period = 300,
          stat = "Sum",
          region = data.aws_region.current.name,
          yAxis = {
            left = {
              label = "Bytes",
              min = 0
            }
          }
        }
      },
      {
        type = "metric",
        x    = 0,
        y    = 6,
        width = 24,
        height = 6,
        properties = {
          view = "timeSeries",
          title = "Disk Usage Percentage",
          region = data.aws_region.current.name,
          metrics = [
            [
              "CWAgent", 
              "disk_used_percent", 
              "device",   "nvme0n1p1",
              "fstype",   "ext4",
              "path",     "/",
              "host",     "ip-${replace(aws_instance.ubuntu_ec2.private_ip, ".", "-")}"
            ]
          ],
          period = 300,
          stat = "Average",
          region = data.aws_region.current.name,
          yAxis = {
            left = {
              min = 0,
              max = 100
            }
          }
        }
      }
    ]
  })
}

# 出力情報(必要に応じて追加)
output "instance_id" {
  description = "The ID of the EC2 instance"
  value       = aws_instance.ubuntu_ec2.id
}

# 出力情報(ダッシュボードURL)
output "dashboard_url" {
  description = "CloudWatch DashboardのURL"
  value       = "https://${data.aws_region.current.name}.console.aws.amazon.com/cloudwatch/home?region=${data.aws_region.current.name}#dashboards:name=${aws_cloudwatch_dashboard.ec2_dashboard.dashboard_name}"
}

最初にEC2にアタッチするためのインスタンスプロファイルのためのロールec2_ssm_roleを作成しています。Session Managerに必要なAmazonSSMManagedInstanceCore、CloudWatch Agentに必要なCloudWatchAgentServerPolicyのマネージドポリシーをアタッチ。

セキュリティグループは、セッションマネージャーだとインバウンドは不要。ただ、出ていくための通信を許可する必要があるので、アウトバンドのIPv4とv6を許可。

AMIは、Canonicalが提供しているAMI(Ubuntuの公式AMI)のうち、Ubuntu24.04のx64版を選択。ただし、nameプレフィックスが24.04では少し変わっており、以下のようにgp3(EBSのストレージ)を明記しないとクエリでAMIが出てこない。ChatGPTは知識が古く、ここをhvm-ssdとしてしまうため注意が必要。

ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*

結果

CloudWatch Metrics単体で見ると以下の通り。これはディスク使用率のメトリック。

作成されたダッシュボード。CloudWatch Dashboardで標準メトリクスといっしょに可視化している。

EC2上で、定期的に10MBのファイルを作成するプログラムを動かしているため、どんどん使用率が上がっている。

解説

ユーザーデータについて

ユーザーデータでは、EC2起動時に自動で実行するスクリプトを定義しています。このスクリプトでは、以下の手順でAmazon CloudWatch Agentをインストールし、EC2インスタンスのメトリクスをCloudWatchに送信する設定を行っています。

1) apt-get update -y
・パッケージインデックスの更新を行います。

2) apt-get install -y unzip wget
・ダウンロード用のwgetと、ZIPファイルを扱うためのunzipをインストールします。

3) CloudWatch Agentのダウンロードとインストール
・wgetでCloudWatch Agentのdebパッケージ (amazon-cloudwatch-agent.deb) を取得し、dpkgコマンドでインストールします。

4) 設定ファイルの作成
/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json にCloudWatch Agentの設定をJSON形式で書き込みます。
– metrics_collection_interval: 60
→ 60秒間隔でメトリクスを収集。
– diskメトリクス: “/”(ルートディスク) の used_percent(使用率) を収集し、それ以外に sysfs、proc、tmpfs、devtmpfs、devfs は除外します。

5) CloudWatchエージェントの起動
amazon-cloudwatch-agent-ctl コマンドで設定ファイルを読み込み、CloudWatchエージェントを有効にします。

上記の設定により、EC2インスタンスが起動すると同時にCloudWatchエージェントが導入・起動され、ルートディスクの使用率 (disk_used_percent) やそのほか必要なメトリクス(今後追加すれば)をクラウド側に送信可能になります。

CloudWatch Agentのホストの送信について

特に設定しなくても、どのEC2から送信したか(ホスト名)は自動送信されます。ただし、プライベートサブネットにEC2をデプロイした場合、ip-172-18-4-30のようなプライベートIPになります。したがって、インスタンスIDや任意の名前がホスト名としてデフォルトで送られるわけではありません。

もしインスタンスIDを送る場合は、IMDSv2からメタデータを取得するなどして、別途送信が必要になります。ただ少し面倒になるので今回は割愛します。

ユーザーデータのデバッグ

EC2に接続して確認します。EC2のマネジメントコンソールから接続→Session Managerが手軽です。

以下のコマンドで、ユーザーデータを実行時の出力が確認できます。エラーが出ていればここに出ます。

sudo cat /var/log/cloud-init-output.log

また、CloudWatch Agentがデーモン化されているかは以下のコマンドで確認できます(ユーザーデータのセットアップに1分ぐらいかかるので、EC2を起動してすぐだと出ないかもしれません)

systemctl status amazon-cloudwatch-agent

以下のようにactive (runnig)と表示されていればOK。

$ systemctl status amazon-cloudwatch-agent
● amazon-cloudwatch-agent.service - Amazon CloudWatch Agent
     Loaded: loaded (/etc/systemd/system/amazon-cloudwatch-agent.service; enabled; preset: enabled)
     Active: active (running) since Sun 2024-12-22 03:19:05 UTC; 2h 32min ago
   Main PID: 1514 (amazon-cloudwat)
      Tasks: 8 (limit: 1078)
     Memory: 23.1M (peak: 25.4M)
        CPU: 31.691s
     CGroup: /system.slice/amazon-cloudwatch-agent.service
             └─1514 /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent -config /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.toml -envconfig /opt/aws/amazo>

Warning: some journal files were not opened due to insufficient permissions.

ダッシュボードについて

aws_cloudwatch_dashboard リソースでは、ダッシュボードのレイアウトとウィジェットをJSON形式 (dashboard_body) で指定しています。

1) dashboard_name: “EC2-Dashboard”
・CloudWatchコンソール上でのダッシュボードの名前。

2) dashboard_body: jsonencode({ … })
・ダッシュボードに配置するウィジェットの定義をJSONで書き、jsonencodeでTerraformのコードに落とし込みます。
・ここでは下記のように3つのウィジェットを配置しています。

(1) CPU Utilization

  {
     type       = "metric"
     x          = 0
     y          = 0
     width      = 12
     height     = 6
     properties = {
       view   = "timeSeries"
       title  = "CPU Utilization"
       region = data.aws_region.current.name
       metrics = [
         [ "AWS/EC2", "CPUUtilization", "InstanceId", aws_instance.ubuntu_ec2.id ]
       ]
       period = 300
       stat   = "Average"
       yAxis  = {
         left = {
           min = 0
           max = 100
         }
       }
     }
   }  
  • CPU使用率 (CPUUtilization) をモニタするためのウィジェット
  • period = 300 (5分間隔) で平均値を表示
  • Y軸は0~100%で固定

(2) Network In/Out

  {
     type       = "metric"
     x          = 12
     y          = 0
     width      = 12
     height     = 6
     properties = {
       view   = "timeSeries"
       title  = "Network In/Out"
       region = data.aws_region.current.name
       metrics = [
         [ "AWS/EC2", "NetworkIn",  "InstanceId", aws_instance.ubuntu_ec2.id ],
         [ ".",       "NetworkOut", ".",          "."                       ]
       ]
       period = 300
       stat   = "Sum"
       yAxis  = {
         left = {
           label = "Bytes"
           min   = 0
         }
       }
     }
   }  
  • ネットワークの IN と OUT の合計バイト数に関するメトリクスを同時にプロット
  • period = 300 でスケールが合わせやすいように5分単位の合計値を表示

(3) Disk Usage Percentage

   {
     type = "metric"
     x    = 0
     y    = 6
     width = 24
     height = 6
     properties = {
       view   = "timeSeries"
       title  = "Disk Usage Percentage"
       region = data.aws_region.current.name
       metrics = [
         [
           "CWAgent", 
           "disk_used_percent", 
           "device",   "nvme0n1p1",
           "fstype",   "ext4",
           "path",     "/",
           "host",     "ip-${replace(aws_instance.ubuntu_ec2.private_ip, ".", "-")}"
         ]
       ]
       period = 300
       stat   = "Average"
       yAxis  = {
         left = {
           min = 0
           max = 100
         }
       }
     }
   }  
  • CloudWatchエージェント経由で送信された `”disk_used_percent” (ルート領域) を表示
  • “CWAgent” はクラウドウォッチエージェントから送信されるデフォルトの名前空間
  • device や fstype, path, host のように複数のディメンションを指定
    • host が "ip-XXX-XXX-XXX-XXX" 形式になるよう、Terraform内でプライベートIPアドレス (aws_instance.ubuntu_ec2.private_ip) の “.” を “-” に置き換えています。これにより、エージェント側のDimension(ホスト名)と一致したメトリクスを取得できます。
  • period は300秒 (5分) ごとに、stat = “Average” の値を表示
  • Y軸を 0~100% に指定

参考:EC2上で定期的にファイル作成するプログラム

#!/bin/bash

# 保存先ディレクトリを指定(必要に応じて変更)
OUTPUT_DIR="./output_files"

# ディレクトリが存在しない場合は作成
mkdir -p "$OUTPUT_DIR"

counter=1

while true; do
    # 現在の日時を取得してファイル名に含める(ユニーク性を確保)
    timestamp=$(date +"%Y%m%d_%H%M%S")
    filename="${OUTPUT_DIR}/file_${timestamp}_${counter}.dat"

    # 10MBのファイルを作成
    dd if=/dev/urandom of="$filename" bs=1M count=10 status=none

    # 作成したファイル名を表示
    echo "Created $filename"

    # カウンターをインクリメント
    counter=$((counter + 1))

    # 10秒間待機
    sleep 10
done


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です