【AWS CLIで構築】Lambdaを使用してEFS上のファイルをS3へ移行する

アプリのログファイルをEFSに吐いていたのですが、溜まってきたので古いファイルをS3に移行させるバッチをLambdaで作ってみた。簡易的だけど以下のような構成になります。今回は一部の既存のリソース(EFS, S3, SNS)を使用する必要があり、CloudFormationではなくAWS CLIで各リソースを作成・構築したため、実装はコマンドベース(Windows PowerShell)で紹介します。

※移行元のEFS、移行先のS3バケット、通知先のSNSトピックは既に作成済の前提となります

1. Lambdaの設定

1.1 Lambda実行Roleの作成

まずはLambdaの実行ロールを作成します。信頼Policyはdefaultの状態のままで問題ないので、以下の内容でファイルを作成し、任意のフォルダに格納します。

[my-lambda-function-trust-pol.json]

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

WindowsPowerShellを開きAWSにログイン(この手順は省略します)し、↑のファイルを格納したディレクトリまで移動します。

cd 【↑のファイルを格納したディレクトリパス】

以下のコマンドを実行し、IAM Roleを作成します。(Mac.terminalの場合は改行コードを「/」に置き換えてください)

aws iam create-role `
--role-name my-lambda-function-role `
--assume-role-policy-document file://my-lambda-function-trust-pol.json

1.2 IAM Policyの作成

Lambdaの実行ロールにAttachするIAM Policyを作成します。まずは以下のようなPolicyの内容を記述したjsonファイルを作成し、任意のフォルダに格納します。「【】」の中身は適宜入れ替えてください。

[my-lambda-function-pol.json]

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:【my-account-no】:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:【my-account-no】:log-group:/aws/lambda/【my-lambda-function】:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:aws:sns:ap-northeast-1:588907989152:【my-sns-topic】"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::【my-s3-bucket】/*"
        }
    ]
}

ファイルを格納したディレクトリにいることを確認し、以下のコマンドを実行し、IAM Policyを作成します。

aws iam create-policy `
--policy-name my-lambda-function-pol `
--policy-document file://my-lambda-function-pol.json

1.3 Lamba実行ロールにPolicyを追加

1.2で作成したPolicyを含め、必要な権限を持ったPolicyを1.1で作成したIAM Roleに追加します。

aws iam attach-role-policy `
--role-name my-lambda-function-role `
--policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute
aws iam attach-role-policy `
--role-name my-lambda-function-role `
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
aws iam attach-role-policy `
--role-name my-lambda-function-role `
--policy-arn arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess
aws iam attach-role-policy `
--role-name my-lambda-function-role `
--policy-arn arn:aws:iam::【my-account-no】:policy/my-lambda-function-pol

1.4 Lambda関数の作成

1.3までで準備したLambda実行ロールを指定してLambda関数を作成します。まずはアップロードする関数自体をローカル環境に作成します。以下は「EFSのある特定のフォルダにあるzipログファイルを2週間以上前であればS3に移行する」というサンプルコードになりますが、テスト的に利用する場合はこちらをローカルの任意の場所にコピー/配置してください。

GitHub - yuichiroyamaji/efs-to-s3: Lambda function transferring file from efs to s3

コードが配置されているフォルダ上で以下コマンドを実行し、zip化します。

zip -r function.zip ./

作成したzipファイルを指定して、Lambda関数を作成します。

aws lambda create-function `
--function-name my-lambda-function `
--zip-file fileb://function.zip `
--handler lambda_function.lambda_handler `
--runtime python3.9 `
--role arn:aws:iam::【my-account-no】:role/my-lambda-function-role

1.5 Lambda関数用のSecurityGroupの作成

こちらはEFSが存在するVPCのIDを指定して、ルール自体はdefaultの状態で作成します。

aws ec2 create-security-group --group-name my-lambda-function-sg `
--description "SG for My Lambda Function" `
--vpc-id 【EFSが存在するVPC ID】

1.6 EFSと同じSubnetにLambdaを配置、Timeout、FileSystem設定でEFSのAccessPointを指定

今回EFS側のAccessPointは既に作成済であることを前提に作成手順は割愛しますが、作成手順の詳細が必要な方は以下の記事で紹介してますのでご参考下さい。Timeout値も念のため最大値の900秒に設定します。

k9s.hatenablog.jp

aws lambda update-function-configuration `
--function-name my-lamba-function `
--vpc-config SubnetIds=【EFSが配置されているSubnetID】,SecurityGroupIds=【1.5で作成したSecurityGroupID】 `
--file-system-configs Arn=【AccessPointのARN】,LocalMountPath=【Lambda側のMountPath(/mnt/dataなど)】 `
--timeout 900

1.7 (サンプルコードを利用する場合) 環境変数の設定

こちらはサンプルコードを利用する場合のみ必要となりますので、自身の関数ファイルでLambdaを作成された方はスキップしてください。

aws lambda update-function-configuration `
--function-name tci-dev-oms-efs-logs-s3 `
--environment "Variables={ `
SNS_TOPIC_ARN=【サンプルコード内:通知連絡用SNSトピックのARN, `
EFS_ROOT_PATH=【EFS側のマウントPath(/mnt/dataなど)】, `
EFS_ZIP_PATH=【ESF側でzipファイルが格納されているフォルダパス(/mnt/data/logs/zipなど)】, `
EC2_ZIP_PATH=【zipコマンドファイルのフォルダパス】, `
ENV_NAME='[開発環境]', `
S3_BUCKET=sample-bucket, `
S3_FOLDER=EFS/, `
}" 

2. EFSの設定

2.1 AccessPointの設定

今回こちらの手順は割愛します。作成手順の詳細が必要な方は以下の記事で紹介してますのでご参照ください。

k9s.hatenablog.jp

2.2 EFSのSecurityGroupのInboundルール追加

↑の記事内でも紹介していますが、今回LambdaからEFSにアクセスするにあたって、EFSのSecurityGroupのInboundルールで、Lambda側のSecurityGroupを指定してアクセスを許可してやる必要があります。

aws ec2 authorize-security-group-ingress `
    --group-id 【EFSのSecurityGroupID】 `
    --ip-permissions IpProtocol=tcp,FromPort=2049,ToPort=2049,UserIdGroupPairs='[{GroupId=【LambdaのSecurityGoupID】,Description="Allow My Lambda Function to access"}]'

3 EventBridgeの設定

3.1 Ruleの作成

ここでは毎日0:00にLambdaを呼び出すよう設定します。cron形式で設定する場合タイムゾーンUTCのため、マイナス9時間で計算して指定しています。

aws events put-rule `
--name my-lambda-function-scheduler `
--schedule-expression "cron(0 15 * * ? *)" `
--description "My Lambda Function関数を呼び出すスケジューラー"

3.2 Targetの設定

Ruleで指定した時間に起動させるLambdaを指定します。

aws events put-targets `
--rule my-lambda-function-scheduler `
--targets Id=1,Arn=【LambdaのARN】

3.3 Lambda側でEventBridgeの呼び出し許可を設定

Lambda側のリソースベースポリシーにEventBridgeのRuleを実行できる権限を付与し、トリガーを追加します。

aws lambda add-permission `
--function-name my-lambda-function `
--statement-id '1' `
--action 'lambda:InvokeFunction' `
--principal events.amazonaws.com `
--source-arn 【3.1で作成したEventBridgeのRuleのARN】

以上で必要なリソースは全て作成完了となります。EventBridgeで設定した時間になるとLambdaが起動するハズなので確認してみてください。