【AWS】CloudWatch エージェントを利用してApache HTTPを監視する。

# 1. はじめに
### 1-1 ご挨拶
初めまして、井村と申します。
AWSのAmazon Linux 2023にCloudWatchエージェントをインストールしApache HTTPの監視設定を行いました。
Apache HTTPのプロセスとログをCloudWatchに転送します。
備忘録の記事になります。

### 1-2 対象読者
- AWSに興味がある
- 監視に興味がある

### 1-3 CloudWatch エージェントの適用方法について
AWS Systems Manager のパラメータストアに予め作成したCloudWatch エージェント設定ファイルをEC2のユーザーデータを用いて適用させます。

# 2. 構築
### 2-1 test.yaml構成

CloudFormationで各リソースを作成します。

<details><summary>test.yaml</summary>

```yaml:test.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Simple Web Architecture

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: "Common"
      Parameters:
        - SystemName
        - EnvType
    - Label:
        default: "EC2 Configuration"
      Parameters:
        - LastestAmiId

Parameters:
  SystemName:
      Description: "Enter System name"
      Type: String
      Default: "timura"
  EnvType:
      Description: "Select Environment type"
      Type: String
      Default: "dev"
      AllowedValues:
      - "dev"
      - "prod"
  LatestAmiId:
      Type: AWS::SSM::Parameter::Value<String>
      Default: "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"


Mappings:
  EnvConfig:
    prod:
      EC2InstanceType: t3.micro
      EC2DiskSize: 20
      RDSInstanceType: db.t3.micro
      RDSStorageSize: 20
    dev:
      EC2InstanceType: t2.micro
      EC2DiskSize: 20
      RDSInstanceType: db.t3.micro
      RDSStorageSize: 20

Resources:
  VPC:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: 10.0.0.0/16
        EnableDnsSupport: true
        EnableDnsHostnames: true
        InstanceTenancy: default
        Tags:
          - Key: Name
            Value: !Sub ${SystemName}-${EnvType}-vpc

  PublicSubnet01:
      Type: AWS::EC2::Subnet
      Properties:
          VpcId: !Ref VPC
          CidrBlock: 10.0.1.0/24
          AvailabilityZone: !Select [ 0, !GetAZs "" ]
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-public-subnet01

  PublicSubnet02:
      Type: AWS::EC2::Subnet
      Properties:
          VpcId: !Ref VPC
          CidrBlock: 10.0.2.0/24
          AvailabilityZone: !Select [ 1, !GetAZs "" ]
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-public-subnet02

  PrivateSubnet01:
      Type: AWS::EC2::Subnet
      Properties:
          VpcId: !Ref VPC
          CidrBlock: 10.0.3.0/24
          AvailabilityZone: !Select [ 0, !GetAZs "" ]
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-private-subnet01

  PrivateSubnet02:
      Type: AWS::EC2::Subnet
      Properties:
          VpcId: !Ref VPC
          CidrBlock: 10.0.4.0/24
          AvailabilityZone: !Select [ 1, !GetAZs "" ]
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-private-subnet02

  InternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties:
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-igw

  AttachGateway:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
          VpcId: !Ref VPC
          InternetGatewayId: !Ref InternetGateway

  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${EnvType}-ngw

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${EnvType}-public-rtb

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${EnvType}-private-rtb

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
          RouteTableId: !Ref PublicRouteTable
          DestinationCidrBlock: '0.0.0.0/0'
          GatewayId: !Ref InternetGateway

  PrivateRoute:
    Type: AWS::EC2::Route
    Properties:
          RouteTableId: !Ref PrivateRouteTable
          DestinationCidrBlock: '0.0.0.0/0'
          NatGatewayId: !Ref NatGateway

  PublicSubnet01RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01

  PublicSubnet02RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet02

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

  PrivateSubnet02RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet02

  ALBSecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties:
          GroupName: !Sub ${SystemName}-${EnvType}-alb-sg
          GroupDescription: "Allow http https from users"
          VpcId: !Ref VPC
          SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: "0.0.0.0/0"
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-alb-sg

  EC2SecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties:
          GroupName: !Sub ${SystemName}-${EnvType}-ec2-sg
          GroupDescription: "Allow http https from alb"
          VpcId: !Ref VPC
          SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: "0.0.0.0/0"
          Tags:
              - Key: Name
                Value: !Sub ${SystemName}-${EnvType}-ec2-sg

  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${SystemName}-${EnvType}-ec2-role
      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/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/CloudWatchAgentAdminPolicy
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

  EC2Profile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub ${SystemName}-${EnvType}-ec2-profile
      Roles:
        - !Ref EC2Role

  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !FindInMap [ EnvConfig, !Ref EnvType, EC2InstanceType ]
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp3
            VolumeSize: !FindInMap [ EnvConfig, !Ref EnvType, EC2DiskSize ]
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      SubnetId: !Ref PrivateSubnet01
      IamInstanceProfile: !Ref EC2Profile
      UserData:
        "Fn::Base64": |
          #!/bin/bash
          # パッケージの最新化
          dnf update -y
          # パッケージのインストール
          dnf install -y httpd
          dnf install -y amazon-cloudwatch-agent
          # サービスの起動
          systemctl enable httpd
          systemctl start httpd
          systemctl enable amazon-cloudwatch-agent
          # SSM パラメータから設定を読み込んで CloudWatch エージェントを起動する
          /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c ssm:AmazonCloudWatch-config-qiita-web -s

      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${EnvType}-ec2

  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${SystemName}-${EnvType}-alb
      Scheme: internet-facing
      Type: application
      Subnets:
        - !Ref PublicSubnet01
        - !Ref PublicSubnet02
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${EnvType}-alb

  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${SystemName}-${EnvType}-alb-tg
      VpcId: !Ref VPC
      TargetType: instance
      Port: 80
      Protocol: HTTP
      Targets:
        - Id: !Ref EC2Instance
          Port: 80
      HealthCheckProtocol: HTTP
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${EnvType}-alb-tg

  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP

```
</details>



