Amazon Web Services ブログ

Amazon S3 オブジェクトを AWS Backup でリストアする際に最終更新日時のタイムスタンプを保存する方法

このブログは 2023 年 4 月 12 日に Luca Licheri (Senior Technical Account Manager) と Sergio Simone (Solutions Architect) によって執筆された内容を日本語化したものです。原文はこちらを参照してください。

通常、規制の厳しい業界のお客様は、データの整合性を維持し、ライフタイム全体を通じて利用できるよう義務付けられた規則に従っています。整合性要件を満たすためには、関連する任意の監査証跡やオブジェクト作成日時、最終更新日時、タグなどのメタデータ情報と共にデータはリストア可能でなければなりません。

AWS Backup で取得した Amazon Simple Storage Service (S3) オブジェクトのバックアップをリストアする際、リストアされたオブジェクトの一意のハッシュ値を検証することで、オリジナルデータの整合性が保証されます。しかしながら、リストアされたオブジェクトは新しいオブジェクトとして扱われ、その結果、最終更新日時のメタデータは新しい作成日時が設定されます。最終更新日時は、システムで定義されたオブジェクトのメタデータフィールドで、リストア後に元の値に修正することはできません。そのため、リストアされたオブジェクトの状態は、AWS Backup により取得された元の状態と同一ではありません。

この記事では、Amazon S3 オブジェクトタグを使用して、バックアップとリストア後に、新規および既存のオブジェクトの最終更新日時に関するタイムスタンプを保持する方法を説明します。このソリューションを実装することで、元のオブジェクトの最終更新日時に関するタイムスタンプを保持する必要があっても、お客様は AWS Backup を利用することができます。検査や監査を実施する際に、バックアップのリストアなどオブジェクトに影響を与えるすべてのイベントの完全な監査証跡へのアクセスが容易になります。

この記事の執筆時点では、Amazon S3 のオブジェクトあたりのタグ数は 10 個に制限されていることに注意してください。そのため、すでにこの制限に達している場合、このソリューションはお客様のユースケースに適していない可能性があります。また、Amazon S3 オブジェクトタグのコストについては、こちらのページを参照してください。

ソリューションの概要

このソリューションは、Amazon S3 オブジェクトタグを利用して、オブジェクトの最終更新日時のタイムスタンプを永続的に保持します。セクション 1 では、AWS Backup で保護するバケットにおいて、新しく作成されるオブジェクトに対して、最終更新日時に関するタイムスタンプを含む Amazon S3 オブジェクトタグの作成を自動化する方法を示します。

最終更新日時のタイムスタンプが Amazon S3 オブジェクトタグとして自動的に保存された後、AWS Backup がバックアップの一部として最終更新日時のタイムスタンプを保存します。これによって、そのタグをバックアップの一部として保存し、必要に応じて保護されたオブジェクトと共にリストアすることができるようになります。

この情報は、オブジェクトのユーザー定義メタデータとしてではなく、オブジェクトタグとして保存することをお勧めします。オブジェクトのユーザー定義メタデータを編集する場合、実際には Amazon S3 はオブジェクトの新しいバージョンを作成するため、AWS Lambda 関数が再びトリガーされ、不必要な繰り返しが発生します。

次に、セクション 2 では、バケット内にすでに存在するオブジェクトの最終更新日時のタイムスタンプを含む Amazon S3 オブジェクトタグの作成を自動化する方法を紹介します。これにより、Amazon S3 のすべての保護対象オブジェクトが、Amazon S3 のオブジェクトタグとしてバックアップされ、保存されている最終更新日時のタイムスタンプが自動的に含まれるようになります。

ソリューションの一部として、新しいタグを追加する際に、各オブジェクトに存在するすべてのタグを保持する方法についても紹介します。現時点では、オブジェクトにタグを追加する API コールは、既存のすべてのタグを新しいタグに置き換えます。このため、保存したい既存のタグを収集し、新しいタグと一緒に書き込む方法をコードで説明します。

セクション 1: 新規および更新されたオブジェクトに最終更新日時のタイムスタンプオブジェクトタグを追加する

最終更新日時に関するオブジェクトタグを、新しく作成した Amazon S3 オブジェクトに追加し、既存のオブジェクトでは保持したいと考えます。大まかな説明となりますが、AWS Lambda 関数をトリガーするすべてのオブジェクト作成に関するイベント (PUT、POST、COPY、MultipartUpload) に基づいて、Amazon S3 イベント通知を設定する方法を説明します。次に、AWS Lambda 関数を使用してオブジェクトから最終更新日時に関するデータを自動的に取得し、それを AWS Backup によってオブジェクトとともにバックアップし最終的にリストアされるように、オブジェクトタグとして保存する方法を示します。

