AQ Tech Blog

CloudWatch エージェントを使用してインスタンスのログをS3に転送するCloudFormationの作成 | AQ Tech Blog

作成者: toshiki.imamura|2023年03月24日

はじめに


AWSの起動テンプレートに記載したインスタンスのUserdataを使用して、インスタンス起動時からログをAmazon S3に配信するCloudFormationを作成する機会があったため、執筆します。
ハマったところや注意点をナレッジとして役立てていただければと思います。

この記事の対象

  • AWS初学者

  • EC2のログ転送を検討している方

  • CloudFormationに興味のある方

アーキテクチャ図

作成yamlファイル

ファイル詳細
AWSTemplateFormatVersion: 2010-09-09
Resources:
mainVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
igwName:
Type: AWS::EC2::InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref mainVPC
InternetGatewayId: !Ref igwName
PublicRouteTable:
Type: AWS::EC2::RouteTable
DependsOn: AttachGateway
Properties:
VpcId: !Ref mainVPC
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref igwName
PublicSubnet1:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
CidrBlock: 10.0.210.0/24
AvailabilityZoneId: 'use1-az2'
MapPublicIpOnLaunch: 'True'
VpcId: !Ref mainVPC
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnet2:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
CidrBlock: 10.0.220.0/24
AvailabilityZoneId: 'use1-az4'
MapPublicIpOnLaunch: 'True'
VpcId: !Ref mainVPC
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.1.0/24
AvailabilityZoneId: 'use1-az2'
MapPublicIpOnLaunch: 'false'
VpcId: !Ref mainVPC
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateSubnet1RouteTable
PrivateSubnet1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref mainVPC
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.2.0/24
AvailabilityZoneId: 'use1-az4'
MapPublicIpOnLaunch: 'false'
VpcId: !Ref mainVPC
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateSubnet2RouteTable
PrivateSubnet2RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref mainVPC
PublicSecGroupName:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: handson-lbsg
GroupDescription: SecGroupLB
VpcId: !Ref mainVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
PrivateSecGroupName1:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: handson-websg
GroupDescription: SecGroupWEB
VpcId: !Ref mainVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
PrivateSecGroupName2:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: handson-endppointsg
GroupDescription: SecGroupDB
VpcId: !Ref mainVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/16
ssmEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .ssm
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
VpcId: !Ref mainVPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref PrivateSecGroupName2
PrivateDnsEnabled: true
ssmmessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .ssmmessages
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
VpcId: !Ref mainVPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref PrivateSecGroupName2
PrivateDnsEnabled: true
ec2messagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .ec2messages
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
VpcId: !Ref mainVPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref PrivateSecGroupName2
PrivateDnsEnabled: true
logsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .logs
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
VpcId: !Ref mainVPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref PrivateSecGroupName2
PrivateDnsEnabled: true
MonitoringEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .monitoring
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
VpcId: !Ref mainVPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref PrivateSecGroupName2
PrivateDnsEnabled: true
ec2Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .ec2
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
VpcId: !Ref mainVPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref PrivateSecGroupName2
PrivateDnsEnabled: true
s3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Join
- ''
- - com.amazonaws.
- !Ref 'AWS::Region'
- .s3
VpcId: !Ref mainVPC
RouteTableIds:
- !Ref PrivateSubnet1RouteTable
- !Ref PrivateSubnet2RouteTable
EC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: handson-webRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchAgentAdminPolicy
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/AmazonSSMFullAccess
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref EC2Role
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: handson-launchtemplates
LaunchTemplateData:
IamInstanceProfile:
Arn: !GetAtt InstanceProfile.Arn
SecurityGroupIds:
- !Ref PrivateSecGroupName1
- !Ref PrivateSecGroupName2
ImageId: ami-0b5eea76982371e91
InstanceType: t2.micro
Monitoring:
Enabled: true
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install httpd -y
touch /var/www/html/index.html
echo "Hello World from user data" > /var/www/html/index.html
systemctl enable httpd
systemctl start httpd

amazon-linux-extras enable collectd
yum clean metadata
yum -y install collectd

