Windowsインスタンスを構築してCloudWatch Alarmを試験してみる

2024.02.24

はじめに

データアナリティクス事業本部ビッグデータチームのyosh-kです。
今回は、Windows OSのEC2インスタンスを構築し、CloudWatch Alarmを設定してAlarmが発報されるまでの負荷試験を実施したいと思います。

前提

今回の要件は以下になります。

  • EC2インスタンス内に以下のソフトウェアをインストール
    • chocolatey
    • awscli
    • GoogleChrome
    • vscode
    • Microsoft Visual C++ Redistributable
  • EBSの容量は150GBであり、内訳は以下とする。
    • Cドライブ: 100
    • Dドライブ: 50
  • CloudWatch Alarmでは以下を監視する。
    • 30 分内の6データポイントのCPUUtilization > 80
    • 15 分内の2データポイントのメモリ使用量 > 75
    • 15 分内の2データポイントのディスク使用量 < 20

全体的な構成としては以下になります。

  • CloudWathはEC2インスタンスからメトリクスを取得します。メモリ使用量やディスク使用量のメトリクスはデフォルトでは取得できないため、CloudWath AgentをEC2インスタンス内にインストールして取得できるように設定します。
  • CloudWath Alarmを条件に合わせて設定します。
  • Alarm条件となった場合は、EventBridgeでステータスがALARMとなったことを検知し、EventBridgeのTargetとして、Step Functionsを設定します。
  • Step FunctionsでSNS TOPICとmessageを指定し、Publishすることで、該当のメールアドレスに送信します。

今回Step Functionsを使用している理由は、SNS TOPICでのPublish時に日本語でのメール文言で送信するためになります。 今回の構成については、以下記事を参考に実装しています。

実装

それでは実装になります。実装コードはリンクに格納しています。

windows_ec2.yml

AWSTemplateFormatVersion: 2010-09-09
Description: Windows Server for IICS Secure Agent
Parameters:
  Prefix:
    Description: Prefix
    Type: String
  AZ1:
    Description: AZ1
    Type: String
    Default: a
  LatestWindowsAmiId :
    Type : 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    AllowedValues: 
      - '/aws/service/ami-windows-latest/Windows_Server-2016-Japanese-Full-Base'
      - '/aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base'
      - '/aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base'
    Default: '/aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base'
  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
    Type: String
    MinLength: '1'
    MaxLength: '64'
    AllowedPattern: '[-_ a-zA-Z0-9]*'
    ConstraintDescription: can contain only alphanumeric characters, spaces, dashes
      and underscores.

Resources:
  #-----------------------------------------------------------------------------
  # VPC
  #-----------------------------------------------------------------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 192.168.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}-vpc"
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}-Public-rtb"
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}${AZ1}"
      VpcId: !Ref VPC
      CidrBlock: 192.168.0.0/28
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}-Public-subnet"
  PublicSubnetAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId:
        Ref: PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Application
        Value:
          Ref: AWS::StackId
      - Key: Network
        Value: Public
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: VPC
      InternetGatewayId:
        Ref: InternetGateway

  #-----------------------------------------------------------------------------
  # EC2 Instance
  #-----------------------------------------------------------------------------
  InstanceWindows:
    Type: "AWS::EC2::Instance"
    Properties:
      IamInstanceProfile: !Ref ServerProfile
      ImageId: !Ref LatestWindowsAmiId
      InstanceType: m5a.xlarge
      KeyName:
        Ref: KeyName
      BlockDeviceMappings:
      - DeviceName: "/dev/sda1" # C ドライブ
        Ebs:
          VolumeType: 'gp2'
          VolumeSize: 150
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
      UserData:
        Fn::Base64: |
          <script>
          @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command " [System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
          tzutil.exe /s "Tokyo Standard Time"
          Set-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -name "HideFileExt" -Value 0
          Set-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -name "Hidden" -Value 1
          Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
          choco install awscli -y
          choco install GoogleChrome -y
          choco install vscode -y
          @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "
          $installerUrl = 'https://aka.ms/vs/17/release/vc_redist.x64.exe'
          $installerPath = '$env:TEMP\vc_redist.x64.exe'
          Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
          Start-Process -FilePath $installerPath -ArgumentList '/install', '/quiet', '/norestart' -Wait
          Remove-Item $installerPath
          " && echo VC++ 2015 Redistributable installed
          </script>
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}-windows-instance"
  #-----------------------------------------------------------------------------
  # IAM
  #-----------------------------------------------------------------------------
  ServerRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action:
                - "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
        - "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
  S3BucketPolicyForSSM:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: S3BucketPolicyForSSM
      Roles:
        - !Ref ServerRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Action: "s3:GetObject"
            Resource:
              - !Sub "arn:aws:s3:::aws-ssm-${AWS::Region}/*"
              - !Sub "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*"
              - !Sub "arn:aws:s3:::amazon-ssm-${AWS::Region}/*"
              - !Sub "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*"
              - !Sub "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*"
              - !Sub "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*"
  ServerProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: "/"
      Roles:
        - Ref: ServerRole
      InstanceProfileName: !Sub "${Prefix}-Server"