次の図は、新しく作成または更新されたオブジェクトにタグを自動的に挿入するアーキテクチャを示しています。

Diagram of Amazon S3 triggering AWS Lambda on each new creation and modification to save last-modified timestamp on the object tag

最終更新日時のタイムスタンプを取得する AWS Lambda 関数を作成する

 まず最初に、AWS Lambda 関数を作成することから始めましょう:

マネジメントコンソールの AWS Lambda のページから、関数の作成に進みます。

  1. 一から作成を選択し、関数名を入力し、ランタイムとして Python 3.9 を選択します。
  2. コストを低くするために Graviton 2 を利用する arm64 を選択します。
  3. 正しいアクセス権限を選択します(または新規に作成します)。AWS Lambda には、Amazon S3 のソースバケットのオブジェクトタグを読み書きする権限が必要です。
  4. 関数の作成を選択します。

以下のサンプルコードをエディターに入力します:

import json
import boto3
import datetime

s3 = boto3.resource('s3')
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    
    s3BucketName = event['Records'][0]['s3']['bucket']['name']
    s3ObjectKey = event['Records'][0]['s3']['object']['key']
    
    try:
        tags = s3_client.get_object_tagging(
            Bucket=s3BucketName,
            Key=s3ObjectKey,
        )
        current_tags = tags['TagSet']
    except:
        current_tags = []
        
    try:
        object = s3.Object(s3BucketName,s3ObjectKey)
        
        last_modified_date = object.last_modified.strftime("%Y-%m-%d %X%z")
        
        #Update list with new last modified date value
        updated_tags = updateTags(current_tags, 'last-modified-date', last_modified_date)
        
        s3_client.put_object_tagging(
            Bucket = s3BucketName,
            Key = s3ObjectKey,
            Tagging={
                'TagSet': updated_tags,
            },
    )
        
    except Exception as e:
        raise e

# Define functions to get and put existing object tags
def keyExists(data, key):
    return any(d['Key'] == key for d in data)

def addKey(data, key, value):
    data.append({'Key': key, 'Value': value})
    return data

def replaceKey(d, key, value):
    if d['Key'] == key:
        d['Value'] = value
        return d
    else:
        return d

def updateTags(data, key, value):
    if(keyExists(data, key)):
        return[replaceKey(d, key, value) for d in data]
    else:
        return addKey(data, key, value)

次のステップでは、オブジェクトが作成または更新されるたびに AWS Lambda 関数をトリガーする Amazon S3 イベント通知を設定します。これらのイベントが発生すると、Amazon S3 は JSON 形式でイベントを AWS Lambda に送信し、lambda_handler によって処理されます。

この時点で、上記の関数は次の 4 つの処理を行います。

  1. オブジェクトの最終更新日時 (または作成日時) を取得します。
  2. オブジェクトのすべてのタグを、Python の辞書型の変数に格納します。
  3. Python の辞書型の変数における最終更新日時に関するタイムスタンプを置き換えます (新しいオブジェクトの場合は追加します)。
  4. すべてのタグをオブジェクトに付与します。

AWS Lambda 用の Amazon S3 イベント通知を作成する

次に、この AWS Lambda 関数をトリガーする Amazon S3 イベント通知を作成する必要があります。

Amazon S3 ソースバケットにおいて:

  1. プロパティに移動します。
  2. イベント通知においてイベント通知を作成を選択します。
  3. オブジェクトの特定のサブセットのみを対象とする場合には、プレフィックスサフィックスを指定します。
  4. イベントタイプすべてのオブジェクト作成イベントを選択します。
  5. 送信先で AWS Lambda 関数が選択されていることを確認します。
  6. 前のステップで作成した AWS Lambda 関数を選択します。
  7. 変更の保存を選択します。

ソースバケットのオブジェクトには、最終更新日時に関するオブジェクトタグが付与されます。その結果、新しいバックアップにも、最終更新日時に関するオブジェクトタグが付与されます。

セクション 2: 最終更新日時のタイムスタンプオブジェクトタグを、既存のオブジェクトに追加する

これで、新規および更新されたオブジェクトの最終更新日時のタイムスタンプを保存できました。AWS Backup で既存のオブジェクトを保護したい場合にも、AWS Lambda と Amazon S3 のイベント通知を利用することで、同じことができます。

バケット内の既存のオブジェクトには、最終更新日時のタイムスタンプを Amazon S3 オブジェクトタグとして保存する AWS Lambda 関数をトリガーする Amazon S3 イベント通知は発生しなかったため、次のステップに進む必要があります。

