In modern software development, automation is key to achieving efficiency and reliability in deployment processes. In this blog post, we will walk through a step-by-step guide on how to automate deployments on Amazon EC2 instances using AWS Lambda and AWS Systems Manager (SSM). Additionally, we will explore how to set up a trigger using AWS CodeCommit for seamless integration.
Architecture:
Prerequisites
AWS Account Setup: Ensure you have an active AWS account with the necessary permissions to create Lambda functions, IAM roles, and utilize Systems Manager.
CodeCommit Repository: Set up a CodeCommit repository to store your deployment configuration.
Lambda A and Lambda B: Create two Lambda functions - Lambda A to trigger the deployment and Lambda B to perform specific tasks after successful deployment.
Step 1: Configuring IAM Roles
IAM (Identity and Access Management) roles are crucial for defining the permissions needed by different AWS services.
EC2 Instance Role
Inline Policies
Inline Policy -
InlinePolicyName
Description: Briefly describe the purpose of this inline policy.
Permissions:
ssm:SendCommand
Other necessary permissions for your specific use case.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": "arn:aws:ec2:REGION:ACCOUNT NUMBER:instance/INSTANCE-ID"
}
]
}
SSM Managed Policy -
AmazonSSMManagedInstanceCore
- Description: This policy grants necessary permissions for Systems Manager to manage EC2 instances.
Lambda Execution Role
Attached Policies
AWS Managed Policy -
AmazonEC2ReadOnlyAccess
- Description: Provides read-only access to EC2 instances.
AWS Managed Policy -
AmazonSSMFullAccess
Description: Grants full access to AWS Systems Manager.
Permissions:
ssm:*
Full access to all Systems Manager actions.
AWS Managed Policy -
AWSCodeCommitFullAccess
Description: Provides full access to CodeCommit repositories.
Permissions:
codecommit:*
Full access to all CodeCommit actions.
AWS Managed Policy -
AWSLambdaExecute
Description: Grants basic permissions for Lambda function execution.
Permissions:
AWSLambdaExecute
policy permissions.
AWS Managed Policy -
AWSLambdaRole
Description: Grants Lambda functions permissions to create and manage other AWS resources.
Permissions:
AWSLambdaRole
policy permissions.
Inline Policies
Inline Policy -
LambdaInvokePolicy
Description: Additional inline policy providing specific permissions for Lambda A to invoke other Lambda functions.
Permissions:
lambda:InvokeFunction
Specify ARNs of Lambda functions that Lambda A should be allowed to invoke.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:REGION:ACCOUNT NUMBER:function:lambda_b"
}
]
}
Writing the Deployment Script
Create a deployment script (deploy.sh
) that contains the necessary commands to deploy your application. Ensure it has executable permissions (chmod 777
deploy.sh
).
Creating a CodeCommit Repository
If you haven't set up a CodeCommit repository yet, follow these steps:
Open the AWS CodeCommit Console in your AWS Management Console.
Click on the Create repository button.
Provide a repository name, like "test", optionally add a description.
Click on Create repository to create your CodeCommit repository.
Upload a "config.yaml" file that contains
enable: true
Lambda A - Triggering Deployments
Write the Lambda A function in Python. This function retrieves the content of the config.yaml
file from your CodeCommit repository. If the content contains "enable: true," Lambda A triggers Lambda B.
# -*- coding: utf-8 -*-
import json
import boto3
def lambda_handler(event, context):
# boto3 clients
codecommit = boto3.client("codecommit")
lambda_client = boto3.client("lambda")
# Retrieve the content of the config.yaml file from CodeCommit
repo_name = "test"
commit_specifier = "main" # This can be a branch name or a commit ID
file_path = "config.yaml"
try:
response = codecommit.get_file(
repositoryName=repo_name,
commitSpecifier=commit_specifier,
filePath=file_path
)
config_content = response["fileContent"].decode("utf-8")
# Check if the content of the config.yaml file contains "enable: true"
if "enable: true" == config_content.strip():
print("The 'enable: true' condition is met. Triggering Lambda B.")
# Trigger Lambda B
lambda_b_name = "lambda_b"
lambda_b_payload = {} # You can pass any additional payload if needed
lambda_client.invoke(
FunctionName=lambda_b_name,
InvocationType='Event',
Payload=json.dumps(lambda_b_payload)
)
return {"statusCode": 200, "body": json.dumps("Lambda B triggered successfully.")}
else:
# Log a message indicating that the condition is not met
print("The 'enable: true' condition is not met. Skipping Lambda B.")
except Exception as e:
# Handle exceptions, log errors, and optionally raise or return an error response
print(f"Error retrieving or processing config.yaml file: {e}")
return {"statusCode": 500, "body": json.dumps("Error processing config.yaml file.")}
return {"statusCode": 200, "body": json.dumps("Thanks from Srce Cde!")}
Lambda B - Post-Deployment Tasks
Write the Lambda B function that performs tasks after a successful deployment. In this example, Lambda B invokes specific actions, but you can customize it based on your requirements.
# -*- coding: utf-8 -*-
import time
import json
import boto3
def lambda_handler(event, context):
# boto3 client
client = boto3.client("ec2")
ssm = boto3.client("ssm")
# getting instance information
describeInstance = client.describe_instances()
InstanceId = []
# fetching instance id of the running instances
for i in describeInstance["Reservations"]:
for instance in i["Instances"]:
if instance["State"]["Name"] == "running":
InstanceId.append(instance["InstanceId"])
# looping through instance ids
for instanceid in InstanceId:
# command to run deployment.sh on instance
response = ssm.send_command(
InstanceIds=[instanceid],
DocumentName="AWS-RunShellScript",
Parameters={
"commands": ["chmod +x /deploy.sh", "/deploy.sh"]
}
)
# fetching command id for the output
command_id = response["Command"]["CommandId"]
time.sleep(3)
# fetching command output
output = ssm.get_command_invocation(CommandId=command_id, InstanceId=instanceid)
print(output)
return {"statusCode": 200, "body": json.dumps("Thanks from Srce Cde!")}
Configuring Lambda A Trigger
Now, let's configure the trigger for Lambda A:
Open the AWS Lambda Console.
Click on your Lambda A function (
lambda_a
) to open its configuration.In the designer section, click on Add trigger.
From the trigger configuration options, select CodeCommit.
In the "Repository" dropdown, choose the CodeCommit repository you created earlier.
In the "Branch" dropdown, select
main
. This means Lambda A will be triggered whenever changes are pushed to themain
branch.Optionally, configure the trigger to filter by file path or file name if needed.
Click on Add to add the CodeCommit trigger.
Update Lambda A Configuration
Open the AWS Lambda Console.
Find and click on the
lam``bda_a
function to open its configuration.In the "Configuration" tab, scroll down to the "General configuration" section.
Locate the "Timeout" setting.
Change the value from the current setting to
1 minute
(60 seconds).Save the changes.
Update Lambda B Configuration
Open the AWS Lambda Console.
Find and click on the
lam``bda_b
function to open its configuration.In the "Configuration" tab, scroll down to the "General configuration" section.
Locate the "Timeout" setting.
Change the value from the current setting to
1 minute
(60 seconds).Save the changes.
By updating the timeout setting to 1 minute for both Lambda functions, you ensure that each function has a maximum execution time of 1 minute before it's automatically terminated. This setting is useful to prevent functions from running indefinitely and incurring unnecessary costs.
Testing the Trigger
Now, whenever changes are pushed to the main
branch of your CodeCommit repository, Lambda A will be triggered. It will then check
Conclusion:
In conclusion, automating deployment with AWS Lambda and Systems Manager offers a scalable and efficient solution. IAM roles, Lambda functions, and CodeCommit integration combine for a responsive and reliable deployment pipeline. By adjusting scripts, configurations, and testing thoroughly, developers can achieve streamlined and high-quality software deployments. Happy deploying!