I have been struggling for days trying to get a deployment of Lambda+EFS that actually works
I am using Pulumi in Python
Everything I've tried results in:
Calling the invoke API action failed with this message: The function couldn't connect to the Amazon EFS file system with access point arn:aws:elasticfilesystem:eu-west-2:003478557222:access-point/fsap-0bf5cec491c7daa7d. Check your network configuration and try again.
I want them both to live in a private subnet with no NAT gateway
This article: https://docs.aws.amazon.com/lambda/latest/dg/troubleshooting-invocation.html#troubleshooting-invocation-efsconnect ...suggests that this error is due to:
The function couldn't establish a connection to the function's file system with the NFS protocol (TCP port 2049). Check the security group and routing configuration for the VPC's subnets.
Initially I had thought that I could put the Lambda and the EFS Mount Target in the same security group and then wouldn't need explicit NFS ingress/egress rules. AFAICT that didn't work.
In the process of following various advice and docs I have subsequently ended up with two security groups (e.g. as described here: https://docs.aws.amazon.com/efs/latest/ug/network-access.html) one having NFS egress and the other having NFS ingress. This doesn't work either.
I am beginning to suspect that this error may have another cause as I think by now I have tried about every combination of SG rules trying to enable connectivity.
But maybe I've done something stupid that I'm not seeing.
My current code looks like:
vpc = awsx.ec2.Vpc(
"myvpc",
subnet_specs=[
awsx.ec2.SubnetSpecArgs(type=awsx.ec2.SubnetType.PUBLIC),
awsx.ec2.SubnetSpecArgs(type=awsx.ec2.SubnetType.PRIVATE),
],
subnet_strategy=awsx.ec2.SubnetAllocationStrategy.AUTO,
number_of_availability_zones=1,
nat_gateways=awsx.ec2.NatGatewayConfigurationArgs(
strategy=awsx.ec2.NatGatewayStrategy.NONE,
),
# allow VPC endpoints:
enable_dns_hostnames=True,
enable_dns_support=True,
)
lambda_sg = aws.ec2.SecurityGroup(
"lambda-sg",
vpc_id=vpc.vpc_id,
ingress=[],
egress=[],
)
def lambda_sg_rules(subnet_ids: Sequence[str]):
subnets = [aws.ec2.get_subnet(id=subnet_id) for subnet_id in subnet_ids]
cidr_blocks = [subnet.cidr_block for subnet in subnets]
aws.ec2.SecurityGroupRule(
"lambda-sg-ingress-https",
security_group_id=lambda_sg.id,
type="ingress",
from_port=443,
to_port=443,
protocol="tcp",
cidr_blocks=pulumi.Output.all(*cidr_blocks),
)
aws.ec2.SecurityGroupRule(
"lambda-sg-egress-nfs",
security_group_id=lambda_sg.id,
type="egress",
from_port=2049,
to_port=2049,
protocol="tcp",
cidr_blocks=pulumi.Output.all(*cidr_blocks),
)
vpc.private_subnet_ids.apply(lambda_sg_rules)
efs_sg = aws.ec2.SecurityGroup(
"efs-sg",
vpc_id=vpc.vpc_id,
ingress=[],
egress=[],
)
aws.ec2.SecurityGroupRule(
"efs-sg-ingress-nfs",
security_group_id=efs_sg.id,
type="ingress",
from_port=2049,
to_port=2049,
protocol="tcp",
source_security_group_id=lambda_sg.id,
)
aws.ec2.SecurityGroupRule(
"efs-sg-egress-all",
security_group_id=efs_sg.id,
type="egress",
from_port=0,
to_port=0,
protocol="-1",
cidr_blocks=["0.0.0.0/0"],
)
if Lambda is in private subnet with no NAT then we need to add VPC endpoints
for service in ("ssm", "ssmmessages", "ec2messages", "logs", "elasticfilesystem"):
aws.ec2.VpcEndpoint(
f"{service}-endpoint",
vpc_id=vpc.vpc_id,
service_name=f"com.amazonaws.{aws.get_region().name}.{service}",
vpc_endpoint_type="Interface",
private_dns_enabled=True,
subnet_ids=vpc.private_subnet_ids,
security_group_ids=[lambda_sg.id, efs_sg.id],
)
db_efs = aws.efs.FileSystem("db-filesystem")
aws.efs.BackupPolicy(
"db-filesystem-backup-policy",
file_system_id=db_efs.id,
backup_policy=aws.efs.BackupPolicyBackupPolicyArgs(status="ENABLED"),
)
db_efs_mount_targets = vpc.public_subnet_ids.apply(
lambda subnet_ids: [
aws.efs.MountTarget(
f"db-filesystem-mount-target-subnet-{i}",
file_system_id=db_efs.id,
subnet_id=subnet_id,
security_groups=[lambda_sg.id],
)
for i, subnet_id in enumerate(subnet_ids)
]
)
aws.efs.AccessPointRootDirectoryCreationInfoArgs
db_efs_access_point = aws.efs.AccessPoint(
"db-filesystem-accesspoint",
file_system_id=db_efs.id,
posix_user=aws.efs.AccessPointPosixUserArgs(
uid=1000,
gid=1000,
),
root_directory=aws.efs.AccessPointRootDirectoryArgs(
path="/dbroot",
creation_info=aws.efs.AccessPointRootDirectoryCreationInfoArgs(
owner_gid=1000,
owner_uid=1000,
permissions="755",
),
),
opts=pulumi.ResourceOptions(depends_on=db_efs_mount_targets),
)
lambda_network_policy = aws.iam.get_policy_document(
statements=[
aws.iam.GetPolicyDocumentStatementArgs(
effect="Allow",
actions=[
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
],
resources=["*"],
),
]
)
lambda_logging_policy = aws.iam.get_policy_document(
statements=[
aws.iam.GetPolicyDocumentStatementArgs(
effect="Allow",
actions=[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
resources=["arn:aws:logs:::*"],
)
]
)
djangoadmin_lambda_role = aws.iam.Role(
"djangoadmin-lambda-role",
assume_role_policy=lambda_assume_role_policy.json,
managed_policy_arns=[
"arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy",
"arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess",
],
)
aws.iam.RolePolicy(
"djangoadmin-lambda-network-policy",
role=djangoadmin_lambda_role.name,
policy=lambda_network_policy.json,
)
aws.iam.RolePolicy(
"djangoadmin-lambda-logging-policy",
role=djangoadmin_lambda_role.name,
policy=lambda_logging_policy.json,
)
djangoadmin_lambda_log_group = aws.cloudwatch.LogGroup(
"djangoadmin-lambda-log-group",
name=f"/aws/lambda/{APP_NAME}",
retention_in_days=30,
)
djangoadmin_lambda = aws.lambda_.Function(
"djangoadmin-lambda",
opts=pulumi.ResourceOptions(depends_on=[djangoadmin_lambda_log_group]),
package_type="Image",
image_uri=lambda_image_uri,
architectures=["arm64"],
memory_size=512,
timeout=30,
reserved_concurrent_executions=1, # limit max concurrency
file_system_config=aws.lambda_.FunctionFileSystemConfigArgs(
arn=db_efs_access_point.arn,
local_mount_path="/mnt/efs",
),
role=djangoadmin_lambda_role.arn,
environment=aws.lambda_.FunctionEnvironmentArgs(
variables={
"AWS_LWA_PORT": "5000",
},
),
vpc_config=aws.lambda_.FunctionVpcConfigArgs(
subnet_ids=vpc.private_subnet_ids,
security_group_ids=[lambda_sg.id],
),
logging_config=aws.lambda_.FunctionLoggingConfigArgs(
log_format="JSON",
log_group=djangoadmin_lambda_log_group.name,
),
)