2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Storage Gateway(File Gataway)でS3 Glacierをいい感じに使いたい!!

Last updated at Posted at 2024-12-18

はじめに

お疲れ様です。矢儀 @yuki_ink です。

コンプラ都合で、ドキュメントを長期間保存しないといけない!
可能な限りコストは下げたい!
・・・S3 Glacierだ!

WindowsからSMBでファイルの格納/取得をできるようにしたい!
・・・Storage Gatewayだ!!

ということで、「Storage Gateway Glacier」などとググると、以下のAWS公式ブログを見つけました。

今回やること

上記のブログを参考に環境構築してみた、というのが今回の内容です。

まず、ブログの内容をざっくりご紹介します。

【前提】
Storage Gateway(今回はFile Gateway)の裏にあるS3バケットでライフサイクルを設定し、オブジェクトをGlacierに移行する。

【課題】
ユーザがGlacierに移行されたオブジェクトにアクセスしようとすると、IOエラー(ファイルにアクセスできません!)が発生する。
オブジェクトをGlacierから復元する必要があるが、普段Storage Gateway に甘やかされている を使っているユーザにわざわざマネコンを操作させるというのもイケてない。
image.png

【解決策】
IOエラーが発生したら、それをトリガーとして、自動で対象のオブジェクトをリストアする仕組みを作ればいいじゃない!
image.png

今回はこれを実装していきます。

手順は以下の通りです。

  1. Storage Gateway(File Gateway)の設定
  2. Glacierへオブジェクトを移行するライフサイクルの設定
  3. 通知用SNSトピックの設定
  4. オブジェクト復元用Lambda関数の設定
  5. CloudWatch LogsとLambda関数の接続
  6. オブジェクト復元完了通知を受け取るための設定

1. Storage Gateway(File Gateway)の設定

こちらの記事を参考にして進めます。

Storage Gateway(File Gataway)を利用するための全てのステップが丁寧に記載されていました!感謝!

  • 対象S3バケットの作成
  • Gatewayインスタンス用サブネットの作成
  • Gatewayインスタンス用ルートテーブルの作成
  • VPCの設定変更
  • S3用VPCエンドポイントの作成
  • ゲートウェイの作成
  • VPCエンドポイントの作成
  • ゲートウェイのアクティブ化
  • ファイル共有の作成
  • Windowsからのマウント

詳細な手順は上記の記事に委ねますが、留意点をいくつか残しておきます。

CloudWatch Logs へのログ出力設定

ゲートウェイのアクティブ化 のところで、CloudWatch Logs にログを吐く設定をしておきましょう。
image.png

これでStorage Gatewayのログを収集できるようになります。

Storage Gateway自体のログとは別に、ファイル共有ごとに監査ログを取得することもできます。
ファイル共有の接続元IPアドレスや接続ユーザなど、より細かい情報を残したい場合は、こちらもあわせて設定してください。

ファイル共有の作成 の画面
「設定をカスタマイズ」ボタンから「監査ログ」の設定を行います。
image.png
image.png

Windowsからのマウント

ファイル共有を作成する際、認証方法を「ゲストアクセス」にしたのですが、私のケースだと、「ファイル共有」のページに表示されているコマンドにユーザの表記がありませんでした。
image.png

なので、このコマンドをそのまま使ってWindowsからマウントしようとすると、ユーザ名とパスワードの入力が求められます。
そのような場合、ユーザ名には (ゲートウェイID)\smbguest を入れます。
パスワードには、ファイル共有を作成する際に指定したものを入れます。

※ゲートウェイIDは、「ゲートウェイ」のページから確認できます。
image.png

コマンドプロンプトの入力は以下のようになります(デバイスレターがYの場合)

>## マネコンに表示されているコマンドを実行
>net use Y: \\172.31.24.50\standard-bucket-for-storage-gateway
Enter the user name for '172.31.24.50': sgw-A3B102CA\smbguest
Enter the password for 172.31.24.50: ********

The command completed successfully.

無事にマウントできました!
Windowsのエクスプローラーからファイルを配置できます。
image.png

この時、S3の画面からも同じオブジェクトが確認できます。
image.png