VPCなどのネットワークとEC2インスタンス、EC2インスタンス用IAM Roleを定義しています。 以下記事の内容をCOPYしたものとなりますが、追加でUserDataにMicrosoft Visual C++ Redistributableをインストールするコマンドを実装しています。

health_resource_check_role.yml

AWSTemplateFormatVersion: 2010-09-09
Description: Template for Creating Statemachine SNS Publish Role

Resources:
  StateMachineSNSPublishRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "SNSPublishFromStateMachineRole"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - states.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: StateMachineSNSPublishPolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Resource: "*"
                Action:
                  - sns:Publish
              
  EventBridgeStateMachineExecuteRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "EventBridgeToStateMachineExecutionRole"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - events.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: EventBridgeStateMachineExecutePolicy
          PolicyDocument:
            Statement:
              - Effect: Allow
                Resource: "*"
                Action:
                  - states:StartExecution

Alarmが発報した際に使用するEventBridgeとStep FunctionsのIAM Roleを定義しています。

health_resource_check_sns.yml

AWSTemplateFormatVersion: 2010-09-09
Description: SNS for CloudWatch Alarm notification for dev

Parameters:
  NotificateDestinationEmail:
    Description: Destination Email of for SNS
    Type: String

Resources:
  ######################## 共通りソース ########################

  ######################
  #     SNSトピック     #
  ######################
  # 通知先設定

  ResourceSnsTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: CloudWatchAlarmResourceNotificationTopic
      Subscription:
        - Endpoint: !Sub ${NotificateDestinationEmail}
          Protocol: email

Alarmが発報してSNS PublishするSNS TOPICを定義しています。

health_resource_check_alarm.yml

AWSTemplateFormatVersion: 2010-09-09
Description: CWAlarm & SNS notification

Parameters:
  StatemachineExecRoleARN:
    Description: State Machine Execute Role ARN
    Type: String
  SNSPublishRoleARN:
    Description: SNS Publish Role ARN
    Type: String
  InstanceId:
    Description: InstanceId which is watched for CPUUtilization
    Type: String
  InstanceType:
    Description: InstanceType which is watched for Memory
    Type: String
  CloudWatchAlarmResourceNotificationTopicARN:
    Description: ARN of CloudWatchAlarmResourceNotificationTopic
    Type: String

