Skip to content

Commit

Permalink
Add a Lambda + EFS example (pulumi#709)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukehoban committed Jun 16, 2020
1 parent 55f4f49 commit af3c39e
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 0 deletions.
8 changes: 8 additions & 0 deletions aws-ts-lambda-efs/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: aws-ts-lambda-efs
runtime: nodejs
description: Examples of using Lambda + EFS
template:
config:
aws:region:
description: The AWS region to deploy into
default: us-east-2
142 changes: 142 additions & 0 deletions aws-ts-lambda-efs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Using Amazon EFS with AWS Lambda

This example shows how to use Amazon EFS with AWS Lambda in Pulumi. See the [Using AWS Lambda with Amazon Elastic File System (EFS)](https://www.pulumi.com/blog/aws-lambda-efs) blog post for a detailed walkthrough of this example.

![Architecture Diagram](./lambdaefs.png)

## Prerequisites

- [Node.js](https://nodejs.org/en/download/)
- [Download and install the Pulumi CLI](https://www.pulumi.com/docs/get-started/install/)
- [Connect Pulumi with your AWS account](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/) (if your AWS CLI is configured, no further changes are required)

## Running the Example

After cloning this repo, `cd` into it and run these commands:

1. Create a new stack, which is an isolated deployment target for this example:

```bash
$ pulumi stack init dev
```

2. Set your desired AWS region:

```bash
$ pulumi config set aws:region us-east-1 # any valid AWS region will work
```

3. Deploy everything with a single `pulumi up` command. This will show you a preview of changes first, which
includes all of the required AWS resources (clusters, services, and the like). Don't worry if it's more than
you expected -- this is one of the benefits of Pulumi, it configures everything so that so you don't need to!

```bash
$ pulumi up
```

After being prompted and selecting "yes", your deployment will begin. It'll complete in a few minutes:

```
Updating (demo):
Type Name Status
+ pulumi:pulumi:Stack aws-ts-lambda-efs-demo created
+ ├─ awsx:x:ec2:Vpc vpc created
+ │ ├─ aws:ec2:Vpc vpc created
+ │ ├─ awsx:x:ec2:Subnet vpc-public-0 created
+ │ │ ├─ aws:ec2:Subnet vpc-public-0 created
+ │ │ ├─ aws:ec2:RouteTable vpc-public-0 created
+ │ │ ├─ aws:ec2:Route vpc-public-0-ig created
+ │ │ └─ aws:ec2:RouteTableAssociation vpc-public-0 created
+ │ ├─ awsx:x:ec2:Subnet vpc-public-1 created
+ │ │ ├─ aws:ec2:RouteTable vpc-public-1 created
+ │ │ ├─ aws:ec2:Subnet vpc-public-1 created
+ │ │ ├─ aws:ec2:RouteTableAssociation vpc-public-1 created
+ │ │ └─ aws:ec2:Route vpc-public-1-ig created
+ │ ├─ awsx:x:ec2:NatGateway vpc-1 created
+ │ │ ├─ aws:ec2:Eip vpc-1 created
+ │ │ └─ aws:ec2:NatGateway vpc-1 created
+ │ ├─ awsx:x:ec2:Subnet vpc-private-0 created
+ │ │ ├─ aws:ec2:RouteTable vpc-private-0 created
+ │ │ ├─ aws:ec2:Subnet vpc-private-0 created
+ │ │ ├─ aws:ec2:RouteTableAssociation vpc-private-0 created
+ │ │ └─ aws:ec2:Route vpc-private-0-nat-0 created
+ │ ├─ awsx:x:ec2:InternetGateway vpc created
+ │ │ └─ aws:ec2:InternetGateway vpc created
+ │ ├─ awsx:x:ec2:Subnet vpc-private-1 created
+ │ │ ├─ aws:ec2:RouteTable vpc-private-1 created
+ │ │ ├─ aws:ec2:Subnet vpc-private-1 created
+ │ │ ├─ aws:ec2:RouteTableAssociation vpc-private-1 created
+ │ │ └─ aws:ec2:Route vpc-private-1-nat-1 created
+ │ └─ awsx:x:ec2:NatGateway vpc-0 created
+ │ ├─ aws:ec2:Eip vpc-0 created
+ │ └─ aws:ec2:NatGateway vpc-0 created
+ ├─ aws:apigateway:x:API api created
+ │ ├─ aws:apigateway:RestApi api created
+ │ ├─ aws:apigateway:Deployment api created
+ │ ├─ aws:lambda:Permission api-2c087c3e created
+ │ ├─ aws:lambda:Permission api-c171fd88 created
+ │ ├─ aws:lambda:Permission api-7857d17d created
+ │ └─ aws:apigateway:Stage api created
+ ├─ awsx:x:ecs:FargateService nginx created
+ │ └─ aws:ecs:Service nginx created
+ ├─ awsx:x:ecs:FargateTaskDefinition nginx created
+ │ ├─ aws:iam:Role nginx-execution created
+ │ ├─ aws:cloudwatch:LogGroup nginx created
+ │ ├─ aws:iam:Role nginx-task created
+ │ ├─ aws:iam:RolePolicyAttachment nginx-execution-9a42f520 created
+ │ ├─ aws:iam:RolePolicyAttachment nginx-task-32be53a2 created
+ │ ├─ aws:iam:RolePolicyAttachment nginx-task-fd1a00e5 created
+ │ └─ aws:ecs:TaskDefinition nginx created
+ ├─ awsx:x:ec2:SecurityGroup nginx-0 created
+ ├─ awsx:x:ecs:Cluster cluster created
+ │ ├─ aws:ecs:Cluster cluster created
+ │ └─ awsx:x:ec2:SecurityGroup cluster created
+ │ ├─ awsx:x:ec2:IngressSecurityGroupRule cluster-containers created
+ │ │ └─ aws:ec2:SecurityGroupRule cluster-containers created
+ │ ├─ awsx:x:ec2:EgressSecurityGroupRule cluster-egress created
+ │ │ └─ aws:ec2:SecurityGroupRule cluster-egress created
+ │ ├─ awsx:x:ec2:IngressSecurityGroupRule cluster-ssh created
+ │ │ └─ aws:ec2:SecurityGroupRule cluster-ssh created
+ │ └─ aws:ec2:SecurityGroup cluster created
+ ├─ aws:iam:Role getHandler created
+ ├─ aws:iam:Role execHandler created
+ ├─ aws:efs:FileSystem filesystem created
+ ├─ aws:iam:Role uploadHandler created
+ ├─ aws:iam:RolePolicyAttachment execHandler-32be53a2 created
+ ├─ aws:iam:RolePolicyAttachment execHandler-23f1a522 created
+ ├─ aws:iam:RolePolicyAttachment getHandler-32be53a2 created
+ ├─ aws:iam:RolePolicyAttachment getHandler-23f1a522 created
+ ├─ aws:iam:RolePolicyAttachment uploadHandler-32be53a2 created
+ ├─ aws:iam:RolePolicyAttachment uploadHandler-23f1a522 created
+ ├─ aws:efs:MountTarget fs-mount-1 created
+ ├─ aws:efs:MountTarget fs-mount-0 created
+ ├─ aws:efs:AccessPoint ap created
+ ├─ aws:lambda:Function getHandler created
+ ├─ aws:lambda:Function uploadHandler created
+ └─ aws:lambda:Function execHandler created
Outputs:
url: "https://280f2167f1.execute-api.us-east-1.amazonaws.com/stage/"
Resources:
+ 75 created
Duration: 5m52s
```

4. At this point, your app is running! The URL was published so it's easy to interact with:

```bash
$ curl -X POST -d '<h1>Hello world</h1>' $(pulumi stack output url)files/index.html
$ curl -X GET $(pulumi stack output url)files/file.txt
<h1>Hello world</h1>
```

5. Once you are done, you can destroy all of the resources, and the stack:

```bash
$ pulumi destroy
$ pulumi stack rm
```
118 changes: 118 additions & 0 deletions aws-ts-lambda-efs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2016-2019, Pulumi Corporation. All rights reserved.

import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
import * as fs from "fs";
import * as cp from "child_process";

export = async () => {

// VPC
const vpc = new awsx.ec2.Vpc("vpc", { subnets: [{ type: "private" }, { type: "public" }] });
const subnetIds = await vpc.publicSubnetIds;

// EFS
const filesystem = new aws.efs.FileSystem("filesystem");
const targets = [];
for (let i = 0; i < subnetIds.length; i++) {
targets.push(new aws.efs.MountTarget(`fs-mount-${i}`, {
fileSystemId: filesystem.id,
subnetId: subnetIds[i],
securityGroups: [vpc.vpc.defaultSecurityGroupId],
}));
}
const ap = new aws.efs.AccessPoint("ap", {
fileSystemId: filesystem.id,
posixUser: { uid: 1000, gid: 1000 },
rootDirectory: { path: "/www", creationInfo: { ownerGid: 1000, ownerUid: 1000, permissions: "755" } },
}, { dependsOn: targets });

// Lambda
function efsvpcCallback(name: string, f: aws.lambda.Callback<awsx.apigateway.Request, awsx.apigateway.Response>) {
return new aws.lambda.CallbackFunction(name, {
policies: [aws.iam.ManagedPolicies.AWSLambdaVPCAccessExecutionRole, aws.iam.ManagedPolicies.AWSLambdaFullAccess],
vpcConfig: {
subnetIds: vpc.privateSubnetIds,
securityGroupIds: [vpc.vpc.defaultSecurityGroupId],
},
fileSystemConfigs: [{ arn: ap.arn, localMountPath: "/mnt/storage" }],
callback: f,
});
}

// API Gateway
const api = new awsx.apigateway.API("api", {
routes: [
{
method: "GET", path: "/files/{filename+}", eventHandler: efsvpcCallback("getHandler", async (ev, ctx) => {
try {
const f = "/mnt/storage/" + ev.pathParameters!.filename;
const data = fs.readFileSync(f)
return {
statusCode: 200,
body: data.toString(),
};
} catch {
return { statusCode: 500, body: "" }
}
}),
},
{
method: "POST", path: "/files/{filename+}", eventHandler: efsvpcCallback("uploadHandler", async (ev, ctx) => {
try {
const f = "/mnt/storage/" + ev.pathParameters!.filename;
const data = new Buffer(ev.body!, 'base64');
fs.writeFileSync(f, data)
return {
statusCode: 200,
body: "",
};
} catch {
return { statusCode: 500, body: "" }
}
}),
},
{
method: "POST", path: "/", eventHandler: efsvpcCallback("execHandler", async (ev, ctx) => {
const cmd = new Buffer(ev.body!, 'base64').toString()
const buf = cp.execSync(cmd);
return {
statusCode: 200,
body: buf.toString(),
};
}),
},
],
});

// ECS Cluster
const cluster = new awsx.ecs.Cluster("cluster", { vpc: vpc });
const efsVolumeConfiguration: aws.types.input.ecs.TaskDefinitionVolumeEfsVolumeConfiguration = {
fileSystemId: filesystem.id,
authorizationConfig: { accessPointId: ap.id, },
rootDirectory: "/www",
};

// Fargate Service
const nginx = new awsx.ecs.FargateService("nginx", {
cluster: cluster,
taskDefinitionArgs: {
container: {
image: "nginx",
memory: 128,
portMappings: [{ containerPort: 80, hostPort: 80, protocol: "tcp" }],
mountPoints: [{ containerPath: "/usr/share/nginx/html", sourceVolume: "efs" }],
},
volumes: [{ name: "efs", efsVolumeConfiguration }],
},
securityGroups: [vpc.vpc.defaultSecurityGroupId, ...cluster.securityGroups],
subnets: vpc.publicSubnetIds,
platformVersion: "1.4.0",
});

// Exports
return {
url: api.url,
};

}
Binary file added aws-ts-lambda-efs/lambdaefs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions aws-ts-lambda-efs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "aws-ts-lambda-efs",
"version": "0.1.0",
"dependencies": {
"@pulumi/aws": "^2.10.0",
"@pulumi/awsx": "^0.20.0",
"@pulumi/pulumi": "^2.0.0"
}
}
19 changes: 19 additions & 0 deletions aws-ts-lambda-efs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true
},
"files": [
"index.ts"
]
}

0 comments on commit af3c39e

Please sign in to comment.