FargateAWSLambda | 8 Min Read
Starting an Existing Fargate Service Using Lambda and the AWS SDK
Learn how to deploy a new AWS Fargate service using the AWS CDK as well as how to start that service on-demand using Lambda and the AWS SDK!
AWS Fargate allows us to make use of containers for our applications but without the need to manage the infrastructure as we’d normally need to when using something like ECS. So, in this post, we’re going to take a look at how we can deploy a Fargate service using the AWS CDK as well as how we can start that Fargate service using a Lambda function and the AWS SDK.
But, before we jump into the code and start defining resources, you might be wondering why we want to be able to start a Fargate service from a Lambda function. Typically this type of architecture is quite helpful when you want to be able to start a container in response to something happening like an API request for example.
This setup is helpful for when you’re trying to minimise costs because with Fargate you only get billed for the time your container is running. This means you can stop your container when it’s been sitting ideal for a long time before then have it start up again when a request comes into the Lambda function.
So, now we know how this setup can help us, let’s jump into the code and start building!
Defining the Fargate Service
We’re going to start by defining our Fargate service in our CDK stack so if you don’t already have a CDK stack you’d like to add the Fargate service to, make sure to create a new one with the command `cdk init app --language typescript`
.
Once you have your CDK stack, head into the stack definition file in the `lib`
directory and we can start defining the resources we’ll need for this project.
The first couple of resources we will need to define are a VPC and an ECS cluster. We need the VPC to create the ECS cluster and then we’ll use the ECS cluster to create our Fargate service in a moment. To create our VPC and ECS cluster, add the below code inside your stack.
1// Update these values as you'd like2const clusterName = "CLUSTER_NAME";3const serviceName = "SERVICE_NAME";4
5// Create a VPC for our ECS Cluster6const vpc = new Vpc(this, "Vpc");7
8// Create an ECS Cluster9const cluster = new Cluster(this, "EcsCluster", {10 vpc: vpc,11 clusterName,12});
tsThen make sure to import the `Vpc`
and `Cluster`
constructs by adding the below import statements to the top of the file
1import { Vpc } from "aws-cdk-lib/aws-ec2";2import { Cluster } from "aws-cdk-lib/aws-ecs";
tsWith our VPC and ECS cluster now defined, we’re ready to define our Fargate service which we can do with the `ApplicationLoadBalancedFargateService`
construct. This construct will create a new Fargate service running on the ECS cluster we defined while also placing the Fargate service behind an application load balancer. Now, we don’t need to worry about the load balancer for this tutorial but it’s helpful to know that it’s created by this construct. You can learn more about the construct here.
To create the Fargate service, add the below code under the code we just added for our VPC and ECS cluster.
1// Create a Fargate Service2const fargateService = new ApplicationLoadBalancedFargateService(3 this,4 "FargateService",5 {6 cluster,7 serviceName,8 cpu: 256,9 memoryLimitMiB: 512,10 desiredCount: 1,11 taskImageOptions: {12 image: ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),13 },14 }15);
tsIn this code, we create our new Fargate service as well as define some configuration properties about it such as the CPU units and memory available. We also set its desired count to 1 (minimum allowed) which means when we deploy it to AWS, one instance of our container will be started automatically. Finally, we provide the container image we want to run in the service which in this case is just a sample image provided by AWS.
Finally, after configuring the service, don’t forget to update your imports to import the new constructs we’ve used.
1// Added `ContainerImage` to this import2import { Cluster, ContainerImage } from "aws-cdk-lib/aws-ecs";3import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns";
tsAt this point, we’ve now finished defining our Fargate service and the required resources for it and we’re ready to move on to defining and writing our Lambda function.
Defining the Lambda function
With our Fargate service now defined and ready to go, let’s define the Lambda function we’re going to use to start our Fargate service. To do this, add the below code under the code we just added for our Fargate service.
1// Create a Lambda function to start the Fargate Service2const startServiceLambda = new NodejsFunction(this, "StartServiceLambda", {3 entry: "resources/start-service.ts",4 handler: "handler",5 environment: {6 CLUSTER: clusterName,7 SERVICE: serviceName,8 REGION: this.region,9 },10});
tsIn this code, we define a new Lambda function using the `NodejsFunction`
construct and in this definition, we pass the file that will contain the code for the Lambda function to run as well as some environment variables we’ll need in the code to start our Fargate service.
IAM Policy
Because in the Lambda function, we’ll be using the `UpdateServiceCommand`
and `DescribeServicesCommand`
from the AWS SDK, we’ll need to create an IAM policy that grants our Lambda function permission to run those commands against our Fargate service.
To create this policy add the below code under the code we just added for defining our Lambda function.
1// Give the Lambda function permission to start the Fargate Service2const ecsPolicy = new Policy(this, "EcsPolicy", {3 statements: [4 new PolicyStatement({5 actions: ["ecs:UpdateService", "ecs:DescribeServices"],6 resources: [fargateService.service.serviceArn],7 }),8 ],9});
tsThen with our IAM policy created, we just need to attach it to our Lambda function which we can do with the below code which we can add under our IAM policy’s code.
1// Attach the policy to the Lambda function's role2startServiceLambda.role?.attachInlinePolicy(ecsPolicy);
tsWriting the function
With our Lambda function now defined and granted the correct permissions for starting a Fargate service we’re ready to start writing the Lambda function itself. But, just before we write the code for our Lambda function, let’s install the AWS SDK package we’ll need which we can do by running the command `npm i @aws-sdk/client-ecs`
in our terminal.
Now, with the required package installed, let’s start creating our Lambda function which we can do by creating a new file in our CDK stack at `./resources/start-service.ts`
to align with the file path we gave the Lambda definition. Then, we can add the code below to this file.
1import {2 DescribeServicesCommand,3 ECSClient,4 UpdateServiceCommand,5} from "@aws-sdk/client-ecs";6
7const REGION = process.env.REGION;8const CLUSTER = process.env.CLUSTER;9const SERVICE = process.env.SERVICE;10
11export async function handler() {12 const ecs = new ECSClient({ region: REGION });13
14 if (REGION == null || CLUSTER == null || SERVICE == null) {15 throw new Error("Missing environment variables");16 }17
18 const response = await ecs.send(19 new DescribeServicesCommand({20 cluster: CLUSTER,21 services: [SERVICE],22 })23 );24
25 const desired = response?.services?.[0].desiredCount;26
27 if (desired === 0) {28 await ecs.send(29 new UpdateServiceCommand({30 cluster: CLUSTER,31 service: SERVICE,32 desiredCount: 1,33 })34 );35
36 console.log("Updated desiredCount to 1");37 } else {38 console.log("desiredCount already at 1");39 }40}
tsIn this code, we take in the environment variables we passed to the Lambda function’s definition a moment ago before checking if any of them aren’t present and creating a new `ECSClient`
for us to use in the Lambda.
With the new `ECSClient`
, we use the `DescribeServicesCommand`
to retrieve the current `desiredCount`
of our Fargate service. If this count is `1`
we know the service is already running so we can return from the function. However, if the count is `0`
we know the Fargate service isn’t running so we can use the `UpdateServiceCommand`
to set the `desiredCount`
to `1`
which will then start our Fargate service.
Deployment & Testing
With all of our resources now defined and our Lambda function written, we’re ready to deploy our CDK stack and resources to AWS. To do this, run the command `cdk deploy`
in your terminal. Then accept any prompts given to you and wait for the deployment process to complete.
Once the deployment has finished we’re ready to test our project and make sure our Lambda function can start our Fargate service correctly. But, before we can do this, we first need to stop our deployed Fargate service because if you remember back to when we defined it, we set the `desiredCount`
to `1`
which means one instance of it would be started when we deployed.
To stop the Fargate service you can use the AWS dashboard or you can use the AWS CLI and run the command `aws ecs update-service --service SERVICE_NAME --cluster CLUSTER_NAME --desired-count 0`
, making sure to swap in the `SERVICE_NAME`
and `CLUSTER_NAME`
that you used in the CDK stack before running the command.
You can then validate that the Fargate service has been successfully stopped by running the command `aws ecs describe-services --service SERVICE_NAME --cluster CLUSTER_NAME`
(make sure to swap in the `SERVICE_NAME`
and `CLUSTER_NAME`
again before running this command) and then check the `runningCount`
in the output is `0`
.
Once you’ve confirmed there are no running instances of your Fargate service, you can invoke the Lambda function, and then after a few moments, you can re-run the `describe-services`
command and check that the `runningCount`
is now `1`
. And, if that is the case, then congrats everything worked as planned!
Closing Thoughts
In this tutorial, we looked at AWS Fargate and how we can create a Fargate Service using the AWS CDK as well as how we can start that Fargate service using a Lambda function and the AWS SDK.
If you’re interested in seeing the full example code for this tutorial, you can see it over in my CDK Tutorials GitHub repository.
Thanks for reading