logo

Hi everyone,

?This little write up came up as a result of a few things I have been thinking of at work with regards to integration of AWS, Lambda and Cost improvements for Customers. As I often do I figure if I can figure it out then maybe I can help others do the same. This blog should give a good overview as to the problem statement (outlined below) and how I solved it using AWS CDK. I also decided to demonstrate how it would work with a console based approach for someone just starting out.

?Problem: Requirements given to us for Security Governance and Cost Optimisation need to ensure that Lambda functions operate as cost effectively as possible and operate within the secure boundaries of a customer’s environment

Why put lambda inside a VPC?

This is a question that I have been asked before and a key reason for doing that is security. A number of Governing Security Benchmarks see a requirement to place a Lambda Function within a VPC and to be honest I wouldnt be surprised if AWS eventually add it as a Foundational Best Practice in the future. It is already listed in the Operational Best practices guidelines for CIS listed Here and general recommendation is that if your Lambda requires access to any resources in your secured environment (ie VPC resources or Databases) then placing the function inside the VPC operating within strict networking security constructs as with any other solution in a VPC (ie EC2). Access to Lambda works via either NAT Gateway, Internet Gateway from a public subnet (Not recommended) or a Lambda VPC Endpoint (Recommended) to handle traffic between the VPC and the backend services. By the end of this blog you should hopefully have a good understanding of how to do that and then also some AWS CDK Code to work from (The blog will cover both Console and Code mechanism for doing this)

?And what is Graviton2 Processors and why use them for lambda?

AWS have progressively across many of their services started using AMD Graviton2 Processors as a low cost alternative to the x86_64 based Intel chipsets. The cost to performance ratio can be over 20% and for Lambda it seems to be in the 30% range for economic value. So for Lambda functions where you only pay for compute when you need it saving money on that compute through a ARM based architecture makes a good amount of sense.

If you want more information on the introduction of Graviton Processors for Lambda I recommend this AWS Blog which was recently published and was a great source of information for me.

console Based Process

If you have access to an AWS Account where you can learn and create resources then feel free to use it. AWS do offer up to 12 months free access to certain resources for new customers and that proves quite valuable when you want to run lab environments like I do.

 

Step1: In your AWS Environment Access the Lambda Console

 

Step 2: Create a Function

When the function creation occurs you can define the architecture of the hardware. I am assuming that you have a VPC already as creating one is out of scope for this documentation. For the purpose of the rest of this console demonstration I will use a basic Hello World Function in Lambda

?Step 3: Define the Architecture

Lambda uses x86_64 architecture by default but as I said we are looking at improving the performance to cost value of lambda in our environments so what are doing is swapping the functions to use an ARM Architecture. You do this when you Create the function on the console as per the following by changing the architecture to arm64

 

Step 4: Define the Permissions

like any service that AWS has which interacts with another Lambda does need an execution role and for this purpose we just need a basic one which provides just enough permissions to execute. In fact an example of the Execution role code is below

 

 

