AWS Lambda Functions Powered by AWS Graviton2 Processors
Posted on
In late 2018, AWS launched their first EC2 instances powered by ARM-based AWS Graviton Processors. These instances had been optimized for performance and cost. Since that initial launch, Amazon has continued to innovate in the Graviton space. In June 2021, they launched the Graviton Challenge for users to move their applications to AWS Graviton2. AWS Graviton2 processor instance types are up to 20% lower cost than x86 based instance types and see up to 40% better price performance.
Today, AWS launched Graviton2 support for AWS Lambda Functions, which means that your Lambda Functions can now execute on Graviton2 processors and take advantage of the improved price performance of that architecture.
Deploying a Lambda Function with Graviton2 Support
Let’s use Pulumi to deploy a new AWS Lambda Function using a Graviton2 architecture. First, let’s create the IAM role and policies required to create a Lambda Function:
import * as aws from "@pulumi/aws";
const role = new aws.iam.Role("lambda-role", {
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
Service: "lambda.amazonaws.com"
}),
});
const fullAccess = new aws.iam.RolePolicyAttachment("mylambda-access", {
role: role,
policyArn: aws.iam.ManagedPolicy.LambdaFullAccess,
});
import pulumi
import json
import pulumi_aws as aws
test_role = aws.iam.Role("lambda-role",
assume_role_policy=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Sid": "",
"Principal": {
"Service": "lambda.amazonaws.com",
},
}],
}))
full_access = aws.iam.RolePolicyAttachment("mylambda-access",
role=role.name,
policy_arn=aws.iam.ManagedPolicy.LAMBDA_FULL_ACCESS)
package main
import (
"encoding/json"
"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// create IAM role and policy wiring
assumeRolePolicyJSON, err := json.Marshal(map[string]interface{}{
"Version": "2012-10-17",
"Statement": []interface{}{
map[string]interface{}{
"Action": "sts:AssumeRole",
"Principal": map[string]interface{}{
"Service": "lambda.amazonaws.com",
},
"Effect": "Allow",
},
},
})
if err != nil {
return err
}
role, err := iam.NewRole(ctx, "lambda-role", &iam.RoleArgs{
AssumeRolePolicy: pulumi.String(assumeRolePolicyJSON),
})
if err != nil {
return err
}
_, err = iam.NewRolePolicyAttachment(ctx, "mylambda-access", &iam.RolePolicyAttachmentArgs{
Role: role.Name,
PolicyArn: pulumi.String("arn:aws:iam::aws:policy/AWSLambda_FullAccess"),
})
if err != nil {
return err
}
return nil
})
}
using Pulumi;
using Pulumi.Aws.Iam;
using Pulumi.Aws.Lambda;
class LambdaStack : Stack
{
public LambdaStack()
{
var role = new Role("lambda-role", new RoleArgs
{
AssumeRolePolicy =
@"{
""Version"": ""2012-10-17"",
""Statement"": [
{
""Action"": ""sts:AssumeRole"",
""Principal"": {
""Service"": ""lambda.amazonaws.com""
},
""Effect"": ""Allow"",
""Sid"": """"
}
]
}"
});
var fullAccess = new Aws.Iam.RolePolicyAttachment("mylambda-access", new Aws.Iam.RolePolicyAttachmentArgs
{
Role = role.Name,
PolicyArn = "arn:aws:iam::aws:policy/AWSLambda_FullAccess",
});
}
}
This IAM role can then be used when deploying the Lambda Function:
const lambda = new aws.lambda.Function("mylambda", {
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./app"),
}),
role: role.arn,
handler: "index.handler",
runtime: aws.lambda.NodeJS12dXRuntime,
architectures: ["arm64"],
});
function = aws.lambda_.Function("mylambda",
role=role.arn,
runtime="nodejs12.x",
handler="index.handler",
code=pulumi.AssetArchive({
'.': pulumi.FileArchive('./app')
}),
architectures=["arm64"],
)
function, err := lambda.NewFunction(ctx, "mylambda", &lambda.FunctionArgs{
Handler: pulumi.String("index.handler"),
Role: role.Arn,
Runtime: pulumi.String("nodejs12.x"),
Code: pulumi.NewFileArchive("./app"),
Architectures: pulumi.StringArray{pulumi.String("arm64")},
})
if err != nil {
return nil
}
var lambda = new Function("mylambda", new FunctionArgs
{
Runtime = "nodejs12.x",
Code = new FileArchive("./app"),
Handler = "index.handler",
Role = role.Arn
Architectures = {"arm64"}
});
The only new thing we need to do to deploy our Lambda function to Graviton2-based infrastructure is to provide the architectures
input,
specifying a value of arm64
. With that small change, our Lambda Function will execute on an Arm-based instance.
When we deploy this via Pulumi, we can see that the Function has been deployed with the correct architecture in AWS:
$ pulumi up
Previewing update (dev)
View Live: https://app.pulumi.com/stack72/lambda-graviton-function/dev/previews/ed6a2b10-d0a8-4c08-ba2d-c749f216db67
Type Name Plan
+ pulumi:pulumi:Stack lambda-graviton-function-dev create
+ ├─ aws:iam:Role lambda-role create
+ ├─ aws:iam:RolePolicyAttachment mylambda-access create
+ └─ aws:lambda:Function mylambda create
Resources:
+ 4 to create
Do you want to perform this update? yes
Updating (dev)
View Live: https://app.pulumi.com/stack72/lambda-graviton-function/dev/updates/1
Type Name Status
+ pulumi:pulumi:Stack lambda-graviton-function-dev created
+ ├─ aws:iam:Role lambda-role created
+ ├─ aws:lambda:Function mylambda created
+ └─ aws:iam:RolePolicyAttachment mylambda-access created
Resources:
+ 4 created
Duration: 25s
If the architectures
input is not specified, AWS will set the value by default to x86_64
.
Since we are running our application code on ARM, we need to ensure that the code in the archive we use for our
Lambda function can run natively on the ARM-based infrastructure. For interpreted languages like Node.js and Python, most applications
will work as is. For compiled languages like Go, we need to ensure that our Go code is cross-compiled for
GOARCH=arm64
, and that the resulting ARM64 binary is included in our Lambda source archive.
Creating a Lambda Layer with Graviton2 Support
To create a Lambda Layer with Graviton2 support, we can extend our example above. Firstly, let’s create a lambda layer:
const layer = new aws.lambda.LayerVersion("my-layer-version", {
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./base-layer"),
}),
compatibleRuntimes: [
aws.lambda.Runtime.NodeJS14dX,
],
layerName: "my-base-layer",
compatibleArchitectures: ["arm64", "x86_64"]
});
layer = aws.lambda_.LayerVersion("my-layer-version",
compatible_runtimes=["nodejs12.x"],
code=pulumi.FileArchive("./base-layer"),
layer_name="my-base-layer",
compatible_architectures=["arm64", "x86_64"])
layer, err := lambda.NewLayerVersion(ctx, "my-layer-version", &lambda.LayerVersionArgs{
LayerName: pulumi.String("my-base-layer"),
Code: pulumi.NewFileArchive("./base-layer"),
CompatibleRuntimes: pulumi.StringArray{pulumi.String("nodejs12.x")},
CompatibleArchitectures: pulumi.StringArray{pulumi.String("nodejs12.x")},
})
if err != nil {
return nil
}
var layer = new LayerVersion("my-layer-version", new LayerVersionArgs
{
CompatibleRuntimes =
{
"nodejs12.x",
},
CompatibleArchitectures =
{
"arm64",
"x86_64",
},
Code = new FileArchive("./base-layer"),
LayerName = "my-base-layer",
});
This will create a Lambda layer that is compatible with NodeJS 12.x and ARM64 & X86_64. Layers can be created that are compatible with ARM64, X86_64 or both architectures. We can then build a new lambda based on this Lambda layer:
const lambda = new aws.lambda.Function("mylambda", {
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./app"),
}),
role: role.arn,
handler: "index.handler",
runtime: aws.lambda.NodeJS12dXRuntime,
architectures: ["arm64"],
layers: [layer.arn]
});
function = aws.lambda_.Function("mylambda",
role=role.arn,
runtime="nodejs12.x",
handler="index.handler",
code=pulumi.AssetArchive({
'.': pulumi.FileArchive('./app')
}),
architectures=["arm64"],
layers=[layer.arn]
)
function, err := lambda.NewFunction(ctx, "mylambda", &lambda.FunctionArgs{
Handler: pulumi.String("index.handler"),
Role: role.Arn,
Runtime: pulumi.String("nodejs12.x"),
Code: pulumi.NewFileArchive("./app"),
Architectures: pulumi.StringArray{pulumi.String("arm64")},
Layers: pulumi.StringArray{layer.Arn},
})
if err != nil {
return nil
}
var lambda = new Function("mylambda", new FunctionArgs
{
Runtime = "nodejs12.x",
Code = new FileArchive("./app"),
Handler = "index.handler",
Role = role.Arn
Architectures = {"arm64"}
Layers =
{
lambdaLayer.Arn,
},
});
This will deploy a function built on top of the Lambda layer targeting the Graviton2 architecture and NodeJS 12.x runtime.
Summary
AWS support for Lambda Functions powered by AWS Graviton2 offers users the ability to get better price performance for their workloads. We’ve shown how you can build your Functions using this ability. You can read more about AWS Lambda functions on the AWS Documentation website and getting started with AWS Graviton on GitHub.