以下がCloudWatch エージェントで必要な部分になります。
1行目はCloudWatch エージェントをインストールします。
2行目はAWS Systems Manager のパラメータストアから設定ファイルを読込みエージェントを起動させます。
AmazonCloudWatch-config-qiita-webはパラメータストアの名前になります。

```
dnf install -y amazon-cloudwatch-agent

/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c ssm:AmazonCloudWatch-config-qiita-web -s
```

```
# 以下はコマンドのヘルプになります。

sh-5.2$ amazon-cloudwatch-agent-ctl -help



        usage:  amazon-cloudwatch-agent-ctl -a
                stop|start|status|fetch-config|append-config|remove-config|set-log-level
                [-m ec2|onPremise|onPrem|auto]
                [-c default|all|ssm:<parameter-store-name>|file:<file-path>]
                [-s]
                [-l INFO|DEBUG|WARN|ERROR|OFF]

        e.g.
        1. apply a SSM parameter store config on EC2 instance and restart the agent afterwards:
            amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c ssm:AmazonCloudWatch-Config.json -s
        2. append a local json config file on onPremise host and restart the agent afterwards:
            amazon-cloudwatch-agent-ctl -a append-config -m onPremise -c file:/tmp/config.json -s
        3. query agent status:
            amazon-cloudwatch-agent-ctl -a status

        -a: action
            stop:                                   stop the agent process.
            start:                                  start the agent process.
            status:                                 get the status of the agent process.
            fetch-config:                           apply config for agent, followed by -c. Target config can be based on location (ssm parameter store name, file name), or 'default'.
            append-config:                          append json config with the existing json configs if any, followed by -c. Target config can be based on the location (ssm parameter store name, file name), or 'default'.
            remove-config:                          remove config for agent, followed by -c. Target config can be based on the location (ssm parameter store name, file name), or 'all'.
            set-log-level:                          sets the log level, followed by -l to provide the level in all caps.

        -m: mode
            ec2:                                    indicate this is on ec2 host.
            onPremise, onPrem:                      indicate this is on onPremise host.
            auto:                                   use ec2 metadata to determine the environment, may not be accurate if ec2 metadata is not available for some reason on EC2.

        -c: amazon-cloudwatch-agent configuration
            default:                                default configuration for quick trial.
            ssm:<parameter-store-name>:             ssm parameter store name.
            file:<file-path>:                       file path on the host.
            all:                                    all existing configs. Only apply to remove-config action.

        -s: optionally restart after configuring the agent configuration
            this parameter is used for 'fetch-config', 'append-config', 'remove-config' action only.

        -l: log level to set the agent to INFO, DEBUG, WARN, ERROR, or OFF
            this parameter is used for 'set-log-level' only.

```

### 2-2 CloudWatch エージェント設定ファイル

パラメータストアに格納するjsonファイルがCloudWatch エージェント設定ファイルになります。

![parameter_store.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1989465/203a21fc-b710-4ca3-a61d-403475213005.png)

<details><summary>AmazonCloudWatch-config-qiita-web</summary>

