Lambda上でAWSCLIを動かしてS3 Syncする

記事タイトルとURLをコピーする

こんにちは、技術一課の加藤です。

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は同一アカウント上に存在

という前提でご説明をします。

クロスアカウントで実装したい場合(そのパターンが圧倒的に多いと思いますが)この辺の記事を参照しつつLambdaからS3へのアクセス許可を設定してみてください。
 
また以下のリソースはご自身で用意しておいてください。
  • 送信元/先のS3バケット(計2つ)
  • 作ったバケットに対するアクセス権限を持つLambda
なおS3 Syncの仕様上、Lambdaの実行権限には最低でも
  • s3:ListBucket
  • s3:GetObject
  • s3:PutObject

が必要となります。もし権限を絞るのが面倒であれば、S3へのフルアクセスを与えておいてください。

ちなみに対象リソースは「バケット自体」と「バケット以下のオブジェクト全て」を指定する必要があります。
バケット名が「src-bucket」であれば、「arn:aws:s3:::src-bucket」および「arn:aws:s3:::src-bucket/*」へのアクセス権限を与えてください。

関数の作成

まずディレクトリを作成し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ファイルが正しく動作するか確認してください。helpが表示されればOKです。
$ ./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を動かしたい場合、カスタムランタイムを使うという方法もあります。こちらも後日試しに実装してみる予定なので、また記事にしますね。
とはいえやっぱりアクロバティック感は否めないので、ご利用は自己責任でお願いしますね。