Lambdaで発生するこちらのエラー。 /tmp
領域を使った処理などでディスク不足で発生するのですが、特殊なファイル形式など、どうしてもファイルを扱いたい場面には悩まされることありました。
OSError: [Errno 28] No space left on device
少し前の発表ですが、「Amazon Elastic File System for AWS Lambda」というのが発表さたので、LambdaからEFSをマウントすることでLambdaのディスク枯渇問題は回避可能になりました。
EFSの作成含めServerless Frameworkからデプロイしてみるサンプルを記載します。
コード
Lambda関数 (handler.py)
EFSのマウント情報を出力する処理を定義します。
import os import subprocess from typing import List EFS_MOUNT_DIR = os.getenv("EFS_MOUNT_DIR") def exec_command(cmd: List[str]): print(f"Command -> {cmd} -------------------------------------------") out = subprocess.run(cmd, stdout=subprocess.PIPE) print(out.stdout.decode()) def lambda_handler(event, context): exec_command(["df", "-h"]) exec_command(["cat", "/proc/mounts"]) exec_command(["touch", f"{EFS_MOUNT_DIR}/{os.uname().nodename}"]) exec_command(["ls", "-l", EFS_MOUNT_DIR])
設定ファイル(.env)
useDotenv: true
を設定してるので .env
ファイルをServerless Frameworkの設定ファイルとして定義します。
AWS_REGION=ap-northeast-1 VPC_ID=vpc-xxxxxxxx LAMBDA_SUBNET_ID=subnet-xxxxxxxx EFS_MOUNT_DIR=/mnt/efsdata EFS_ROOT_DIR=/efsdata
Serverless Framework定義(serverless.yml)
Serverless Frameworkの定義ファイルです。
service: lambda-efs-mount-sample frameworkVersion: "2" useDotenv: true provider: name: aws runtime: python3.8 region: ${env:AWS_REGION} stage: prod lambdaHashingVersion: 20201221 functions: func: handler: handler.lambda_handler environment: EFS_MOUNT_DIR: ${env:EFS_MOUNT_DIR} fileSystemConfig: localMountPath: ${env:EFS_MOUNT_DIR} arn: !GetAtt ["EFSAccessPoint", "Arn"] vpc: subnetIds: - ${env:LAMBDA_SUBNET_ID} securityGroupIds: - !GetAtt ["LambdaSecurityGroup", "GroupId"] dependsOn: EFSMountTarget resources: Resources: LambdaSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Lambda Access for EFS VpcId: ${env:VPC_ID} Tags: - Key: Name Value: LambdaEFSSecurityGroup EFSSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: EFS Allowed Ports VpcId: ${env:VPC_ID} SecurityGroupIngress: - IpProtocol: tcp FromPort: 2049 ToPort: 2049 SourceSecurityGroupId: !GetAtt ["LambdaSecurityGroup", "GroupId"] Description: from Lambda Tags: - Key: Name Value: EFSSecurityGroup EFSFileSystem: Type: AWS::EFS::FileSystem Properties: FileSystemTags: - Key: Name Value: MyFileSystem BackupPolicy: Status: ENABLED Encrypted: true LifecyclePolicies: - TransitionToIA: AFTER_30_DAYS PerformanceMode: generalPurpose EFSMountTarget: Type: AWS::EFS::MountTarget Properties: FileSystemId: !Ref EFSFileSystem SecurityGroups: - !Ref EFSSecurityGroup SubnetId: ${env:LAMBDA_SUBNET_ID} DependsOn: EFSFileSystem EFSAccessPoint: Type: AWS::EFS::AccessPoint Properties: FileSystemId: !Ref EFSFileSystem PosixUser: Uid: 1001 Gid: 1001 RootDirectory: Path: ${env:EFS_ROOT_DIR} CreationInfo: OwnerGid: 1001 OwnerUid: 1001 Permissions: 750 AccessPointTags: - Key: Name Value: MyAccessPoint DependsOn: EFSFileSystem
ポイント
ポイントを少し解説書きます。
LambdaのEFSマウント設定
Lambdaの定義のところで fileSystemConfig
というセクションを作ることでLambdaローカルのマウントディレクトリと、EFSのアクセスポイントを定義可能です。
EFSのアクセスポイントは resources
セクションのCFnの定義から引用できるのが便利です。
functions: func: #省略 fileSystemConfig: localMountPath: ${env:EFS_MOUNT_DIR} arn: !GetAtt ["EFSAccessPoint", "Arn"] #省略
LambdaのDependsOn設定
EFSのマウントターゲットは作成まで時間がかかります。先にServerless FrameworkからLambdaの生成が走るとエラーとなるので dependsOn
を設定しておきます。
functions: func: #省略 dependsOn: EFSMountTarget
LambdaのUID/GID
Lambdaの実行ユーザは、UID=1001
GID=1001
なのでアクセスポイントにも、それを受けた設定を行います。
EFSAccessPoint: Type: AWS::EFS::AccessPoint Properties: FileSystemId: !Ref EFSFileSystem PosixUser: Uid: 1001 Gid: 1001 RootDirectory: Path: ${env:EFS_ROOT_DIR} CreationInfo: OwnerGid: 1001 OwnerUid: 1001 Permissions: 750
実行してみる
Lambdaを実行して、ログを確認してみます。
実行
$ sls invoke -f func null $ sls logs -f func
結果確認
/mnt/efsdata
ディレクトリにディスクが割り当たっているのが確認できます。
Command -> ['df', '-h'] ------------------------------------------- Filesystem Size Used Avail Use% Mounted on /mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/python3.8-amzn-2 9.8G 8.4G 1.4G 87% / /dev/vdb 1.5G 14M 1.4G 1% /dev /dev/vdd 526M 872K 514M 1% /tmp /dev/root 9.8G 8.4G 1.4G 87% /etc/passwd /dev/vdc 128K 128K 0 100% /var/task 127.0.0.1:/ 8.0E 0 8.0E 0% /mnt/efsdata
nfs
の設定が入っています。
Command -> ['cat', '/proc/mounts'] ------------------------------------------- /mnt/root-rw/opt/amazon/asc/worker/tasks/rtfs/python3.8-amzn-2 / overlay ro,nosuid,nodev,relatime,lowerdir=/tmp/es073328941/6c4d99488764391:/tmp/es073328941/b9f5c9402371752 0 0 /dev/vdb /dev ext4 rw,nosuid,noexec,noatime,data=writeback 0 0 /dev/vdd /tmp ext4 rw,relatime,data=writeback 0 0 none /proc proc rw,nosuid,nodev,noexec,noatime 0 0 /dev/vdb /proc/sys/kernel/random/boot_id ext4 ro,nosuid,nodev,noatime,data=writeback 0 0 /dev/root /etc/passwd ext4 ro,nosuid,nodev,relatime,data=ordered 0 0 /dev/root /var/rapid ext4 ro,nosuid,nodev,relatime,data=ordered 0 0 /dev/vdb /etc/resolv.conf ext4 ro,nosuid,nodev,noatime,data=writeback 0 0 /dev/vdb /mnt ext4 rw,noatime,data=writeback 0 0 /dev/vdc /var/task squashfs ro,nosuid,nodev,relatime 0 0 127.0.0.1:/ /mnt/efsdata nfs4 rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,noresvport,proto=tcp,port=20372,timeo=600,retrans=2,sec=sys,clientaddr=127.0.0.1,local_lock=none,addr=127.0.0.1 0 0
ファイルの書き込みもできているようです。
Command -> ['touch', '/mnt/efsdata/169.254.131.181'] ------------------------------------------- Command -> ['ls', '-l', '/mnt/efsdata'] ------------------------------------------- total 4 -rw-rw-r-- 1 1001 1001 0 Oct 5 02:51 169.254.131.181
暫くしてから実行すると別のIPでの書き込みされるのが確認できると思います。
Command -> ['touch', '/mnt/efsdata/169.254.171.253'] ------------------------------------------- Command -> ['ls', '-l', '/mnt/efsdata'] ------------------------------------------- total 8 -rw-rw-r-- 1 1001 1001 0 Oct 5 02:51 169.254.131.181 -rw-rw-r-- 1 1001 1001 0 Oct 5 03:04 169.254.171.253