この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、崔です。
Aurora MySQLの各種ログを出力し、そのログファイルをCloudWatch LogsからKinesis Data Firehoseを用いて、S3まで連携する機会がありましたので、参考までにCloudFormationのテンプレートを紹介させていただきます。
今回試したAuroraのエンジンバージョンは5.7.mysql_aurora.2.04.5
です。
ログ出力の設定
まずAuroraの各種ログ出力を設定していきます。
監査ログの設定
最初にAurora MySQLにて監査ログを設定します。 監査ログの設定はクラスタパラメータグループで設定します。 以下のパラメータを設定します。
パラメータ名 | 値 | 備考 |
---|---|---|
server_audit_events | CONNECT,QUERY,TABLE | 監査対象のイベント |
server_audit_logging | 1 | 監査ログを有効にする |
server_audit_excl_users | <なし> | 監査対象外とするユーザ |
server_audit_incl_users | <なし> | 監査対象とするユーザ |
CloudFormationのテンプレートは下記のように記載します。
DBClusterParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Family: aurora-mysql5.7
Parameters:
server_audit_events: 'CONNECT,QUERY,TABLE'
server_audit_logging: 1
パラメータの設定内容を補足します。
server_audit_events
には記録するイベントをカンマ区切り、大文字で指定します。
次のイベントの任意の組み合わせを記録できます。
イベント名 | 備考 |
---|---|
CONNECT | 成功した接続と失敗した接続の両方、および切断を記録。このイベントにはユーザ情報が含まれる。 |
QUERY | すべてのクエリをプレーンテキストで記録。構文またはアクセス権限エラーで失敗したものも含む。 |
QUERY_DCL | QUERYイベントと同様だが、GRANT,REVOKEなどのデータ制御言語(DCL)クエリのみ返す。 |
QUERY_DDL | QUERYイベントと同様だが、CREATE,ALTERなどのデータ定義言語(DDL)クエリのみ返す。 |
QUERY_DML | QUERYイベントと同様だが、INSERT,UPDATE,SELECTなどのデータ操作言語(DML)クエリのみ返す。 |
TABLE | クエリ実行の影響を受けたテーブルを記録する。 |
今回は全てを記録するために、CONNECT,QUERY,TABLE
を指定します。
また、server_audit_excl_users
には監査対象外とするユーザをカンマ区切りで指定します。
server_audit_excl_users
を指定した場合は、そのユーザ以外が監査対象となります。
server_audit_incl_users
には監査対象とするユーザをカンマ区切りで指定します。
今回はどちらも指定しません。この場合、全てのユーザが監査対象となります。
どちらのパラメータにも同じユーザを指定した場合は、server_audit_incl_users
が優先されるため、監査対象となります。
一般ログ、スロークエリログの設定
一般ログ、スロークエリログはパラメータグループで設定します。 以下のパラメータを設定します。
パラメータ名 | 値 | 備考 |
---|---|---|
general_log | 1 | 一般ログを有効にする |
slow_query_log | 1 | スロークエリログを有効にする |
long_query_time | 1 | スロークエリの閾値(秒) |
long_query_time
には各システムの閾値となる秒数を指定して下さい。
CloudFormationのテンプレートは下記のように記載します。
RDSDBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Family: aurora-mysql5.7
Parameters:
general_log: '1'
slow_query_log: '1'
long_query_time: '1'
CloudWatch Logsへの出力
各種ログを設定した後、AuroraからCloudWatch Logsにログを出力します。 ログ出力先であるCloudWatch Logsのロググループ名、ログストリーム名は以下の形式となります。
ログ種別 | ロググループ名 | ログストリーム名 |
---|---|---|
監査ログ | /aws/rds/cluster/<クラスタ名>/audit | <DBインスタンス名>.audit.log.n.YYYY-MM-DD-HH-MI.n.n |
エラーログ | /aws/rds/cluster/<クラスタ名>/error | <DBインスタンス名> |
一般ログ | /aws/rds/cluster/<クラスタ名>/general | <DBインスタンス名> |
スロークエリログ | /aws/rds/cluster/<クラスタ名>/slowquery | <DBインスタンス名> |
CloudWatch Logsへの出力設定は、CloudFormationのテンプレートでは、DBクラスタ内で設定します。
RDSCluster:
Type: AWS::RDS::DBCluster
Properties:
EnableCloudwatchLogsExports:
- audit
- general
- error
- slowquery
また、CloudWatch Logsの保存期間を設定する場合は、ロググループを下記のように設定します。 今回は保存期間90日で設定します。 (ログ出力時にロググループは自動作成されますので、明示的に作成しなくても良いです。が、今回は保存期間やS3への連携を設定するために作成します。)
RDSauditLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/audit"
RetentionInDays: 90
RDSerrorLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/error"
RetentionInDays: 90
RDSgeneralLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/general"
RetentionInDays: 90
RDSslowqueryLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/slowquery"
RetentionInDays: 90
S3へ転送
CloudWatch Logsへの出力設定のあとは、Kinesis Data Firehoseを用いたS3への転送設定を行います。 こちらのブログを参考に設定できます。
S3バケットの作成、IAMポリシー/IAMロールの作成、Kinesis Data Firehoseの転送ストリームを作成します。 S3では90日でIntelligent-Tieringへの移動、365日で削除としました。 (ひとまず監査ログの転送ストリームになります。)
S3bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
Properties:
BucketName: !Sub ${DBInstanceId}-log-${AWS::AccountId}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
LifecycleConfiguration:
Rules:
- Id: AutoDelete
Status: Enabled
ExpirationInDays: 365
Transitions:
- StorageClass: INTELLIGENT_TIERING
TransitionInDays: 90
deliveryPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: firehose_delivery_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:::${DBInstanceId}-log-${AWS::AccountId}'
- !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}*'
Roles:
- !Ref 'deliveryRole'
deliveryRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ''
Effect: Allow
Principal:
Service: firehose.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref 'AWS::AccountId'
AuditLogstream:
Type: AWS::KinesisFirehose::DeliveryStream
DependsOn: S3bucket
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}'
BufferingHints:
IntervalInSeconds: '60'
SizeInMBs: '50'
CompressionFormat: GZIP
Prefix: audit/
RoleARN: !GetAtt 'deliveryRole.Arn'
ProcessingConfiguration:
Enabled: 'false'
CloudWatchLoggingOptions:
Enabled: 'true'
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-auditlog-deliverystream'
LogStreamName: 'AuditLogDelivery'
AuditLogstreamlogstream:
Type: AWS::Logs::LogStream
Properties:
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-auditlog-deliverystream'
LogStreamName: 'AuditLogDelivery'
AuditSubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !GetAtt 'AuditLogstream.Arn'
FilterPattern: ''
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/audit"
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 'AuditLogstream.Arn'
- !GetAtt 'ErrorLogstream.Arn'
- !GetAtt 'GeneralLogstream.Arn'
- !GetAtt 'SlowqueryLogstream.Arn'
これでCloudWatch Logsに出力されたログが、S3に出力されるようになります。
まとめ
Amazon Auroraの各種ログ、特に監査ログは、セキュリティ要件によっては、ある程度の期間保存しておくケースがあると思います。 今回のように、CloudWatch Logsには一部の期間のログだけを保存し、S3に長期間分を保存しておくことで、コスト面でのメリットが見出だせます。
最後にテンプレートを貼り付けておきます。ご参考まで。
Auroraログ転送用テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: Build Database RDS Aurora template.
Parameters:
DBInstanceId:
Description: RDS DB InstanceId.
Type: String
DataBaseName:
Description: RDS database name.
Type: String
RDSDbMasterUserName:
Description: RDS DB Master User Name.
Type: String
RDSDbMasterPassword:
Description: RDS DB Master Password.
Type: String
NoEcho: 'TRUE'
ProtectedSubnet1:
Description: The SubnetId of Availability Zone 1
Type: AWS::EC2::Subnet::Id
ProtectedSubnet2:
Description: The SubnetId of Availability Zone 2
Type: AWS::EC2::Subnet::Id
RDSInstanceClass:
Type: String
Description: Database InstanceType
SecurityGroupId:
Description: SecurityGroupID attache Aurora.
Type: AWS::EC2::SecurityGroup::Id
KmsKeyId:
Type: String
Description: KmsKeyID
Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnets available for the RDS DB Instance
DBSubnetGroupName: !Sub "${DBInstanceId}-subnetgroup"
SubnetIds:
- !Ref 'ProtectedSubnet1'
- !Ref 'ProtectedSubnet2'
RDSCluster:
Type: AWS::RDS::DBCluster
Properties:
BackupRetentionPeriod: 10
DatabaseName: !Ref 'DataBaseName'
DBClusterIdentifier: !Sub "${DBInstanceId}-cluster"
DBClusterParameterGroupName: !Ref 'DBClusterParameterGroup'
DBSubnetGroupName: !Ref 'DBSubnetGroup'
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.04.5
KmsKeyId: !Ref 'KmsKeyId'
MasterUsername: !Ref 'RDSDbMasterUserName'
MasterUserPassword: !Ref 'RDSDbMasterPassword'
PreferredBackupWindow: 18:00-18:30
PreferredMaintenanceWindow: sun:18:30-sun:19:00
StorageEncrypted: true
VpcSecurityGroupIds:
- !Ref 'SecurityGroupId'
EnableCloudwatchLogsExports:
- general
- error
- slowquery
- audit
RDSDBInstance1:
Type: AWS::RDS::DBInstance
Properties:
AutoMinorVersionUpgrade: false
AvailabilityZone: ap-northeast-1a
DBClusterIdentifier: !Ref 'RDSCluster'
DBInstanceClass: !Ref 'RDSInstanceClass'
DBInstanceIdentifier: !Sub "${DBInstanceId}-1"
DBParameterGroupName: !Ref 'RDSDBParameterGroup'
DBSubnetGroupName: !Ref 'DBSubnetGroup'
Engine: aurora-mysql
OptionGroupName: !Ref 'OptionGroup'
PreferredMaintenanceWindow: sun:19:00-sun:19:30
PubliclyAccessible: false
RDSDBInstance2:
Type: AWS::RDS::DBInstance
Properties:
AutoMinorVersionUpgrade: false
AvailabilityZone: ap-northeast-1c
DBClusterIdentifier: !Ref 'RDSCluster'
DBInstanceClass: !Ref 'RDSInstanceClass'
DBInstanceIdentifier: !Sub "${DBInstanceId}-2"
DBParameterGroupName: !Ref 'RDSDBParameterGroup'
DBSubnetGroupName: !Ref 'DBSubnetGroup'
Engine: aurora-mysql
OptionGroupName: !Ref 'OptionGroup'
PreferredMaintenanceWindow: sun:19:00-sun:19:30
PubliclyAccessible: false
DBClusterParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Description: !Ref 'DBInstanceId'
Family: aurora-mysql5.7
Parameters:
character_set_server: utf8
character_set_client: utf8
character_set_connection: utf8
character_set_results: utf8
character_set_database: utf8
time_zone: Asia/Tokyo
server_audit_logging: 1
server_audit_events: 'CONNECT,QUERY,TABLE'
RDSDBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Description: !Ref 'DBInstanceId'
Family: aurora-mysql5.7
Parameters:
general_log: '1'
slow_query_log: '1'
long_query_time: '1'
OptionGroup:
Type: "AWS::RDS::OptionGroup"
Properties:
EngineName: "aurora-mysql"
MajorEngineVersion: "5.7"
OptionGroupDescription: !Ref 'DBInstanceId'
Tags:
- Key: Name
Value: !Ref 'DBInstanceId'
RDSauditLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/audit"
RetentionInDays: 90
RDSerrorLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/error"
RetentionInDays: 90
RDSgeneralLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/general"
RetentionInDays: 90
RDSslowqueryLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/slowquery"
RetentionInDays: 90
S3bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
Properties:
BucketName: !Sub ${DBInstanceId}-log-${AWS::AccountId}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
LifecycleConfiguration:
Rules:
- Id: AutoDelete
Status: Enabled
ExpirationInDays: 365
Transitions:
- StorageClass: INTELLIGENT_TIERING
TransitionInDays: 90
deliveryPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: firehose_delivery_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:::${DBInstanceId}-log-${AWS::AccountId}'
- !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}*'
Roles:
- !Ref 'deliveryRole'
deliveryRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ''
Effect: Allow
Principal:
Service: firehose.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref 'AWS::AccountId'
AuditLogstream:
Type: AWS::KinesisFirehose::DeliveryStream
DependsOn: S3bucket
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}'
BufferingHints:
IntervalInSeconds: '60'
SizeInMBs: '50'
CompressionFormat: GZIP
Prefix: audit/
RoleARN: !GetAtt 'deliveryRole.Arn'
ProcessingConfiguration:
Enabled: 'false'
CloudWatchLoggingOptions:
Enabled: 'true'
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-auditlog-deliverystream'
LogStreamName: 'AuditLogDelivery'
AuditLogstreamlogstream:
Type: AWS::Logs::LogStream
Properties:
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-auditlog-deliverystream'
LogStreamName: 'AuditLogDelivery'
AuditSubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !GetAtt 'AuditLogstream.Arn'
FilterPattern: ''
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/audit"
RoleArn: !GetAtt 'LogsRole.Arn'
ErrorLogstream:
Type: AWS::KinesisFirehose::DeliveryStream
DependsOn: S3bucket
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}'
BufferingHints:
IntervalInSeconds: '60'
SizeInMBs: '50'
CompressionFormat: GZIP
Prefix: error/
RoleARN: !GetAtt 'deliveryRole.Arn'
ProcessingConfiguration:
Enabled: 'false'
CloudWatchLoggingOptions:
Enabled: 'true'
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-errorlog-deliverystream'
LogStreamName: 'ErrorLogDelivery'
ErrorLogstreamlogstream:
Type: AWS::Logs::LogStream
Properties:
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-errorlog-deliverystream'
LogStreamName: 'ErrorLogDelivery'
ErrorSubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !GetAtt 'ErrorLogstream.Arn'
FilterPattern: ''
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/error"
RoleArn: !GetAtt 'LogsRole.Arn'
GeneralLogstream:
Type: AWS::KinesisFirehose::DeliveryStream
DependsOn: S3bucket
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}'
BufferingHints:
IntervalInSeconds: '60'
SizeInMBs: '50'
CompressionFormat: GZIP
Prefix: general/
RoleARN: !GetAtt 'deliveryRole.Arn'
ProcessingConfiguration:
Enabled: 'false'
CloudWatchLoggingOptions:
Enabled: 'true'
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-generallog-deliverystream'
LogStreamName: 'GeneralLogDelivery'
GeneralLogstreamlogstream:
Type: AWS::Logs::LogStream
Properties:
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-generallog-deliverystream'
LogStreamName: 'GeneralLogDelivery'
GeneralSubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !GetAtt 'GeneralLogstream.Arn'
FilterPattern: ''
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/general"
RoleArn: !GetAtt 'LogsRole.Arn'
SlowqueryLogstream:
Type: AWS::KinesisFirehose::DeliveryStream
DependsOn: S3bucket
Properties:
ExtendedS3DestinationConfiguration:
BucketARN: !Sub 'arn:aws:s3:::${DBInstanceId}-log-${AWS::AccountId}'
BufferingHints:
IntervalInSeconds: '60'
SizeInMBs: '50'
CompressionFormat: GZIP
Prefix: slowquery/
RoleARN: !GetAtt 'deliveryRole.Arn'
ProcessingConfiguration:
Enabled: 'false'
CloudWatchLoggingOptions:
Enabled: 'true'
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-slowquerylog-deliverystream'
LogStreamName: 'SlowqueryLogDelivery'
SlowqueryLogstreamlogstream:
Type: AWS::Logs::LogStream
Properties:
LogGroupName: !Sub '/aws/firehose/${DBInstanceId}-slowquerylog-deliverystream'
LogStreamName: 'SlowqueryLogDelivery'
SlowquerySubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Properties:
DestinationArn: !GetAtt 'SlowqueryLogstream.Arn'
FilterPattern: ''
LogGroupName: !Sub "/aws/rds/cluster/${DBInstanceId}-cluster/slowquery"
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 'AuditLogstream.Arn'
- !GetAtt 'ErrorLogstream.Arn'
- !GetAtt 'GeneralLogstream.Arn'
- !GetAtt 'SlowqueryLogstream.Arn'