Resources:
  ######################## 監視対象ごとに作成するりソース ########################

  ######################
  # CloudWatch アラーム #
  ######################

  CPUAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: cpu-check-alarm
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 6
      EvaluationPeriods: 6
      Threshold: 80
      Namespace: AWS/EC2
      Dimensions:
          - Name: InstanceId
            Value: !Ref InstanceId
      MetricName: CPUUtilization
      Period: 300
      Statistic: Maximum
      TreatMissingData: notBreaching

  MemoryAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: memory-check-alarm
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 2
      EvaluationPeriods: 3
      Threshold: 75
      MetricName: "Memory % Committed Bytes In Use"
      Namespace: CWAgent
      Dimensions:
          - Name: InstanceId
            Value: !Ref InstanceId
          - Name: objectname
            Value: Memory
          - Name: InstanceType
            Value: !Ref InstanceType
      Period: 300
      Statistic: Maximum
      TreatMissingData: notBreaching

  CDiskAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: c-disk-free-space-alarm
      ComparisonOperator: LessThanThreshold
      DatapointsToAlarm: 2
      EvaluationPeriods: 3
      Threshold: 20
      MetricName: "LogicalDisk % Free Space"
      Namespace: CWAgent
      Dimensions:
        - Name: instance
          Value: "C:"
        - Name: InstanceId
          Value: !Ref InstanceId
        - Name: objectname
          Value: "LogicalDisk"
        - Name: InstanceType
          Value: !Ref InstanceType
      Period: 300
      Statistic: Minimum
      TreatMissingData: notBreaching

  DDiskAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: d-disk-free-space-alarm
      ComparisonOperator: LessThanThreshold
      DatapointsToAlarm: 2
      EvaluationPeriods: 3
      Threshold: 20
      MetricName: "LogicalDisk % Free Space"
      Namespace: CWAgent
      Dimensions:
        - Name: instance
          Value: "D:"
        - Name: InstanceId
          Value: !Ref InstanceId
        - Name: objectname
          Value: "LogicalDisk"
        - Name: InstanceType
          Value: !Ref InstanceType
      Period: 300
      Statistic: Minimum
      TreatMissingData: notBreaching

  ######################
  #     EventBridge    #
  ######################
  CPUAlarmEvent:
    Type: AWS::Events::Rule
    Properties: 
      Description: String
      Name: cpu-check-event
      EventPattern: !Sub |
        {
          "source": ["aws.cloudwatch"],
          "detail-type": ["CloudWatch Alarm State Change"],
          "resources": [{"prefix": "arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:cpu-check-alarm"}],
          "detail": {"state": {"value": ["ALARM"]}}
        }
      Targets: 
        - Arn: !Ref SNSResourceStateMachine
          Id: step-function
          RoleArn: !Ref StatemachineExecRoleARN
          InputTransformer:
            InputPathsMap:
              "Account": "$.account"
              "AlarmName": "$.detail.alarmName"
              "MetricsName": "$.detail.configuration.metrics[0].metricStat.metric.name"
              "Reason": "$.detail.state.reason"
              "Time": "$.time"
            InputTemplate: |
              {
                "subject": "CPU使用率超過通知",
                "message": "CPU使用率超過通知 \n AWSアカウントID: \"<Account>\" \n 時間: \"<Time>\" \n アラーム名: \"<AlarmName>\" \n メトリック: \"<MetricsName>\" \n 理由: \"<Reason>\""
              }

  MemoryAlarmEvent:
    Type: AWS::Events::Rule
    Properties: 
      Description: String
      Name: memory-check-event
      EventPattern: !Sub |
        {
          "source": ["aws.cloudwatch"],
          "detail-type": ["CloudWatch Alarm State Change"],
          "resources": [{"prefix": "arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:memory-check-alarm"}],
          "detail": {"state": {"value": ["ALARM"]}}
        }
      Targets: 
        - Arn: !Ref SNSResourceStateMachine
          Id: step-function
          RoleArn: !Ref StatemachineExecRoleARN
          InputTransformer:
            InputPathsMap:
              "Account": "$.account"
              "AlarmName": "$.detail.alarmName"
              "MetricsName": "$.detail.configuration.metrics[0].metricStat.metric.name"
              "Reason": "$.detail.state.reason"
              "Time": "$.time"
            InputTemplate: |
              {
                "subject": "メモリ使用率超過通知",
                "message": "メモリ使用率超過通知 \n AWSアカウントID: \"<Account>\" \n 時間: \"<Time>\" \n アラーム名: \"<AlarmName>\" \n メトリック: \"<MetricsName>\" \n 理由: \"<Reason>\""
              }

  CDiskAlarmEvent:
    Type: AWS::Events::Rule
    Properties: 
      Description: String
      Name: c-disk-check-event
      EventPattern: !Sub |
        {
          "source": ["aws.cloudwatch"],
          "detail-type": ["CloudWatch Alarm State Change"],
          "resources": [{"prefix": "arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:c-disk-free-space-alarm"}],
          "detail": {"state": {"value": ["ALARM"]}}
        }
      Targets: 
        - Arn: !Ref SNSResourceStateMachine
          Id: step-function
          RoleArn: !Ref StatemachineExecRoleARN
          InputTransformer:
            InputPathsMap:
              "Account": "$.account"
              "AlarmName": "$.detail.alarmName"
              "MetricsName": "$.detail.configuration.metrics[0].metricStat.metric.name"
              "Reason": "$.detail.state.reason"
              "Time": "$.time"
            InputTemplate: |
              {
                "subject": "Cドライブディスク使用率超過通知",
                "message": "Cドライブディスク使用率超過通知 \n AWSアカウントID: \"<Account>\" \n 時間: \"<Time>\" \n アラーム名: \"<AlarmName>\" \n メトリック: \"<MetricsName>\" \n 理由: \"<Reason>\""
              }
  DDiskAlarmEvent:
    Type: AWS::Events::Rule
    Properties: 
      Description: String
      Name: !Sub "d-disk-check-event"
      EventPattern: !Sub |
        {
          "source": ["aws.cloudwatch"],
          "detail-type": ["CloudWatch Alarm State Change"],
          "resources": [{"prefix": "arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:d-disk-free-space-alarm"}],
          "detail": {"state": {"value": ["ALARM"]}}
        }
      Targets: 
        - Arn: !Ref SNSResourceStateMachine
          Id: step-function
          RoleArn: !Ref StatemachineExecRoleARN
          InputTransformer:
            InputPathsMap:
              "Account": "$.account"
              "AlarmName": "$.detail.alarmName"
              "MetricsName": "$.detail.configuration.metrics[0].metricStat.metric.name"
              "Reason": "$.detail.state.reason"
              "Time": "$.time"
            InputTemplate: |
              {
                "subject": "Dドライブディスク使用率超過通知",
                "message": "Dドライブディスク使用率超過通知 \n AWSアカウントID: \"<Account>\" \n 時間: \"<Time>\" \n アラーム名: \"<AlarmName>\" \n メトリック: \"<MetricsName>\" \n 理由: \"<Reason>\""
              }

  ######################## 共通りソース ########################

  ######################
  #     ステートマシン   #
  ######################
  ## 件名カスタマイズ用

  SNSResourceStateMachine:
    Type: "AWS::StepFunctions::StateMachine"
    Properties:
      StateMachineName: windows-ec2-resource-check-state-machine
      DefinitionString: !Sub |-
            {
              "StartAt": "PublishSns",
              "States": {
                "PublishSns": {
                  "Type": "Task",
                  "Resource": "arn:aws:states:::sns:publish",
                  "Parameters": {
                    "TopicArn": "${CloudWatchAlarmResourceNotificationTopicARN}",
                    "Message.$": "$.message",
                    "Subject.$": "$.subject"
                  },
                  "End": true
                }
              }
            }
      RoleArn: !Ref SNSPublishRoleARN