Amazon S3 Batch Operations ジョブを 1 回実行して、既存の各オブジェクトの最終更新日時のタイムスタンプをキャプチャし、その値を初めにオブジェクトタグに保存する方法をお勧めします。Amazon S3 Batch Operations ジョブには、Amazon S3 Batch Operations ジョブにアクションを実行させたいすべてのオブジェクトをリストしたマニフェストが必要です。Amazon S3 インベントリを利用してマニフェストを作成できます。

このセクションでは、以下の方法を紹介します:

  1. 既存のオブジェクトのリストを用いて、Amazon S3 Batch Operations のマニフェストを生成します。
  2. 前回同様に AWS Lambda 関数を作成して、最終更新日時のタイムスタンプをオブジェクトタグに追加します。
  3. マニフェストにリストされている各オブジェクトに対して、AWS Lambda 関数を実行する Batch Operations ジョブを作成します。

以下の図はソリューションを示しています:

Diagram of S3 Batch Operation running an AWS Lambda function on each object of the Amazon S3 inventory

インベントリ

Amazon S3 インベントリレポートの作成から始めましょう。インベントリレポートには、AWS Lambda 関数を実行して、既存の Amazon S3 オブジェクトタグと現在の最終更新日時に関するタイムスタンプ情報を取得する予定のすべてのオブジェクトのリストが含まれます。

次に、このインベントリレポートを使用して Amazon S3 Batch Operations を実行するマニフェストを作成し、最終更新日時のタイムスタンプを含む新しい Amazon S3 オブジェクトタグを追加します。

  1. ソースバケットの管理セクションまで進み、インベントリ設定までスクロールします。ここで、インベントリ設定を作成できます。
  2. 項目を入力し、送信先バケットを選択します (この際、ソースバケットに特定のプレフィックスを使用できます)。このバケット/プレフィックスは、マニフェストファイルの保存に使用されます。
  3. 頻度として日別を選択します。ただし、使用するのは 1 回だけです。
  4. 出力形式は CSV を選択します。
  5. 作成を選択します。

インベントリが作成されると、毎日実行され、選択した送信先バケットの中にマニフェスト CSV ファイルとして結果を保存します。インベントリの対象となるバケット内のオブジェクトの数によっては、最初のマニフェストが生成されるまでに最大 48 時間かかる場合があることに注意してください。インベントリが完了したときのイベント通知を設定できます。

Amazon S3 Batch Operations ジョブを実行する AWS Lambda 関数を作成する

最後のステップでは、Amazon S3 Batch Operations ジョブが各オブジェクトに対して実行する AWS Lambda 関数を作成します。

以下のサンプルコードをエディターに入力します:

import json
import boto3
import datetime

s3 = boto3.resource('s3')
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    
    # Parse job parameters from S3 batch operations
    jobId = event['job']['id']
    invocationId = event['invocationId']
    invocationSchemaVersion = event['invocationSchemaVersion']
    
    # Prepare results
    results = []
    
    # S3 batch operations currently only passes a single task at a time in the array of tasks.
    task = event['tasks'][0]
    
    # Extract the task values we might want to use
    taskId = task['taskId']
    s3Key = task['s3Key']
    s3VersionId = task['s3VersionId']
    s3BucketArn = task['s3BucketArn']
    s3BucketName = s3BucketArn.split(':')[-1]
    
    #Get current (if any) tags from object. If no tags exist, create an empty list
    try:
        # If object does not have tags, it will generate an error
        # In this case ignore the error and create an empty list
        tags = s3_client.get_object_tagging(
            Bucket=s3BucketName,
            Key=s3Key,
        )
        current_tags = tags['TagSet']
    except:
        current_tags = []
        

    try:
        # Assume it will succeed for now
        resultCode = 'Succeeded'
        resultString = 'Last Modified date added to Tag'
        
        object = s3.Object(s3BucketName,s3Key)
        
        last_modified_date = object.last_modified.strftime("%Y-%m-%d %X%z")
        
        #Update list with new last modified date value
        updated_tags = updateTags(current_tags, 'last-modified-date', last_modified_date)
        
        s3_client.put_object_tagging(
        Bucket = s3BucketName,
        Key = s3Key,
        Tagging={
            'TagSet': updated_tags,
        },
    )
    except Exception as e:
        # If we run into any exceptions, fail this task so batch operations does not retry it and
        # return the exception string so we can see the failure message in the final report
        # created by batch operations.
        resultCode = 'PermanentFailure'
        resultString = 'Exception: {}'.format(e)
    finally:
        # Send back the results for this task.
        results.append({
            'taskId': taskId,
            'resultCode': resultCode,
            'resultString': resultString
        })
    return {
        'invocationSchemaVersion': invocationSchemaVersion,
        'treatMissingKeysAs': 'PermanentFailure',
        'invocationId': invocationId,
        'results': results
        }
        