(余談)プライベートサブネットでStorage Gatewayを作りたい場合

今回はSHIFTさんの記事を参考にパブリックサブネットでStorage Gatewayを構築しましたが、プライベートサブネットで構築したい場合は、以下の記事が参考になると思います。

2. Glacierへオブジェクトを移行するライフサイクルの設定

こちらの記事を参考にして進めます。

まず、バージョニングを有効化。
image.png

続いて、ライフサイクルルールを設定します。
今回は、以下のような設定をすることで、全てのオブジェクトを即座にGlacier Deep Archiveに移行するようにします。

現行バージョンの移行:
 移行先: Glacier Deep Archive
 オブジェクトの作成から: 0日後
 
非現行バージョンの移行 (バージョニングが有効な場合):
 移行先: Glacier Deep Archive
 オブジェクトが非現行になってから: 0日後

image.png
image.png

ライフサイクルルールが設定できました。
image.png

test2.pdf というファイルをStorage Gateway経由で配置してみます。
image.png

S3の画面上で確認で確認すると・・・あれ、スタンダード?
ライフサイクルルールで、すぐGlacierに移行されると思ってたんですが(;'∀')
image.png

調べてみると、UTCの0時まで待ちなされということでした。

S3 ライフサイクルは 1 日に 1 回のみ実行されます。さらに、Amazon S3 はオブジェクトの移行日または有効期限を翌日の午前 0 時 (UTC) に四捨五入します。たとえば、2020 年 1 月 1 日 10:30 UTC に、3 日後にオブジェクトを移行するライフサイクルルールを使用してオブジェクトを作成したとします。このオブジェクトの場合、移行日は 2020 年 1 月 5 日 00:00 UTC です。ライフサイクルルールが満たされているかどうかを確認する前に、十分な時間が経過していることを確認してください。

(出典)1 日以上前に適用したのに Amazon S3 バケットのライフサイクルルールが機能しないのはなぜですか?

というわけで、しばらく放置(2日くらい放置しました)。
再び確認すると、無事にGlacier Deep Archiveに移行できたようです。
image.png

128 KB 未満のオブジェクトは、デフォルトではどのストレージクラスにも移行されません!
ご注意ください!

128 KB 未満のオブジェクトは、デフォルトではどのストレージクラスにも移行されません。
Amazon S3 は S3 ライフサイクル設定にデフォルトの動作を適用し、128 KB 未満のオブジェクトが任意のストレージクラスに移行されないようにします。128 KB 未満のオブジェクトの移行はお勧めしません。オブジェクトごとに移行リクエストが課金されるためです。つまり、オブジェクトが小さいほど、移行コストがストレージの節約額を上回る可能性があります。移行リクエストのコストの詳細については、Amazon S3 料金ページのストレージとリクエストタブの「リクエストとデータの取得」を参照してください。

小さいオブジェクトを移行できるようにするには、カスタム最小サイズ (ObjectSizeGreaterThan) または最大サイズ (ObjectSizeLessThan) を指定するオブジェクトサイズフィルターをライフサイクル移行ルールに追加できます。詳細については、「例: 128 KB 未満のオブジェクトの移行を許可する」を参照してください。

(出典)移行に関する制約と考慮事項

ちなみに・・・
S3上でオブジェクトがGlacierに移行されたからと言って、それが即ち「Storage Gateway(File Gataway)から開けなくなる」ということではありません。
そのファイルが頻繁に開かれるなどして、Storage Gateway(File Gataway)のキャッシュとして残っていれば、普通にそのファイルは開けます。

今回は検証を進めるために、Storage Gateway(File Gataway)の裏にあるS3バケットに直接ファイルを配置することで、疑似的にキャッシュが消えた状況を再現しました。

  • kensho_glacier.pdfkensho_standard.pdf を用意し、S3バケットに直接格納(ファイル名が違うだけで、内容は同じPDFファイルです)
  • kensho_glacier.pdf は手動操作(「アクション」>「ストレージクラスを編集する」)でGlacier Deep Archiveへ移行

▼S3バケットに保管されている2つのオブジェクト
image.png

▼そのオブジェクトがWindowsのエクスプローラー画面からも確認できる
image.png