CloudWatch Alarm、EventBridge、 Step Functionsを定義しています。CloudWatch Alarmの設定内容やメトリクスの意味について先にまとめておきます。

  • CloudWath Alarm:
    • ComparisonOperator: 閾値の条件を表します。GreaterThanThresholdはより大きい。LessThanThresholdはより小さい。
    • DatapointsToAlarm: アラームが ALARM 状態に移るために閾値を超過する必要がある評価期間内のデータポイントの数です。
    • EvaluationPeriods: アラームの状態を決定するまでに必要とする最新の期間またはデータポイントの数です。
    • Threshold: 閾値。
    • Period: アラームの各データポイントを作成する期間です。
    • Statistic: メトリクスデータをどのような方法で統計するかを設定します。Maximumは最大値です。
    • TreatMissingData: 評価したいデータが取得できず欠落データになる場合の設定。notBreachingは欠落データは閾値内と判断します。
  • メトリクス:
    • CPUUtilization : CPU使用率を監視するメトリクス。
    • Memory % Committed Bytes In Use: 仮想メモリの最大値に対してどのくらいの割合でメモリを確保(コミット)しているかを表すメトリクス。
    • LogicalDisk % Free Space: 論理ディスクドライブの利用可能な空き領域を表すメトリクス。

意味を整理した上でAlarmの設定です。

  • CPUAlarm: 300秒(5分)ごとのデータポイント取得を6回行い、その全てで80%を超過したらアラートとなります。
  • MemoryAlarm: 300秒(5分)ごとのデータポイント取得を3回行い、そのうち2回以上で75%を超過したらアラートとなります。
  • CDiskAlarm: 300秒(5分)ごとのデータポイント取得を3回行い、そのうち2回以上で20%を下回るとアラートとなります。
  • DDiskAlarm: DドライブのAlarmでAlarm内容はCDiskAlarmと同様になります。
  • EventBridge: それぞれのAlarmのstateがALARM状態の際に、Step Functionsを起動するように設定しています。
  • Step Functions: 渡されたパラメータを元にSNS Publishを実行します。

