0
0

More than 1 year has passed since last update.

S3オブジェクトをトリガーにした zip展開するLambda構築ハンズオン

Posted at

はじめに

先日他のアカウントから、自分の保有している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に通知をするためにNotificationConfigurationEventBridgeConfigurationtrueにしています。
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'

挙動の確認

①構築したS3にzipファイルを保存する

※S3は既に削除されています

②S3の画面に戻り更新を押下すると、zipファイルの中身が展開される

20221217.zipをS3に保存した後、検知して3秒後にzip化されていたsample.txtが展開されてアップロードされました

③展開LambdaのCloudWatchLogsを確認すると、Lambdaが起動されているログを確認することができる


さいごに

LambdaにアタッチするIAMロールですが、最初バケットに対してしか付与しておらずClientError: An error occurred (403) when calling the HeadObject operation: Forbidden が出たりして、改めてIAMロールの記述方法を学び直しました。
とかく使い所は局所的(たぶんzip化させる方が機会的には多そう)ですが、無事にzipを展開できて良かったです。
残り少ない今年に捉われることもなく、時間を見つけては検証して、自分の作れるものを増やしていきたいと思います!
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0