▼ kensho_glacier.pdf を開くとIOエラーが発生
image.png

▼ kensho_standard.pdf を開くと、正常にPDFが表示される(IOエラーは発生しない)
image.png

これで、「Standardに配置されているオブジェクトはStorage Gateway経由で開けるが、Glacierに配置されているオブジェクトは(キャッシュがなければ)Storage Gateway経由で開けない」 という状況を作ることができました。

3. 通知用SNSトピックの設定

こちらの記事を参考にして進めます。

アクセスポリシーは以下のように設定しておきます。
arn:aws:sns:ap-northeast-1:123456789012:MySNSTopic の部分は、ご自身が作成するSNSトピックのARNに置き換えてください。

AccessPolicy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3ToPublishToSNS",
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"
      },
      "Action": "SNS:Publish",
      "Resource": "arn:aws:sns:ap-northeast-1:123456789012:MySNSTopic",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3:::*"
        }
      }
    },
    {
      "Sid": "AllowLambdaToPublishToSNS",
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "SNS:Publish",
      "Resource": "arn:aws:sns:ap-northeast-1:123456789012:MySNSTopic",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:*"
        }
      }
    }
  ]
}

メール通知を行うSNSトピックができました!

サブスクリプションの設定も忘れずにやっておきましょう!

4. オブジェクト復元用Lambda関数の設定

今回、LambdaのランタイムはPython 3.13を利用します。
コード、環境変数、アクセス権限、タイムアウト値は、それぞれ以下のように設定します。

4.1. コード

原典で紹介されているPythonのコードが古かったので、Python3に対応する形に修正しました。
また、通知周りを充実させました。

コードは以下の通りです。

FGWGlacierRestore.py
import os
import json
import boto3
import base64
import gzip
from botocore.exceptions import ClientError
from io import BytesIO

def lambda_handler(event, context):
    # CloudWatch Logsデータを取得し、base64デコードしてgzip解凍
    cw_data = event['awslogs']['data']
    cw_logs = gzip.GzipFile(fileobj=BytesIO(base64.b64decode(cw_data))).read()
    log_events = json.loads(cw_logs)

    # 各ログエントリを処理
    result = None
    for log_entry in log_events['logEvents']:
        result = process_recall(log_entry)
        print(result)

    return {
        'statusCode': 200,
        'body': result
    }

def process_recall(log_entry):
    print("message contents: " + log_entry['message'])
    message_json = json.loads(log_entry['message'])
    print(message_json)

    # SNSクライアントの作成
    sns_client = boto3.client('sns')
    
    # エラータイプを確認
    if 'type' in message_json:
        print("Found ErrorType")
        error_type = message_json['type']
        print("ErrorType = " + error_type)
        if error_type != "InaccessibleStorageClass":
            # エラータイプが異なる場合のSNS通知
            sns_topic_arn = os.getenv('SnsTopicArn')
            sns_client.publish(
                TopicArn=sns_topic_arn,
                Message=f"Unexpected error type '{error_type}' received, which is not related to storage class.",
                Subject="S3 Object Restoration Not Initiated"
            )
            return "Unexpected error: not related to storage class"
    else:
        return "error: no type entry"

    # バケット名とオブジェクトキーを確認
    s3_bucket = message_json.get('bucket')
    s3_key = message_json.get('key')
    if not s3_bucket:
        return "error: no bucket"
    if not s3_key:
        return "error: no key"

    # 環境変数の確認と設定
    restore_days = os.getenv('RestoreDays')
    recall_tier = os.getenv('RecallTier')
    sns_topic_arn = os.getenv('SnsTopicArn')

    if not restore_days or not recall_tier or not sns_topic_arn:
        return "error: missing required environment variables"

    # SNS通知を送信(復元開始通知)
    try:
        sns_message = (f"Restoration started for object '{s3_key}' in bucket '{s3_bucket}' "
                       f"because the error type was '{error_type}', indicating an inaccessible storage class.")
        sns_client.publish(
            TopicArn=sns_topic_arn,
            Message=sns_message,
            Subject="S3 Object Restoration Started"
        )
    except ClientError as e:
        print(f"Failed to send SNS notification: {e}")
        return "error: SNS notification failed"

    # S3オブジェクトの復元処理
    s3 = boto3.resource('s3')
    s3_object = s3.Object(s3_bucket, s3_key)
    try:
        result = s3_object.restore_object(RestoreRequest={
            'Days': int(restore_days),
            'GlacierJobParameters': {'Tier': recall_tier}
        })
        # 復元処理が正常にリクエストされた場合のSNS通知
        sns_message = (f"Restoration request for object '{s3_key}' in bucket '{s3_bucket}' "
                       "has been successfully initiated. The restoration process may take several hours, "
                       "and you will be notified once it is complete.")
        sns_client.publish(
            TopicArn=sns_topic_arn,
            Message=sns_message,
            Subject="S3 Object Restoration Request Successful"
        )
    except ClientError as e:
        # エラー発生時のSNS通知
        if e.response['Error']['Code'] == 'RestoreAlreadyInProgress':
            error_message = "Restoration is already in progress for this object."
        else:
            error_message = f"Unexpected error while attempting to recall object: {e}"
        
        sns_client.publish(
            TopicArn=sns_topic_arn,
            Message=f"Failed to initiate restoration for object '{s3_key}' in bucket '{s3_bucket}': {error_message}",
            Subject="S3 Object Restoration Failed"
        )
        return error_message

    print(result)
    return result