参考情報

cloudwatch_agent_config.json

{
	"metrics": {
		"namespace": "CWAgent",
		"append_dimensions": {
            "InstanceId": "${aws:InstanceId}",
            "InstanceType": "${aws:InstanceType}"
        },
		"metrics_collected": {
			"Processor": {
				"measurement": [
					"% Idle Time",
					"% Interrupt Time",
					"% Privileged Time",
					"% User Time"
				],
				"metrics_collection_interval": 60,
				"resources": [
					"*"
				],
				"totalcpu": true
			},
			"LogicalDisk": {
				"measurement": [
					"% Free Space"
				],
				"drop_device": true,
				"metrics_collection_interval": 60,
				"resources": [
					"*"
				]
			},
			"Memory": {
				"measurement": [
					"% Committed Bytes In Use"
				],
				"metrics_collection_interval": 60
			}
		}
	},
	"logs": {
		"logs_collected": {
			"windows_events": {
				"collect_list": [
					{
						"event_format": "xml",
						"event_levels": [
							"VERBOSE",
							"INFORMATION",
							"WARNING",
							"ERROR",
							"CRITICAL"
						],
						"event_name": "System",
						"log_group_name": "CWAgentSystem",
						"log_stream_name": "{instance_id}"
					},
					{
						"event_format": "xml",
						"event_levels": [
							"VERBOSE",
							"INFORMATION",
							"WARNING",
							"ERROR",
							"CRITICAL"
						],
						"event_name": "Application",
						"log_group_name": "CWAgentApplication",
						"log_stream_name": "{instance_id}"
					},
					{
						"event_format": "xml",
						"event_levels": [
							"VERBOSE",
							"INFORMATION",
							"WARNING",
							"ERROR",
							"CRITICAL"
						],
						"event_name": "Security",
						"log_group_name": "CWAgentSecurity",
						"log_stream_name": "{instance_id}"
					}
				]
			}
		}
	}
}

configファイルを元にCloudWatchメトリクスの取得を行います。 ProcessorメトリクスやWindows logについては、今回の監視対象ではありませんが、CloudWatchメトリクスとして取得する設定としています。

構築

EC2 キーペア作成

まずはEC2画面からキーペアを任意の名前で作成します。名前以外はデフォルトで作成します。

Windows EC2構築

cliコマンドでもデプロイできますが、今回はAWS Management Consoleからデプロイします。

CloudFormationの画面からスタックの作成、templateはwindows_ec2.ymlを選択し新しいソースを作成します。

パラメータは、先ほど入力したキーペアとEC2インスタンスのPrefixなどに使用する任意のPrefixを入力します。

問題なく構築が完了しました。次に構築されたEC2に2パターンで接続をしてみたいと思います。

