michimani.net

[レポート] JAWS-UG CLI 専門支部 #170R S3基礎 ライフサイクル に参加しました #jawsug_cli

2020-10-30

JAWS-UG CLI専門支部 #170R S3基礎 ライフサイクル に参加したので、そのレポートです。

connpass のイベントページはこちら。

これまでの CLI 専門支部参加レポートはこちら。

目次

S3 の全体像、ストレージクラス

ハンズオン

今回のハンズオンでは、新たに作成した S3 バケットに対して、プレフィックス別に複数のライフサイクルを設定し、各ルールが適用されるようなオブジェクトを作成しました。ライフサイクルの性質上、実際の挙動は最短でも翌日 (厳密には次の 00:00 UTC) にならないとわからないので、結果については翌日以降に確認します。

今回も、ハンズオンの詳細な手順についてはイベントページの資料におまかせするとして、今回のメインであるライフサイクルの設定部分のコマンドについて書いておきます。

2.1 S3 ライフサイクルドキュメントの作成

S3 バケットのライフサイクルは、複数の ルール を設定可能で、日付の計算に用いるタイムゾーンは UTC、起算日はオブジェクトの作成日となっています。公式ドキュメントでは次のように記載されています。

Amazon S3 は、ルールに指定された日数をオブジェクトの作成時間に加算し、得られた日時を翌日の午前 00:00 UTC (協定世界時) に丸めることで、時間を算出します。たとえば、あるオブジェクトが 2014 年 1 月 15 日午前 10 時 30 分 (UTC) に作成され、移行ルールに 3 日と指定した場合、オブジェクトの移行日は 2014 年 1 月 19 日 0 時 0 分 (UTC) となります。

ライフサイクル設定の要素 - Amazon Simple Storage Service

ハンズオンでは下記のような 3 つのルールを設定しました。

JSON で記述すると下記のような内容になります。ちなみに、公式ドキュメントでは XML での指定方法が書かれているが、 JSON での指定方法が書かれていない。(日本語だけでなく English でも。)

{
  "Rules": [
    {
      "ID": "temporary-objects",
      "Filter": {
        "Prefix": "tmp/"
      },
      "Status": "Enabled",
      "Expiration": {
        "Days": 1
      }
    },
    {
      "ID": "archive-objects",
      "Filter": {
        "Prefix": "archive/"
      },
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 0,
          "StorageClass": "GLACIER"
        }
      ],
      "Expiration": {
        "Days": 1
      }
    },
    {
      "ID": "short-keep-objects",
      "Filter": {
        "Prefix": "logs/"
      },
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 60,
          "StorageClass": "INTELLIGENT_TIERING"
        },
        {
          "Days": 90,
          "StorageClass": "ONEZONE_IA"
        },
        {
          "Days": 120,
          "StorageClass": "GLACIER"
        },
        {
          "Days": 210,
          "StorageClass": "DEEP_ARCHIVE"
        }
      ],
      "Expiration": {
        "Days": 211
      }
    }
  ]
}

S3 バケットへのルールの適用は s3api put-bucket-lifecycle-configuration コマンドを使います。

$ aws s3api put-bucket-lifecycle-configuration \
--bucket ${S3_BUCKET_NAME} \
--lifecycle-configuration file://s3-bucket-lifecycle.json

各ストレージクラスには最小期間が設定されているので、例えば Glacier への移行日数 120 日に対して Glacier Deep Archive への移行日数を 200 日に設定しようとすると、 s3api put-bucket-lifecycle-configuration 実行時に下記のようなエラーが出ます。

An error occurred (InvalidArgument) when calling the PutBucketLifecycleConfiguration operation: ‘Days’ in the ‘Transition’ action for StorageClass ‘DEEP_ARCHIVE’ for filter ‘(prefix=logs/)’ must be 90 days more than ‘filter ‘(prefix=logs/)’’ in the ‘Transition’ action for StorageClass ‘GLACIER’

ライフサイクルが設定されると、マネジメントコンソールの S3 バケット詳細で次のように表示されます。
余談ですが、バケット詳細画面の UI が変わってますね。

S3 Bucket lifecycle

また、各ルールの詳細画面では日数の経過とストレージクラスの遷移がビジュアライズされた形で確認することができます。

S3 Bucket lifecycle detail

3.3 S3 オブジェクトのライフサイクル確認

ルールが適用されるオブジェクトについては、 s3api head-object コマンドで得られるメタデータの中に含まれる Expiration の値を確認することで、ライフサイクルを確認することができます。

$ aws s3api head-object \
--bucket ${S3_BUCKET_NAME} \
--key tmp/2020-10-29.txt \
--query 'Expiration' \
--output text

expiry-date="Sat, 31 Oct 2020 00:00:00 GMT", rule-id="temporary-objects"