```json:AmazonCloudWatch-config-qiita-web.json
{
  "agent": {
    "metrics_collection_interval": 60
  },
  "metrics": {
    "namespace": "AWS/EC2",
    "append_dimensions": {
      "InstanceId": "${aws:InstanceId}"
    },
    "metrics_collected": {
      "mem": {
        "measurement": [
          "mem_used_percent"
        ],
        "metrics_collection_interval": 60
      },
      "cpu": {
        "measurement": [
          "cpu_usage_iowait"
        ],
        "metrics_collection_interval": 60
      },
      "procstat": [
        {
          "pattern": "httpd",
          "measurement": [
            "pid_count"
          ],
          "metrics_collection_interval": 60
        },
        {
          "pattern": "amazon-cloudwatch-agent",
          "measurement": [
            "pid_count"
          ],
          "metrics_collection_interval": 60
        }
      ]
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/httpd/error_log",
            "log_group_name": "/var/log/httpd/error_log",
            "log_stream_name": "{instance_id}",
            "timezone": "Local",
            "retention_in_days": 7
          }
        ]
      }
    }
  }
}
```
</details>

# 3. 確認

CloudFormationにてリソースを作成しました。CloudWatchにてメトリクス、ログを確認します。

### 3-1 プロセスの確認
EC2のコンソール画面からインスタンス IDをコピーし、CloudWatchのメトリクス画面にコピーしたインスタンス IDを貼り付けた結果が以下になります。


![cloudwatch1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1989465/30a723bc-33bf-4b59-b3a0-722c373bd22c.png)

EC2からコマンドを用いてプロセスを確認します。

```
sh-5.2$ ps aux | grep httpd
root       23628  0.0  1.1  18380 10780 ?        Ss   07:44   0:00 /usr/sbin/httpd -DFOREGROUND
apache     23922  0.0  0.3  18036  3556 ?        S    07:44   0:00 /usr/sbin/httpd -DFOREGROUND
apache     23924  0.0  0.9 1250040 9604 ?        Sl   07:44   0:00 /usr/sbin/httpd -DFOREGROUND
apache     23925  0.0  0.7 1086136 7700 ?        Sl   07:44   0:00 /usr/sbin/httpd -DFOREGROUND
apache     23926  0.0  0.9 1086000 9596 ?        Sl   07:44   0:00 /usr/sbin/httpd -DFOREGROUND
ssm-user   29624  0.0  0.0    604     4 pts/0    R+   08:43   0:00 grep httpd
sh-5.2$
```

大丈夫ですね。

### 3-2 ログの確認

今回はエラーログをパラメータストアに設定しましたので確認します。
ロググループにてhttpと入力すると"/var/log/httpd/error_log"がヒットします。対象のEC2のインスタンス IDを選択した画面が以下になります。
ログが取得できています。

![cloudwatch2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1989465/452acc39-1214-41de-8033-b4eb43c2fda2.png)


EC2からコマンドを用いてログを確認します。

```

sh-5.2$
sh-5.2$ sudo ls -l /var/log/httpd/
total 176
-rw-r--r--. 1 root root 45119 May 24 08:53 access_log
-rw-r--r--. 1 root root 74672 May 24 08:53 error_log
sh-5.2$
sh-5.2$ sudo tail /var/log/httpd/error_log
[Sat May 24 08:51:37.206600 2025] [autoindex:error] [pid 23926:tid 24101] [client 10.0.2.128:61720] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:51:43.672449 2025] [autoindex:error] [pid 23926:tid 24103] [client 10.0.1.26:31552] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:52:07.236589 2025] [autoindex:error] [pid 23924:tid 24078] [client 10.0.2.128:28056] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:52:13.699488 2025] [autoindex:error] [pid 23926:tid 24105] [client 10.0.1.26:58628] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:52:37.264202 2025] [autoindex:error] [pid 23926:tid 24107] [client 10.0.2.128:56494] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:52:43.730390 2025] [autoindex:error] [pid 23924:tid 24055] [client 10.0.1.26:19354] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:53:07.288319 2025] [autoindex:error] [pid 23926:tid 24109] [client 10.0.2.128:29636] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:53:13.759869 2025] [autoindex:error] [pid 23926:tid 24111] [client 10.0.1.26:1700] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:53:37.307834 2025] [autoindex:error] [pid 23926:tid 24112] [client 10.0.2.128:45472] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Sat May 24 08:53:43.790564 2025] [autoindex:error] [pid 23926:tid 24114] [client 10.0.1.26:30770] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
```

大丈夫ですね。

# 4. 終わりに
本記事を最後まで読んで頂きましてありがとうございます。
CloudWatchエージェントを扱った案件に初めて参画したのでとても勉強になりました。

# 5. 参考記事
- [SSM を使用して CloudWatch エージェントをダウンロード、設定、実行する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/download-CloudWatch-Agent-on-EC2-Instance-SSM-first.html)
- [CloudWatch エージェント設定ファイルを手動で作成または編集する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html)
- [EC2 の起動時に CloudWatch エージェントを設定するユーザーデータスクリプトを作成してみた（Linux編）](https://dev.classmethod.jp/articles/user-data-script-to-configure-cloudwatch-agent-agent-in-ec2-linux/)