This guide will walk you through deploying multiple AWS Lambda functions using Pulumi, an infrastructure as code tool that allows you to define and manage cloud resources using familiar programming languages like JavaScript, TypeScript, Python, Go, and more.
Before you begin, ensure you have the following prerequisites installed:
- Pulumi CLI: Install the Pulumi CLI by following the instructions on the official Pulumi website.
- AWS Account: You’ll need an AWS account with appropriate permissions to create Lambda functions, IAM roles, and other required resources.
- Programming Language: Choose a programming language supported by Pulumi. In this example, we’ll use TypeScript, but you can use JavaScript, Python, Go, etc.
I. Configure AWS profile
Configuring a local AWS profile allows you to manage multiple AWS credentials on your local machine, which is particularly useful when working with different AWS accounts or roles. Here’s how you can configure a local AWS profile:
• If you haven’t already installed the AWS Command Line Interface (CLI), you can do so by following the instructions on the AWS CLI documentation.
• Run the following command in your terminal:
aws configure --profile <profile-name>
Replace with the desired name for your profile. For example, when prompted, enter the following information:
• AWS Access Key ID: This is your AWS Access Key ID, which you can obtain from the AWS Management Console under IAM (Identity and Access Management) > Users > Security credentials.
• AWS Secret Access Key: This is your AWS Secret Access Key, which is provided when you generate an access key in the AWS Management Console.
• Specify Default Region and Output Format
You’ll also be prompted to specify the default region and output format. These are optional, but it’s recommended to set them for convenience.
• Default region name: Enter the AWS region you prefer to use, such as eu-west-1.
• Default output format: You can choose the output format, such as json.
• Confirm Configuration – review the configuration details displayed on the terminal and confirm that they are correct. Once confirmed, the AWS profile will be configured locally on your machine.
• Verify Configuration – to verify that your profile has been successfully configured, you can check the AWS CLI configuration files stored on your local machine.
– On Linux or macOS, check the ~/.aws/config and ~/.aws/credentials files.
– On Windows, check the C:\Users\USERNAME.aws\config and C:\Users\USERNAME.aws\credentials files.
II. Create private bucket to store the Pulumi state files
To create a private S3 bucket using the AWS Command Line Interface (CLI), you can utilize the AWS s3api create-bucket command along with specifying the appropriate ACL (Access Control List) settings. Here’s how to create a private bucket:
aws s3api create-bucket --bucket --region --profile --acl private
Replace with the desired name for your S3 bucket and with the AWS region where you want to create the bucket. This command creates an S3 bucket with the specified name in the specified region, and sets its ACL to private, meaning only the bucket owner will have access to it by default.
III. Clone and setup
Create a new directory and Clone our official repository https://github.com/joto2itgix/lambda_deploy. And initialize the Pulumi project:
mkdir lambda_deploy
cd lambda_deploy
git clone
npm install
npm install @pulumi/archive
IV. Review the Pulumi Code
Open the index.ts file in your preferred editor
import * as aws from "@pulumi/aws";
import * as ELEMENTS from "./elements"
(async () => {
var clientName = "lambdas"
var env = clientName+"-dev"
var CONFIG: any;
CONFIG = await ELEMENTS.readFile(env+".yaml");
const vpc_main = ELEMENTS.createVPC(env, CONFIG["vpc"]["name"], CONFIG["vpc"]["cidr"], CONFIG["vpc"]["tenancy"], CONFIG["default_tags"])
const gw1 = ELEMENTS.createGW(env, "gw", vpc_main, CONFIG["default_tags"])
const subnets:any = []
CONFIG["subnets"].forEach(function (subnet: any) {
subnets.push(
ELEMENTS.createSubnet (env, subnet["name"], vpc_main, subnet["cidr"], CONFIG["region"], subnet["zone"], gw1, CONFIG["default_tags"])
)
});
var ingress_public = [
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
{ protocol: "tcp", fromPort: 443, toPort: 443, cidrBlocks: ["0.0.0.0/0"] },
]
var egress = [
{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },
]
const webSecurityGroup = ELEMENTS.createNSG (env, "-sg-web", vpc_main, ingress_public, egress)
//SECRETS
const secretManager1 = new aws.secretsmanager.Secret(env+"-sm", {
forceOverwriteReplicaSecret: false,
});
// Store a new secret version
const secretVersion1 = new aws.secretsmanager.SecretVersion(env+"-secrets", {
secretId: secretManager1.id,
secretString: '{"test_var1":"test_valu1","test_valu2":"asd","test_var3":"test_valu3"}',
});
const secretManagerEndpoint = new aws.ec2.VpcEndpoint(env+"-sm-endpoint", {
vpcId: vpc_main.id,
serviceName: "com.amazonaws."+CONFIG["region"]+".secretsmanager",
vpcEndpointType: "Interface",
privateDnsEnabled: true,
subnetIds: subnets,
securityGroupIds: [],
});
//Hello World Lambda
var recipe = {
src: "helloworld",
runtime: "python3.12",
handler: "lambda_handler"
}
var actions = [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
]
const lambdaHW = ELEMENTS.createLambda(env, "hw-py", recipe, actions, [subnets[0].id], [webSecurityGroup.id], {foo: "bar"})
const lambdaHWDistribution = ELEMENTS.createCF(env, "hw-py", lambdaHW.functionUrl)
//Print env Lambda
var recipe = {
src: "printenv",
runtime: "python3.12",
handler: "lambda_handler"
}
var actions = [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
]
const lambdaPE = ELEMENTS.createLambda(env, "pe-py", recipe, actions, [subnets[0].id], [webSecurityGroup.id], {foo2: "bar2"})
})()
This code utilizes the Pulumi infrastructure as code (IaC) library to define and deploy AWS resources. Let’s break down the main components and actions taking place in the script:
• Imports: The script imports necessary modules from Pulumi’s @pulumi/aws library and a custom module ./elements.
• Asynchronous Function: The script defines an asynchronous function using an immediately invoked function expression (IIFE). This function appears to be the main entry point for deploying AWS resources.
• Environment Configuration: Loads environment-specific configuration from YAML files using the ELEMENTS.readFile function.
• Creation of VPC and Subnets: The script creates a Virtual Private Cloud (VPC) and associated subnets using the ELEMENTS.createVPC and ELEMENTS.createSubnet functions respectively.
• Network Security Groups: It creates a Network Security Group (NSG) for web traffic (webSecurityGroup).
• Secrets Management: Secret Manager resources are created (secretManager1, secretVersion1, secretManagerEndpoint) for managing secrets in AWS Secrets Manager.
• Lambda Functions: The script defines two Lambda functions (lambdaHW, lambdaPE) using the ELEMENTS.createLambda function. Each Lambda function is associated with specific actions and security groups.
• CloudFront: It creates a CloudFront distribution (lambdaHWDistribution) for the Lambda function lambdaHW.
• Script Execution: The script executes immediately when run.
V. Prepare Lambda Function Code
In this example, the lambda functions are simple and separate python files. But with ease can be extended to complex, or ones with different runtime.
VI. Deploy
Run the following command to deploy your Pulumi stack:
pulumi login 's3://itgix-lambdas-pulumi-state?region=eu-west-1&profile=itgix'
export PULUMI_CONFIG_PASSPHRASE=
pulumi stack select lambdas --create --non-interactive
pulumi config set aws:region eu-west-1
pulumi config set aws:profile itgix
pulumi preview
pulumi up --yes --non-interactive
• pulumi login: This command is used to authenticate with the Pulumi service. It specifies a state storage location (s3://itgix-lambdas-pulumi-state?region=eu-west-1&profile=itgix) where the state of the Pulumi stack will be stored. The –region and –profile options specify the AWS region and AWS profile to use for authentication, respectively.
• export PULUMI_CONFIG_PASSPHRASE=: This command exports an empty passphrase to the PULUMI_CONFIG_PASSPHRASE environment variable. This variable is used for encrypting sensitive configuration values in the Pulumi stack.
• pulumi stack select lambdas –create –non-interactive: This command selects the Pulumi stack named lambdas. If the stack does not exist, it will be created (–create). The –non-interactive option specifies that the command should not prompt for user input, which is useful for automation.
• pulumi config set aws:region eu-west-1: This command sets the AWS region configuration for the selected Pulumi stack to eu-west-1. This region will be used for deploying AWS resources.
• pulumi config set aws:profile itgix: This command sets the AWS profile configuration for the selected Pulumi stack to itgix. This profile will be used for authentication when interacting with AWS services.
• pulumi preview: This command displays a preview of the changes that will be applied to the infrastructure. It shows the resources that will be created, updated, or deleted based on the current Pulumi program.
• pulumi up –yes –non-interactive: This command applies the changes to the infrastructure. The –yes option automatically approves and applies the changes without asking for confirmation. The –non-interactive option ensures that the command does not prompt for user input.
Overall, this script logs in to the Pulumi service, selects or creates a Pulumi stack, configures AWS region and profile settings, previews the infrastructure changes, and applies the changes to deploy the AWS resources defined in the Pulumi program.
The output will look something like this.
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi login 's3://itgix-lambdas-pulumi-state?region=eu-west-1&profile=itgix'
Logged in to localhost.localdomain as vladimir.dimitrov (s3://itgix-lambdas-pulumi-state?region=eu-west-1&profile=itgix)
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy>
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> export PULUMI_CONFIG_PASSPHRASE=
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi stack select lambdas --create --non-interactive
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi config set aws:region eu-west-1
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi config set aws:profile itgix
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy>
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi preview
Previewing update (lambdas):
Type Name Plan
pulumi:pulumi:Stack lambdas-lambdas create
├─ aws:iam:Policy lambdas-dev-pe-py-policy create
├─ aws:iam:Policy lambdas-dev-hw-py-policy create
├─ aws:secretsmanager:Secret lambdas-dev-sm create
├─ aws:ec2:Vpc lambdas-dev-vpc create
├─ aws:secretsmanager:SecretVersion lambdas-dev-secrets create
├─ aws:iam:Role lambdas-dev-pe-py-iam create
├─ aws:iam:Role lambdas-dev-hw-py-iam create
├─ aws:ec2:RouteTable lambdas-dev-rt1 create
├─ aws:ec2:Subnet lambdas-dev-subnet create
├─ aws:ec2:InternetGateway lambdas-dev-gw create
├─ aws:ec2:SecurityGroup lambdas-dev-sg-web create
├─ aws:ec2:VpcEndpoint lambdas-dev-sm-endpoint create
├─ aws:ec2:RouteTableAssociation lambdas-dev-rt1a create
├─ aws:lambda:Function lambdas-dev-pe-py-printenv create
├─ aws:lambda:FunctionUrl lambdas-dev-pe-py-url create
├─ aws:lambda:Function lambdas-dev-hw-py-helloworld create
└─ aws:lambda:FunctionUrl lambdas-dev-hw-py-url create
Resources:
18 to create
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy>
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi up --yes --non-interactive
Previewing update (lambdas):
pulumi:pulumi:Stack lambdas-lambdas create
@ previewing update….
aws:secretsmanager:Secret lambdas-dev-sm create
aws:iam:Policy lambdas-dev-hw-py-policy create
aws:ec2:Vpc lambdas-dev-vpc create
aws:iam:Policy lambdas-dev-pe-py-policy create
aws:secretsmanager:SecretVersion lambdas-dev-secrets create
aws:iam:Role lambdas-dev-hw-py-iam create
aws:iam:Role lambdas-dev-pe-py-iam create
aws:ec2:Subnet lambdas-dev-subnet create
aws:ec2:InternetGateway lambdas-dev-gw create
aws:ec2:SecurityGroup lambdas-dev-sg-web create
aws:ec2:RouteTable lambdas-dev-rt1 create
aws:ec2:VpcEndpoint lambdas-dev-sm-endpoint create
aws:lambda:Function lambdas-dev-pe-py-printenv create
aws:lambda:Function lambdas-dev-hw-py-helloworld create
aws:ec2:RouteTableAssociation lambdas-dev-rt1a create
aws:lambda:FunctionUrl lambdas-dev-pe-py-url create
aws:lambda:FunctionUrl lambdas-dev-hw-py-url create
pulumi:pulumi:Stack lambdas-lambdas create
Resources:
18 to create
Updating (lambdas):
pulumi:pulumi:Stack lambdas-lambdas creating (0s)
@ updating……
aws:iam:Policy lambdas-dev-pe-py-policy creating (0s)
@ updating….
aws:iam:Policy lambdas-dev-pe-py-policy created (1s)
aws:iam:Policy lambdas-dev-hw-py-policy creating (0s)
aws:secretsmanager:Secret lambdas-dev-sm creating (0s)
aws:ec2:Vpc lambdas-dev-vpc creating (0s)
aws:iam:Role lambdas-dev-pe-py-iam creating (0s)
@ updating….
aws:iam:Policy lambdas-dev-hw-py-policy created (1s)
@ updating….
aws:secretsmanager:Secret lambdas-dev-sm created (1s)
aws:iam:Role lambdas-dev-hw-py-iam creating (0s)
@ updating….
aws:iam:Role lambdas-dev-pe-py-iam created (2s)
aws:secretsmanager:SecretVersion lambdas-dev-secrets creating (0s)
@ updating….
aws:iam:Role lambdas-dev-hw-py-iam created (1s)
@ updating….
aws:secretsmanager:SecretVersion lambdas-dev-secrets created (1s)
@ updating…………
aws:ec2:Vpc lambdas-dev-vpc created (13s)
aws:ec2:InternetGateway lambdas-dev-gw creating (0s)
aws:ec2:Subnet lambdas-dev-subnet creating (0s)
aws:ec2:SecurityGroup lambdas-dev-sg-web creating (0s)
@ updating…..
aws:ec2:InternetGateway lambdas-dev-gw created (1s)
aws:ec2:Subnet lambdas-dev-subnet created (1s)
aws:ec2:RouteTable lambdas-dev-rt1 creating (0s)
@ updating….
aws:ec2:VpcEndpoint lambdas-dev-sm-endpoint creating (0s)
@ updating….
aws:ec2:SecurityGroup lambdas-dev-sg-web created (3s)
aws:ec2:RouteTable lambdas-dev-rt1 created (2s)
@ updating….
aws:lambda:Function lambdas-dev-hw-py-helloworld creating (0s)
aws:lambda:Function lambdas-dev-pe-py-printenv creating (0s)
aws:ec2:RouteTableAssociation lambdas-dev-rt1a creating (0s)
@ updating…..
aws:ec2:RouteTableAssociation lambdas-dev-rt1a created (1s)
@ updating…………………………………..
aws:ec2:VpcEndpoint lambdas-dev-sm-endpoint created (42s)
@ updating…………………………………………………………………………………………………………………………………………
aws:lambda:Function lambdas-dev-hw-py-helloworld created (192s)
aws:lambda:FunctionUrl lambdas-dev-hw-py-url creating (0s)
@ updating….
aws:lambda:FunctionUrl lambdas-dev-hw-py-url created (1s)
@ updating….
aws:cloudfront:Distribution lambdas-dev-hw-py-cf creating (0s)
@ updating…….
aws:lambda:Function lambdas-dev-pe-py-printenv created (199s)
@ updating….
aws:lambda:FunctionUrl lambdas-dev-pe-py-url creating (0s)
@ updating….
aws:lambda:FunctionUrl lambdas-dev-pe-py-url created (0.93s)
@ updating……………………………………………………………………………………………………………………………………………………………………………….
aws:cloudfront:Distribution lambdas-dev-hw-py-cf created (201s)
@ updating….
pulumi:pulumi:Stack lambdas-lambdas created (1s)
Resources:
19 created
Duration: 7m2s
VII. Test
After deployment is complete, you can test your Lambda functions via the AWS Management Console, AWS CLI, or by invoking them pro-grammatically.
We will test our two lambda function through their URLs. The URL can be find inside the lambda itself.
First one is returning simple hello world text.
With the same code we created a CloudFront Distribution attached to the lambda. So, we can access it through there too.
Running the CF Domain will give us the same result, as the request is forwarded to the lambda URL.
The second lambda is simple too. But this time is configured to read an environment variable and to print it as an output, instead of the hello world text.
VIII. Updating the lambda code
Updating and deploying the lambda code is simpler than it sounds.
Open any lambda script, update it, and save the code. For our example we will change the hello world text from “Hello from Lambda!” to “I just made an update!”
import json
def lambda_handler(event, context):
#TODO implement
return {
'statusCode': 200,
'body': json.dumps('I just made an update!')
}
Then run:
pulumi preview
pulumi up --yes --non-interactive
Lambda is updated in less than 10sec. and the output looks something like this. You can see that Pulumi is detecting changes inside the lambda code and is triggering the update.
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi preview
Previewing update (lambdas):
Type Name Plan Info
pulumi:pulumi:Stack lambdas-lambdas
~ └─ aws:lambda:Function lambdas-dev-hw-py-helloworld update [diff: ~code]
Resources:
~ 1 to update
17 unchanged
vladimir.dimitrov@localhost:~/github/PROJECTS/lambda_deploy> pulumi up --yes --non-interactive
Previewing update (lambdas):
pulumi:pulumi:Stack lambdas-lambdas running
@ previewing update….
.
.
.
Resources:
~ 1 updated
17 unchanged
Duration: 9s
IX. Conclusion
Congratulations! You’ve successfully deployed multiple AWS Lambda functions using Pulumi. You can extend this setup to include additional resources, configure event triggers, and manage your entire infrastructure as code. For further customization and advanced features, refer to the Pulumi documentation.