[et_pb_dmb_code_snippet code=”ewogICAgIlZlcnNpb24iOiAiMjAxMi0xMC0xNyIsCiAgICAiU3RhdGVtZW50IjogWwogICAgICAgIHsKICAgICAgICAgICAgIkVmZmVjdCI6ICJBbGxvdyIsCiAgICAgICAgICAgICJBY3Rpb24iOiAibG9nczpDcmVhdGVMb2dHcm91cCIsCiAgICAgICAgICAgICJSZXNvdXJjZSI6ICJhcm46YXdzOmxvZ3M6YXAtc291dGhlYXN0LTI6MTIzNDU2Nzg5MDEyOioiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJFZmZlY3QiOiAiQWxsb3ciLAogICAgICAgICAgICAiQWN0aW9uIjogWwogICAgICAgICAgICAgICAgImxvZ3M6Q3JlYXRlTG9nU3RyZWFtIiwKICAgICAgICAgICAgICAgICJsb2dzOlB1dExvZ0V2ZW50cyIKICAgICAgICAgICAgXSwKICAgICAgICAgICAgIlJlc291cmNlIjogWwogICAgICAgICAgICAgICAgImFybjphd3M6bG9nczphcC1zb3V0aGVhc3QtMjoxMjM0NTY3ODkwMTI6bG9nLWdyb3VwOi9hd3MvbGFtYmRhL0hlbGxvV29ybGQ6KiIKICAgICAgICAgICAgXQogICAgICAgIH0KICAgIF0KfQ==” _builder_version=”4.10.7″ _module_preset=”default” global_colors_info=”{}”]ewogICAgIlZlcnNpb24iOiAiMjAxMi0xMC0xNyIsCiAgICAiU3RhdGVtZW50IjogWwogICAgICAgIHsKICAgICAgICAgICAgIkVmZmVjdCI6ICJBbGxvdyIsCiAgICAgICAgICAgICJBY3Rpb24iOiAibG9nczpDcmVhdGVMb2dHcm91cCIsCiAgICAgICAgICAgICJSZXNvdXJjZSI6ICJhcm46YXdzOmxvZ3M6YXAtc291dGhlYXN0LTI6MTIzNDU2Nzg5MDEyOioiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJFZmZlY3QiOiAiQWxsb3ciLAogICAgICAgICAgICAiQWN0aW9uIjogWwogICAgICAgICAgICAgICAgImxvZ3M6Q3JlYXRlTG9nU3RyZWFtIiwKICAgICAgICAgICAgICAgICJsb2dzOlB1dExvZ0V2ZW50cyIKICAgICAgICAgICAgXSwKICAgICAgICAgICAgIlJlc291cmNlIjogWwogICAgICAgICAgICAgICAgImFybjphd3M6bG9nczphcC1zb3V0aGVhc3QtMjoxMjM0NTY3ODkwMTI6bG9nLWdyb3VwOi9hd3MvbGFtYmRhL0hlbGxvV29ybGQ6KiIKICAgICAgICAgICAgXQogICAgICAgIH0KICAgIF0KfQ==[/et_pb_dmb_code_snippet]