Windows EC2 Fleet Managerでの接続

EC2インスタンスを選択し、接続ボタンを押下します。 RDPクライアントからFleet Managerを選択し、ユーザー名はデフォルトのAdministratorとします。 認証タイプでは、作成したキーペアを選択しConnectします。

接続に成功しました。

Windows EC2 SSM port forward経由でRDP接続

以下の記事を参考にMacOSでport forward接続をしてみます。

Windows端末からPrivate環境下のWindowsサーバにSSMポートフォワード経由でRDPする方法をまとめてみた | DevelopersIO

事前にMicrosoft Remote Desktopをインストールします。

Microsoft Remote Desktop

次にターミナル上でセッションを開始するコマンドを実行します。INSTANCE_IDとIAM_PROFILEは自身の環境のものに置き換えます。

aws ssm start-session --target <INSTANCE_ID> --document-name AWS-StartPortForwardingSession --parameters portNumber=3389,localPortNumber=3389 --profile <IAM_PROFILE>

Starting session with SessionId: botocore-session-1708740374-047d3aac745bd4f82
Port 3389 opened for sessionId botocore-session-1708740374-047d3aac745bd4f82.
Waiting for connections...

次に先ほどインストールしたMicrosoft Remote Desktopを開き、接続を行います。 PC nameはlocalhostとします。 接続する際にAdministratorで接続しますが、その際にはパスワードが必要となります。 パスワードはEC2インスタンスのセキュリティのWindowsパスワードを取得から取得ができます。 プライベートキーファイルには作成したキーペアを読み込ませてパスワードを取得します。 取得したパスワードから問題なくログインができました。

インストールされたソフトウェアの確認

コントロールパネルのプログラムと機能からインストールされたソフトウェアを確認します。想定通り全てインストールされています。

chocolateyは画面上からは確認できませんでしたので、Powershell上で確認しました。

Windows上でのメトリクス確認

今回監視するメトリクスをWindows上で確認します。管理ツールからパフォーマンスモニターを選択します。 パフォーマンスモニタからプラスボタンを選択します。 遷移した画面から今回監視するメトリクスを確認できます。

Cドライブ、Dドライブディスク分割

今回の要件として、Cドライブ:100、Dドライブ:50とあるので、Windows上から設定を行います。

Windows環境のAWS EBS Root Volumeを縮小または分割してみる | DevelopersIO

Windowsボタンを右クリックし、ディスクの管理を選択します。 構築時は150GBが全てCドライブに割り当てられているので、ボリュームの縮小からCドライブを100GBに縮小します。1GBは1024MBなので、50GBを縮小するには50GB * 1024MB/GB = 51200MBとなります。

Cドライブが100GBになりました。次は未割り当て領域を右クリックし、新しいシンプルボリュームを作成します。こちらはデフォルトでDドライブが選択されるので、そのままデフォルト設定で空き領域全てに割り当てる形で作成します。 Cドライブ100GB、Dドライブ50GBとなりました。

EventBridge, Step Functions用IAM Role作成

CloudFormationの画面からスタックの作成、templateはhealth_resource_check_role.ymlを選択し新しいソースを作成します。

SNS TOPIC作成

CloudFormation画面からスタックの作成、templateはhealth_resource_check_sns.ymlを選択し新しいソースを作成します。 メールアドレスは検証用の任意のアドレスを入力します。 スタック作成後はメールアドレスにサブスクリプションを承認するためのメールが届いているはずですので、メール本文からサブスクリプションを承認し、ステータスを確認済みとします。

CloudWatch Agent Install

続いてCloudWatch AgentをInstallします。

以下のSSM Run CommandをAWS CloudShellから実行します。

aws ssm send-command \
 --document-name "AWS-ConfigureAWSPackage" \
 --document-version "1" \
 --targets Key=InstanceIds,Values="<INSTANCE_ID>" \
 --parameters action=Install,installationType="Uninstall and reinstall",name=AmazonCloudWatchAgent,version=latest \
 --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --region ap-northeast-1