yum -y install amazon-cloudwatch-agent
systemctl enable amazonn-cloudwatch-agent.service
systemctl start amazonn-cloudwatch-agent.service
cat << EOF > /opt/aws/amazon-cloudwatch-agent/bin/config.json
{
"agent": {
"run_as_user": "root"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/messages",
"log_group_name": "handson-log-group",
"log_stream_name": "/var/log/messages",
"retention_in_days": -1
}
]
}
}
},
"metrics": {
"metrics_collected": {
"statsd": {
"metrics_aggregation_interval": 60,
"metrics_collection_interval": 10,
"service_address": ":8125"
}
}
}
}
EOF
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s
systemctl restart amazonn-cloudwatch-agent.service
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MaxSize: '1'
MinSize: '1'
DesiredCapacity: '1'
TargetGroupARNs:
- !Ref TargetGroup
VPCZoneIdentifier:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: !Ref mainVPC
Protocol: HTTP
Port: 80
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: handson-alb
Scheme: internet-facing
SecurityGroups:
- !Ref PublicSecGroupName
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ApplicationLoadBalancer
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
Port: 80
Protocol: HTTP
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: handson-log-group
FirehoseSubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !GetAtt Deliverystream.Arn
FilterPattern: ''
LogGroupName: !Ref LogGroup
RoleArn: !GetAtt LogsRole.Arn
LogsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- firehose:PutRecord
- firehose:PutRecords
Resource: !GetAtt 'Deliverystream.Arn'
FirehoseRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ''
Effect: Allow
Principal:
Service: firehose.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: handson-firehose-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:AbortMultipartUpload
- s3:GetBucketLocation
- s3:GetObject
- s3:ListBucket
- s3:ListBucketMultipartUploads
- s3:PutObject
Resource:
- !Sub 'arn:aws:s3:::${S3bucket}'
- !Sub 'arn:aws:s3:::${S3bucket}*'
Deliverystream:
Type: AWS::KinesisFirehose::DeliveryStream
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub 'arn:aws:s3:::${S3bucket}'
RoleARN: !GetAtt 'FirehoseRole.Arn'
S3bucket:
Type: AWS::S3::Bucket

留意事項

  • 本構成は「米国東部リージョン(バージニア北部)」にて作成しています。
    別のリージョンで作成をする際には、上記のyamlテンプレートの「us-east-1」を、任意のリージョンに書き換えてご使用ください。

  • 各サービスの名前を省略しているため、作成されるリソースはランダムな文字列の名前となります。
    リソースに任意の名前をつけたい場合は、リソースにNameプロパティを追加し、任意の名前を設定してください。プロパティの記法の確認には下記参考情報に各リソースのドキュメントのリンクを記載しましたのでご活用ください。

  • アーキテクチャ図では、2つのプライベートサブネットにインスタンスをデプロイしていますが、上記のyamlテンプレートは検証用のため2つあるプライベートサブネットのどちらかに1台のインスタンスがデプロイされるようにしています。

構成のポイント

本構成では、Load balancersとAuto Scalling グループで構成されたWebサーバの作成、及び作成したサーバからシステムログを収集し、S3にて保管を想定しています。
まずはシステム構成について説明します。

本構成ではAuto Scalling グループを使用した動的拡張を実施しています。
そのため、Auto Scalling グループが利用する起動テンプレートにWebサービスの起動及びCloudWatch エージェントの設定を記述することで、AutoScalingによって追加されたサーバに改めて設定を行うことなく、ログ収集が行えるようにしています。
Auto Scalling グループを使用した動的拡張のトリガーとして、Application Load balancersとターゲットグループを作成し、ヘルスチェックを行っています。

上記の構成のインスタンスのログをAmazon S3に配信するために今回CloudWatch ロググループとAmazon Kinesis Data Firehoseのサービスを使用しました。
配信の流れは以下の通りです。
インスタンスのログをCloudWatch エージェント経由でCloudWatch ロググループに転送し、Amazon Kinesis Data Firehoseを使用して、CloudWatch ロググループからAmazon S3内のバケットに配信をしています。

 

ログの処理フロー図

続きまして、今回の構成について細かい設定解説をしていきます。長くなるので、インスタンス→CloudWatch ロググループまでと、CloudWatch ロググループ→Amazon S3までに分けて紹介していきます。

