Amazon Auroraの各種ログをCloudWatch LogsからS3に連携してみた
こんにちは、崔です。
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'