Amazon CloudWatch SyntheticsでVPC内のエンドポイントを監視してみた

2023.03.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Amazon CloudWatch SyntheticsでVPC内にあるエンドポイントを監視する機会があったのでブログに残します。

環境

今回はCloudWatch Syntheticsを使用してプライベートサブネット内にあるEC2 (Webサーバー) を監視します。
以下のドキュメントに記載されている通りVPC内でCanaryを実行するにはインターネットアクセスもしくはVPC エンドポイントが必要となるのでCloudWatchのVPCエンドポイントとS3のVPCエンドポイントを作成します。
VPC で Canary を実行する

  • 構成図は今回メインとなる部分を抜粋して記載しています。
  • EC2への接続はSession Managerを使用するのでインターフェイス型VPCエンドポイントを作成します。

設定

CloudWatch Synthetics以外の部分は全てCloudFormationで作成します。
作成は以下のCloudFormationテンプレートで行います。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: Test Stack

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  VPCCIDR:
    Default: 192.168.0.0/16
    Type: String

  PrivateSubnet01CIDR:
    Default: 192.168.0.0/24
    Type: String

  EC2VolumeSize:
    Default: 32
    Type: Number

  EC2VolumeIOPS:
    Default: 3000
    Type: Number

  EC2AMI:
    Default: ami-06fdbb60c8e83aa5e
    Type: AWS::EC2::Image::Id

  EC2InstanceType:
    Default: t3.micro
    Type: String

Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: test-vpc

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet01CIDR
      Tags: 
        - Key: Name
          Value: test-subnet-private-1a
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: test-private-rtb

  PrivateRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet01

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for EC2
      GroupName: test-sg-ec2
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress: 
        - CidrIp: 192.168.0.0/16
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      Tags: 
        - Key: Name
          Value: test-sg-ec2
      VpcId: !Ref VPC

  VPCEndpointSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for VPC Endpoint
      GroupName: test-sg-vpc-endpoint
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress: 
        - CidrIp: 192.168.0.0/16
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags: 
        - Key: Name
          Value: test-sg-vpc-endpoint
      VpcId: !Ref VPC

  CloudWatchSyntheticsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for CloudWatch Synthetics
      GroupName: test-sg-cloudWatch-synthetics
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      Tags: 
        - Key: Name
          Value: test-sg-cloudWatch-synthetics
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  EC2IAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - "s3:GetObject"
            Resource: 
              - !Join 
                - ''
                - - 'arn:aws:s3:::amazonlinux.'
                  - !Sub ${AWS::Region}
                  - '.amazonaws.com/*'
              - !Join 
                - ''
                - - 'arn:aws:s3:::amazonlinux-2-repos-'
                  - !Sub ${AWS::Region}
                  - '/*'
      ManagedPolicyName: test-iam-policy-ec2

  EC2IAMRole:
    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
        - !Ref EC2IAMPolicy
      RoleName: test-iam-role-ec2
      Tags:
        - Key: Name
          Value: test-iam-role-ec2

  EC2IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: test-iam-instanceprofile-ec2
      Roles: 
        - !Ref EC2IAMRole

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------# 
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: !Ref EC2VolumeIOPS
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp3
      DisableApiTermination: false
      IamInstanceProfile: !Ref EC2IAMInstanceProfile
      ImageId: !Ref EC2AMI
      InstanceType: !Ref EC2InstanceType
      NetworkInterfaces: 
        - DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref EC2SG
          SubnetId: !Ref PrivateSubnet01
      Tags:
        - Key: Name
          Value: test-ec2
      UserData: !Base64 |
        #!/bin/bash
        yum update -y
        yum install httpd -y
        systemctl start httpd
        systemctl enable httpd
        echo "CloudWatch Synthetics Test" > /var/www/html/index.html