4.2. 環境変数

以下のように環境変数を設定しておきます。

キー 値(例) 説明
RecallTier Standard 取り出しオプション(復元のスピード)を指定
RestoreDays 1 復元したオブジェクトが一時的にアクセス可能となる日数を指定(整数値)
SnsTopicArn arn:aws:sns:(リージョン):(アカウントID):(トピック名) 復元開始時やエラー発生時の通知を送信するSNSトピックのARN

image.png

RecallTier に指定できる値は以下の通りです。

S3 Glacier Flexible Retrieval ストレージクラスでは、Expedited、Standard、Bulk の取り出しオプションを使用できます。
ただし、S3 Glacier Deep Archive ストレージクラスに使用できるのは、Standard または Bulk の取り出しオプションのみです。

(出典)AWS CLI を使用して S3 Glacier Flexible Retrival または S3 Glacier Deep Archive ストレージクラスから Amazon S3 オブジェクトを復元するにはどうすればよいですか?

各オプションの詳細は以下を確認してください。
image.png
(出典)アーカイブ取得オプションを理解する

4.3. アクセス権限

Lambda関数に紐づくIAMロールには、オブジェクトの復元やSNSトピックへの通知などに必要な権限を付与しておきます。
今回は以下のポリシーを適用します。
"Resource" の値は適宜修正してください。

IAMPolicyForLambda.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:RestoreObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-storage-gateway-bucket-name/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:my-region:my-account-id:MySnsTopic"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

4.4. タイムアウト値

デフォルトの3秒だとタイムアウトしてしまうので、タイムアウト値を伸ばします。

とりあえず1分にしておきます。
image.png

5. CloudWatch LogsとLambda関数の接続

まず、CloudWatch Logsの画面から、Storage Gatewayのログが出力されているロググループを選択し、ファイル共有ごとにログストリームが作成されていることを確認します。
image.png

ログストリームからログを覗いてみると、以下のようなログが出ているはずです。
▼ kensho_glacier.pdf を開いてIOエラーが発生したときのログ

sgwlog.json
{
    "severity": "ERROR",
    "bucket": "standard-bucket-for-storage-gateway",
    "s3Key": "kensho_glacier.pdf",
    "roleArn": "(IAMロールのARN)",
    "source": "share-44EE8C21",
    "type": "InaccessibleStorageClass",
    "operation": "s3:GetObject",
    "key": "kensho_glacier.pdf",
    "gateway": "sgw-A3B102CA",
    "timestamp": "1731903680519"
}

ここまで確認できれば、CloudWatch LogsとLambda関数を接続していきましょう。

先ほど作成したLambda関数の設定画面から、トリガーを設定します。
image.png

トリガーの設定は以下の通りです。

