101: Solving AWS Lambda LogGroup Persistence Issue with CDK Constructs
Introduction of the Problem
AWS Lambda is a powerful serverless computing service that allows developers to run their code without provisioning or managing servers. However, one persistent issue with Lambda functions is the handling of their corresponding LogGroups. When a Lambda function is deleted, the associated LogGroup often remains, causing deployment failures when attempting to redeploy a Lambda function with the same name. In this article, we will explore this problem and provide a solution using AWS CDK Constructs. We’ll delve into the code example for a Node.js Lambda function Construct that implements the necessary logic to create and remove the LogGroup, ensuring a smoother deployment process.
Introducing CDK Constructs
AWS Cloud Development Kit (CDK) is an open-source software development framework that enables developers to define cloud infrastructure resources using familiar programming languages. CDK Constructs are reusable cloud components encapsulating AWS resources and their associated configurations. In this case, we will create a CDK Construct to handle the creation and deletion of the LogGroup automatically.
Implementing the CDK Construct
Full code example:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
export interface NodejsLambdaConstructProps extends Omit<cdk.aws_lambda_nodejs.NodejsFunctionProps, "runtime"> {}
export class NodejsLambdaConstruct extends Construct {
public lambda: cdk.aws_lambda_nodejs.NodejsFunction;
public logGroup: cdk.aws_logs.LogGroup;
constructor(scope: Construct, id: string, props: NodejsLambdaConstructProps = {}) {
super(scope, id);
const { logRetention, ...lambdaProps } = props;
this.lambda = new cdk.aws_lambda_nodejs.NodejsFunction(this, id, {
runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
maxEventAge: cdk.Duration.seconds(3),
memorySize: 1024,
retryAttempts: 2,
bundling: { minify: true },
...lambdaProps,
});
this.logGroup = new cdk.aws_logs.LogGroup(this, "LambdaLogGroup", {
logGroupName: `/aws/lambda/${this.lambda.functionName}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logRetention ?? cdk.aws_logs.RetentionDays.TWO_WEEKS,
});
}
}
The class defines the NodejsLambdaConstructProps
interface, which extends the NodejsFunctionProps
. This interface allows developers to customize the Lambda function’s properties.
Please note that you should update the runtime
property inside the this.lambda
initialization to the appropriate version for your specific use case. The best practice is to avoid using different runtimes for functions written in the same language. Therefore, in this case, we will exclude the runtime
property from the NodejsLambdaConstructProps
interface by omitting it from the original interface.:
export interface NodejsLambdaConstructProps extends Omit<cdk.aws_lambda_nodejs.NodejsFunctionProps, "runtime"> {}
Within the constructor we create a new instance of the NodejsFunction
class to define the Lambda function. Except runtime various properties such as memory size, bundling options, etc., can be customized.
this.lambda = new cdk.aws_lambda_nodejs.NodejsFunction(this, id, {
runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
maxEventAge: cdk.Duration.seconds(3),
memorySize: 1024,
retryAttempts: 2,
bundling: { minify: true },
...lambdaProps,
});
Next, we need to create a new instance of the LogGroup class to define the LogGroup associated with the Lambda function. To associate the LogGroup with the Lambda function we need to set the logGroupName
property to /aws/lambda/${this.lambda.functionName}
, ensuring a consistent naming convention.
To enable automatic deletion of the LogGroup when the Lambda function is deleted we need to set removalPolicy
to DESTROY
. Additionally, the retention property can be customized, specifying the number of days the logs should be retained. For example, it could be useful to specify a different retention policy for dev
, stage
and prod
environments.
this.logGroup = new cdk.aws_logs.LogGroup(this, "LambdaLogGroup", {
logGroupName: `/aws/lambda/${this.lambda.functionName}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logRetention ?? cdk.aws_logs.RetentionDays.TWO_WEEKS,
});
Usage
The code snippet below showcases the usage of the NodejsLambdaConstruct
within the context of an MyAWSomeStack
class:
export class MyAWSomeStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const { lambda, logGroup } = new NodejsLambdaConstruct(this, "NodejsLambdaConstruct");
new cdk.CfnOutput(this, "LambdaArn", {
value: lambda.functionArn,
description: "Lambda ARN",
exportName: "LambdaArn"
});
new cdk.CfnOutput(this, "LambdaLogGroup", {
value: logGroup.logGroupName,
description: "Lambda Log Group",
exportName: "LambdaLogGroup"
});
}
}
Conclusion
The persistence of LogGroups after deleting Lambda functions can cause deployment issues in AWS. By leveraging the power of CDK Constructs, developers can automate the creation and removal of LogGroups for Lambda functions, mitigating the problem. The provided code example demonstrates a Node.js Lambda Construct that encapsulates the logic to create and remove the LogGroup, making deployments more seamless and efficient. By incorporating this solution into your AWS Lambda workflows, you can save time and avoid unnecessary deployment failures due to lingering LogGroups.