はじめに
先日他のアカウントから、自分の保有しているS3に対してzipファイルが送られてくる事案がありました。
どうせなら受け取った際に『zipを展開出来ないか?』とのことだったので、展開するためのLambdaを構築したので記事として残します。
構成図
ハンズオン
構築の流れ
1.S3作成
2.Lambda作成
3.EventBridge作成
上記の順番で構築を行なっていきます。
最終的には、今回作成したS3にzipファイルを保存すると、EventBridgeで検知して、展開Lambdaでzipファイルの展開を行います。
※20221217.zip
をS3に保存すると、EventBridgeで検知して、展開Lambdaが起動されてsample.txt
が展開されてアップロードされます(20221217.zipを削除することもLambdaで可能でしたが、S3にライフサイクルを導入しているので設定しませんでした。)
1.S3作成
EventBridgeに通知をするためにNotificationConfiguration
のEventBridgeConfiguration
をtrue
にしています。
S3のバケット名は全世界でユニークのため、cfn-s3-20221217-inamura
は各自修正ください
※現在既にcfn-s3-20221217-inamura
は削除されております
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation to create S3 Bucket
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "S3 Configuration"
Parameters:
- S3BucketName
- AccessControl
- BlockPublicAcls
- BlockPublicPolicy
- IgnorePublicAcls
- RestrictPublicBuckets
- ExpirationInDays
- EventBridgeConfiguration
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
S3BucketName:
Type: String
Default: "cfn-s3-20221217-inamura"
Description: Type of this BacketName.
VersioningConfiguration:
Type: String
Default: "Enabled"
Description: VersioningConfiguration.
AccessControl:
Type: String
Description: AccessControl.
Default: "Private"
AllowedValues: [ "Private", "PublicRead", "PublicReadWrite", "AuthenticatedRead", "LogDeliveryWrite", "BucketOwnerRead", "BucketOwnerFullControl", "AwsExecRead" ]
BlockPublicAcls:
Type: String
Description: BlockPublicAcls.
Default: "True"
AllowedValues: [ "True", "False" ]
BlockPublicPolicy:
Type: String
Description: BlockPublicPolicy.
Default: "True"
AllowedValues: [ "True", "False" ]
IgnorePublicAcls:
Type: String
Description: IgnorePublicAcls.
Default: "True"
AllowedValues: [ "True", "False" ]
RestrictPublicBuckets:
Type: String
Description: RestrictPublicBuckets.
Default: "True"
AllowedValues: [ "True", "False" ]
ExpirationInDays:
Type: String
Description: Lifecycle Days.
Default: "7"
EventBridgeConfiguration:
Type: String
Description: EventBridgeConfiguration.
Default: "true"
AllowedValues: [ "true", "false" ]
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
VersioningConfiguration:
Status: !Ref VersioningConfiguration
AccessControl: !Ref AccessControl
PublicAccessBlockConfiguration:
BlockPublicAcls: !Ref BlockPublicAcls
BlockPublicPolicy: !Ref BlockPublicPolicy
IgnorePublicAcls: !Ref IgnorePublicAcls
RestrictPublicBuckets: !Ref RestrictPublicBuckets
LifecycleConfiguration:
Rules:
- Id: LifeCycleRule
Status: Enabled
ExpirationInDays: !Ref ExpirationInDays
NotificationConfiguration:
EventBridgeConfiguration:
EventBridgeEnabled: !Ref EventBridgeConfiguration
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
S3BucketName:
Value: !Ref S3Bucket
Export:
Name: cfn-s3-BucketName
2.Lambda作成
EventBridgeのイベント通知を受けて、Lambdaの/tmp/配下にzipファイルを配置して、展開を行いS3にアップロードを行うLambdaを構築します。
Lambdaのエフェメラルストレージ(/tmp)はデフォルトの512MBのままで、毎回/tmpは以下を削除する記述をしています。削除しないと、こちらのブログで記載されている「Lambda突然の死」「思わぬリソースが枯渇」AWSしくじり先生 part.2のような問題が発生する可能性があったからです。
展開するzipファイルの容量によっていは、/tmp配下を変更することも必要かと思います。Lambdaは現在10GBまでエフェメラルストレージをサポートしています。
参考URL:AWS Lambda が最大 10 GB のエフェメラル ストレージをサポートするようになりました
AWSTemplateFormatVersion: '2010-09-09'
Description:
Lambda Create
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Lambda Configuration"
Parameters:
- FunctionName
- Description
- Handler
- MemorySize
- Runtime
- Timeout
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
FunctionName:
Type: String
Default: "cfn-lmd-deployment-inamura"
Description:
Type: String
Default: "cfn-lmd-deployment-inamura"
Handler:
Type: String
Default: "index.lambda_handler"
MemorySize:
Type: String
Default: "128"
Runtime:
Type: String
Default: "python3.9"
Timeout:
Type: String
Default: "180"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
Lambda:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: |
import boto3
import os
import urllib.parse
import zipfile
import glob
s3 = boto3.client('s3')
def lambda_handler(event, context):
FileListInZip = []
Object = urllib.parse.unquote_plus(event['detail']['object']['key'], encoding='utf-8')
Bucket = urllib.parse.unquote_plus(event['detail']['bucket']['name'], encoding='utf-8')
FilePath = '/tmp/' + Object
#/tmp/配下保存
s3.download_file(Bucket, Object, FilePath)
#zipファイル内一覧取得
zfile = zipfile.ZipFile(FilePath)
FileListInZip = zfile.namelist()
#/tmp/配下展開
zfile.extractall('/tmp/')
zfile.close()
#展開したファイルをS3アップロード
for f in FileListInZip:
try:
if os.path.isfile('/tmp/' + f):
s3.upload_file('/tmp/'+f, Bucket, f)
except Exception as e:
print(e)
pass
else:
pass
#Lambda /tmp/配下削除
for p in glob.glob('/tmp/' + '*'):
if os.path.isfile(p):
os.remove(p)
return 0
Description: !Ref Description
FunctionName: !Ref FunctionName
Handler: !Ref Handler
MemorySize: !Ref MemorySize
Runtime: !Ref Runtime
Timeout: !Ref Timeout
Role: !GetAtt LambdaRole.Arn
LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${FunctionName}-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "sts:AssumeRole"
Principal:
Service: lambda.amazonaws.com
Policies:
- PolicyName: !Sub "${FunctionName}-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "logs:CreateLogGroup"
Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"
- Effect: "Allow"
Action:
- "s3:GetObject"
- "s3:PutObject"
- "s3:DeleteObject"
Resource: !Sub
- "arn:aws:s3:::${BucketName}/*"
- BucketName: {'Fn::ImportValue': cfn-s3-BucketName}
- Effect: "Allow"
Action:
- "s3:ListBucket"
Resource: !Sub
- "arn:aws:s3:::${BucketName}"
- BucketName: {'Fn::ImportValue': cfn-s3-BucketName}
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
LambdaArn:
Value: !GetAtt Lambda.Arn
Export:
Name: !Sub "${FunctionName}-arn"
3.EventBridge作成
S3にオブジェクト(条件:サフィックスが.zip
)が保存された場合、EventBridgeが検知をして、展開Lambdaを呼び出す
※zip展開をした中に、zipファイルがあった場合は再度EventBridgeで通知を受けるため、展開Lambdaが起動する
AWSTemplateFormatVersion: "2010-09-09"
Description:
EventBridge gets s3 events and sends them to lambda
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Eventbridge Configuration"
Parameters:
- Name
- EventBusName
- State
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
EventBusName:
Type: String
Default: "default"
Name:
Type: String
Default: "cfn-evb-deploymentlmd-inamura"
State:
Type: String
Default: "ENABLED"
# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------#
Resources:
S3EventsRule:
Type: AWS::Events::Rule
Properties:
Description: "Get S3 events and send to Lambda"
EventBusName: !Ref EventBusName
Name: !Ref Name
State: !Ref State
EventPattern:
source:
- aws.s3
detail-type:
- Object Created
detail:
bucket:
name:
- !ImportValue cfn-s3-BucketName
object:
key:
- "suffix" : ".zip"
Targets:
- Arn: !ImportValue cfn-lmd-deployment-inamura-arn
Id: "cfn-lmd-deployment-inamura"
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: cfn-lmd-deployment-inamura
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt 'S3EventsRule.Arn'