インスタンス→CloudWatch ロググループ

まずはインスタンス→CloudWatch ロググループまでの説明です。
インスタンスのログをCloudWatch ロググループへ出力させるためには以下の2点の設定を追加しています。

  • インスタンスにCloudWatch エージェントのインストール及び設定

  • インスタンスにCloudWatchへの権限付与

また、本構成ではインスタンスはプライベートサブネットに配置されているため、以下の設定を追加しています。

  • VPCエンドポイントの作成

  • セキュリティグループの追加

 

インスタンスにCloudWatch エージェントのインストール及び設定

インスタンスの任意のログをCloudWatchを使用して収集するためには、インスタンスにCloudWatch エージェントをインストール及び設定をする必要があります。
CloudWatch エージェントのインストール及び設定は起動テンプレートのUserdataを利用しました。

  • コマンド
    yum install amazon-CloudWatch-agent

 

また、amazon-CloudWatch-agentの設定ファイルを変更することで、収集対象のファイル、転送するCloudWatch ロググループを指定します。

  • 設定ファイル

    /opt/aws/amazon-CloudWatch-agent/bin/config.json

 

CloudWatch エージェントの設定は、以下のウィザードを使用した対話方式の設定と、Systems Manager パラメータストア等から設定ファイルを作成する、2種類の方法があります。

  • 対話方式による設定コマンド

    amazon-CloudWatch-agent-config-wizard

 

今回は、Systems Manager パラメータストアを使用せずに、Userdataからcatコマンドを使用してjsonファイルを作成する方法を選択しています。
config.jsonは以下の3つのセクションから構成されます。

  • agentセクション : エージェント全体の設定に関する内容を記載。本構成ではroot権限でログを収集することを記載。
  • logsセクション : CloudWatch ロググループに発行されるログに関する内容を記載。本構成では収集対象のファイル及びログを出力するCloudWatch ロググループ名を記載。
  • metricsセクション:ログの収集と発行に関するカスタムメトリクスを記載。本構成ではログの収集間隔及びポート番号を記載。

上記のUserdataの編集をすることで、用途に合わせて追加のログを収集することができます。
(本構成では/var/log/messageを収集対象としています。)

設定ファイルを作成しただけでは設定は反映がされないため、以下のコマンドでCloudWatch エージェントの設定を更新します。

  • 設定適用コマンド
    /opt/aws/amazon-CloudWatch-agent/bin/amazon-CloudWatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-CloudWatch-agent/bin/config.json -s

 

インスタンスにCloudWatchへの権限付与

インスタンスからCloudWatch エージェントを使用してCloudWatch ロググループにログを出力させるためには、EC2がCloudWatchにアクセスする権限が必要です。
以下のIAMポリシーを関連付けたIAMロールを作成しています。

適用ポリシー

  • CloudWatchLogsFullAccess
  • CloudWatchAgentAdminPolicy

また、今回インスタンスに対してSSH接続ではなく、SSMセッションマネージャーを使用したコンソール接続を想定しているため、追加で以下のポリシーも設定しています。
SSMセッションマネージャーを使用しない場合、以下のポリシーは不要です。

適用ポリシー

  • AmazonSSMFullAccess

作成したロールを起動テンプレート内に関連づけることで、作成したインスタンスがCloudWatchへアクセスすることが可能です。

 

VPCエンドポイントの作成

プライベートサブネットに配置しているインスタンスからCloudWatch ロググループにログを出力するには、VPCエンドポイントを配置して出力経路を確保する必要があります。
そのため、ログ出力用のVPCエンドポイントを作成し、プライベートサブネットに関連付けています。

  • com.amazonaws.us-east-1.ec2
  • com.amazonaws.us-east-1.monitoring
  • com.amazonaws.us-east-1.logs

本構成では米国東部リージョンに作成したため、「us-east-1」としておりますが、こちらを任意のリージョンにすることで他のリージョンでも同様の設定することができます。
インスタンスをパブリックサブネット、プライベートサブネットするに関わらず、CloudWatch エージェント側での追加の設定は不要です。
プライベートサブネットに配置する場合はVPCエンドポイントの設定を忘れないようにしましょう。

 

セキュリティグループの追加

