AWS LambdaからAmazon EFSをマウントしてみた

AWS LambdaからAmazon EFSをマウントしてみた

Clock Icon2024.08.30

AWS LambdaからAmazon EFSをマウントする機会があったのでブログに残します。

やること

今回は以下のドキュメントの手順を参考にCloudFormation 化してLambdaからEFSをマウントします。
https://repost.aws/ja/knowledge-center/efs-mount-with-lambda-function
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-filesystem.html

マウント後にファイルの作成とストレージ内にファイルが作成されているか確認を行います。
簡単ですが構成図は以下のようになります。
lambda-efs

CloudShellはEFS内のファイル確認用に使用します。
以下のブログで紹介した手順でCloudShellからEFSをマウントします。
https://dev.classmethod.jp/articles/tried-mounting-amazon-efs-from-aws-cloudshell/

設定

AWSリソースの作成は以下のCloudFormationテンプレートで作成を行いました。

AWSTemplateFormatVersion: "2010-09-09"

Description: Network Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for System Name Prefix
        Parameters:
          - SystemPrefix
          - Environment
      - Label: 
          default: Parameters for VPC
        Parameters:
          - VPCCIDR
      - Label: 
          default: Parameters for Subnet
        Parameters:
          - PublicSubnet01CIDR

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  SystemPrefix:
    Default: test
    Type: String

  Environment:
    AllowedValues:
      - dev
      - prod
      - stg
    Default: dev
    Type: String

  VPCCIDR:
    Default: 10.0.0.0/16
    Type: String

  PublicSubnet01CIDR:
    Default: 10.0.0.0/24
    Type: String

Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-vpc

# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------# 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet01CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-pub-01
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-public-rtb

  PublicRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicRouteTable

  PublicRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EFSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for EFS
      GroupName: !Sub ${SystemPrefix}-${Environment}-efs-sg
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress: 
        - CidrIp: !Ref VPCCIDR
          FromPort: 2049
          IpProtocol: tcp
          ToPort: 2049
      Tags: 
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-efs-sg
      VpcId: !Ref VPC

  CloudShellSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for CloudShell
      GroupName: !Sub ${SystemPrefix}-${Environment}-cloudshell-sg
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      Tags: 
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-cloudshell-sg
      VpcId: !Ref VPC

  LambdaSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for Lambda
      GroupName: !Sub ${SystemPrefix}-${Environment}-lambda-sg
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      Tags: 
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-lambda-sg
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# EFS
# ------------------------------------------------------------# 
  EFS:
    Type: AWS::EFS::FileSystem
    Properties:
      Encrypted: true
      FileSystemTags: 
      - Key: Name
        Value: !Sub ${SystemPrefix}-${Environment}-efs

  MountTarget1:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref EFS
      SecurityGroups: 
        - !Ref EFSSG
      SubnetId: !Ref PublicSubnet01

  AccessPoint:
    Type: AWS::EFS::AccessPoint
    Properties:
      FileSystemId: !Ref EFS
      PosixUser:
        Gid: 1001
        Uid: 1001
      RootDirectory:
        CreationInfo: 
          OwnerGid: 1001
          OwnerUid: 1001
          Permissions: 755
        Path: "/efs"

# ------------------------------------------------------------#
# ElasticIP
# ------------------------------------------------------------# 
  EIP:
    Type: AWS::EC2::EIP
    Properties: 
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${SystemPrefix}-${Environment}-eip

# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------# 
  LambdaIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
        - arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess
      Policies:
        - PolicyName: lambda-iam-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - cloudwatch:PutMetricAlarm
                  - cloudwatch:DeleteAlarms
                Resource: "*"

  Lambda:
    DependsOn: MountTarget1
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import os
          import datetime

          def lambda_handler(event, context):
              now = datetime.datetime.now()
              strnow = now.strftime('%Y-%m-%d-%H-%M-%S')
              print(strnow)
              filepath = f'/mnt/efs/{strnow}.txt'
              with open(filepath, 'w') as f:
                  f.write(strnow)
              print(os.listdir(os.path.join('/mnt/efs')))
      FunctionName: efs-lambda
      FileSystemConfigs:
        - Arn: !GetAtt AccessPoint.Arn
          LocalMountPath: "/mnt/efs"
      Handler: index.lambda_handler
      Role: !GetAtt LambdaIAMRole.Arn
      Runtime: python3.12
      Timeout: 5
      VpcConfig:
        SecurityGroupIds:
          - !Ref LambdaSG
        SubnetIds:
          - !Ref PublicSubnet01

173~201行目でEFSを作成して218~273行目でLambdaを作成しています。
EFSではマウントターゲットの作成だけでなくアクセスポイントの作成も行っています。
また、LambdaではEFSで作成したアクセスポイントを参照しています。(262行目のFileSystemConfigsで設定)

Lambda関数のコードは以下のようになっています。
コード自体はシンプルでLambdaを実行した時間をテキストファイルのファイル名として設定してEFS内に書き込むようにしています。

import os
import datetime

def lambda_handler(event, context):
    now = datetime.datetime.now()
    strnow = now.strftime('%Y-%m-%d-%H-%M-%S')
    print(strnow)
    filepath = f'/mnt/efs/{strnow}.txt'
    with open(filepath, 'w') as f:
        f.write(strnow)
    print(os.listdir(os.path.join('/mnt/efs')))

CloudFormationのデプロイは以下のコマンドを実行します。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM

デプロイが完了したらLambdaのコンソールから「efs-lambda」という関数を開きます。
開いたらテストタブをクリックして「テスト」をクリックします。
スクリーンショット 2024-08-30 212305
正常にLambdaの実行が完了するとファイル名が出力されます。
スクリーンショット 2024-08-30 212448
上記のログからだけでなくCloudShellからEFSをマウントしてファイルを確認してみます。
CloudShellからEFSのマウントは以下のブログを参照してください。
今回使用しているCloudFormationを正常にデプロイできていればElasticIPアドレスとセキュリティグループが作成されているのでそちらを使用してください。
https://dev.classmethod.jp/articles/tried-mounting-amazon-efs-from-aws-cloudshell/

CloudShellからEFSをマウントしたらlsコマンドでファイルが作成されていることを確認します。
正常に作成されていると以下のようにファイルが確認できます。

ls -la efs/efs/
total 12
drwxr-xr-x. 2 1001 1001 6144 Aug 30 12:24 .
drwxr-xr-x. 3 root root 6144 Aug 30 12:24 ..
-rw-rw-r--. 1 1001 1001   19 Aug 30 12:24 2024-08-30-12-24-32.txt

マネジメントコンソールからLambdaの設定を確認

マネジメントコンソールから設定を確認するには該当のLambdaを開いて「設定」→「ファイルシステム」で確認することができます。
スクリーンショット 2024-08-30 213408
編集をクリックするとEFSやアクセスポイントの設定が変更できます。
スクリーンショット 2024-08-30 213624

さいごに

Lambdaの/tmp領域が10GBまでしか使用できないため、それを超えるようなファイルを作成する時やEFSにファイルを元々溜めていてLambdaでバッチ処理を行いたいみたいな時に使用するのがよいと思います。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.