AWS CodeDeploy with Blue/Green (Lambda) Deployment
Passionate DevOps/Cloud/Database Architect with a proven track record in automation, cloud optimization, and Database management. Experienced in IT , I specialize in designing and implementing CI/CD pipelines, automating deployments, and leveraging cloud technologies to build scalable, secure, and high-performing infrastructures. Seeking opportunities to drive innovation and efficiency in modern software development environments.
AWS CodeDeploy provides a seamless way to manage traffic shifting when deploying new versions of AWS Lambda functions. However, CodeDeploy does not deploy the function itself. For function deployment, AWS CodeBuild or CodePipeline should be used.
In this blog, we will walk through the CodeDeploy setup for a Lambda function, the appspec.yml configuration, and the use of lifecycle hooks to control traffic shifting.
Lambda Function Setup
Let's assume we have a Lambda function named myfunc. Below are the existing versions of this function:
aws lambda list-versions-by-function --function-name myfunc --query "Versions[*].Version" --profile sharma --region ap-south-1
Output:
[
"$LATEST",
"1",
"2"
]
Additionally, an alias myalias is created, which currently points to Version 1:
aws lambda list-aliases --function-name myfunc --profile sharma --region ap-south-1
Output:
{
"Aliases": [
{
"AliasArn": "arn:aws:lambda:ap-south-1:*******:function:myfunc:myalias",
"Name": "myalias",
"FunctionVersion": "1"
}
]
}
The function code is simple:
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda! Version 1') # Version 2 will have 'Version 2'
}
Now, invoking the function returns Version 1:
aws lambda invoke --function-name myfunc:myalias response.json --profile sharma --region ap-south-1
{
"StatusCode": 200,
"ExecutedVersion": "1"
}
AWS CodeDeploy Setup
Creating an Application and Deployment Group
I have already created created application/Deployment group and deployment.
To verify:
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$ aws deploy get-application --application-name myapp --profile sharma --region ap-south-1
{
"application": {
"applicationId": "a073a597-802d-4526-afed-955bff8f7a0c",
"applicationName": "myapp",
"createTime": "2025-02-11T18:08:39.065000+05:30",
"linkedToGitHub": false,
"computePlatform": "Lambda"
}
}
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$ aws deploy get-deployment-group --application-name myapp --deployment-group-name mygrp --profile sharma --region ap-south-1
{
"deploymentGroupInfo": {
"applicationName": "myapp",
"deploymentGroupId": "7d1b84c7-38f0-459f-8c66-b96d8781a8f1",
"deploymentGroupName": "mygrp",
"deploymentConfigName": "CodeDeployDefault.LambdaLinear10PercentEvery1Minute", #####Linear Traffic Shifting
"ec2TagFilters": [],
"onPremisesInstanceTagFilters": [],
"autoScalingGroups": [],
"serviceRoleArn": "arn:aws:iam::*******:role/roleforcodedeploy",
"triggerConfigurations": [],
"alarmConfiguration": {
"enabled": false,
"ignorePollAlarmFailure": false,
"alarms": []
},
"deploymentStyle": {
"deploymentType": "BLUE_GREEN", #####Blue Green Deployment
"deploymentOption": "WITH_TRAFFIC_CONTROL"
},
"outdatedInstancesStrategy": "UPDATE",
"computePlatform": "Lambda",
"terminationHookEnabled": false
}
}
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$ aws deploy get-deployment --deployment-id d-AQUTB8CY9 --profile sharma --region ap-south-1
{
"deploymentInfo": {
"applicationName": "myapp",
"deploymentGroupName": "mygrp",
"deploymentConfigName": "CodeDeployDefault.LambdaLinear10PercentEvery1Minute",
"deploymentId": "d-AQUTB8CY9",
"revision": {
"revisionType": "String",
"string": {
"sha256": "150d06d2af4317157d087d6e03d2a793d7a2b15e0dccb6e4b68d0f4ccf6922d1"
}
},
"status": "InProgress",
"createTime": "2025-02-11T18:14:54.602000+05:30",
"deploymentOverview": {
"Pending": 0,
"InProgress": 1,
"Succeeded": 0,
"Failed": 0,
"Skipped": 0,
"Ready": 0
},
"creator": "user",
"ignoreApplicationStopFailures": false,
"updateOutdatedInstancesOnly": false,
"deploymentStyle": {
"deploymentType": "BLUE_GREEN",
"deploymentOption": "WITH_TRAFFIC_CONTROL"
},
"instanceTerminationWaitTimeStarted": false,
"fileExistsBehavior": "DISALLOW",
"deploymentStatusMessages": [],
"computePlatform": "Lambda"
}
}
The AppSpec File
The appspec.yml file defines traffic shifting and lifecycle hooks:
version: 0.0
Resources:
- myfunc:
Type: AWS::Lambda::Function
Properties:
Name: "myfunc"
Alias: "myalias"
CurrentVersion: "1"
TargetVersion: "2"
Hooks:
- BeforeAllowTraffic: "myfunc1"
BeforeAllowTraffic: A hook function myfunc1 is executed before shifting traffic.
The appspec.yml file varies in structure depending on the service being used. Below is an example specific to AWS Lambda. In this setup, I am using a hook, which is another Lambda function triggered by the BeforeAllowTraffic lifecycle event. These hooks return an event to CodeDeploy, allowing the deployment to proceed. Without a successful response from the hook, the deployment would remain stuck in the Pre-Deployment stage.
Lifecycle Hook Function
The myfunc1 function validates the deployment before allowing traffic:
import json
import boto3
def lambda_handler(event, context):
print("BeforeAllowTraffic Hook Triggered")
deployment_id = event.get("DeploymentId", "Unknown") ####BeforeAllowTraffic Hook sent an Event to myfunc1 function. Now we will work on that event to proceed further
lifecycle_event_hook_execution_id = event.get("LifecycleEventHookExecutionId", "Unknown")
print(f"Deployment ID: {deployment_id}")
print(f"Lifecycle Event Hook Execution ID: {lifecycle_event_hook_execution_id}")
if deployment_id != "Unknown":
print("Validation successful")
status = "Succeeded"
else:
print("Validation failed")
status = "Failed"
# Notify CodeDeploy about the result
codedeploy_client = boto3.client("codedeploy") ###Calling Code Deploy service with boto3 and if the status is "Succeeded", Next phase of the CodeDeploy will be executed, else the deployment will fail
codedeploy_client.put_lifecycle_event_hook_execution_status(
deploymentId=deployment_id,
lifecycleEventHookExecutionId=lifecycle_event_hook_execution_id,
status=status
)
return {
"statusCode": 200,
"body": json.dumps({"status": status})
}
BeforeAllowTraffic will send event to this function to work on.
As the deployment is in Progress. Checking the status of traffic being routed:
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$ for i in {1..5}; do aws lambda invoke --function-name myfunc:myalias --profile sharma --region ap-south-1 response.json; sleep 20; done
{
"StatusCode": 200,
"ExecutedVersion": "1"
}
{
"StatusCode": 200,
"ExecutedVersion": "1"
}
{
"StatusCode": 200,
"ExecutedVersion": "1"
}
{
"StatusCode": 200,
"ExecutedVersion": "1"
}
{
"StatusCode": 200,
"ExecutedVersion": "2"
}
The output shows that traffic is now being routed to both versions ( based on the traffic shifting provided)
Final Deployment Status:
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$ aws deploy get-deployment --deployment-id d-AQUTB8CY9 --profile sharma --region ap-south-1
{
"deploymentInfo": {
"applicationName": "myapp",
"deploymentGroupName": "mygrp",
"deploymentConfigName": "CodeDeployDefault.LambdaLinear10PercentEvery1Minute",
"deploymentId": "d-AQUTB8CY9",
"revision": {
"revisionType": "String",
"string": {
"sha256": "150d06d2af4317157d087d6e03d2a793d7a2b15e0dccb6e4b68d0f4ccf6922d1"
}
},
"status": "Succeeded",
"createTime": "2025-02-11T18:14:54.602000+05:30",
"completeTime": "2025-02-11T18:24:02.432000+05:30",
"deploymentOverview": {
"Pending": 0,
"InProgress": 0,
"Succeeded": 1, ####Deployment Succeeded
"Failed": 0,
"Skipped": 0,
"Ready": 0
},
"creator": "user",
"ignoreApplicationStopFailures": false,
"updateOutdatedInstancesOnly": false,
"deploymentStyle": {
"deploymentType": "BLUE_GREEN",
"deploymentOption": "WITH_TRAFFIC_CONTROL"
},
"instanceTerminationWaitTimeStarted": false,
"fileExistsBehavior": "DISALLOW",
"deploymentStatusMessages": [],
"computePlatform": "Lambda"
}
}
After deployment, Traffic being routed to Version 2 Only
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$ aws lambda invoke --function-name myfunc:myalias response.json --profile sharma --region ap-south-1
{
"StatusCode": 200,
"ExecutedVersion": "2"
}
kunal@kunal-Lenovo-U410:~/cloudFormation/chapter1$