VPCエンドポイントはHTTPS(443番ポート)を使用して通信を行うため、インスタンスのセキュリティグループにHTTPS通信を許可する必要があります。

  • 許可ポート:443
  • 許可IPアドレス:10.0.0.0/16
     VPCエンドポイントの通信はAWS内部通信のため、VPCのIPアドレス範囲を指定

本構成では、区別がしやすいようにインスタンスのセキュリティグループを、Load balancersで使用する外部アクセス用のセキュリティグループと、VPCエンドポイントで使用する内部アクセス用のセキュリティグループに分けて作成しています。

CloudWatch ロググループ→Amazon S3

続いてCloudWatch ロググループ→Amazon S3までの設定説明です。
CloudWatch ロググループに出力したログを、Amazon Kinesis Data Firehoseを経由してAmazon S3のバケットに配信するためには、以下の4点を設定します。

  • ログを格納するAmazon S3バケットの作成

  • CloudWatch及びAmazon Kinesis Data Firehoseのアクセス権限設定

  • Amazon Kinesis Data Firehose の作成

  • CloudWatch ロググループのサブスクリプションフィルターを作成

 

Amazon S3バケット作成

まずはログを格納するバケットの作成です。
本構成ではバージョニングやライフサイクルポリシーの設定を省略して一意のバケットを作成しています。

 

CloudWatch及びAmazon Kinesis Data Firehoseのアクセス権限設定

CloudWatch ロググループがAmazon Kinesis Data Firehoseにログを配信するためのロールと、Amazon Kinesis Data FirehoseがAmazon S3にログを配信するためのロールの2つを作成しています。

  • CloudWatch用のロール

Amazon Kinesis Data Firehose のサービスに対して、単一(PutRecord)または複数(PutRecords)のレコードを送信するポリシー及びロールを作成します。
作成したロールはサブスクリプションフィルターにて関連付けします。

 

  • Amazon Kinesis Data Firehose用のロール

Amazon S3のバケットに対してログを格納するポリシー及びロールを作成します。
作成したロールはAmazon Kinesis Data Firehose にて関連付けします。

 

Amazon Kinesis Data Firehose の作成

前述したAmazon S3バケット及びAmazon Kinesis Data Firehose用のロールを関連付けした、Amazon Kinesis Data Firehose の配信を作成します。
Amazon Kinesis Data FirehoseからAmazon S3に配信を行う場合、ExtendedS3DestinationConfigurationのプロパティタイプを使用します。

 

CloudWatch ロググループのサブスクリプションフィルターを作成

CloudWatch ロググループとサブスクリプションフィルターの関連付けを行います。

まとめ

以上、インスタンス起動時からログをAmazon S3に配信するCloudFormationの解説でした。
CloudFormationの作成経験が浅いため、設定を確認するためにマネジメントコンソールで実際にサービスを作成したり、 AWS公式ドキュメントのパラメータを確認しながら作成をしました。

VPCから作成を行ったため、上記のyamlファイルをコピーペーストすることでCloudFormationで本記事の構成が再現できるようになっています。
ただし、その分yamlファイルが長くなったため、必要な設定値はなるべく最小限にしています。
必要に応じて、以下のAWSドキュメントから必要な設定値を追記してご使用ください。
また、本構成はAWSの無料使用枠以外のサービスも使用しているため、検証時にはリソースの削除漏れにご注意ください。
作成したリソースはCloudFormation上から削除できますが、事前にS3バケットに転送したログの削除をするようにしてください。

参考情報

AWS::EC2::VPCEndpoint

AWS::IAM::Role

AWS::EC2::LaunchTemplate

AWS::IAM::InstanceProfile

AWS::AutoScaling::AutoScalingGroup

AWS::ElasticLoadBalancingV2::TargetGroup

AWS::ElasticLoadBalancingV2::LoadBalancer

AWS::ElasticLoadBalancingV2::Listener

AWS::Logs::SubscriptionFilter

AWS::KinesisFirehose::DeliveryStream

CloudWatch エージェント設定ファイルを手動で作成または編集する

Amazon Linux2にCloudWatchエージェントを設定する手順(ユーザデータでの手順あり)

[AWS CFn] Cloudwatch LogsのログをFirehose経由でS3に転送する