If you just create a standard AWS Lambda Function without being inside a VPC then this is the permissions it needs (ie it needs to be able to create log groups and streams in Cloudwatch Logs and put log events into those groups.

But since we are also defining the functio to be inside a VPC the basic default role also creates additional permissions to act in the VPC (create network interfaces, etc.). That snippet is below:

[et_pb_dmb_code_snippet code=”ewogICAgIlZlcnNpb24iOiAiMjAxMi0xMC0xNyIsCiAgICAiU3RhdGVtZW50IjogWwogICAgICAgIHsKICAgICAgICAgICAgIkVmZmVjdCI6ICJBbGxvdyIsCiAgICAgICAgICAgICJBY3Rpb24iOiBbCiAgICAgICAgICAgICAgICAiZWMyOkNyZWF0ZU5ldHdvcmtJbnRlcmZhY2UiLAogICAgICAgICAgICAgICAgImVjMjpEZWxldGVOZXR3b3JrSW50ZXJmYWNlIiwKICAgICAgICAgICAgICAgICJlYzI6RGVzY3JpYmVOZXR3b3JrSW50ZXJmYWNlcyIKICAgICAgICAgICAgXSwKICAgICAgICAgICAgIlJlc291cmNlIjogIioiCiAgICAgICAgfQogICAgXQp9″ _builder_version=”4.10.7″ _module_preset=”default” global_colors_info=”{}”]ewogICAgIlZlcnNpb24iOiAiMjAxMi0xMC0xNyIsCiAgICAiU3RhdGVtZW50IjogWwogICAgICAgIHsKICAgICAgICAgICAgIkVmZmVjdCI6ICJBbGxvdyIsCiAgICAgICAgICAgICJBY3Rpb24iOiBbCiAgICAgICAgICAgICAgICAiZWMyOkNyZWF0ZU5ldHdvcmtJbnRlcmZhY2UiLAogICAgICAgICAgICAgICAgImVjMjpEZWxldGVOZXR3b3JrSW50ZXJmYWNlIiwKICAgICAgICAgICAgICAgICJlYzI6RGVzY3JpYmVOZXR3b3JrSW50ZXJmYWNlcyIKICAgICAgICAgICAgXSwKICAgICAgICAgICAgIlJlc291cmNlIjogIioiCiAgICAgICAgfQogICAgXQp9[/et_pb_dmb_code_snippet]

Generally speaking in a real world scenario you will define a more bespoke role (ie if you lambda needs to interact with s3 or DynamoDB the permission will be defined to access specific buckets or tables as needed).

Just like in everything we do in AWS when thinking Well architected the Principle of least privilege applies. We shouldnt define functions that can access any and all resources of a particular type.

 

Step 5: Define the Network

When you define a Lambda Function operating within a VPC it requires a VPC and then you specify the subnets and create/define security group for access.

For this function and many others the lambda function really only needs outbound access to lambda service endpoint and that connectivity occurs on port 443.

When defining Subnets it is generally very important to ensure that at least 2 if not more are defined in disparate Availability zones to ensure redundancy.

 

After Clicking Create Function then it is done. It will create the Network Interfaces in the defined subnets and create the function with the chosen architecture.?

 

Creating using AWS CDK

This is a little bit more advanced but if you find clicking on consoles clunky and boring like I do then you will want to codify everything you do where possible. Thanks to AWS CDK that is relative easyand I will show you a code snippet from a sample App that can do it using CDK 1.127.0. ARM support in CDK is fairly recent so you need to have either 1.125.0 or higher (in CDK 1.0) or 2.0.0 RC.24 (in CDK 2.0). I used CDK 1.127.0 as it is the latest as of time of writing and it also fixes a bug that caused issues in certain situations where architecture was specified.

NOTE: When this was written CDK 2.0 Release Candidate didnt support this feature so the code was written in CDK 1.0. I have added an additional snippet for CDK 2.0 which added support in RC.24

[et_pb_dmb_code_snippet code=”aW1wb3J0IHsgU3RhY2ssIFN0YWNrUHJvcHMsIENvbnN0cnVjdCwgfSBmcm9tICdAYXdzLWNkay9jb3JlJzsKaW1wb3J0ICogYXMgTGFtYmRhIGZyb20gJ0Bhd3MtY2RrL2F3cy1sYW1iZGEnOwppbXBvcnQgKiBhcyBlYzIgZnJvbSAnQGF3cy1jZGsvYXdzLWVjMic7CgppbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnOwoKaW50ZXJmYWNlIExhbWJkYVN0YWNrUHJvcHMgZXh0ZW5kcyBTdGFja1Byb3BzIHsKICB2cGNJZDogc3RyaW5nOyAvL0lEIG9mIFZQQy4gTmVlZHMgdG8gYmUgcGFzc2VkIGluIHRvIHRoZSBTdGFjawp9CgpleHBvcnQgY2xhc3MgTGFtYmRhdGVzdFN0YWNrIGV4dGVuZHMgU3RhY2sgewogIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzPzogTGFtYmRhU3RhY2tQcm9wcykgewogICAgc3VwZXIoc2NvcGUsIGlkLCBwcm9wcyk7CgoKICAgIC8vSW1wb3J0IFZQQyBpbnRvIHlvdXIgQ0RLIEFwcAogICAgbGV0IHZwY0lkID0gcHJvcHM/LnZwY0lkIQogICAgdmFyIHZwYyA9IGVjMi5WcGMuZnJvbUxvb2t1cCh0aGlzLCdleGlzdGluZ3ZwYycsewogICAgICB2cGNJZDogdnBjSWQKICAgIH0pOwoKICAgIGNvbnN0IGxhbWJkYSA9IG5ldyBMYW1iZGEuRnVuY3Rpb24odGhpcywnaGVsbG93b3JsZCcsewogICAgICB2cGMsCiAgICAgIHZwY1N1Ym5ldHM6IHsKICAgICAgICBzdWJuZXRUeXBlOiBlYzIuU3VibmV0VHlwZS5QUklWQVRFX1dJVEhfTkFULAogICAgICAgIG9uZVBlckF6OiB0cnVlCiAgICAgIH0sCiAgICAgIAogICAgICBydW50aW1lOiBMYW1iZGEuUnVudGltZS5OT0RFSlNfMTRfWCwKICAgICAgaGFuZGxlcjogJ21haW4nLAogICAgICBjb2RlOiBMYW1iZGEuQ29kZS5mcm9tQXNzZXQocGF0aC5qb2luKF9fZGlybmFtZSwgYC8uLi9zcmMvbXktbGFtYmRhL2luZGV4LnRzYCkpLAogICAgICBhcmNoaXRlY3R1cmU6IExhbWJkYS5BcmNoaXRlY3R1cmUuQVJNXzY0CgogICAgfSkKICAgIC8vIFRoZSBjb2RlIHRoYXQgZGVmaW5lcyB5b3VyIHN0YWNrIGdvZXMgaGVyZQogIH0KfQ==” _builder_version=”4.11.2″ _module_preset=”default” hover_enabled=”0″ global_colors_info=”{}” title=”Using CDK 1.0″ sticky_enabled=”0″]aW1wb3J0IHsgU3RhY2ssIFN0YWNrUHJvcHMsIENvbnN0cnVjdCwgfSBmcm9tICdAYXdzLWNkay9jb3JlJzsKaW1wb3J0ICogYXMgTGFtYmRhIGZyb20gJ0Bhd3MtY2RrL2F3cy1sYW1iZGEnOwppbXBvcnQgKiBhcyBlYzIgZnJvbSAnQGF3cy1jZGsvYXdzLWVjMic7CgppbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnOwoKaW50ZXJmYWNlIExhbWJkYVN0YWNrUHJvcHMgZXh0ZW5kcyBTdGFja1Byb3BzIHsKICB2cGNJZDogc3RyaW5nOyAvL0lEIG9mIFZQQy4gTmVlZHMgdG8gYmUgcGFzc2VkIGluIHRvIHRoZSBTdGFjawp9CgpleHBvcnQgY2xhc3MgTGFtYmRhdGVzdFN0YWNrIGV4dGVuZHMgU3RhY2sgewogIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzPzogTGFtYmRhU3RhY2tQcm9wcykgewogICAgc3VwZXIoc2NvcGUsIGlkLCBwcm9wcyk7CgoKICAgIC8vSW1wb3J0IFZQQyBpbnRvIHlvdXIgQ0RLIEFwcAogICAgbGV0IHZwY0lkID0gcHJvcHM/LnZwY0lkIQogICAgdmFyIHZwYyA9IGVjMi5WcGMuZnJvbUxvb2t1cCh0aGlzLCdleGlzdGluZ3ZwYycsewogICAgICB2cGNJZDogdnBjSWQKICAgIH0pOwoKICAgIGNvbnN0IGxhbWJkYSA9IG5ldyBMYW1iZGEuRnVuY3Rpb24odGhpcywnaGVsbG93b3JsZCcsewogICAgICB2cGMsCiAgICAgIHZwY1N1Ym5ldHM6IHsKICAgICAgICBzdWJuZXRUeXBlOiBlYzIuU3VibmV0VHlwZS5QUklWQVRFX1dJVEhfTkFULAogICAgICAgIG9uZVBlckF6OiB0cnVlCiAgICAgIH0sCiAgICAgIAogICAgICBydW50aW1lOiBMYW1iZGEuUnVudGltZS5OT0RFSlNfMTRfWCwKICAgICAgaGFuZGxlcjogJ21haW4nLAogICAgICBjb2RlOiBMYW1iZGEuQ29kZS5mcm9tQXNzZXQocGF0aC5qb2luKF9fZGlybmFtZSwgYC8uLi9zcmMvbXktbGFtYmRhL2luZGV4LnRzYCkpLAogICAgICBhcmNoaXRlY3R1cmU6IExhbWJkYS5BcmNoaXRlY3R1cmUuQVJNXzY0CgogICAgfSkKICAgIC8vIFRoZSBjb2RlIHRoYXQgZGVmaW5lcyB5b3VyIHN0YWNrIGdvZXMgaGVyZQogIH0KfQ==[/et_pb_dmb_code_snippet][et_pb_dmb_code_snippet _builder_version=”4.11.2″ _module_preset=”default” code=”aW1wb3J0IHsgU3RhY2ssIFN0YWNrUHJvcHMsIGF3c19sYW1iZGEgYXMgTGFtYmRhLCBhd3NfZWMyIGFzIGVjMiB9IGZyb20gJ2F3cy1jZGstbGliJzsKaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSAnY29uc3RydWN0cyc7CmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7CgppbnRlcmZhY2UgTGFtYmRhU3RhY2tQcm9wcyBleHRlbmRzIFN0YWNrUHJvcHMgewogIHZwY0lkOiBzdHJpbmc7IC8vSUQgb2YgVlBDLiBOZWVkcyB0byBiZSBwYXNzZWQgaW4gdG8gdGhlIFN0YWNrCn0KCmV4cG9ydCBjbGFzcyBMYW1iZGF0ZXN0U3RhY2sgZXh0ZW5kcyBTdGFjayB7CiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM/OiBMYW1iZGFTdGFja1Byb3BzKSB7CiAgICBzdXBlcihzY29wZSwgaWQsIHByb3BzKTsKCgogICAgLy9JbXBvcnQgVlBDIGludG8geW91ciBDREsgQXBwCiAgICBsZXQgdnBjSWQgPSBwcm9wcz8udnBjSWQhCiAgICB2YXIgdnBjID0gZWMyLlZwYy5mcm9tTG9va3VwKHRoaXMsJ2V4aXN0aW5ndnBjJyx7CiAgICAgIHZwY0lkOiB2cGNJZAogICAgfSk7CgogICAgY29uc3QgbGFtYmRhID0gbmV3IExhbWJkYS5GdW5jdGlvbih0aGlzLCdoZWxsb3dvcmxkJyx7CiAgICAgIHZwYywKICAgICAgdnBjU3VibmV0czogewogICAgICAgIHN1Ym5ldFR5cGU6IGVjMi5TdWJuZXRUeXBlLlBSSVZBVEVfV0lUSF9OQVQsCiAgICAgICAgb25lUGVyQXo6IHRydWUKICAgICAgfSwKICAgICAgCiAgICAgIHJ1bnRpbWU6IExhbWJkYS5SdW50aW1lLk5PREVKU18xNF9YLAogICAgICBoYW5kbGVyOiAnbWFpbicsCiAgICAgIGNvZGU6IExhbWJkYS5Db2RlLmZyb21Bc3NldChwYXRoLmpvaW4oX19kaXJuYW1lLCBgLy4uL3NyYy9teS1sYW1iZGEvaW5kZXgudHNgKSksCiAgICAgIGFyY2hpdGVjdHVyZTogTGFtYmRhLkFyY2hpdGVjdHVyZS5BUk1fNjQKCiAgICB9KQogICAgLy8gVGhlIGNvZGUgdGhhdCBkZWZpbmVzIHlvdXIgc3RhY2sgZ29lcyBoZXJlCiAgfQp9″ title=”Using CDK 2.0″ hover_enabled=”0″ sticky_enabled=”0″]aW1wb3J0IHsgU3RhY2ssIFN0YWNrUHJvcHMsIGF3c19sYW1iZGEgYXMgTGFtYmRhLCBhd3NfZWMyIGFzIGVjMiB9IGZyb20gJ2F3cy1jZGstbGliJzsKaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSAnY29uc3RydWN0cyc7CmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7CgppbnRlcmZhY2UgTGFtYmRhU3RhY2tQcm9wcyBleHRlbmRzIFN0YWNrUHJvcHMgewogIHZwY0lkOiBzdHJpbmc7IC8vSUQgb2YgVlBDLiBOZWVkcyB0byBiZSBwYXNzZWQgaW4gdG8gdGhlIFN0YWNrCn0KCmV4cG9ydCBjbGFzcyBMYW1iZGF0ZXN0U3RhY2sgZXh0ZW5kcyBTdGFjayB7CiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM/OiBMYW1iZGFTdGFja1Byb3BzKSB7CiAgICBzdXBlcihzY29wZSwgaWQsIHByb3BzKTsKCgogICAgLy9JbXBvcnQgVlBDIGludG8geW91ciBDREsgQXBwCiAgICBsZXQgdnBjSWQgPSBwcm9wcz8udnBjSWQhCiAgICB2YXIgdnBjID0gZWMyLlZwYy5mcm9tTG9va3VwKHRoaXMsJ2V4aXN0aW5ndnBjJyx7CiAgICAgIHZwY0lkOiB2cGNJZAogICAgfSk7CgogICAgY29uc3QgbGFtYmRhID0gbmV3IExhbWJkYS5GdW5jdGlvbih0aGlzLCdoZWxsb3dvcmxkJyx7CiAgICAgIHZwYywKICAgICAgdnBjU3VibmV0czogewogICAgICAgIHN1Ym5ldFR5cGU6IGVjMi5TdWJuZXRUeXBlLlBSSVZBVEVfV0lUSF9OQVQsCiAgICAgICAgb25lUGVyQXo6IHRydWUKICAgICAgfSwKICAgICAgCiAgICAgIHJ1bnRpbWU6IExhbWJkYS5SdW50aW1lLk5PREVKU18xNF9YLAogICAgICBoYW5kbGVyOiAnbWFpbicsCiAgICAgIGNvZGU6IExhbWJkYS5Db2RlLmZyb21Bc3NldChwYXRoLmpvaW4oX19kaXJuYW1lLCBgLy4uL3NyYy9teS1sYW1iZGEvaW5kZXgudHNgKSksCiAgICAgIGFyY2hpdGVjdHVyZTogTGFtYmRhLkFyY2hpdGVjdHVyZS5BUk1fNjQKCiAgICB9KQogICAgLy8gVGhlIGNvZGUgdGhhdCBkZWZpbmVzIHlvdXIgc3RhY2sgZ29lcyBoZXJlCiAgfQp9[/et_pb_dmb_code_snippet]

So I think I should explain this a little bit to help people understand it. There are few key things to know with defining this code in CDK.

1. You need to pass an actual VPC to the lambda function rather than the VPC Id and thats why I import the VPC from lookup so that the subnets and VPC are fully visible to the Construct

2. You dont need to define a role in this code construct as that is done automatically. Once the function is created you can then grant it access to other resources within a stack using an easy grant option (ie for dynamo db table you can add a grant instruction for the lambda handler as follows:

table.grantReadWriteData(Lambda.handler);

3. When defining Lambda types such as Runtime or Architecture you always have to put it in the form of lambda.Runtime.xxxxxxx or lambda.Architecture.xxxxx and that also applies to code assets (I am aware I used a capital letter in my module import but more often than not I will use lower case)

4. If you dont specify onePerAz: true when defining vpcSubnets it will create an attachment in EVERY subnet of the matching type. If you dont specify vpcSubnets it will create an attachment in every subnet inside a VPC

5. Lambda Functions inside a VPC need access to the Lambda Service. This may be via Internet (ie via a NAT Gateway) or via a Lambda VPC Interface Endpoint if a VPC is truly cut off from the Internet. This needs to be considered when architecting inside a VPC. And Security Groups for the VPC Endpoint must allow inbound access from the Lambda Function (usually it allows it from the entire VPC by default).

6. The code path as defined needs to point to a lambda index.ts file that contains the appropriate handler name (ie ‘main’ in my case and the code). The path is relative to the lib path within your CDK App

And thats it. I hope this was useful to someone. It was a question that was posed to me by a Datacom colleague today so I did some investigation and this was the outcome. I am using CDK but I thought it would be good to show how you can do it either way.