# Define functions to get and put existing object tags
def keyExists(data, key):
    return any(d['Key'] == key for d in data)

def addKey(data, key, value):
    data.append({'Key': key, 'Value': value})
    return data

def replaceKey(d, key, value):
    if d['Key'] == key:
        d['Value'] = value
        return d
    else:
        return d

def updateTags(data, key, value):
    if(keyExists(data, key)):
        return[replaceKey(d, key, value) for d in data]
    else:
        return addKey(data, key, value)

マニフェストにリストされている各オブジェクトに対して、この AWS Lambda 関数を実行する Amazon S3 Batch Operations ジョブを作成する前に、関数のコードが実行する内容を確認しましょう。Batch Operations ジョブは、lambda_handler 関数内からアクセス可能なイベントオブジェクトを介して、すべての必要となる Amazon S3 のオブジェクトに関する情報を渡します。taskId、s3Key、s3VersionId、s3BucketArn、s3BucketName の値を抽出し、Amazon S3 オブジェクトの識別に利用します。次に、最終更新日時のタイムスタンプを読み取り、その値をオブジェクトタグにコピーします。その他の実行内容は、前のセクションで紹介した AWS Lambda 関数同様です。

Amazon S3 Batch Operations ジョブを作成して、既存のオブジェクトに対して AWS Lambda 関数を実行する

次に、Amazon S3 Batch Operations ジョブを作成します。

  1. Amazon S3 の左側のメニューから Batch Operations とリージョンを選択します。
  2. マニフェスト形式については、Amazon S3 インベントリレポート (manifest.json) を選択し、マニフェストオブジェクトの正確なパスを入力して、次へを選択します。
  3. オペレーションで、AWS Lambda 関数を呼び出すを選択します。
  4. 前のステップで作成した AWS Lambda 関数を選択し、次へを選択します。
  5. 完了レポートを保存する場合はバケット/プレフィックス、関数を実行するためのロールを選択し、ジョブの作成を選択します。
  6. ジョブの準備ができたら、そのジョブを選択し、ジョブを実行を選択してプロセスを実行できます。

これで、既存の各オブジェクトには、最終更新日時のタイムスタンプが付いた新しいタグが付けられ、すべての新しいバックアップに統合されます。

Cleaning up

バックアップに含みたい既存の各オブジェクトの最終更新日時のタイムスタンプをインポートできました。次の通りにリソースの削除を行ってください:

  • Batch Operations ジョブ
  • マニフェストファイル
  • 前のセクションで紹介した既存のすべてのオブジェクトに最終更新日時のタイムスタンプを書き込む Lambda 関数

Conclusion

このブログでは、AWS Lambda、Amazon S3 イベント通知、Amazon S3 Batch Operations、Amazon S3 オブジェクトタグを組み合わせて、新規および既存のオブジェクトの最終更新日時のタイムスタンプを自動的にキャプチャして永続的に保存する方法を説明しました。

Amazon S3 オブジェクトとそのオブジェクトタグを保護する AWS Backup または他の AWS Storage Competency Partner バックアップソリューションを使用して、Amazon S3 オブジェクトの最終更新日時のタイムスタンプを保存できるようになりました。これにより、データのコンプライアンスやバックアップ監査の目的で必要になった際、Amazon S3 のバックアップからまとめてリストアできます。AWS ストレージパートナーはこちらからご覧いただけます。

AWS Backup を Amazon S3 と組み合わせて使用する際、最終更新日時のタイムスタンプを保存しなければならないことがあります。このブログは、AWS Backup からリストアした後でも最終更新日時のタイムスタンプを自動的に保持できるソリューションを構築することに役立ちます。これにより、コンプライアンスに関する認証やその他のデータ整合性評価をする際、手助けすることができます。

翻訳はソリューションアーキテクトの佐藤真也が担当しました。

Luca Licheri

Luca Licheri

Luca Licheri は、インフラストラクチャとクラウド環境で 15 年以上の経験を持つシニア テクニカル アカウントマネージャーで、世界中のお客様のオペレーショナルエクセレンスの向上に取り組んでいます。Luca はテクノロジーに情熱を注いでおり、自由時間には読書やギター演奏を楽しんでいます。

Sergio Simone

Sergio Simone

Sergio Simone は、25 年の実務システム管理、エンタープライズアーキテクチャ、チームリーダーの経験を持つグローバルソリューションアーキテクトであり、過去 7 年以上にわたり、ヘルスケアおよびライフサイエンス業界の成功に注力してきました。