Run Commandの実行結果はSystem Mangerから確認できます。

CloudWatchメトリクス取得設定

続いてCloud Watch メトリクスを取得する設定を行います。実装したcloudwatch_agent_config.jsonについては、SSM Parameter Storeに格納します。パラメータファイル名はAmazonCloudWatch-のprefixとします。

SSM_PARAMETER_NAMEは先ほど作成したパラメータファイル名に修正してコマンドをCloudShell上で実行します。

aws ssm send-command --document-name "AmazonCloudWatch-ManageAgent" \
 --document-version "6" \
 --targets '[{"Key":"InstanceIds","Values":["<INSTANCE_ID>"]}]' \
 --parameters '{"action":["configure"],"mode":["ec2"],"optionalConfigurationSource":["ssm"],"optionalConfigurationLocation":["<SSM_PARAMETER_NAME>"],"optionalOpenTelemetryCollectorConfigurationSource":["ssm"],"optionalOpenTelemetryCollectorConfigurationLocation":[""],"optionalRestart":["yes"]}' \
 --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --region ap-northeast-1

実行結果はSSM Run Commandから確認できます。また、数分後にはCloudWatchメトリクスが取得できていることが画面上からも確認できます。

CloudWatch Alarm, EventBridge, Step Functions構築

最後にCloudWatch Alarm, EventBridge, Step Functionsを構築します。

CloudFormationの画面からスタックの作成、templateはhealth_resource_check_alarm.ymlを選択し新しいソースを作成します。 パラメータにはこれまで作成したリソース名等を入力します。

CloudWatch Alarm, EventBridge, Step Functionsが正常に構築できました。

私の場合は、CloudWatch Alarmにメトリクスが表示されないケースで多くの時間を費やしましたが、設定項目が足りなかったことが原因でした。同じようにCloudWatchメトリクスが取得できているのにCloudWatch Alarmにメトリクスが表示されない事象となった場合は、手動でCloudWatch Alarmを作成してみて、設定項目に不足がないか確認してみると良いと思いました。

負荷試験

それではそれぞれのAlarmに対し、以下記事を参考に負荷試験をしていきたいと思います。

Windowsで使用できるMicrosoftの負荷ツールを使ってみた | ITStudy

CPU負荷試験

CPU負荷試験では、Microsoftが提供しているCPU Stressという負荷ツールを使用します。

CpuStres - Sysinternals | Microsoft Learn

条件として30分間80%超過していれば発報されるので、CPU Stressをローカルにインストールし、4つのProcessのActivityをBusy、1つをMediumとすることで80%を超過するCPU使用率となりました。

30分後に確認しました。6データポイント後にアラート状態となり、メール通知が来ていることも確認できました。

メモリ負荷試験

メモリ負荷試験では、Microsoftが提供しているTestlimitという負荷ツールを使用します。

Testlimit - Sysinternals | Microsoft Learn

条件として15分間75%超過していれば発報されるので、Testlimitをローカルにインストールし、Testlimitで13GBのメモリを確保してみます。

.\Testlimit64.exe -d -c 13000

15分後に確認したところ、アラーム状態となり、そのタイミングで通知が来ていることを確認できました。

ディスク負荷試験

ディスク負荷試験では、指定のファイルサイズのファイルを作成する.NetコマンドをCドライブ、Dドライブ用に実行します。

条件として15分間20%下回れば発報されるので、以下のコマンドをPowershell上で実行します。

$path = "C:\largefile.txt"
$file = [io.file]::Create($path)
$file.SetLength(60GB)
$file.Close()
$path = "D:\largefile.txt"
$file = [io.file]::Create($path)
$file.SetLength(45GB)
$file.Close()

Cドライブ、Dドライブともに15分後にアラーム状態となり、そのタイミングで通知が来ていることを確認できました。

最後に

Windowsのメトリクスを取得してCloudWatchアラームを発報するまでに多くの時間を調査したので、詰まった際は画面上で試しに作成してみて設定を確認した方が早いと学びました。少しでも役立つと幸いです。