211 日経過後に破棄される logs/ のオブジェクトについては、次のような出力が得られます。

$ aws s3api head-object \
--bucket ${S3_BUCKET_NAME} \
--key logs/2020-10-29.log \
--query 'Expiration' \
--output text

expiry-date="Sat, 29 May 2021 00:00:00 GMT", rule-id="short-keep-objects"

また、今回のハンズオンで設定したルールの中で その挙動が最短で確認できるのは archive-objects のルールです。 "Days": 0Glacier への移行を設定しているので、翌日の 09:00 JST には結果が確認できます。ということで、移行前のストレージクラスを事前に確認しておきます。date コマンドで実行時の日時も一緒に出力しています。

$ date && aws s3api list-objects-v2 \
--bucket ${S3_BUCKET_NAME} \
--prefix "archive/" \
--query "Contents[].[join(\`\`, [Key, \`: \`,  StorageClass])]" \
--output text

2020年 10月29日 木曜日 22時07分14秒 JST
archive/2020-10-29.txt: STANDARD

(翌日)

翌日の 09:00 JST 以降にあらためて確認してみます。

$ date && aws s3api list-objects-v2 \
--bucket ${S3_BUCKET_NAME} \
--prefix "archive/" \
--query "Contents[].[join(\`\`, [Key, \`: \`,  StorageClass])]" \
--output text

2020年 10月30日 金曜日 22時00分50秒 JST
archive/2020-10-29.txt: GLACIER

ストレージクラスが S3 Glacier に移行しています。 09:00 JST (= 00:00 UTC) になってすぐに確認したときにはまだ S3 標準 のままでした。どうやらライフサイクルによるストレージクラスの移行には遅延が発生する場合があるようです。移行が完了していない場合でも、発生するコストの変更に関してはライフサイクルルールを満たした時点で適用されるみたいです。

プラス α

ハンズオン中にいくつか疑問に思ったことがあったので、それらについて試してみます。

既存のバケットにライフサイクルを設定する

ハンズオンでは、新たに作成したバケットに対してライフサイクルを設定し、その後オブジェクトを追加しました。では、既にオブジェクトが存在するバケットに対してライフサイクルを設定した場合はどのような挙動になるのでしょうか。

今回は、個人で運用している Web サービス (と言って良いのかはさておき) の ココイチ注文料金簡易カリキュレータ の CloudFront で生成しているログを対象に、ライフサイクルを設定してみます。

ログは既にとあるバケット (cf-logs-bucket (仮称)) に生成されていて、例えば 2019 年6月のログオブジェクトとストレージクラスは次のようになっています。date コマンドで実行時の日時も一緒に出力しています。

$ date && aws s3api list-objects-v2 \
--bucket cf-logs-bucket \
--prefix coco1.app/2019-06 \
--max-keys 5 \
--query "Contents[].[join(\`\`, [Key, \`: \`,  StorageClass])]" \
--output text

2020年 10月30日 金曜日 22時05分55秒 JST
coco1.app/2019-06-09-08-17-46-3A0F0F0D5E81E231: STANDARD
coco1.app/2019-06-09-08-18-35-A18FDA5904E7D007: STANDARD
coco1.app/2019-06-09-08-19-59-53378BC489E1596B: STANDARD
coco1.app/2019-06-09-08-20-53-D6015EC587DF38C1: STANDARD
coco1.app/2019-06-09-08-22-23-4DB13667C68BCEBA: STANDARD

このバケットに対して、 coco1.app/ というプレフィックスを対象に、今回のハンズオンと同様に次のようなライフサイクルを設定してみます。現時点 (2020/10/30) でオブジェクトが生成されてから 1 年以上経過しているので、設定する日数は次のようにします。

{
  "Rules": [
    {
      "ID": "keep-log-objects",
      "Filter": {
        "Prefix": "coco1.app/"
      },
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 360,
          "StorageClass": "GLACIER"
        },
        {
          "Days": 540,
          "StorageClass": "DEEP_ARCHIVE"
        }
      ],
      "Expiration": {
        "Days": 720
      }
    }
  ]
}

上記のルールを設定します。

$ aws s3api put-bucket-lifecycle-configuration \
--bucket cf-logs-bucket \
--lifecycle-configuration file://coco1_log_lifecycle_rule.json

一つのオブジェクトに対して有効期限を確認してみます。

$ aws s3api head-object \
--bucket cf-logs-bucket \
--key coco1.app/2019-06-09-08-17-46-3A0F0F0D5E81E231 \
--query Expiration \
--output text

expiry-date="Sun, 30 May 2021 00:00:00 GMT", rule-id="keep-log-objects"

ルールが適用されているのがわかります。
2020/10/31 は 2019/06/09 から 508 日経過しているので、上の条件から S3 Glacier に移行すると想定されます。