項目 値(例)
ソース CloudWatch Logs
ロググループ ※Storage Gatewayのログが出力されているロググループ
フィルターの名前 ※任意の値(FGWLogsFilter など)
フィルターパターン ERROR

原典ではフィルターパターンとして InaccessibleStorageClass を設定していますが、今回はあえて ERROR を設定します。
※エラーが発生した際、オブジェクトの復元を待てばいいのか、あるいは別のアクションが必要なのか、ユーザ側で判断できるようにするため、とりあえず全てのエラー通知をLambda関数に連携し、エラーの内容に応じてユーザに通知を行うようにします。

トリガーが設定できました!
image.png

6. オブジェクト復元完了通知を受け取るための設定

こちらの記事を参考にして進めます。

プレフィクスとサフィックスは設定しません。
image.png

復元が完了しました (s3:ObjectRestore:Completed) にチェックを入れておきます。
今回はついでに 復元されたオブジェクトの有効期限切れ (s3:ObjectRestore:Delete) にもチェックを入れておきました。
復元が開始されました (s3:ObjectRestore:Post) にもチェックを入れてもいいですが、Lambdaからの通知と被ってしまうので、いったんなしで。
image.png

送信先として、先ほど作ったSNSトピックを指定します。
image.png

できた!
image.png

やってみた

まず、Windowsのエクスプローラーから kensho_glacier.pdf を開いて、IOエラーを発生させます。
image.png

CloudWatch Logsにも、エラーログが出力されました。
image.png

Lambda関数も正常に動いたようです。
▼ Lambda関数のログ
image.png

通知メールもちゃんと届いていました!
▼「リストア処理が始まったよ」
image.png

▼「復元のリクエストは正常終了したよ。復元完了したらまた通知行くから、それまで待ってね。」
image.png

S3から kensho_glacier.pdf を確認すると、「復元が進行中」という表示がされていました。
よさそう!
image.png

待つこと12時間(長いがDeep Archiveなので仕方ない)、無事に復元が完了!
「復元の完了」という表示がされ、有効期限も示されています。
image.png

復元が完了したら、ストレージクラスがStandardになるのかなと思っていたのですが、マネジメントコンソールに表示されているストレージクラスは、あくまでGlacier Deep Archiveのままでした。
image.png

通知メールはこのような形で届いていました。
生のJSONではありますが、、笑
"eventName":"ObjectRestore:Completed" の記載から、内容は分かります。
image.png

そして、Windowsのエクスプローラーからも問題なく kensho_glacier.pdf が開けました!
オブジェクト復元後、復元の有効期間中はIOエラーは発生しないことが確認できました。
image.png

※復元の有効期限が切れても、Storage Gateway側にキャッシュが残っていれば、しばらくはそのままファイルを開けます。

Tips

最初はCloudFormationでStorage Gatewayを作ろうとしたのですが、思わぬ苦労があったので供養します。

そもそもStorage GatewayがCloudFormationに対応していなかった・・・!!

CloudFormationでやろうと思うと、Storage Gateway用のEC2を作るところまでが限界です。
Storage Gateway用に専用のAMIが用意されているので、それを使ってEC2を作ります。
ただ、そのEC2を使ってStorage Gatewayを作ろうとすると、EC2からアクティベーションキーを取得する必要があります。

それをCloudFormationでやろうとするとかなり大変だったので諦めました😅

CloudFormationでStorage Gateway用のEC2を作って、ユーザーデータを使ってアクティベーションキーの取得など色々やろうとしたのですが、ユーザーデータのスクリプトが動かず。
調査のため、Storage Gateway用のEC2にSSHしたらこんな感じでした。

image.png

これはダメそーー!!
Storage Gateway専用AMI感がありますね。

一応「6: Command Prompt」を選択してみて、適当にコマンドを打ってみます。
・・・基本的なコマンドさえ打てない!
image.png

ということで、Storage Gatewayを作るときは、今のところは大人しくマネジメントコンソールから操作することをおすすめします。

終わりに

コストを抑えるための手段としてGlacierは非常に強力な手段ですが、ファイルのサイズや更新・取り出しの頻度によっては、むしろStandardより利用料が高額になってしまうことがあります。

ご注意ください!!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?