こんにちは、技術一課の加藤です。
AWS CLIのS3 Sync、便利ですよね。
コピー元とコピー先の付き合わせやバージョンの比較など、Sync処理において面倒だな、と思うことを全部端折ってくれるS3 Syncは、まさにバケット間のデータ移動の最適解といえるでしょう。
しかしこれを定期実行したい、となるとやや面倒です。実はAWS SDKにはS3 Syncと同様の機能がないため、Lambdaでいい感じに実装しようと思うと考慮すべきことが色々出てきてしまいます。
S3バケットのSyncがしたい! でもAWS SDKで実装するのも面倒くさい!
そうしてやってみたのが「Lambda上でAWS CLIを動かす」というアクロバティック実装です。
極力AWS CLIをLambda内で使うために必要な手順に絞って解説を行うため、
- S3バケット同士のSync
- 2つのS3バケットおよびLambdaは同一アカウント上に存在
という前提でご説明をします。
- 送信元/先のS3バケット(計2つ)
- 作ったバケットに対するアクセス権限を持つLambda
- s3:ListBucket
- s3:GetObject
- s3:PutObject
が必要となります。もし権限を絞るのが面倒であれば、S3へのフルアクセスを与えておいてください。
関数の作成
まずディレクトリを作成しAWS CLIをローカルインストールします
(※以降のコマンド・コードはPython 3.7.2環境にて動作確認済みになります。他のバージョンで実行する際にはご注意ください。)
$ mkdir lambda-cli $ cd lambda-cli $ pip install awscli -t .
次に、AWS CLIをPythonスクリプトから呼ぶためのラッパースクリプトを作成します。
AWS CLIは文字通りCLIから呼び出すことを想定して作られているため、ローカルインストールしたAWS CLIを普通にプログラムにimportして使おうと思うと引数の与え方などがやや面倒です。このため、コマンド的に利用できるようにラッパースクリプトを挟んでやる必要があります。
$ touch aws $ chmod +x aws
awsファイルには以下を書き込んでください。
#!/usr/bin/env python3 import sys import awscli.clidriver def main(): return awscli.clidriver.main() if __name__ == '__main__': result = main() sys.exit(result)
$ ./aws usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters] To see help text, you can run: aws help aws <command> help aws <command> <subcommand> help aws: error: the following arguments are required: command
最後にlambda_functionファイルを作成します
$ touch lambda_function.py
以下のコードを書き込んでください。subprocessを用いて実際にawsコマンドを呼び出すスクリプトになります。
import subprocess def lambda_handler(event, context): cmd = './aws s3 sync s3://<src bucket name> s3://<dest bucket name>' result = subprocess.run( cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) print(result.stdout.decode())
以上が完了したら、コードをzip化しLambdaにデプロイします。
$ zip -r lambda-cli ./*
今回は特に渡すパラメータもないので、送信元バケットにファイルをおき、Lambdaのテスト実行を行うと動作が確認できます。
処理に意外と時間がかかるので、1ファイルのSyncであってもタイムアウトを10秒程度まで伸ばしておくことをお勧めします。
おわりに
LambdaでAWS CLIを動かしたい場合、カスタムランタイムを使うという方法もあります。こちらも後日試しに実装してみる予定なので、また記事にしますね。
とはいえやっぱりアクロバティック感は否めないので、ご利用は自己責任でお願いしますね。