(翌日)

ルールの条件適用の判定 (という言い方が正しいかは一旦スルー) は 00:00 UTC に行われるので、翌日の 09:00 JST 以降に確認してみます。

$ date && aws s3api list-objects-v2 \
--bucket cf-logs-bucket \
--prefix coco1.app/2019-06 \
--max-keys 5 \
--query "Contents[].[join(\`\`, [Key, \`: \`,  StorageClass])]" \
--output text

2020年 10月31日 土曜日 16時37分03秒 JST
coco1.app/2019-06-09-08-17-46-3A0F0F0D5E81E231: GLACIER
coco1.app/2019-06-09-08-18-35-A18FDA5904E7D007: GLACIER
coco1.app/2019-06-09-08-19-59-53378BC489E1596B: GLACIER
coco1.app/2019-06-09-08-20-53-D6015EC587DF38C1: GLACIER
coco1.app/2019-06-09-08-22-23-4DB13667C68BCEBA: GLACIER

想定通り、ストレージクラスが S3 Glacier に移行しています。

サポートされていないライフサイクル設定

ライフサイクルによるストレージクラスの移行には、サポートされていない (移行がされない) パターンがあります。それは、 対象となるオブジェクトのサイズが 128 KB 未満の場合 です。対象となるオブジェクトのサイズが 128 KB 未満の場合、次のようなストレージクラスの移行は実行されません。

これらの移行は、小さいサイズのオブジェクトにとっては費用対効果が悪いため、 Amazon S3 が移行させないようにしています。

ストレージクラスの変更を Slack に通知する (できませんでした)

今回のハンズオンでわかった通り、ライフサイクルルールによるストレージクラスの変更には多少時間がかかります。設定だけしてあとで忘れてしまう気がしたので、ストレージクラスが変わったときに Slack に通知できないかな?と考えました。S3 ではオブジェクトに対するさまざまなイベントを SNS 、 SQS 、 Lambda に通知することができるので、その機能を使って通知できないか試してみます。

S3 のイベントについて公式ドキュメントを読んでみると、残念ながらストレージクラスの変更を通知するようなストレートなイベントはありませんでした。

じゃあストレージクラスの変更って裏でどう動いているのかなと思って調べてみると、どうやら元のオブジェクトを新しいストレージクラスとしてコピーしているようでした。というのも、ストレージクラスの変更は手動でも可能で、 AWS CLI を使って手動でストレージクラスを変更する際には次のような形で s3api copy-object コマンドを使うからです。

$ aws s3api copy-object \
--bucket ${S3_BUCKET_NAME} \
--copy-source ${S3_BUCKET_NAME}/${OBJECT_KEY} \
--key ${OBJECT_KEY} \
--storage-class DEEP_ARCHIVE

なので、 S3 イベントとしては s3:ObjectCreated:Copy を通知するようにしてみます。このあたりの内容は次回の CLI 専門支部で扱うようなので、今回はマネジメントコンソール上で設定してしまいます。

S3 event notifiation setting

もしかしたら COPY じゃないかもしれないので念のため PUT と POST についても通知対象にしています。通知用の Lambda 関数は、受け取ったイベントを整形して Slack に投げるだけの関数です。

import json
from urllib.request import Request, urlopen

SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/********************'

def lambda_handler(event, context):
    try:
        try:
            slack_message = '```\n' + json.dumps(event, indent=2) + '\n```'
        except Exception as e:
            print(e)
            slack_message = str(event)
        
        headers = {"Content-Type": "application/json"}
        data = bytes(json.dumps({'text': slack_message}), 'utf-8')
        request = Request(SLACK_WEBHOOK_URL, data=data, headers=headers)
        urlopen(request)
    except Exception as e:
        print(e)

イベント通知の設定後、ストレージクラスが変更されるであろう時間に通知がくる想定だったんですが、残念ながら通知は来ず。ライフサイクルルールによるストレージクラスの移行は、手動でやる場合とは動作が異なるのかもしれません。ストレージクラスの移行を通知する何か良い方法があれば教えていただきたいです。

まとめ

JAWS-UG CLI専門支部 #170R S3基礎 ライフサイクル の参加レポートでした。

前回のバージョニングと比べると使い所は大いにありそうだなと思いました。ただし、 Glacier 破産という言葉もあるようなので、実際にライフサイクルを導入する際には対象となるオブジェクトのサイズや取り出し頻度などを考慮してコスト計算をしっかりしておく必要があります。

CLI 専門支部のレポートブログもこれで 4 本目になりますが、なんとなく AWS CLI 力が高まってきたような気がしてます。来月から新しい会社での勤務が始まりますが、勤務時間には融通が効く (はずな) ので引き続き参加したいと思います。


comments powered by Disqus