やりたいこと
- S3ファイルをまとめてダウンロードしたい
- AWS CLIを使えば解決
- その環境がない人用の環境構築
- AWS CLIを使えば解決
仕様
- AWS Lambda: Python 2.7
- Lambdaを手動で実行したらS3に圧縮して保存
- 対象の絞り込み(bucket、key、拡張子)と出力先はLambda内に記述
- 何パターンか必要な場合、テストイベントの設定のテンプレートに情報を保存し、そちらを使うようようにすると楽です
- 想定外の大量ファイルを指定できないよう最大1000件まで(そもそも処理に使うlist_objectsの最大が1000件)
- 1000件から先はlist_objectsのMarkerを設定して続きから処理する(名前の昇順で取得される)
- 対象の絞り込み(bucket、key、拡張子)と出力先はLambda内に記述
- 手動が嫌な場合は下記のような方法もあります
- S3 notifications
- 指定したS3の場所にファイルが追加、削除などされたら実行
- CloudWatch Events
- cronの時間指定で実行
- S3 notifications
Lambdaの処理時間
ファイル数 | 時間(ms) |
---|---|
10 | 2600 |
100 | 17100 |
1000 | 177500 |
※平均25KBの画像ファイル
上記の結果からLambdaのタイムアウトをを3秒→3分に設定しました
AWS料金
- 月数十回なのでLambdaは無料範囲内
- S3のダウンロード料金で数十円程度
Lambda
関数の作成
- 名前: s3downloads
- ランタイム: Python 2.7
- ロール: テンプレートから新しいロールを作成
- ロール名: Lambda_s3downloads
- ポリシーテンプレート: 空欄(下記のIAMロールで追加)
IAM ロール
- Lambda_s3downloadsロールを選択
- ポリシーのアタッチから「AmazonS3FullAccess」を追加する
- ※対象のS3を限定的にする場合はカスタムしてください
Lambdaのその他の設定
- タイムアウト3秒から3分に変更
- index.jsをlambda_function.pyにRename
- コードの内容は下記のPythonコードを貼り付け
- テストイベントは今回は使用しないのでデフォルトのまま保存して実行します
Pythonのコード
lambda_function.py
import boto3
import tarfile
import os
s3_client = boto3.client('s3')
in_bucket = 's3inbucket'
in_prefix = 'hoge/neko/'
in_extension = '.jpg'
in_max = 1000
in_marker = ''
out_bucket = 's3outbucket'
out_dir = 'out/'
out_filenmae = 'archive'
def lambda_handler(event, context):
response = s3_client.list_objects(Bucket=in_bucket, Prefix=in_prefix, MaxKeys=in_max, Marker=in_marker)
if 'Contents' not in response:
return
s3_files = [c['Key'] for c in response['Contents'] if c['Key'].endswith(in_extension)]
if s3_files:
get_s3_files(s3_files)
create_archive()
return s3_files
def get_s3_files(s3_files):
for s3_file in s3_files:
basename = os.path.basename(s3_file)
tmp_file = os.path.join('/tmp/', basename)
s3_client.download_file(in_bucket, s3_file, tmp_file)
def create_archive():
out_file = os.path.join(out_dir, out_filenmae + '.tar.gz')
archive_file = os.path.join('/tmp/', out_filenmae + '.tar.gz')
archive = tarfile.open(archive_file, mode='w:gz')
archive.add('/tmp/', arcname=out_filenmae)
archive.close()
s3_client.upload_file(archive_file, out_bucket, out_file)
print('output ' + out_bucket + ':' + out_file)
変数の設定
変数 | 説明 |
---|---|
in_bucket | 対象のバケット |
in_prefix | 対象のバケットのキー |
in_extension | 対象の拡張子(空の場合は全て) |
in_max | 最大取得数(0〜1000) |
in_marker | 取得数を超えた場合、最後のキー(フルパス)をここに記述することで続きからの処理になる。※対象のフルパス一覧はテスト結果の詳細に出力されます |
out_bucket | 出力先のバケット |
out_dir | 出力先のディレクトリ |
out_filenmae | 圧縮ファイル名 |
Prefixで指定した階層だけを対象にしたい場合
下記のように「Delimiter='/'」を追加する
response = s3_client.list_objects(Bucket=in_bucket, Prefix=in_prefix, Delimiter='/', MaxKeys=in_max, Marker=in_marker)
毎回はまるところ
コメントに日本語が含まれているとエラー・・・
2019/08/13 追記
先頭に下記を記述することで日本語コメントをつけることができるようになる(emoseiさん教えていただきありがとうございます)
## coding: UTF-8
応用
S3ファイルをまとめてダウンロードできる環境をテストイベントの設定を利用して作る