# ------------------------------------------------------------#
# VPC Endpoint
# ------------------------------------------------------------# 
  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties: 
      RouteTableIds: 
        - !Ref PrivateRouteTable
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcEndpointType: Gateway
      VpcId: !Ref VPC

  SystemsManagerEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet01
      SecurityGroupIds:
        - !Ref VPCEndpointSG

  SystemsManagerMessageEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet01
      SecurityGroupIds:
        - !Ref VPCEndpointSG

  CloudWatchEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.monitoring
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet01
      SecurityGroupIds:
        - !Ref VPCEndpointSG

上記のCloudFormationテンプレートで監視対象のEC2やVPCエンドポイントなどCloudWatch Synthetics以外のリソースが作成されます。

デプロイは以下のコマンドを実行します。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM


リソースの作成が完了したらマネジメントコンソールからCloudWatchのダッシュボードへ移動します。
移動したら画面左の項目から「Synthetics Canaries」をクリックします。

画面が遷移したら「Canaryを作成」をクリックします。

画面が遷移したら設定を行っていきます。
まずは監視スクリプトどのように準備するかの設定です。
今回はAWSの準備したテンプレートを使用するので「設計図を使用する」を選択しています。
監視対象は通常のWebページなので「ハートビートのモニタリング」を選択します。

次に監視するエンドポイントの設定を行います。
「アプリケーションまたはエンドポイント URL」にCloudFormationで作成したEC2のプライベートIPアドレスを設定します。

次の項目で「スクリプトエディタ」が編集できますが今回はデフォルトで設定します。
カスタムしたい場合は以下の公式ドキュメントを参考にしてください。
Canary スクリプトの作成

次の項目で「スケジュール」と「データ保持」の設定が行えます。
「スケジュール」は監視間隔の設定を行う項目です。
「データ保持」は監視を行った際の実行データの保持設定です。
今回はデフォルトで設定します。

次の項目で「データストレージ」と「アクセス許可」の設定が行えます。
「データストレージ」は実行データの保存先S3の設定になります。
「アクセス許可」ではS3にデータを保存したりするのに使用する権限がアタッチされたIAMロールを設定します。
今回は自動で作成されるものを使用していきます。

次の項目で「CloudWatch アラーム」と「VPC」の設定が行えます。
「CloudWatch アラーム」は監視でエラーになった際に通知などが行えますが、今回は設定しません。
「VPC」については、VPC内のエンドポイントを監視するので設定を行っていきます。
「Virtual Private Cloud (VPC)」と「サブネット」でCloudFormationで作成されたVPCを選択します。
「セキュリティグループ」は「for CloudWatch Synthetics」と記載されているものがCloudWatch Synthetics用に作成したものなので選択します。

次の項目で「タグ」、「アクティブトレース」の設定が行えます。
今回は設定を行う必要が無いのでデフォルトのままにします。

全ての設定が完了したら画面下の「Canaryを作成」をクリックします。
作成完了後、しばらく待つと監視が実行されます。

名前の部分をクリックすると実行時のログや画面のスクリーンショットが確認できます。

Webサーバーのアクセスログを確認すると5分おきに監視が行われていることが確認できます。

192.168.0.xx - - [28/Mar/2023:14:58:55 +0000] "GET / HTTP/1.1" 200 27 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4512.0 Safari/537.36 CloudWatchSynthetics/arn:aws:synthetics:ap-northeast-1:xxxxxxxxx:canary:test-page"
192.168.0.xx - - [28/Mar/2023:15:03:32 +0000] "GET / HTTP/1.1" 200 27 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4512.0 Safari/537.36 CloudWatchSynthetics/arn:aws:synthetics:ap-northeast-1:xxxxxxxxx:canary:test-page"
192.168.0.xx - - [28/Mar/2023:15:08:32 +0000] "GET / HTTP/1.1" 200 27 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4512.0 Safari/537.36 CloudWatchSynthetics/arn:aws:synthetics:ap-northeast-1:xxxxxxxxx:canary:test-page"

さいごに

外部に公開していないエンドポイントについても監視できることは今回初めて知りました。
CloudWatch SyntheticsではAPIの監視なども行えるので、内部向けAPI監視に使えるのは便利だなと思いました。